平臺目的:該平臺主要用於室外,通過平板或者手機接收GPS坐標,實時繪製點、線、面數據,以便後續進行海域監測、土地確權、地圖測量提供有效數據和依據。 技術路線:Android studio3.0.1+Arcgis for android 100.2.1+GPS ...
歡迎加入QQ溝通交流群:186178114(群名:外業數據採集(GIS+GPS))
外業數據採集平臺
1. 綜述
在室外,通過平板或者手機接收GPS坐標,實時繪製點、線、面數據,以便為後續進行海域監測、土地確權、地圖繪圖提供有效數據和依據。
2. 技術路線
Android studio3.0.1+Arcgis for android 100.2.1+GPS
2.1 Android studio工具:
2.2 Android studio工具下載地址:
http://www.android-studio.org/index.php/download/hisversion
2.2.1 Android studio安裝與配置
參考博客地址:https://www.cnblogs.com/xiadewang/p/7820377.html
2.3 Arcgis for android
Arcgis for android是ESRI公司專門為Android手機開發GIS地圖軟體的一套API,整合廣泛的地圖和GIS能力線上或離線,包括編輯,分析,地理編碼,路由,網路地圖管理,數據可視化,移動地圖包和矢量平鋪層。
2.3.1 Arcgis for android SDK
https://developers.arcgis.com/android/latest/
2.3.2 Arcgis 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 shapefile 12 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(envelope 24 , LayerUtil.FullExtendPadding); 25 } 26 } 27 } else { 28 String error = "Shapefile feature table failed to load: " + 29 finalShapefileFeatureTable 30 .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 @Override 19 public void run() { 20 mMapView.setViewpointGeometryAsync(rasterLayer.getFullExtent(), LayerUtil 21 .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<List<IdentifyLayerResult>> identifyFuture = mMapView 5 .identifyLayersAsync( 6 screenPoint, 20, false, 25); 7 identifyFuture.addDoneListener(new Runnable() { 8 @Override 9 public void run() { 10 try { 11 List<IdentifyLayerResult> 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<String, Object> 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,List<String> listChecked) { 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 layers 19 * @param layerType //1:底圖 2:操作類 20 * @return 21 */ 22 private boolean initialLayers(LayerList layers, int layerType,List<String> 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 List<Field> fieldList= featureLayer.getFeatureTable().getFields(); 5 List<LabelDefinition> 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<Field> 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 else 67 { 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(), new 20 LinearUnit(LinearUnitId.METERS), GeodeticCurveType.GEODESIC); 21 Graphic lengthGriphic = new Graphic(ptCurrent, getTextSymbol(getFormatString(length, 22 2, "米"), TextSymbol.HorizontalAlignment.RIGHT, TextSymbol.VerticalAlignment 23 .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 AreaUnit 11 (AreaUnitId.SQUARE_METERS), GeodeticCurveType.GEODESIC); 12 Graphic areaGriphic = new Graphic(ptCurrent, getTextSymbol(getFormatString(area, 2, 13 "平方米"), TextSymbol.HorizontalAlignment.LEFT, TextSymbol.VerticalAlignment 14 .BOTTOM)); 15 drawLayer.getGraphics().add(areaGriphic); 16 17 double length = GeometryEngine.lengthGeodetic(polygonGeometry.toPolyline(), new 18 LinearUnit(LinearUnitId.METERS), GeodeticCurveType.GEODESIC); 19 Point ptShift = mapView.screenToLocation(new android.graphics.Point(Math.round(point 20 .getX()), Math.round(point.getY()) + 30)); 21 Graphic lengthGriphic = new Graphic(ptShift, getTextSymbol(getFormatString(length, 22 2, "米"), TextSymbol.HorizontalAlignment.RIGHT, TextSymbol.VerticalAlignment 23 .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 @Override 23 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