ArcGIS Runtime 100.2.0的正式发布带来个更多移动端的处理地图的能力,例如支持WMS图层、支持海图(ENC)图层,再如基于场景相机(Camera)的视域分析。同时还提供了新的统计查询API(总和,平均值,计数,最小值,最大值,标准差或方差)。而今天要描述的重点是ArcGIS Runtime 100.2.0对Shapefile数据格式的支持,当然ArcGIS Runtime 100.2.0还增加了对OGC GeoPackage格式的支持。
对于Shapfile这一文件格式,ArcGIS Runtime SDK for Android 在100.2.0系列中推出了两大重磅利好消息:
(1) 实现对本地 Shapfile 文件的加载、显示和查询
(2) 实现对本地 Shapefile 文件的编辑,即增删改操作
毋庸置疑,对于广大的ArcGIS移动端爱好者而言,支持Shapefile编辑操作绝对是一则振奋人心的好消息。
Shapefile文件的加载
加载shp文件,数据量大的时候,加载速度比较慢。
shp文件 必须有least three files (.shp, .shx, .dbf) 如果有prj文件那就更好。位置准确。
在安卓端加载Shapefile文件的关键是ShapefileFeatureTable(com.esri.arcgisruntime.data.ShapefileFeatureTable)。
相比于.geodatabase文件,Shapefile文件的缺点在于只是单图层,且没有符号化,当然可以通过移动端的可视化API进行处理。
shp文件加载方式1
/**
* 加载shp方式1
*/
private void loadShapefile1() {
// 构建ShapefileFeatureTable,引入本地存储的shapefile文件
ShapefileFeatureTable shapefileFeatureTable = new ShapefileFeatureTable(shpPath);
shapefileFeatureTable.loadAsync();
// 构建featureLayerr
mFeatureLayer = new FeatureLayer(shapefileFeatureTable);
// 设置Shapefile文件的渲染方式
SimpleLineSymbol lineSymbol = new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.RED, 1.0f);
SimpleFillSymbol fillSymbol = new SimpleFillSymbol(SimpleFillSymbol.Style.SOLID, Color.YELLOW, lineSymbol);
SimpleRenderer renderer = new SimpleRenderer(fillSymbol);
mFeatureLayer.setRenderer(renderer);
//设置选中颜色
mFeatureLayer.setSelectionWidth(5);
mFeatureLayer.setSelectionColor(Color.GREEN);
mMapView.setViewpointGeometryAsync(mFeatureLayer.getFullExtent());
// 添加到地图的业务图层组中
mArcGISMap.getOperationalLayers().add(mFeatureLayer);
}
shp文件加载方式2
/**
* 查询shp方式2
*/
private void loadShapefile2() {
mShapefileFeatureTable = new ShapefileFeatureTable(shpPath);
mShapefileFeatureTable.loadAsync();
mShapefileFeatureTable.addDoneLoadingListener(new Runnable() {
@Override
public void run() {
GeometryType gt = mShapefileFeatureTable.getGeometryType();
String name = mShapefileFeatureTable.getTableName();
mFeatureLayer = new FeatureLayer(mShapefileFeatureTable);
if (mFeatureLayer.getFullExtent() != null) {
mMapView.setViewpointGeometryAsync(mFeatureLayer.getFullExtent());
} else {
mFeatureLayer.addDoneLoadingListener(new Runnable() {
@Override
public void run() {
mMapView.setViewpointGeometryAsync(mFeatureLayer.getFullExtent());
}
});
}
mArcGISMap.getOperationalLayers().add(mFeatureLayer);
}
});
SimpleLineSymbol lineSymbol = new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.RED, 1.0f);
SimpleFillSymbol fillSymbol = new SimpleFillSymbol(SimpleFillSymbol.Style.SOLID, Color.YELLOW, lineSymbol);
SimpleRenderer renderer = new SimpleRenderer(fillSymbol);
mFeatureLayer.setRenderer(renderer);
//设置选中颜色
mFeatureLayer.setSelectionWidth(5);
mFeatureLayer.setSelectionColor(Color.GREEN);
}
shp文件的查询*
shp文件查询方式1:selectFeaturesAsync
/**
* 查询shp方式1:selectFeaturesAsync
*/
private void queryBySelectFeaturesAsync() {
mMapView.setOnTouchListener(new DefaultMapViewOnTouchListener(this, mMapView) {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
mFeatureLayer.clearSelection();
final Point clickPoint = mMapView.screenToLocation(new android.graphics.Point(Math.round(e.getX()), Math.round(e.getY())));
int tolerance = 1;
double mapTolerance = tolerance * mMapView.getUnitsPerDensityIndependentPixel();
SpatialReference spatialReference = mMapView.getSpatialReference();
Envelope envelope = new Envelope(clickPoint.getX() - mapTolerance, clickPoint.getY() - mapTolerance,
clickPoint.getX() + mapTolerance, clickPoint.getY() + mapTolerance, spatialReference);
QueryParameters query = new QueryParameters();
query.setGeometry(envelope);
query.setSpatialRelationship(QueryParameters.SpatialRelationship.WITHIN);
final ListenableFuture<FeatureQueryResult> future = mFeatureLayer.selectFeaturesAsync(query, FeatureLayer.SelectionMode.NEW);
future.addDoneListener(new Runnable() {
@Override
public void run() {
try {
FeatureQueryResult result = future.get();
//mFeatureLayer.getFeatureTable().deleteFeaturesAsync(result);
Iterator<Feature> iterator = result.iterator();
int counter = 0;
while (iterator.hasNext()) {
counter++;
Feature feature = iterator.next();
Map<String, Object> attributes = feature.getAttributes();
for (String key : attributes.keySet()) {
Log.e("xyh" + key, String.valueOf(attributes.get(key)));
}
//高亮显示选中区域
mFeatureLayer.selectFeature(feature);
Geometry geometry = feature.getGeometry();
mMapView.setViewpointGeometryAsync(geometry.getExtent());
//也可以通过添加graphic高亮显示选中区域
//
// SimpleLineSymbol lineSymbol = new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.RED, 3);
// SimpleFillSymbol fillSymbol = new SimpleFillSymbol(SimpleFillSymbol.Style.SOLID, Color.RED, lineSymbol);
//
// if (mGraphicsOverlay != null) {
// ListenableList<Graphic> graphics = mGraphicsOverlay.getGraphics();
// if (graphics.size() > 0) {
// graphics.removeAll(graphics);
// }
// }
// Graphic graphic = new Graphic(geometry, fillSymbol);
// mGraphicsOverlay.getGraphics().add(graphic);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
return super.onSingleTapConfirmed(e);
}
});
}
shp文件查询方式2:queryFeaturesAsync
/**
* 查询shp方式2:queryFeaturesAsync
*/
public void queryByQueryFeaturesAsync() {
mMapView.setOnTouchListener(new DefaultMapViewOnTouchListener(this, mMapView) {
@Override
public boolean onSingleTapConfirmed(MotionEvent v) {
android.graphics.Point screenPoint = new android.graphics.Point(Math.round(v.getX()), Math.round(v.getY()));
Point clickPoint = mMapView.screenToLocation(screenPoint);
QueryParameters query = new QueryParameters();
query.setGeometry(clickPoint);// 设置空间几何对象
FeatureTable mTable = mFeatureLayer.getFeatureTable();//得到查询属性表
final ListenableFuture<FeatureQueryResult> featureQueryResult = mTable.queryFeaturesAsync(query);
featureQueryResult.addDoneListener(new Runnable() {
@Override
public void run() {
try {
FeatureQueryResult result = featureQueryResult.get();
Iterator<Feature> iterator = result.iterator();
int counter = 0;
while (iterator.hasNext()) {
counter++;
Feature feature = iterator.next();
Map<String, Object> attributes = feature.getAttributes();
for (String key : attributes.keySet()) {
Log.e("xyh" + key, String.valueOf(attributes.get(key)));
}
//高亮显示选中区域
Geometry geometry = feature.getGeometry();
mMapView.setViewpointGeometryAsync(geometry.getExtent());
SimpleLineSymbol lineSymbol = new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.RED, 3);
SimpleFillSymbol fillSymbol = new SimpleFillSymbol(SimpleFillSymbol.Style.SOLID, Color.RED, lineSymbol);
if (mGraphicsOverlay != null) {
ListenableList<Graphic> graphics = mGraphicsOverlay.getGraphics();
if (graphics.size() > 0) {
graphics.removeAll(graphics);
}
}
Graphic graphic = new Graphic(geometry, fillSymbol);
mGraphicsOverlay.getGraphics().add(graphic);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
return true;
}
});
}
shp文件查询方式3:identifyLayerAsync
private void queryByIdentify() {
mMapView.setOnTouchListener(new DefaultMapViewOnTouchListener(this,mMapView) {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
android.graphics.Point screenPoint = new android.graphics.Point(Math.round(e.getX()), Math.round(e.getY()));
final ListenableFuture<IdentifyLayerResult> identifyLayerResultListenableFuture = mMapView.identifyLayerAsync(
mFeatureLayer, screenPoint, 12, false, 10);
identifyLayerResultListenableFuture.addDoneListener(new Runnable() {
@Override
public void run() {
try {
IdentifyLayerResult identifyLayerResult = identifyLayerResultListenableFuture.get();
String name = identifyLayerResult.getLayerContent().getName();
List<GeoElement> elements = identifyLayerResult.getElements();
for (GeoElement element : elements) {
Map<String, Object> attributes = element.getAttributes();
Geometry geometry = element.getGeometry();
//高亮显示选中区域
mMapView.setViewpointGeometryAsync(geometry.getExtent());
SimpleLineSymbol lineSymbol = new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.RED, 3);
SimpleFillSymbol fillSymbol = new SimpleFillSymbol(SimpleFillSymbol.Style.SOLID, Color.RED, lineSymbol);
if (mGraphicsOverlay != null) {
ListenableList<Graphic> graphics = mGraphicsOverlay.getGraphics();
if (graphics.size() > 0) {
graphics.removeAll(graphics);
}
}
Graphic graphic = new Graphic(geometry, fillSymbol);
mGraphicsOverlay.getGraphics().add(graphic);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
});
return super.onSingleTapConfirmed(e);
}
});
}
Shapefile文件的编辑
只要具备移动端文件的读写能力,便可以轻松的通过FeatureLayer的相关方法进行空间数据的编辑(添加、修改、删除)。
1.添加要素
mShapefileFeatureTable.addFeatureAsync(feature);
/**
* 增加要素
*/
public void addFeatureToShp() {
// 调用isEditable和canAdd方法判断文件是否支持编辑操作,是否可添加要素;否,则抛出信息
if (mShapefileFeatureTable.isEditable() && mShapefileFeatureTable.canAdd()) {
// 构建新增几何
Point mapPoint = new Point(140.0, 39.0, SpatialReference.create(4326));
// 构建待增加的Feature对象,设置几何,设置属性
Feature feature = mShapefileFeatureTable.createFeature();
feature.setGeometry(mapPoint);
feature.getAttributes().put("NAME", "测试点");
// 调用addFeatureAsync方法增加要素
final ListenableFuture<Void> addFeatureOper = mShapefileFeatureTable.addFeatureAsync(feature);
// 在操作完成的监听事件中判断操作是否成功
addFeatureOper.addDoneListener(new Runnable() {
@Override
public void run() {
try {
addFeatureOper.get();
if (addFeatureOper.isDone()) {
Log.e("xyh:", "Feature added!");
}
} catch (InterruptedException interruptedExceptionException) {
// 处理异常
} catch (ExecutionException executionException) {
// 处理异常
}
}
});
} else {
Log.e("xyh:", "The Shapefile cann't be edited");
}
}
2.删除
mShapefileFeatureTable.deleteFeatureAsync(feature);
3.更新
mShapefileFeatureTable.updateFeatureAsync(feature);
下面来看个整体Shapefile文件的编辑的案例,这是参考网上找的一篇比较好的博客。
先看看效果图:
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="esrichina.hymn.symbolizeshapefile.MainActivity">
<com.esri.arcgisruntime.mapping.view.MapView
android:id="@+id/mapView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"></com.esri.arcgisruntime.mapping.view.MapView>
<android.support.v7.widget.LinearLayoutCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:orientation="vertical">
<Button
android:id="@+id/drawButton"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:text="绘制" />
<Button
android:id="@+id/saveButton"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:text="保存" />
<Button
android:id="@+id/selectButton"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:text="选择" />
<Button
android:id="@+id/deleteButton"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:text="删除" />
</android.support.v7.widget.LinearLayoutCompat>
</android.support.constraint.ConstraintLayout>
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private ArcGISMap mainArcGISMap;
private FeatureLayer mainShapefileLayer;
private SketchEditor mainSketchEditor;
private SketchStyle mainSketchStyle;
private boolean isSelect = false;
//底图
private String url = Environment.getExternalStorageDirectory().getAbsolutePath() + "/测试数据/bxbxbx.tpk";
//shp文件
private String shpPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/测试数据/gdtbt.shp";
private MapView mMapView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViews();
String[] reqPermission = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
int requestCode = 2;
if (ContextCompat.checkSelfPermission(this, reqPermission[0]) == PackageManager.PERMISSION_GRANTED) {
showShapefile();
} else {
ActivityCompat.requestPermissions(this, reqPermission, requestCode);
}
}
private void showShapefile() {
TileCache mainTileCache = new TileCache(url);
ArcGISTiledLayer arcGISTiledLayer = new ArcGISTiledLayer(mainTileCache);
Basemap basemap = new Basemap(arcGISTiledLayer);
mainArcGISMap = new ArcGISMap(basemap);
mMapView.setMap(mainArcGISMap);
File file = new File(shpPath);
if (!file.exists()) {
Toast.makeText(this, "shp文件", Toast.LENGTH_SHORT).show();
return;
}
final ShapefileFeatureTable shapefileFeatureTable = new ShapefileFeatureTable(shpPath);
shapefileFeatureTable.loadAsync();
shapefileFeatureTable.addDoneLoadingListener(new Runnable() {
@Override
public void run() {
GeometryType gt = shapefileFeatureTable.getGeometryType();
String name = shapefileFeatureTable.getTableName();
mainShapefileLayer = new FeatureLayer(shapefileFeatureTable);
if (mainShapefileLayer.getFullExtent() != null) {
mMapView.setViewpointGeometryAsync(mainShapefileLayer.getFullExtent());
} else {
mainShapefileLayer.addDoneLoadingListener(new Runnable() {
@Override
public void run() {
mMapView.setViewpointGeometryAsync(mainShapefileLayer.getFullExtent());
}
});
}
mainArcGISMap.getOperationalLayers().add(mainShapefileLayer);
startDrawing();
}
});
SimpleLineSymbol lineSymbol = new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.RED, 1.0f);
SimpleFillSymbol fillSymbol = new SimpleFillSymbol(SimpleFillSymbol.Style.SOLID, Color.YELLOW, lineSymbol);
SimpleRenderer renderer = new SimpleRenderer(fillSymbol);
mainShapefileLayer.setRenderer(renderer);
mainShapefileLayer.setSelectionColor(Color.GREEN);
mainShapefileLayer.setSelectionWidth(5);
}
public void startDrawing() {
try {
mainSketchEditor = new SketchEditor();
mainSketchStyle = new SketchStyle();
mainSketchEditor.setSketchStyle(mainSketchStyle);
mMapView.setSketchEditor(mainSketchEditor);
mMapView.setOnTouchListener(new DefaultMapViewOnTouchListener(this, mMapView) {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (isSelect == true) {
Point clickPoint = mMapView.screenToLocation(new android.graphics.Point(Math.round(e.getX()), Math.round(e.getY())));
int tolerance = 1;
double mapTolerance = tolerance * mMapView.getUnitsPerDensityIndependentPixel();
Envelope envelope = new Envelope(clickPoint.getX() - mapTolerance, clickPoint.getY() - mapTolerance,
clickPoint.getX() + mapTolerance, clickPoint.getY() + mapTolerance, mMapView.getSpatialReference());
QueryParameters query = new QueryParameters();
query.setGeometry(envelope);
query.setSpatialRelationship(QueryParameters.SpatialRelationship.WITHIN);
final ListenableFuture<FeatureQueryResult> future = mainShapefileLayer.selectFeaturesAsync(query, FeatureLayer.SelectionMode.NEW);
future.addDoneListener(new Runnable() {
@Override
public void run() {
try {
FeatureQueryResult result = future.get();
//mainShapefileLayer.getFeatureTable().deleteFeaturesAsync(result);
Iterator<Feature> iterator = result.iterator();
Feature feature;
int counter = 0;
while (iterator.hasNext()) {
feature = iterator.next();
Geometry geometry = feature.getGeometry();
mMapView.setViewpointGeometryAsync(geometry.getExtent());
counter++;
}
} catch (Exception e) {
e.getCause();
}
}
});
}
return super.onSingleTapConfirmed(e);
}
});
} catch (Exception e) {
e.getCause();
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.drawButton: //绘制
isSelect = false;
mainShapefileLayer.clearSelection();
mainSketchEditor.stop();
mainSketchEditor.start(SketchCreationMode.POLYGON);
break;
case R.id.saveButton: //把绘制的图形添加到shp
saveDraw();
break;
case R.id.selectButton: //选择要素
isSelect = true;
mainSketchEditor.stop();
break;
case R.id.deleteButton: //删除要素
final ListenableFuture<FeatureQueryResult> selectResult = mainShapefileLayer.getSelectedFeaturesAsync();
selectResult.addDoneListener(new Runnable() {
@Override
public void run() {
try {
mainShapefileLayer.getFeatureTable().deleteFeaturesAsync(selectResult.get());
} catch (Exception e) {
e.getCause();
}
}
});
break;
}
}
public void saveDraw() {
if (mainSketchEditor.getGeometry() != null) {
java.util.Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("NAME", "自己画的省份");
Feature addedFeature = mainShapefileLayer.getFeatureTable().createFeature(attributes, (Polygon) mainSketchEditor.getGeometry());
final ListenableFuture<Void> addFeatureFuture = mainShapefileLayer.getFeatureTable().addFeatureAsync(addedFeature);
mainSketchEditor.stop();
}
}
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showShapefile();
} else {
// report to user that permission was denied
Toast.makeText(this, "Shapefile 无法打开,因为存储访问权限被拒绝!", Toast.LENGTH_SHORT).show();
}
}
private void findViews() {
mMapView = (MapView) findViewById(R.id.mapView);
Button drawButton = (Button) findViewById(R.id.drawButton);
Button saveButton = (Button) findViewById(R.id.saveButton);
Button selectButton = (Button) findViewById(R.id.selectButton);
Button deleteButton = (Button) findViewById(R.id.deleteButton);
drawButton.setOnClickListener(this);
saveButton.setOnClickListener(this);
selectButton.setOnClickListener(this);
deleteButton.setOnClickListener(this);
}
}
上面出现了个SketchEditor
表示草图编辑器,允许用户在地图视图上以交互方式绘制几何图形。
我会在下面的博客详细讲解他的用法。