外业数据采集平台(GPS+AndroidStudio+Arcgisforandroid100.2.1)
欢迎加入QQ沟通交流群:186178114(群名:外业数据采集(GIS+GPS))
外业数据采集平台
1.综述
在室外,通过平板或者手机接收GPS坐标,实时绘制点、线、面数据,以便为后续进行海域监测、土地确权、地图绘图提供有效数据和依据。
2.技术路线
Android studio3.0.1+Arcgis for android 100.2.1+GPS
2.1Android studio工具:
2.2Android studio工具下载地址:
http://www.android-studio.org/index.php/download/hisversion
2.2.1Android studio安装与配置
参考博客地址:https://www.cnblogs.com/xiadewang/p/7820377.html
2.3Arcgis for android
Arcgis for android是ESRI公司专门为Android手机开发GIS地图软件的一套API,整合广泛的地图和GIS能力在线或离线,包括编辑,分析,地理编码,路由,网络地图管理,数据可视化,移动地图包和矢量平铺层。
2.3.1Arcgis for android SDK
https://developers.arcgis.com/android/latest/
2.3.2Arcgis for android配置
http://www.cnblogs.com/gis-luq/p/4760370.html
2.4技术架构
外业数据采集平台采用的是Android最原生也是最基础的架构,可以理解为MVC,Controller即是Activity和Fragment,但是这两者掌握了Android系统中绝大多数的资源,并且在内部直接控制View,因此传统的Android App一般是以Activity和Fragment为核心,将网络模块,数据库管理模块,文件管理模块,常用工具类等分离成若干工具类包,供Activity和Fragment使用。由于项目不是很大,最后决定采用Android默认的架构,而没有采用MVP或者MVVM架构。
3.平台展示与实例代码
3.1登录与主界面
3.1.1登录
3.1.2主界面
包含最基本的:放大、缩小、旋转、指北针、比例尺等基本功能。
3.2基础功能
3.2.1导入矢量
支持导入内置SD卡和外置SD卡中.shp格式的数据。
1 private ShapefileFeatureTable featureLayerShapefile(String filePath, String fileType, boolean 2 isFullExtent, String dapFilePath, boolean isVisible, float opacity, boolean isCheckLayerName) { 3 if (!check(filePath, "SHP", isCheckLayerName)) { 4 return null; 5 } 6 ShapefileFeatureTable shapefileFeatureTable = new ShapefileFeatureTable(filePath); 7 shapefileFeatureTable.loadAsync(); 8 ShapefileFeatureTable finalShapefileFeatureTable = shapefileFeatureTable; 9 shapefileFeatureTable.addDoneLoadingListener(() -> {10 if (finalShapefileFeatureTable.getLoadStatus() == LoadStatus.LOADED) {11 // create a feature layer to display the shapefile12 FeatureLayer featureLayer = new FeatureLayer(finalShapefileFeatureTable);13 if ("".equals(dapFilePath)) {14 featureLayer.setDescription(filePath);15 } else {16 featureLayer.setDescription(dapFilePath);17 }18 featureLayer.setVisible(isVisible);19 mMapView.getMap().getOperationalLayers().add(featureLayer);20 if (isFullExtent) {21 Envelope envelope = LayerUtil.GetLayerFullExtend(featureLayer);22 if (envelope != null && !envelope.isEmpty()) {23 mMapView.setViewpointGeometryAsync(envelope24 , LayerUtil.FullExtendPadding);25 }26 }27 } else {28 String error = "Shapefile feature table failed to load: " +29 finalShapefileFeatureTable30 .getLoadError().toString();31 Log.e(TAG, error);32 }33 });34 35 return shapefileFeatureTable;36 }
3.2.2导入影像
支持导入内置SD卡和外置SD卡中.tif影像格式的数据。
1 private Layer loadLocalTif(ArcGISMap arcGISMap, String filePath, String extendName, boolean 2 isVisible, float opacity, boolean isCheckLayerName) { 3 if (!check(filePath, extendName, isCheckLayerName)) { 4 return null; 5 } 6 Raster raster = new Raster(filePath); 7 if (raster == null) 8 return null; 9 10 final RasterLayer rasterLayer = new RasterLayer(raster);11 rasterLayer.setName("基础底图");12 rasterLayer.setDescription(filePath);13 rasterLayer.setVisible(isVisible);14 rasterLayer.setOpacity(opacity);15 Basemap basemap = new Basemap(rasterLayer);16 mMapView.getMap().setBasemap(basemap);17 rasterLayer.addDoneLoadingListener(new Runnable() {18 @Override19 public void run() {20 mMapView.setViewpointGeometryAsync(rasterLayer.getFullExtent(), LayerUtil21 .FullExtendPadding);22 mMapView.setViewpointScaleAsync(MyConfig.initialScale);23 }24 });25 mMapView.setViewpointScaleAsync(MyConfig.initialScale);26 27 return rasterLayer;28 }
3.2.3全图
将所有图层中所有要素范围作为地图的显示范围。
1 Envelope envelope = LayerUtil.GetFullExtend(mMapView);2 if (envelope != null) {3 mMapView.setViewpointGeometryAsync(envelope, LayerUtil.FullExtendPadding);4 }
3.2.4属性识别
识别最上层的点、线、面要素。
1 @Override 2 public boolean onSingleTapConfirmed(MotionEvent e) { 3 android.graphics.Point screenPoint = new android.graphics.Point((int) e.getX(), (int) e.getY()); 4 final ListenableFuture> identifyFuture = mMapView 5 .identifyLayersAsync( 6 screenPoint, 20, false, 25); 7 identifyFuture.addDoneListener(new Runnable() { 8 @Override 9 public void run() {10 try {11 List
identifyLayersResults = identifyFuture.get();12 for (IdentifyLayerResult identifyLayerResult : identifyLayersResults) {13 if (identifyLayerResult.getElements().size() > 0) {14 GeoElement topmostElement = identifyLayerResult.getElements().get(0);15 if (topmostElement instanceof Feature) {16 Feature identifiedFeature = (Feature) topmostElement;17 LayerContent layerContent = identifyLayerResult.getLayerContent();18 String layerName = "";19 if (layerContent != null)20 layerName = layerContent.getName();21 Map attrs = identifiedFeature.getAttributes();22 String[] itemArr = new String[items.size()];23 itemArr = items.toArray(itemArr);24 showIdentifyInfo(itemArr, layerName);25 break;26 }27 }28 }29 } catch (InterruptedException | ExecutionException ex) {30 }31 }32 });33 34 return true;35 }
3.2.5图层管理
包括图层顺序(置顶、置底、上移、下移)、图层标注(能标注该图层中多个字段)、图层样式(能改变图层中要素的颜色、宽度等信息)、图层移除、图层缩放到(缩放到选择图层的所有要素的范围)等功能;
1 private void loadLayers(boolean isToast,ListlistChecked) { 2 if (mapView == null) { 3 return; 4 } 5 linearLayoutContent.removeAllViews(); 6 LayerList layers = mapView.getMap().getOperationalLayers(); 7 boolean layersLoaded = initialLayers(layers, 2,listChecked); 8 if(layersLoaded) 9 {10 linearLayoutContent.addView(ViewUtil.AddDividerView(this));11 }12 Basemap basemap = mapView.getMap().getBasemap();13 LayerList baseLayers = basemap.getBaseLayers();14 boolean baseLayersLoaded = initialLayers(baseLayers, 1,listChecked);15 }16 17 /**18 * @param layers19 * @param layerType //1:底图 2:操作类20 * @return21 */22 private boolean initialLayers(LayerList layers, int layerType,List listChecked) {23 if (layers == null || layers.size() == 0) {24 return false;25 }26 for (int j = layers.size() - 1; j > -1; j--) {27 Layer layer = layers.get(j);28 boolean isChecked=false;29 if(listChecked!=null) {30 isChecked= listChecked.contains(layer.getName());31 }32 addItem(layer, layerType,isChecked);33 }34 return true;35 }
1 private void initialLineSymbol(SimpleLineSymbol lineSymbol) 2 { 3 rowFillColor.setVisibility(View.INVISIBLE); 4 rowIsFill.setVisibility(View.INVISIBLE); 5 txtSymbolColor.setText("线颜色:"); 6 txtSymbolSize.setText("线宽度:"); 7 txtType.setText("线样式:"); 8 9 btnSymbolColor.setBackgroundColor(lineSymbol.getColor());10 seekBarSymbolSize.seekBar.setProgress((int)lineSymbol.getWidth());11 initialColorPick(lineSymbol.getColor(),btnSymbolColor);12 }13 14 private SimpleLineSymbol setLineSymbol(SimpleLineSymbol lineSymbol)15 {16 lineSymbol.setColor(ViewUtil.GetButtonBackgoundColor(btnSymbolColor));17 lineSymbol.setWidth(seekBarSymbolSize.seekBar.getProgress());18 return lineSymbol;19 }
1 private void initialListView() 2 { 3 FeatureLayer featureLayer=(FeatureLayer)layer; 4 ListfieldList= featureLayer.getFeatureTable().getFields(); 5 List labelDefinitionList= featureLayer.getLabelDefinitions(); 6 boolean isLabel=featureLayer.isLabelsEnabled(); 7 for (Field field :fieldList) 8 { 9 CheckBox checkBox=new CheckBox(this);10 String fileName=field.getAlias();11 if(TextUtils.isEmpty(fileName))12 {13 fileName=field.getName();14 }15 checkBox.setText(fileName);16 checkBox.setTag(field);17 if(isLabel)18 {19 for (LabelDefinition labelDefinition:labelDefinitionList)20 {21 String jsonString =labelDefinition.toJson();22 try {23 JSONObject jObject = new JSONObject(jsonString);24 if(jObject!=null)25 {26 JSONObject jObjectItem= jObject.getJSONObject("labelExpressionInfo");27 if(jObjectItem!=null)28 {29 String expression=jObjectItem.getString("expression");30 String[] expressionContent=expression.split("\.");31 if(expressionContent.length>1)32 {33 String[] expressionField= expressionContent[1].split(";");34 if(expressionField.length>0)35 {36 if( expressionField[0].equals(field.getName()))37 {38 checkBox.setChecked(true);39 }40 }41 }42 }43 }44 } catch (JSONException e) {45 46 }47 }48 }49 50 listViewLabel.addView(checkBox);51 }52 }53 54 public void btnLabelOk_Click(View view)55 {56 FeatureLayer featureLayer = (FeatureLayer) layer;57 List checkedFieldList = getCheckedFields();58 featureLayer.getLabelDefinitions().clear();59 if(checkedFieldList!=null&&checkedFieldList.size()>0) {60 for (Field field : checkedFieldList) {61 LabelDefinition labelDefinition = LayerUtil.CreateFillLabelDefinition(field.getName());62 featureLayer.getLabelDefinitions().add(labelDefinition);63 }64 featureLayer.setLabelsEnabled(true);65 }66 else67 {68 featureLayer.setLabelsEnabled(false);69 }70 finish();71 }
3.2.6距离测量
通过手在屏幕中点击开始,双击停止,支持动态显示每段线的距离(以米为单位)。
1 public boolean onDoubleTap(MotionEvent point) { 2 3 if (geoType == GeometryType.POLYLINE)//绘制线 4 { 5 if (!lineGeometry.isSketchValid()) { 6 removeGraphic(pCurrGraphic); 7 } else { 8 updateGraphic(lineGeometry.toGeometry()); 9 }10 PartCollection partCollection = lineGeometry.getParts();11 if (partCollection.size() == 0) {12 return false;13 }14 Part part = partCollection.get(partCollection.size() - 1);15 int count = part.getPointCount();16 if (count <= 2) {17 return false;18 }19 double length = GeometryEngine.lengthGeodetic(lineGeometry.toGeometry(), new20 LinearUnit(LinearUnitId.METERS), GeodeticCurveType.GEODESIC);21 Graphic lengthGriphic = new Graphic(ptCurrent, getTextSymbol(getFormatString(length,22 2, "米"), TextSymbol.HorizontalAlignment.RIGHT, TextSymbol.VerticalAlignment23 .TOP));24 drawLayer.getGraphics().add(lengthGriphic);25 26 }27 28 }
3.2.7面积测量
通过手在屏幕中点击开始,双击停止,会形成一个面要素,并显示长度和面积(以米为单位)。
1 public boolean onDoubleTap(MotionEvent point) { 2 3 if (!polygonGeometry.isSketchValid()) { 4 removeGraphic(pCurrGraphic); 5 return true; 6 } else { 7 updateGraphic(polygonGeometry.toGeometry()); 8 } 9 10 double area = GeometryEngine.areaGeodetic(polygonGeometry.toGeometry(), new AreaUnit11 (AreaUnitId.SQUARE_METERS), GeodeticCurveType.GEODESIC);12 Graphic areaGriphic = new Graphic(ptCurrent, getTextSymbol(getFormatString(area, 2,13 "平方米"), TextSymbol.HorizontalAlignment.LEFT, TextSymbol.VerticalAlignment14 .BOTTOM));15 drawLayer.getGraphics().add(areaGriphic);16 17 double length = GeometryEngine.lengthGeodetic(polygonGeometry.toPolyline(), new18 LinearUnit(LinearUnitId.METERS), GeodeticCurveType.GEODESIC);19 Point ptShift = mapView.screenToLocation(new android.graphics.Point(Math.round(point20 .getX()), Math.round(point.getY()) + 30));21 Graphic lengthGriphic = new Graphic(ptShift, getTextSymbol(getFormatString(length,22 2, "米"), TextSymbol.HorizontalAlignment.RIGHT, TextSymbol.VerticalAlignment23 .TOP));24 drawLayer.getGraphics().add(lengthGriphic);25 26 }
3.2.8清除覆盖物
清除上面距离测量或者面积测量的图形要素。
1 if (mearsureGraphicsOveray != null) {2 mearsureGraphicsOveray.getGraphics().clear();3 }
3.2.9当前位置
点击此按钮,将GPS最近一次接受点的位置置于地图中心位置,并增加图标。
1 public boolean startGPSCurrent(boolean isRequestUpdate) { 2 if (GPSLocationManager.gpsLocationManager == null) { 3 GPSLocationManager.gpsLocationManager = GPSLocationManager.getInstances 4 (MainActivity 5 .this); 6 } 7 if (currentLocationListener == null) { 8 currentLocationListener = new MyGPSCurrentLocationListener(); 9 }10 if (isRequestUpdate)11 GPSLocationManager.gpsLocationManager.stop(2);12 //开启定位13 GPSLocationManager.gpsLocationManager.start(currentLocationListener,14 isRequestUpdate, 100, 2);15 16 return true;17 }18 19 20 21 class MyGPSCurrentLocationListener implements GPSLocationListener {22 @Override23 public void UpdateLocation(Location location) {24 if (location != null) {25 LocationUtil.AddOrUpdateLocation(MainActivity.this, location,26 locationGraphicsOveray,27 spatialReference, mMapView);28 if (GPSLocationManager.gpsLocationManager != null) {29 GPSLocationManager.gpsLocationManager.stop(2);30 }31 }32 }33 34 }
3.2.10数据导出
将采集到的点、线、面数据导出成.shp格式数据,并支持删除要素功能。
1 public void initialGridData(ShapefileFeatureTable shapefileFeatureTable, GridView gridView, 2 int gridType, Context context, boolean isDisplayToastInf) { 3 Listfileds = shapefileFeatureTable.getFields(); 4 Map gridTitle = new HashMap (); 5 6 for (String field : LayerUtil.mFields) { 7 String key = field; 8 keysList.add(key); 9 String alias = LayerUtil.GetAliasNameByFieldName(key);10 gridTitle.put(key, alias);11 }12 gridDataList.add(gridTitle);13 QueryParameters qParameters = new QueryParameters();14 String whereClause = "1=1";15 qParameters.setWhereClause(whereClause);16 final ListenableFuture future = shapefileFeatureTable17 .queryFeaturesAsync(qParameters);18 FeatureQueryResult result = null;19 try {20 result = future.get();21 Iterator features = result.iterator();22 while (features.hasNext()) {23 Feature feature = features.next();24 Map attrs = feature.getAttributes();25 26 Map dd = new HashMap ();27 for (String field : LayerUtil.mFields) {28 dd.put(field, String.valueOf(attrs.get(field)));29 }30 gridDataList.add(dd);31 }32 } catch {33 34 }35 String[] keys = keysList.toArray(new String[keysList.size()]);36 GridViewAdapter adapter = new GridViewAdapter(context, gridDataList);37 gridView.setAdapter(adapter);38 }
3.2.11系统设置
设置GPS接收坐标间隔以及地图经纬度显示格式。
1 @Override 2 public boolean onPreferenceChange(Preference preference, Object value) { 3 String stringValue = value.toString(); 4 if (preference instanceof ListPreference) { 5 ListPreference listPreference = (ListPreference) preference; 6 if (listPreference.getKey().equals(updateMapFormatKey)) { 7 PreferencesUtil.setPrefString(this.getActivity(), updateMapFormatKey, String.valueOf(stringValue)); 8 } else if (listPreference.getKey().equals(updateCollectIntervalKey)) { 9 PreferencesUtil.setPrefString(this.getActivity(), updateCollectIntervalKey, String.valueOf(stringValue));10 MyConfig.mMinTime=Long.parseLong(stringValue);11 }12 }13 14 return true;15 }
3.3高级功能
3.3.1拍照
拍取外业测量点的实况,并在照片上增加 经纬度、备注信息,信息录入完毕后,保存即可,为后续数据处理增加依据。
1 public void takePhoto(Activity activity) { 2 int currentapiVersion = android.os.Build.VERSION.SDK_INT; 3 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 4 if (hasSdcard()) { 5 SimpleDateFormat timeStampFormat = new SimpleDateFormat( 6 "yyyy_MM_dd_HH_mm_ss"); 7 String filename = timeStampFormat.format(new Date()); 8 File tempFile = new File(FileUtils.GetDefaultPath(this) + "/" + MyConfig.OutCameraDir, 9 filename + ".jpg");10 imageView.setTag(tempFile);11 if (currentapiVersion < 24) {12 imageUri = Uri.fromFile(tempFile);13 intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);14 } else {15 ContentValues contentValues = new ContentValues(1);16 contentValues.put(MediaStore.Images.Media.DATA, tempFile.getAbsolutePath());17 imageUri = activity.getContentResolver().insert(MediaStore.Images.Media18 .EXTERNAL_CONTENT_URI, contentValues);19 intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);20 }21 }22 }
3.3.2点采集
根据GPS坐标,实时生成点要素,并随位置变化而变化地图中心点,点击 “结束”会弹出属性保存界面;
3.3.3线采集
根据GPS坐标,实时生成线要素,并随位置变化而变化地图中心点,点击 “结束”会弹出属性保存界面;
3.3.4面采集
根据GPS坐标,实时生成面要素,并随位置变化而变化地图中心点,点击 “结束”会弹出属性保存界面;
3.3.5停止编辑
1 private void addOrUpdateGeometry(Location point ) { 2 if (point == null || point.isEmpty() || point.getX() == 0 || point.getY() == 0) 3 return; 4 5 6 switch (collectGeometryType) { 7 case POINT: 8 if (collectPointTable == null) { 9 return;10 }11 collectFeatures.add(pointF);12 toastGpsInfo("点采集数据", "", collectFeatures.size());13 break;14 case POLYLINE:15 if (collectLineTable == null) {16 return;17 }18 lineGeometry.addPoint(point);19 collectPoints.add(point);20 if (lineGeometry.isSketchValid()) {21 if (polylineFeature == null) {22 polylineFeature = collectLineTable.createFeature();23 polylineFeature.setGeometry(lineGeometry.toGeometry());24 collectLineTable.addFeatureAsync(polylineFeature);25 } else {26 polylineFeature.setGeometry(lineGeometry.toGeometry());27 collectLineTable.updateFeatureAsync(polylineFeature);28 }29 }30 toastGpsInfo("线采集数据", "", collectPoints.size());31 break;32 case POLYGON:33 if (collectAreaTable == null) {34 return;35 }36 polygonGeometry.addPoint(point);37 collectPoints.add(point);38 if (polygonGeometry.isSketchValid()) {39 if (polygonFeature == null) {40 polygonFeature = collectAreaTable.createFeature();41 polygonFeature.setGeometry(polygonGeometry.toGeometry());42 collectAreaTable.addFeatureAsync(polygonFeature);43 } else {44 polygonFeature.setGeometry(polygonGeometry.toGeometry());45 collectAreaTable.updateFeatureAsync(polygonFeature);46 }47 }48 toastGpsInfo("面采集数据", "", collectPoints.size());49 break;50 }51 }
3.3.6撤销
若有GPS坐标发生偏移,可以根据此功能进行撤销,一直可以撤销到第一个采集到的点。
1 private boolean deleteFirstFeature(ShapefileFeatureTable shapefileFeatureTable, Feature 2 defaultFeature) { 3 if (shapefileFeatureTable == null) 4 return false; 5 if (shapefileFeatureTable.getTotalFeatureCount() == 1) { 6 if (defaultFeature == null) 7 return false; 8 String delFid = ""; 9 Mapitem = defaultFeature.getAttributes();10 if (item != null) {11 delFid = String.valueOf(item.get(CollectEidtActivity.attrFID));12 }13 QueryParameters qParameters = new QueryParameters();14 String whereClause = CollectEidtActivity.attrFID + "=" + delFid;15 qParameters.setReturnGeometry(true);16 qParameters.setWhereClause(whereClause);17 18 final ListenableFuture featuresResult = shapefileFeatureTable19 .queryFeaturesAsync(qParameters);20 21 FeatureQueryResult features;22 try {23 features = featuresResult.get();24 if (!features.iterator().hasNext()) {25 return false;26 }27 shapefileFeatureTable28 .deleteFeaturesAsync(features).get();29 return true;30 } catch (Exception ex) {31 return false;32 }33 }34 35 return false;36 }
欢迎加入QQ沟通交流群:186178114(群名:外业数据采集(GIS+GPS)) 欢迎各位前来围观,提出建设性意见,谢谢!
查看评论 回复