ArcgisRuntime Android has been used in the project recently, so I plan to make a summary, which not only deepens my impression of this part of knowledge, but also facilitates the reference of students who are not familiar with it. The version used in this article is ArcGIS – Android :100.6.0′, the example part both refer to the official documents and API introduction, but also refer to the column of always make people want to kill me, of course, more or some of their own experience. Let’s start with the introduction

  • ArcgisAn introduction to the sample
  • Locate relevant
  • Map editor
  • Common interface
  • Map access
  • 3 d map
  • Hot like figure

A,ArcgisAn introduction to the sample

layout

<? xml version="1.0" encoding="utf-8"? > <FrameLayout 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="com.vincent.arcgisdemo.ui.MainActivity">

    <com.esri.arcgisruntime.mapping.view.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ffffff"/>

</FrameLayout>
Copy the code

code

class MainActivity :AppCompatActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main) EasyAndroid.init(this) val levelOfDetail = 16 val map = ArcGISMap( Basemap. Type. TOPOGRAPHIC, 30.671475859566514, 104.07567785156248 / / latitude, levelOfDetail / / / / precision zoom level (can only be set up, can't get, And must be greater than 0) mapview. map = map} Override funonResume() {
        mapView.resume()
        super.onResume()
    }
    override fun onPause() {
        mapView.pause()
        super.onPause()
    }
    override fun onDestroy() {
        mapView.dispose()
        super.onDestroy()
    }
}
Copy the code

Sets the map background color

Val mainBackgroundGrid = BackgroundGrid() // sets the background color mainbackgroundgrid. color = -0x1 // sets the BackgroundGrid color MainBackgroundGrid. GridLineColor = 0 x1 / / / / set the background grid line width unit (dp) mainBackgroundGrid. GridLineWidth = 0 f mapView.backgroundGrid = mainBackgroundGridCopy the code

According toArcgisBased on the map

Val levelOfDetail = 16 val map = ArcGISMap (Basemap. Type. TOPOGRAPHIC, 30.671475859566514, // Latitude 104.07567785156248,// Precision levelOfDetail// Zoom level (set only, cannot be obtained, and must be greater than 0)) mapView.map = mapCopy the code

Load the base map layer

val url = "https://www.arcgis.com/home/item.html?id=7675d44bb1e4428aa2c30a9b68f97822"
map.basemap.baseLayers.add(ArcGISTiledLayer(url))
Copy the code

Added map drawing listener

/ / add the map state changes to monitor mapView. AddDrawStatusChangedListener {/ / map Another is to draw DrawStatus IN_PROGRESSif(it. DrawStatus == DrawStatus.COMPLETED) {// Start initLocal()}}Copy the code

Map zoom out

Note that this operation is asynchronous.

. / / zoom in iv_add setOnClickListener {mapView. SetViewpointScaleAsync (mapView. MapScale * 0.5)} / / narrow map iv_reduction.setOnClickListener { mapView.setViewpointScaleAsync(mapView.mapScale * 2) }Copy the code

Second, positioning related

Since it is a map, it must involve positioning, and the following content is about positioning.

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        mapView.map = map
        initLocal()
    }
    
private fun initLocal() {/ / application runtime permissions (here to apply for positioning) EasyPermissions. Create (Manifest. Permission. WRITE_EXTERNAL_STORAGE, Manifest.permission.ACCESS_FINE_LOCATION) .callback {if(it) {/ / positioning val mLocationDisplay = mapView. LocationDisplay / / positioning display mode mLocationDisplay autoPanMode = LocationDisplay. AutoPanMode. RECENTER. / / asynchronous positioning mLocationDisplay startAsync ()}else{
                EasyToast.DEFAULT.show("Please open relevant required permissions for subsequent testing")
            }
        }
        .request(this)
}
Copy the code

  • COMPASS_NAVIGATIONPedestrian navigation

Location symbols are fixed to specific locations on the screen as the user walks and always point to the top edge of the device, best suited for WayPoint navigation.

  • NAVIGATIONVehicle navigation

Best for in-car navigation, the location symbol is fixed to a specific location on the screen and always points to the top of the screen.

  • OFF

User location symbols move with location, but the map does not

  • RECENTER

When the position symbol moves out of the “drift range”, the position symbol remains on the screen by re-centering the position symbol. (Third party explanation: When the user’s position is within the current map range, the user’s position symbol will move with the position change, but the map will not move; When the user is at the edge of the map, the map will automatically pan and the user’s current position will be in the center of the display map.

What if we then need to get the latitude and longitude information of the current anchor point and modify the marker? Next up:

val mLocationDisplay = mapView.locationDisplay mLocationDisplay.autoPanMode = LocationDisplay.AutoPanMode.RECENTER mLocationDisplay.startAsync() val pinStarBlueDrawable = ContextCompat.getDrawable(this, R.mipmap.icon_marker_blue) as BitmapDrawable? / / map image val campsiteSymbol = PictureMarkerSymbol. CreateAsync (pinStarBlueDrawable). The get () MLocationDisplay. AddLocationChangedListener {event - > / / see the returned location information EasyLog. DEFAULT. E (event. The location. The position. The toString ()) / / change the default icon mLocationDisplay defaultSymbol = campsiteSymbol / / mLocationDisplay isShowLocation =false/ / hide symbols mLocationDisplay isShowPingAnimation =false// Hide the symbol animation for position updates}Copy the code

Look at the address information:

And what we’re seeing is localization every three seconds, which is a pretty high frequency. What if we only need to locate once? Then after successful positioning can be closed:

if (mLocationDisplay.isStarted)
    mLocationDisplay.stop()
Copy the code

Note:

After several tests, it was found that closing positioning was not controlled: In the test of Hongmi mobile phone, it could not be closed after successful positioning, and it was found that direct positioning failed after using OFF positioning mode. When using Redrice 4X test, it was found that both OFF and RECENTER tests could locate successfully, but the location could not be closed all the time. The Mi 9 tested the same as the Redmi 4X. Finally, the expectation of displaying marker at the custom position is not realized, and the following exception finally occurs:

The 2019-09-29 19:59:29. 027, 2795-8055 / com. Vincent. Arcgisdemo A/art: art/runtime/indirect_reference_table.cc:137] JNI ERROR (app bug): Weak global reference table overflow (Max = 51200) 2019-09-29 19:59:29. 027, 2795-8055 / com. Vincent. Arcgisdemo A/art: art/runtime/indirect_reference_table.cc:137] weak global reference table dump: The 2019-09-29 19:59:29. 027, 2795-8055 / com. Vincent. Arcgisdemo A/art: art/runtime/indirect_reference_table.cc:137] please find table dumpindropbox: 2795-weak global reference-table-overflow-dump ... The 2019-09-29 19:59:29. 421, 2795-8055 / com. Vincent. Arcgisdemo A/art: art/runtime/runtime.cc:423] at com.vincent.arcgisdemo.ui.MainActivity.showMarker(MainActivity.kt:113) 2019-09-29 19:59:29. 421, 2795-8055 / com. Vincent. Arcgisdemo A/art: art/runtime/runtime.cc:423] at com.vincent.arcgisdemo.ui.MainActivity.access$showMarker(MainActivity. Kt: 23) 19:59:29. 2019-09-29, 421, 2795-8055 / com. Vincent. Arcgisdemo A/art: art/runtime/runtime.cc:423] at com.vincent.arcgisdemo.ui.MainActivity$initLocalThe $1The $1.onLocationChanged(MainActivity.kt:95)
Copy the code

Source:

class MainActivity : AppCompatActivity() {

    private val mGraphicsOverlay = GraphicsOverlay()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(com.vincent.arcgisdemo.R.layout.activity_main) EasyAndroid.init(this) val mainBackgroundGrid = BackgroundGrid() mainBackgroundGrid.color = -0x1 mainBackgroundGrid.gridLineColor = -0x1 mainBackgroundGrid.gridLineWidth = 0f mapView.backgroundGrid = mainBackgroundGrid val levelOfDetail = 16 val map = ArcGISMap (Basemap. The TOPOGRAPHIC, 30.671475859566514, 104.07567785156248 / / latitude, levelOfDetail / / / / precision zoom level (can only be set up, can't get, And must be greater than 0)) // val url ="https://www.arcgis.com/home/item.html?id=7675d44bb1e4428aa2c30a9b68f97822"map.basemap.baseLayers.add(ArcGISTiledLayer(url)) // val map = ArcGISMap(Basemap(ArcGISVectorTiledLayer(url))) // val vp = Viewpoint (47.606726, 122.335564, 72223.819286) / / map. InitialViewpoint = vp mapView. Map = map / / state changes to monitor mapView. Add map addDrawStatusChangedListener {/ / Drawstatus.in_progress drawStatus.in_progress drawStatus.in_progress drawStatus.in_progress drawStatus.in_progressif(it) drawStatus = = PLETED DrawStatus.COM {initLocal ()}} mapView. GraphicsOverlays. Add (mGraphicsOverlay)} / to start listening to * * * * Official documents require the following two permissions, three in the example, Difference is that the Manifest. Permission. ACCESS_COARSE_LOCATION * / private fun initLocal () = EasyPermissions. Create ( Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION ) .callback {if(it) { val mLocationDisplay = mapView.locationDisplay mLocationDisplay.autoPanMode = LocationDisplay.AutoPanMode.RECENTER mLocationDisplay.startAsync() val pinStarBlueDrawable = ContextCompat.getDrawable( this, com.vincent.arcgisdemo.R.mipmap.icon_marker_blue ) as BitmapDrawable? val campsiteSymbol = PictureMarkerSymbol.createAsync(pinStarBlueDrawable).get() MLocationDisplay. AddLocationChangedListener {event - > / / check the returned location information EasyLog.DEFAULT.e(event.location.position.toString()) mLocationDisplay.defaultSymbol = campsiteSymbol // mLocationDisplay.isShowLocation =false. / / / / hide symbols mLocationDisplay isShowPingAnimation =false// Hide the symbol animation for position updatesif (mLocationDisplay.isStarted) {
                        mLocationDisplay.stop()
                    }

                    showMarker(event.location.position.x, event.location.position.y)


                }
            } else {
                EasyToast.DEFAULT.show("Please open relevant required permissions for subsequent testing")
            }
        }
        .request(this)


    private fun showMarker(x: Double, y: Double) {
        EasyLog.DEFAULT.e("x${x}  y${y}")
        val pinStarBlueDrawable =
            ContextCompat.getDrawable(
                this,
                com.vincent.arcgisdemo.R.mipmap.icon_marker_red
            ) as BitmapDrawable?
        val campsiteSymbol = PictureMarkerSymbol.createAsync(pinStarBlueDrawable).get()
        campsiteSymbol.loadAsync()
        val attributes = HashMap<String, Any>()
        val pointGraphic =
            Graphic(Point(x, y), attributes, campsiteSymbol)
        mGraphicsOverlay.graphics.add(pointGraphic)


    }

    override fun onResume() {
        mapView.resume()
        super.onResume()
    }

    override fun onPause() {
        mapView.pause()
        super.onPause()
    }

    override fun onDestroy() {
        mapView.dispose()
        super.onDestroy()
    }
}
Copy the code

Edit the map

Basic operation

Basic operations refer to operations commonly used in maps, eight of which are described below:

  • Plot points
  • Draw a line (polyline segment)
  • Draw curves (draw directly from finger track)
  • Draw polygons
  • Draw the circle
  • Draw picturesmarker
  • Rendering text
  • Draws custom annotations

1. The plot points

The logic is very simple, is to click on the map to draw a point, so you need to set a click event on the map, but the map is not an ordinary View, see the code for specific examples:

mapView.onTouchListener = object : DefaultMapViewOnTouchListener(this,mapView){ override fun onSingleTapConfirmed(e: MotionEvent?) : Boolean { e? :super.onSingleTapConfirmed(e)if(drawType ! = -1){ e? :return falseval clickPoint = mMapView.screenToLocation( android.graphics.Point(e.x.roundToInt(),e.y.roundToInt())) when(drawType){ 0  -> MapUtil.drawPoint(clickPoint) } }returnSuper. OnSingleTapConfirmed (e)}} / / map tool object MapUtil {val mGraphicsOverlay = GraphicsOverlay () / / draw some fun drawPoint(p: Point) {/ / SimpleMarkerSymbol. Style has the following six value, Represent different shapes / / SimpleMarkerSymbol. Style. CIRCLE round / / SimpleMarkerSymbol Style. The CROSS CROSS symbols / / SimpleMarkerSymbol Style. DIAMOND Diamond / / SimpleMarkerSymbol. Style. SQUARE SQUARE / / SimpleMarkerSymbol style.css. TRIANGLE TRIANGLE / / SimpleMarkerSymbol style.css. X X shape, val  simpleMarkerSymbol = SimpleMarkerSymbol(SimpleMarkerSymbol.Style.CIRCLE, Color.RED, 20f) val graphic = Graphic(p, SimpleMarkerSymbol) / / clear on one point mGraphicsOverlay. Graphics. Clear () mGraphicsOverlay. Graphics. The add (graphic)}}Copy the code

2. Draw a line

The logic of drawing a line is similar to that of drawing points, except that points are strung together into a line. The code is shown below:

DrawPoint (drawPoint){0 -> MapUtil. DrawPoint (clickPoint) {0 -> MapUtil. DrawPoint (clickPoint)} Mpointcollection.add (p) val polyline = polyline (mPointCollection / / SimpleLineSymbol. Style line shape / / SimpleLineSymbol. Style. The DASH - / / SimpleLineSymbol. The style.css. DASH_DOT -, -, - / / SimpleLineSymbol. Style. DASH_DOT_DOT -, -, -, -, - / / SimpleLineSymbol. The style.css. DOT... / / SimpleLineSymbol. Style. NULL don't show / / SimpleLineSymbol. Style. The SOLID line val SimpleLineSymbol = SimpleLineSymbol(SimpleLineSymbol.Style.SOLID , Color.BLUE, 3f); val graphic = Graphic(polyline, simpleLineSymbol) mGraphicsOverlay.graphics.add(graphic) }Copy the code

3. Draw a curve

The logic of drawing a curve is exactly the same as that of drawing a straight line, except that the points are denser. Now that need to collect more points than draw a straight line, then line the collection point method is definitely not line, but fortunately the DefaultMapViewOnTouchListener also provides a onScroll methods, is the map scroll event callback methods, we look at the source code:

mapView.onTouchListener = object : DefaultMapViewOnTouchListener(this,mapView){ override fun onSingleTapConfirmed(e: MotionEvent?) : Boolean { e? :super.onSingleTapConfirmed(e)if(drawType ! = -1){ e? :return falseval clickPoint = mMapView.screenToLocation( android.graphics.Point(e.x.roundToInt(),e.y.roundToInt())) when(drawType){ 0  -> MapUtil.drawPoint(clickPoint) 1 -> MapUtil.drawLine(clickPoint) } }returnsuper.onSingleTapConfirmed(e) } override fun onScroll( e1: MotionEvent? , e2: MotionEvent? , distanceX: Float, distanceY: Float ): Boolean {if(drawType == 2){ e1? :return falsee2? :return falseval p1 = mMapView.screenToLocation( android.graphics.Point(e1.x.roundToInt(),e1.y.roundToInt())) val p2 = MMapView. ScreenToLocation (android. Graphics. Point (e2. X.r oundToInt (), e2, y.r oundToInt ())) MapUtil. DrawCurves (p1, p2) / / returntrueMaps will no longer scroll while slidingreturn true
                }
                returnsuper.onScroll(e1, e2, distanceX, Val mGraphicsOverlay = GraphicsOverlay() private val MPointCollection = PointCollection (SpatialReferences getWebMercator (), / / draw the curve fun drawCurves (p1: Point, p2: Point) { mPointCollection.add(p1) mPointCollection.add(p2) val polyline = Polyline(mPointCollection) val simpleLineSymbol = SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.BLUE, 3f); val graphic = Graphic(polyline, simpleLineSymbol) mGraphicsOverlay.graphics.add(graphic) } }Copy the code

From the code, we can see if we are collecting more points, and then drawing all the points with a straight line in the normal way.

4. Draw polygons

In fact, drawing a polygon is to combine lines into a surface, but there are many kinds of surface composition as well as line shapes. As it is a surface, it is demonstrated here through patterns:

  • SimpleFillSymbol.Style.BACKWARD_DIAGONAL
  • SimpleFillSymbol.Style.FORWARD_DIAGONAL
  • SimpleFillSymbol.Style.DIAGONAL_CROSS
  • SimpleFillSymbol.Style.HORIZONTAL

  • SimpleFillSymbol.Style.VERTICAL

  • SimpleFillSymbol.Style.CROSS
  • SimpleFillSymbol.Style.SOLIDColor fill

  • SimpleFillSymbol.Style.NULLWith these background fillings in mind, let’s see how polygons are drawn:
when(drawType){ 0 -> MapUtil.drawPoint(clickPoint) 1 -> MapUtil.drawLine(clickPoint) 3 -> MapUtil drawPolygon(clickPoint)} MapUtil drawPolygon(p: Point) {mGraphicsOverlay. Graphics. Clear () mPointCollection. Add (p) / / a Point can't make a faceif (mPointCollection.size == 1) {
            drawPoint(p)
            return} val polygon = Polygon(mPointCollection) val lineSymbol = SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.GREEN, 3.0 f) val simpleFillSymbol = simpleFillSymbol (simpleFillSymbol. Style. SOLID, Color, YELLOW, lineSymbol) val graphic = Graphic(polygon, simpleFillSymbol) mGraphicsOverlay.graphics.add(graphic) } }Copy the code

Effect preview:

5. Draw the circle

In fact, circle drawing and polygon drawing are different, but circle drawing is based on the center and radius to confirm the position of the circle. When we draw a circle with a compass, the position of a circle can be determined by the point where the two tips are located. It is similar here. The first point determines the location of the center of the circle, and the second point determines the radius. Based on this reasoning, we can get the following code:

when(drawType){ 0 -> MapUtil.drawPoint(clickPoint) 1 -> MapUtil.drawLine(clickPoint) 3 -> MapUtil. DrawPolygon (clickPoint) 4 -> MapUtil. DrawCircle (clickPoint)}  Point) { mGraphicsOverlay.graphics.clear()if(mpointCollection.size == 50) mpointCollection.clear () mpointCollection.add (p) // Only the center of the circle is determinedif (mPointCollection.size == 1) {
            drawPoint(p)
            return} val x = mPointCollection[0].x - mPointCollection[1].x val y = mPointCollection[0].y - Y val radius = SQRT (x.pet (2.0) + y.pet (2.0)) val Center = mPointCollection[0] Mpointcollection.clear () // Retrieves the point of the circle based on the center and radiusfor (point ingetPoints(center, Radius) {mpointCollect.add (point)} val polygon = polygon (mPointCollection) // Edge val lineSymbol = SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.GREEN, 3.0 f) / / fill style Fill Color fill border val simpleFillSymbol = simpleFillSymbol (simpleFillSymbol. Style. SOLID, Color, YELLOW, lineSymbol) val graphic = Graphic(polygon, SimpleFillSymbol) mGraphicsOverlay. Graphics. The add (graphic)} / * * * calculated through the center and radius of circular edge point set * * @ param center * @ param radius * @return*/ private fun getPoints(center: Point, radius: Double): Array<Point? > { val points = arrayOfNulls<Point>(50) var sin: Double var cos: Double var x: Double var y: Doublefor (i in0.. 40) {sine = kotlin.math. Sine (math.pi * 2.0 * I / 50) cos = kotlin.math * sin y = center.y + radius * cos points[i] = Point(x, y) }return points
    }
}
Copy the code

Draw circles After calculating points, draw circles and the specific APi and regular polygons are the same. Here is a preview of the effect:

6. Draw picturesmarker

Marker drawing a picture marker is similar to adding an icon to a fixed point on a map. It is also simple to implement:

when(drawType){ 0 -> MapUtil.drawPoint(clickPoint) 1 -> MapUtil.drawLine(clickPoint) 3 -> MapUtil.drawPolygon(clickPoint) 4 -> MapUtil.drawCircle(clickPoint) 5 -> MapUtil. DrawMarker (clickPoint,this@MainActivity)} Point, context: Context) {/ / for drawable resource val pinStarBlueDrawable = ContextCompat. GetDrawable (Context, R.mipmap.icon_marker_red) as BitmapDrawable? Val campsiteSymbol = PictureMarkerSymbol("Picture network address") val campsiteSymbol = PictureMarkerSymbol. CreateAsync (pinStarBlueDrawable). The get () / / asynchronous loading campsiteSymbol loadAsync (val) Attributes = HashMap<String, Any>() MGraphicsOverlay campsiteSymbol) / / added to the layer. The graphics. The add (pointGraphic)}}Copy the code

6. Draw text markers

Drawing text tags is as simple as drawing image tags.

when(drawType){ 0 -> MapUtil.drawPoint(clickPoint) 1 -> MapUtil.drawLine(clickPoint) 3 -> MapUtil.drawPolygon(clickPoint) 4 -> MapUtil.drawCircle(clickPoint) 5 -> MapUtil. DrawMarker (clickPoint,this@MainActivity) 6 -> MapUtil. DrawText (clickPoint) fun drawText(p: Point) {/ / horizontal has LEFT in the right/LEFT TextSymbol/level. The HorizontalAlignment. LEFT. / / level TextSymbol HorizontalAlignment. The CENTER/right/level TextSymbol. HorizontalAlignment. RIGHT / / vertical support 3 / / vertical TextSymbol VerticalAlignment. TOP / / vertical TextSymbol. VerticalAlignment. MIDDLE / / under vertical TextSymbol VerticalAlignment. BOTTOM val TextSymbol = TextSymbol (20 f,"Tagged text", Color.RED, TextSymbol.HorizontalAlignment.CENTER, TextSymbol. VerticalAlignment. MIDDLE) / / generated drawings val graphic = graphic (p, TextSymbol) / / clear before contents mGraphicsOverlay. Graphics. Clear () / / added to the layer mGraphicsOverlay graphics. The add (graphic)}}Copy the code

Results the preview

7. Draw custom annotations

Although image markers are supported and text content is supported, what if you want to add a marker that contains the image and text on top of the layer? This leads to the following custom annotations. In a simple sentence, it is to display annotation information through a custom View. The specific example is as follows:

when(drawType){ 0 -> MapUtil.drawPoint(clickPoint) 1 -> MapUtil.drawLine(clickPoint) 3 -> MapUtil.drawPolygon(clickPoint) 4 -> MapUtil.drawCircle(clickPoint) 5 -> MapUtil.drawMarker(clickPoint,this@MainActivity) 6 -> MapUtil.drawText(clickPoint) 7 -> MapUtil. DrawCallout (clickPoint,mapView,this@MainActivity)} MapView,context: Context) { val callout = mapView.calloutif(callout.isShowing){ callout.dismiss() } val view = LayoutInflater.from(context).inflate(R.layout.callout_delete_layout,  null,false)
        view.setOnClickListener {
            callout.dismiss()
            EasyToast.DEFAULT.show("Close flag")
        }
        callout.location = p
        callout.content = view
        callout.show()
    }
}
Copy the code

Preview effect (note that sharp and rounded corners are set on the map) :

Sketch editorSketchEditor

The sketch editor does not support drawing circles, other graphics are no problem, and it is very easy to use, directly read the comments:

Private val mSketchEditor = SketchEditor() instantiates the map... SketchEditor = mSketchEditor val builder2 = // add the sketchEditor to the map XPopup.Builder(this).watchView(btn_sketch) btn_sketch.setOnClickListener { builder2.asAttachList( arrayOf("Single point"."More"."Line"."Polygon"."Draw lines freehand."."Freehand polygons."."Step up"."Next step"), null
    ) { position, _ ->
        MapUtil.restDrawStatus()
        when (position) {
            0 -> mSketchEditor.start(SketchCreationMode.POINT)
            1 -> mSketchEditor.start(SketchCreationMode.MULTIPOINT)
            2 -> mSketchEditor.start(SketchCreationMode.POLYLINE)
            3 -> mSketchEditor.start(SketchCreationMode.POLYGON)
            4 -> mSketchEditor.start(SketchCreationMode.FREEHAND_LINE)
            5 -> mSketchEditor.start(SketchCreationMode.FREEHAND_POLYGON)
            6 -> if (mSketchEditor.canUndo()) mSketchEditor.undo()
            7 -> if(msketcheditor.canredo ()) msketcheditor.redo ()}}.show()} btn_rest.setonClickListener {// Reset the first drawingif(drawType ! = -1) { drawType = -1 MapUtil.restDrawStatus() }else{// Draw successfully (no graphics)if(! MSketchEditor. IsSketchValid) {/ / reset mSketchEditor. Stop ()return@set} val sketchGeometry = msketcheditor.geometry msketcheditor.stop ()if(sketchGeometry ! // Create a graphic from the sketch editor val Graphic = graphic (sketchGeometry) // Assign symbols according to the geometry typeif (graphic.geometry.geometryType == GeometryType.POLYGON) {

                    val mLineSymbol =
                        SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, 0xFF8800, 4f)
                    val mFillSymbol =
                        SimpleFillSymbol(SimpleFillSymbol.Style.CROSS, 0x40FFA9A9, mLineSymbol)
                    graphic.symbol = mFillSymbol
                } else if (graphic.geometry.geometryType == GeometryType.POLYLINE) {
                    val mLineSymbol =
                        SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, 0xFF8800, 4f)
                    graphic.symbol = mLineSymbol
                } else if(graphic.geometry.geometryType == GeometryType.POINT || graphic.geometry.geometryType == GeometryType.MULTIPOINT ) { val  mPointSymbol = SimpleMarkerSymbol( SimpleMarkerSymbol.Style.SQUARE, 0xFF0000, . 20 f) graphic symbol = mPointSymbol} / / add graphics to the graphic overlay mGraphicsOverlay. Graphics. The add (graphic)}}}Copy the code

Preview:

Coordinate calculation

The above is a simple drawing, but it is obviously inadequate for development because we do not get the exact latitude and longitude of the drawing point. For latitude and longitude, the following explanation is more appropriate:

Because only the United States has the GLOBAL positioning System (GPS) in the world at present, when we actually do the project, the coordinate data we get are usually the coordinate system established for the use of GPS global positioning system, that is, we call 84 coordinates. Based on China’s national conditions, these real coordinates have been artificially added bias processing, can be published and released. So, the background returns 84 coordinates, and in order to display the correct position on the map, you need to do a coordinate conversion. The original

In the first method above, we get the screen coordinate android.graphics.point, and then convert it to the projection coordinate (of course, map coordinates can also be converted to screen coordinates). At this time, the longitude and latitude value of this coordinate is still not correct, we also need to convert the projection coordinate to space coordinates. This is where the hero of this section comes in!

1.GeometryEngineCoordinate transformation

  • Gets default map coordinates based on projection coordinates
Point wgsPoint = (Point) GeometryEngine.project(dp, mMapView.getSpatialReference(), null);
Copy the code
  • willJ02Coordinates toG84coordinates
val projectedPoint = GeometryEngine.project(clickPoint,SpatialReference.create(4236)) as Point
// SpatialReference.create(4236) = SpatialReferences.getWgs84()
Copy the code
  • willG84Coordinates toJ02coordinates

GCJ02: Also known as Mars coordinate system, it is a geographical coordinate system developed by China National Bureau of Surveying and Mapping, and obtained by WGS84 after encryption.

Since GcJo2 is an encrypted result, it does not have the same WID specification as WSG84, but there are ways to implement it.

package com.vincent.arcgisdemo.util import com.esri.arcgisruntime.geometry.Point import kotlin.math.abs import Kotlin.math. cos import kotlin.math.sin import kotlin.math. SQRT /** * <p> @date 2019/10/10001 <p> * <p>@update 2019/10/10001 <p> * <p> 1<p> ** / object TransformUtil {/** * private fun outOfChina(lat: Double, LNG: Double): Boolean {if(LNG < 72.004 | | LNG > 137.8347) {return true
        }
        returnLat < 0.8293 | | lat > 55.8271} / / decryption latitude private fun transformLat (x: Double, y: Double) : Double {var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * SQRT (abs(x)) ret += (20.0 * sin(6.0 *) X * math.pi) + 20.0 * sin(2.0 * x * math.pi)) * 2.0/3.0 ret += (20.0 * sin(y * math.pi) + 40.0 * sin(y / 3.0 *) Math.pi)) * 2.0/3.0 ret += (160.0 * sin(y / 12.0 * math.pi) + 320 * sin(y * math.pi / 30.0)) * 2.0/3.0returnPrivate fun transformLon(x: Double, y: Double): Double {var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * SQRT (ABS (x)) ret += (20.0 * sin(6.0 * x *) Math. PI) + 20.0 * sin (2.0 * x * Math. PI)) * 2.0/3.0 ret + = (20.0 * sin (x * Math. PI) + 40.0 * sin (x / 3.0 x Math. PI)) * 2.0/3.0 ret += (150.0 * sin(x / 12.0 * math.pi) + 300.0 * sin(x / 30.0 * math.pi)) * 2.0/3.0returnRet} /** * Measure the difference between latitude and longitude * @Param Lat latitude * @Param LNG longitude * @return*/ private fun delta(lat: Double, LNG: Double): DoubleArray {val delta = DoubleArray(2) val a = 6378137.0 val EE = 0.00669342162296594323 val dLat = transformLat(LNG - Lat-35.0) val dLng = transformLon(LNG-105.0, Var magic = sin(radLat) magic = 1 - EE * magic * magic val sqrtMagic = SQRT (magic) delta[0] = dLat * 180.0 / (a * (1-EE)/(Magic * sqrtMagic) * math.pi) delta[1] = dLng * 180.0 / (a / sqrtMagic * cos(radLat) * Math.PI)returnDelta} /** * WSG84 to GCJ02 coordinates * @param Lat latitude * @Param LNG longitude */ fun wsG84toGCJ02Point(latitude:Double,longitude:Double) : Point {if (outOfChina(latitude, longitude)) {
            return Point(latitude, longitude)
        }
        val delta = delta(latitude, longitude)
          returnPoint(latitude + delta[0], Longitude longitude longitude longitude longitude longitude longitude longitude longitude longitude gcJo2toWGS84Point(latitude:Double,longitude:Double):Point {if (TransformUtil.outOfChina(latitude, longitude)) {
            return Point(latitude, longitude)
        }
        val delta = delta(latitude, longitude)
        return Point(latitude - delta[0], longitude - delta[1])
    }

}
Copy the code

Calculate the length of the

  • Calculates the length of a given polyline
GeometryEngine.length ( polyline:Polyline):Double
Copy the code
  • Calculate the geometric length of the earth

Note: LinearUnit — the unit of measurement for the return value. If it is null, then we default to linear units of Id METERS

GeometryEngine.lengthGeodetic ( geometry:Geometry,  lengthUnit:LinearUnit, curveType:GeodeticCurveType):Double
Copy the code

Calculating area

GeometryEngine.area ( polygon:Polygon):Double

GeometryEngine.area ( envelope:Envelope):Double

GeometryEngine.areaGeodetic ( geometry:Geometry,  areaUnit:AreaUnit,  curveType:GeodeticCurveType):Double
Copy the code

Computes the boundaries of a given geometry

GeometryEngine.boundary (geometry:Geometry):Geometry
Copy the code

Merges the ranges of two given geometries

GeometryEngine.com bineExtents (geometry1: Geometry, geometry2: Geometry) : EnvelopeCopy the code

Merges the range of geometric sets

Contains (Container :Geometry, within:Geometry):BooleanCopy the code

Strengthen a given geometry by drawing points between existing vertices in the geometry.

GeometryEngine.densify ( geometry:Geometry,  maxSegmentLength:Double):Geometry

GeometryEngine.densifyGeodetic ( geometry:Geometry,  maxSegmentLength:Double,lengthUnit:LinearUnit,  curveType:GeodeticCurveType):Geometry
Copy the code

Whether two graphs contain

Whether the latter includes the former

GeometryEngine.within ( within:Geometry,  container:Geometry):Boolean
Copy the code

4. Introduction of common interfaces

In the previous example we saw that the latitude and longitude of the click point is obtained by clicking on the event, so why not click on the event using the view. OnClickListener interface? Then introduce everyone including DefaultMapViewOnTouchListener interface!

  • MapScaleChangedListener

This interface has a callback method, mapScaleChanged, which is called when the scale of the map is changed. When will this interface be used? Remember when initializing the map, we set the default map zoom level to 16. What is the maximum and minimum map zoom level? It is said on the Internet that 0~20, but I did not find relevant documents, and I did not find the maximum check value when I checked the source code during the initialization process (there is a judgment that it cannot be less than 0), because the final implementation method is native method. We also didn’t find an Api to get the zoom level, so we can use this callback to handle the scale and zoom level issues to limit the zoom and prevent the map from zooming in and out indefinitely.

Note: the mapScaleChanged method is called frequently, and the scale method setViewpointScaleAsync that resets the scale is asynchronous, so the interaction here will be less than ideal. Solution within DefaultMapViewOnTouchListener finally.

  • MapRotationChangedListener

This interface also has only one callback method, the mapRotationChanged method. Called when the map is rotated, which is also a frequently invoked method. If you need to update the map after rotation, refer to this interface.

  • DefaultMapViewOnTouchListener DefaultMapViewOnTouchListenerIt is a complex implementation class. You can take a look at this diagram first:

Of course, it is not necessary to completely understand the use of every method, just need to understand their own needs.

Click onSingleTapConfirmed in the project through this method to achieve a single click callback, interested students can also learn about the onSingleTapUp method

Long press onLongPress this callback has no objection, the project has not found any problems

Double-click onDoubleTouchDrag this method name is somewhat ambiguous because it only has a double-click callback, not a two-finger swipe callback

This method can be used in conjunction with onScaleBegin to determine the current scale size when scaling. If scaling is no longer supported, consume the event directly

This method can be used in conjunction with onScaleEnd to determine the current scale size at the beginning of scaling. If scaling is no longer supported, consume the event directly

A finger swipe onScroll is the default slide event.ACTION_MOVE, but the onScroll method is called back when the slide ends

OnFling is called when the slide ends, unlike the Fling method in the View

These are the interface callbacks I used for maps in my project.

5. Access to map of heaven and earth

This is relatively simple, go to the official map of the world to register an account, and then apply for an application, and then get the key into the tool class can be used (currently found not checked signature). The renderings below use the data of Map of Heaven and Earth, not the map data of Arcgis official website.

Code:

package com.vincent.arcgisdemo.util

import com.esri.arcgisruntime.arcgisservices.LevelOfDetail
import com.esri.arcgisruntime.arcgisservices.TileInfo
import com.esri.arcgisruntime.geometry.Envelope
import com.esri.arcgisruntime.geometry.Point
import com.esri.arcgisruntime.geometry.SpatialReference
import com.esri.arcgisruntime.layers.WebTiledLayer

object TianDiTuMethodsClass {
    val key = "58a2d8db46a9ea6d009a************"
    private val SubDomain = arrayOf("t0"."t1"."t2"."t3"."t4"."t5"."t6"."t7")
    private val URL_VECTOR_2000 =
        "http://{subDomain}.tianditu.com/DataServer?T=vec_c&x={col}&y={row}&l={level}&tk=$key"
    private val URL_VECTOR_ANNOTATION_CHINESE_2000 =
        "http://{subDomain}.tianditu.com/DataServer?T=cva_c&x={col}&y={row}&l={level}&tk=$key"
    private val URL_VECTOR_ANNOTATION_ENGLISH_2000 =
        "http://{subDomain}.tianditu.com/DataServer?T=eva_c&x={col}&y={row}&l={level}&tk=$key"
    private val URL_IMAGE_2000 =
        "http://{subDomain}.tianditu.com/DataServer?T=img_c&x={col}&y={row}&l={level}&tk=$key"
    private val URL_IMAGE_ANNOTATION_CHINESE_2000 =
        "http://{subDomain}.tianditu.com/DataServer?T=cia_c&x={col}&y={row}&l={level}&tk=$key"
    private val URL_IMAGE_ANNOTATION_ENGLISH_2000 =
        "http://{subDomain}.tianditu.com/DataServer?T=eia_c&x={col}&y={row}&l={level}&tk=$key"
    private val URL_TERRAIN_2000 =
        "http://{subDomain}.tianditu.com/DataServer?T=ter_c&x={col}&y={row}&l={level}&tk=$key"
    private val URL_TERRAIN_ANNOTATION_CHINESE_2000 =
        "http://{subDomain}.tianditu.com/DataServer?T=cta_c&x={col}&y={row}&l={level}&tk=$key"

    private val URL_VECTOR_MERCATOR =
        "http://{subDomain}.tianditu.com/DataServer?T=vec_w&x={col}&y={row}&l={level}&tk=$key"
    private val URL_VECTOR_ANNOTATION_CHINESE_MERCATOR =
        "http://{subDomain}.tianditu.com/DataServer?T=cva_w&x={col}&y={row}&l={level}&tk=$key"
    private val URL_VECTOR_ANNOTATION_ENGLISH_MERCATOR =
        "http://{subDomain}.tianditu.com/DataServer?T=eva_w&x={col}&y={row}&l={level}&tk=$key"
    private val URL_IMAGE_MERCATOR =
        "http://{subDomain}.tianditu.com/DataServer?T=img_w&x={col}&y={row}&l={level}&tk=$key"
    private val URL_IMAGE_ANNOTATION_CHINESE_MERCATOR =
        "http://{subDomain}.tianditu.com/DataServer?T=cia_w&x={col}&y={row}&l={level}&tk=$key"
    private val URL_IMAGE_ANNOTATION_ENGLISH_MERCATOR =
        "http://{subDomain}.tianditu.com/DataServer?T=eia_w&x={col}&y={row}&l={level}&tk=$key"
    private val URL_TERRAIN_MERCATOR =
        "http://{subDomain}.tianditu.com/DataServer?T=ter_w&x={col}&y={row}&l={level}&tk=$key"
    private val URL_TERRAIN_ANNOTATION_CHINESE_MERCATOR =
        "http://{subDomain}.tianditu.com/DataServer?T=cta_w&x={col}&y={row}&l={level}&tk=$key"

    private val DPI = 96
    private val minZoomLevel = 1
    private val maxZoomLevel = 18
    private val tileWidth = 256
    private val tileHeight = 256
    private val LAYER_NAME_VECTOR = "vec"
    private val LAYER_NAME_VECTOR_ANNOTATION_CHINESE = "cva"
    private val LAYER_NAME_VECTOR_ANNOTATION_ENGLISH = "eva"
    private val LAYER_NAME_IMAGE = "img"
    private val LAYER_NAME_IMAGE_ANNOTATION_CHINESE = "cia"
    private val LAYER_NAME_IMAGE_ANNOTATION_ENGLISH = "eia"
    private val LAYER_NAME_TERRAIN = "ter"
    private val LAYER_NAME_TERRAIN_ANNOTATION_CHINESE = "cta"private val SRID_2000 = SpatialReference.create(4490) private val SRID_MERCATOR = SpatialReference.create(102100) Private val X_MIN_2000 = -180.0 private val Y_MIN_2000 = -90.0 private val X_MAX_2000 = 180.0 private val Y_MAX_2000 = 90.0 private val X_MIN_MERCATOR = -20037508.3427892 Private val Y_MIN_MERCATOR = -20037508.3427892 private val X_MIN_MERCATOR = -20037508.3427892 private val X_MIN_MERCATOR = -20037508.3427892 X_MAX_MERCATOR = 20037508.3427892 private val Y_MAX_MERCATOR = 20037508.3427892 private val ORIGIN_2000 = Point (180.0, 90.0, SRID_2000) private val ORIGIN_MERCATOR = Point(-20037508.3427892, 20037508.3427892, SRID_MERCATOR) private val ENVELOPE_2000 = Envelope(X_MIN_2000, Y_MIN_2000, X_MAX_2000, Y_MAX_2000, SRID_2000) private val ENVELOPE_MERCATOR = Envelope(X_MIN_MERCATOR, Y_MIN_MERCATOR, X_MAX_MERCATOR, Y_MAX_MERCATOR, SRID_MERCATOR) private val SCALES = doubleArrayOf(2.958293554545656E8, 1.479146777272828E8, 7.39573388636414E7, 3.69786694318207E7, 1.848933471591035E7, 9244667.357955175, 4622333.678977588, 2311166.839488794, 1155583.419744397, 577791.7098721985, 288895.85493609926, 144447.92746804963, 72223.96373402482, 36111.98186701241, 18055.990933506204, 9027.995466753102, 4513.997733376551, 2256.998866688275, Private val RESOLUTIONS_MERCATOR = doubleArrayOf(78271.51696402048, 39135.75848201024, 19567.87924100512, 9783.93962050256, 4891.96981025128, 2445.98490512564, 1222.99245256282, 611.49622628141, 305.748113140705, 152.8740565703525, 76.43702828517625, 38.21851414258813, 19.109257071294063, 9.554628535647032, 4.777314267823516, 2.388657133911758, 1.194328566955879, 0.5971642834779395, Private val RESOLUTIONS_2000 = doubleArrayOf(0.7031249999985, 0.351562499999994, 0.17578124999999997, 0.08789062500000014, 0.04394531250000007, 0.021972656250000007, 0.01098632812500002, 0.00549316406250001, 0.0027465820312500017, 0.0013732910156250009, 0.000686645507812499, 0.0003433227539062495, 0.00017166137695312503, 0.00008583068847656251, 0.000042915344238281406, 0.000021457672119140645, 0.000010728836059570307, 0.000005364418029785169) fun CreateTianDiTuTiledLayer(layerType: String): WebTiledLayer {return CreateTianDiTuTiledLayer(getTianDiTuLayerType(layerType));
    }

    fun CreateTianDiTuTiledLayer(layerType: LayerType): WebTiledLayer {
        var webTiledLayer: WebTiledLayer? = null
        var mainUrl = ""
        var mainName = ""
        var mainTileInfo: TileInfo? = null
        var mainEnvelope: Envelope? = null
        var mainIs2000 = false
        when (layerType) {
            LayerType.TIANDITU_VECTOR_2000 -> {
                mainUrl = URL_VECTOR_2000
                mainName = LAYER_NAME_VECTOR
                mainIs2000 = true
            }
            LayerType.TIANDITU_VECTOR_MERCATOR -> {
                mainUrl = URL_VECTOR_MERCATOR
                mainName = LAYER_NAME_VECTOR
            }
            LayerType.TIANDITU_IMAGE_2000 -> {
                mainUrl = URL_IMAGE_2000
                mainName = LAYER_NAME_IMAGE
                mainIs2000 = true
            }
            LayerType.TIANDITU_IMAGE_ANNOTATION_CHINESE_2000 -> {
                mainUrl = URL_IMAGE_ANNOTATION_CHINESE_2000
                mainName = LAYER_NAME_IMAGE_ANNOTATION_CHINESE
                mainIs2000 = true
            }
            LayerType.TIANDITU_IMAGE_ANNOTATION_ENGLISH_2000 -> {
                mainUrl = URL_IMAGE_ANNOTATION_ENGLISH_2000
                mainName = LAYER_NAME_IMAGE_ANNOTATION_ENGLISH
                mainIs2000 = true
            }
            LayerType.TIANDITU_IMAGE_ANNOTATION_CHINESE_MERCATOR -> {
                mainUrl = URL_IMAGE_ANNOTATION_CHINESE_MERCATOR;
                mainName = LAYER_NAME_IMAGE_ANNOTATION_CHINESE;
            }
            LayerType.TIANDITU_IMAGE_ANNOTATION_ENGLISH_MERCATOR -> {
                mainUrl = URL_IMAGE_ANNOTATION_ENGLISH_MERCATOR
                mainName = LAYER_NAME_IMAGE_ANNOTATION_ENGLISH
            }
            LayerType.TIANDITU_IMAGE_MERCATOR -> {
                mainUrl = URL_IMAGE_MERCATOR
                mainName = LAYER_NAME_IMAGE
            }
            LayerType.TIANDITU_VECTOR_ANNOTATION_CHINESE_2000 -> {
                mainUrl = URL_VECTOR_ANNOTATION_CHINESE_2000
                mainName = LAYER_NAME_VECTOR_ANNOTATION_CHINESE
                mainIs2000 = true
            }
            LayerType.TIANDITU_VECTOR_ANNOTATION_ENGLISH_2000 -> {
                mainUrl = URL_VECTOR_ANNOTATION_ENGLISH_2000
                mainName = LAYER_NAME_VECTOR_ANNOTATION_ENGLISH
                mainIs2000 = true} LayerType.TIANDITU_VECTOR_ANNOTATION_CHINESE_MERCATOR -> { mainUrl = URL_VECTOR_ANNOTATION_CHINESE_MERCATOR mainName =  LAYER_NAME_VECTOR_ANNOTATION_CHINESE } LayerType.TIANDITU_VECTOR_ANNOTATION_ENGLISH_MERCATOR -> { mainUrl = URL_VECTOR_ANNOTATION_ENGLISH_MERCATOR mainName = LAYER_NAME_VECTOR_ANNOTATION_ENGLISH } LayerType.TIANDITU_TERRAIN_2000  -> { mainUrl = URL_TERRAIN_2000 mainName = LAYER_NAME_TERRAIN mainIs2000 =true
            }
            LayerType.TIANDITU_TERRAIN_ANNOTATION_CHINESE_2000 -> {
                mainUrl = URL_TERRAIN_ANNOTATION_CHINESE_2000
                mainName = LAYER_NAME_TERRAIN_ANNOTATION_CHINESE
                mainIs2000 = true} LayerType.TIANDITU_TERRAIN_MERCATOR -> { mainUrl = URL_TERRAIN_MERCATOR mainName = LAYER_NAME_TERRAIN } LayerType.TIANDITU_TERRAIN_ANNOTATION_CHINESE_MERCATOR -> { mainUrl = URL_TERRAIN_ANNOTATION_CHINESE_MERCATOR mainName =  LAYER_NAME_TERRAIN_ANNOTATION_CHINESE } } val mainLevelOfDetail = mutableListOf<LevelOfDetail>(); var mainOrigin: Point? = nullif (mainIs2000) {
            for (i inminZoomLevel.. maxZoomLevel) { val item = LevelOfDetail(i, RESOLUTIONS_2000[i - 1], SCALES[i - 1]) mainLevelOfDetail.add(item) } mainEnvelope = ENVELOPE_2000 mainOrigin = ORIGIN_2000 }else {
            for (i inminZoomLevel.. maxZoomLevel) { val item = LevelOfDetail(i, RESOLUTIONS_MERCATOR[i - 1], SCALES[i - 1]) mainLevelOfDetail.add(item); } mainEnvelope = ENVELOPE_MERCATOR; mainOrigin = ORIGIN_MERCATOR; } mainTileInfo = TileInfo( DPI, TileInfo.ImageFormat.PNG24, mainLevelOfDetail, mainOrigin, mainOrigin.getSpatialReference(), tileHeight, tileWidth ) webTiledLayer = WebTiledLayer( mainUrl, SubDomain.toList(), mainTileInfo, mainEnvelope ); webTiledLayer.setName(mainName) webTiledLayer.loadAsync()return webTiledLayer
    }

    fun getTianDiTuLayerType(layerType: String): LayerType {
        returnWhen (layerType) {// Skymap vector Mercator projection map service"TIANDITU_VECTOR_MERCATOR"-> layertype. TIANDITU_VECTOR_MERCATOR"TIANDITU_VECTOR_ANNOTATION_CHINESE_MERCATOR"-> layertype. TIANDITU_VECTOR_ANNOTATION_CHINESE_MERCATOR"TIANDITU_VECTOR_ANNOTATION_ENGLISH_MERCATOR"-> layertype. TIANDITU_VECTOR_ANNOTATION_ENGLISH_MERCATOR // Skymap Image Mercator map service"TIANDITU_IMAGE_MERCATOR"-> layertype. TIANDITU_IMAGE_MERCATOR //"TIANDITU_IMAGE_ANNOTATION_CHINESE_MERCATOR"-> layertype. TIANDITU_IMAGE_ANNOTATION_CHINESE_MERCATOR"TIANDITU_IMAGE_ANNOTATION_ENGLISH_MERCATOR"-> layertype. TIANDITU_IMAGE_ANNOTATION_ENGLISH_MERCATOR // Map service for terrain Mercator projection"TIANDITU_TERRAIN_MERCATOR"-> layertype. TIANDITU_TERRAIN_MERCATOR //"TIANDITU_TERRAIN_ANNOTATION_CHINESE_MERCATOR"-> layertype. TIANDITU_TERRAIN_ANNOTATION_CHINESE_MERCATOR // Heaven and earth map vector national coordinate system 2000 map service"TIANDITU_VECTOR_2000"-> layertype. TIANDITU_VECTOR_2000 // Map vector country 2000 coordinate System Chinese annotation"TIANDITU_VECTOR_ANNOTATION_CHINESE_2000"-> layertype. TIANDITU_VECTOR_ANNOTATION_CHINESE_2000"TIANDITU_VECTOR_ANNOTATION_ENGLISH_2000"-> layertype. TIANDITU_VECTOR_ANNOTATION_ENGLISH_2000 // Map service of national coordinate system 2000"TIANDITU_IMAGE_2000"-> layertype. TIANDITU_IMAGE_2000 // Map image country 2000 Coordinates Chinese annotation"TIANDITU_IMAGE_ANNOTATION_CHINESE_2000"-> layertype. TIANDITU_IMAGE_ANNOTATION_CHINESE_2000"TIANDITU_IMAGE_ANNOTATION_ENGLISH_2000"-> layertype. TIANDITU_IMAGE_ANNOTATION_ENGLISH_2000 // Map service of national coordinate system 2000"TIANDITU_TERRAIN_2000"-> layertype. TIANDITU_TERRAIN_2000 // Map terrain country 2000 coordinate system Chinese annotation"TIANDITU_TERRAIN_ANNOTATION_CHINESE_2000" -> LayerType.TIANDITU_TERRAIN_ANNOTATION_CHINESE_2000
            else-> LayerType.TIANDITU_VECTOR_2000}}} enum class LayerType {/** ** TIANDITU_VECTOR_MERCATOR, /** ** Tianditu_annotation_chinese_mercator, /** ** Tianditu_annotation_english_mercator, /** ** TIANDITU_IMAGE_MERCATOR, /** * TIANDITU_IMAGE_ANNOTATION_CHINESE_MERCATOR, /** ** TIANDITU_IMAGE_ANNOTATION_ENGLISH_MERCATOR, /** ** Mercator, /** ** TIANDITU_TERRAIN_ANNOTATION_CHINESE_MERCATOR, /** ** TIANDITU_VECTOR_2000, /** * Tianditu_annotation_chinese_2000, /** ** Tianditu_annotation_english_2000, /** ** TIANDITU_IMAGE_2000, /** * TIANDITU_IMAGE_ANNOTATION_CHINESE_2000, /** ** TIANDITU_IMAGE_ANNOTATION_ENGLISH_2000, /** ** TIANDITU_TERRAIN_2000, /** * TIANDITU_TERRAIN_ANNOTATION_CHINESE_2000}  override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... / / note: Val RequestConfiguration = RequestConfiguration() requestConfiguration.getHeaders().put("referer"."http://www.arcgis.com"); webTiledLayer.setRequestConfiguration(requestConfiguration) webTiledLayer1.setRequestConfiguration(requestConfiguration)  webTiledLayer.loadAsync() webTiledLayer1.loadAsync() val basemap = Basemap(webTiledLayer) basemap.getBaseLayers().add(webTiledLayer1) map.basemap = basemap mapView.map = map ... }Copy the code

Six, three-dimensional map

SceneViewThe sample

Arcgisruntime uses a GeoView class as the base class of the map directly inherits from the ViewGroup, and then MapView and SceneView inherit from the GeoView as the containers of 2d and 3D maps respectively. Actually, just think of SceneView as a MapView, and think of ArcGISScene as an ArcGISMap.

Code examples:

<? xml version="1.0" encoding="utf-8"? > <androidx.constraintlayout.widget.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=".ui.SceneViewActivity">

    <com.esri.arcgisruntime.mapping.view.SceneView
        android:id="@+id/sceneview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </com.esri.arcgisruntime.mapping.view.SceneView>

</androidx.constraintlayout.widget.ConstraintLayout>

class SceneViewActivity : AppCompatActivity() {

    private val brest_buildings =
        " http://tiles.arcgis.com/tiles/P3ePLMYs2RVChkJx/arcgis/rest/services/Buildings_Brest/SceneServer"
    private val elevation_image_service =
        "http://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(com.vincent.arcgisdemo.R.layout.activity_scene_view)
        val arcGISScene = ArcGISScene()
        sceneview.scene = arcGISScene


    }


    override fun onResume() {
        super.onResume()
        sceneview.resume()
    }


    override fun onPause() {
        super.onPause()
        sceneview.pause()
    }

    override fun onDestroyDispose () {super.ondestroy () sceneViet.dispose ()}} Override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val arcGISTiledLayer = ArcGISTiledLayer("https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer")
        arcGISScene.basemap = Basemap(arcGISTiledLayer)
        sceneview.scene = arcGISScene


    }
       
Copy the code

Show a THREE-DIMENSIONAL scene

3d visualizes data information from a perspective close to the real world. The use of 3D scenes is similar to MapView and ArcGISMap, and 2d data can be added into 3D scenes. Different from 2D, 3D scenes have elevation surface.

Elevation free surface (elevation surface)

private val brest_buildings =
        " http://tiles.arcgis.com/tiles/P3ePLMYs2RVChkJx/arcgis/rest/services/Buildings_Brest/SceneServer"override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val arcGISScene = ArcGISScene() arcGISScene.basemap = Basemap.createImagery() sceneview.scene = arcGISScene val sceneLayer = ArcGISSceneLayer(brest_buildings) ArcGISScene. OperationalLayers. Add (sceneLayer) / / set the 3 d scene view lens (camera) / / latitude - the latitude; It could be negative // //longitude; It could be negative // // Alt-altitude; Possibly negative // //heading -- camera horizontal; Can be negative // //pitch -- camera vertical orientation; // //roll- camera = camera (48.378, -4.494, 200.0, 345.0, 65.0, 0.0) sceneview. SetViewpointCamera (camera)}Copy the code

Using an elevation surface (ArcGISTiledElevationSource,RasterElevationSource)

ArcGISTiledElevationSource: online services as surface elevation

RasterElevationSource: Use the local DEM file as the elevation surface

private val elevation_image_service =
        "http://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val arcGISScene = ArcGISScene() arcGISScene.basemap = Basemap.createImagery() sceneview.scene = arcGISScene val elevationSource = ArcGISTiledElevationSource(elevation_image_service) ArcGISScene. BaseSurface. ElevationSources. Add (elevationSource) / / set the 3 d scene view camera (camera) / / latitude - the latitude; It could be negative // //longitude; It could be negative // // Alt-altitude; Possibly negative // //heading -- camera horizontal; Can be negative // //pitch -- camera vertical orientation; Is not necessarily a negative / / / / roll - val rotation Angle camera = camera (28.4, 83.9, 10010.0, 10.0, 80.0, 0.0) sceneview. SetViewpointCamera (camera)}Copy the code

The experience of using and not using the high-level surface is similar to whether the height has changed or not. Interested students can try it for themselves.

For the rest, refer directly to the documentation:

  • According to the scene
  • Create a new scene
  • Navigation scene view
  • Adds features and graphics to the scene view
  • Follow the graphics in the scene view
  • Display the scene in augmented reality

7. Thermal image

The sample
API

  • GeoprocessingJobGeoprocessing jobs are used to run geoprocessing tasks on services
  • GeoprocessingParametersGeoprocessing parameters contain input parameters that are sent to the target geoprocessing task
  • GeoprocessingResultOutput parameters returned from the service
  • GeoprocessingTaskUsed to run aswebGeographic processing tasks for service publishing

You can view the specific effect through the code:

package com.vincent.arcgisdemo.ui

import android.app.DatePickerDialog
import android.app.Dialog
import android.app.ProgressDialog
import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.esri.arcgisruntime.concurrent.Job
import com.esri.arcgisruntime.geometry.Point
import com.esri.arcgisruntime.geometry.SpatialReference
import com.esri.arcgisruntime.mapping.ArcGISMap
import com.esri.arcgisruntime.mapping.Basemap
import com.esri.arcgisruntime.mapping.Viewpoint
import com.esri.arcgisruntime.tasks.geoprocessing.GeoprocessingJob
import com.esri.arcgisruntime.tasks.geoprocessing.GeoprocessingString
import com.esri.arcgisruntime.tasks.geoprocessing.GeoprocessingTask
import com.haoge.easyandroid.easy.EasyToast
import com.vincent.arcgisdemo.R
import kotlinx.android.synthetic.main.activity_hot_spots.*
import kotlinx.android.synthetic.main.custom_alert_dialog.view.*
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*


class HotSpotsActivity : AppCompatActivity() {
    private val TAG = HotSpotsActivity::class.java.simpleName

    private lateinit var mGeoprocessingTask: GeoprocessingTask
    val hotspot_911_calls =
        "https://sampleserver6.arcgisonline.com/arcgis/rest/services/911CallsHotspot/GPServer/911%20Calls%20Hotspot"

    private lateinit var mMinDate: Date
    private lateinit var mMaxDate: Date
    private var canceled: Boolean = false
    private lateinit var mGeoprocessingJob: GeoprocessingJob
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_hot_spots) val map = ArcGISMap(Basemap.createTopographic()) val center = Point(13671170.0, 5693633.0, SpatialReference. Create (3857)) Map.initialViewpoint = Viewpoint(Center, 57779.0) mapView. Map = map mGeoprocessingTask = GeoprocessingTask (hotspot_911_calls) mGeoprocessingTask. LoadAsync () CalendarButton. SetOnClickListener {showDateRangeDialog ()} showDateRangeDialog ()} / hot time interval * * * * choice analysis/private funshowDateRangeDialog() {
        // create custom dialog
        val dialog = Dialog(this)
        val dialogView =
            LayoutInflater.from(this).inflate(R.layout.custom_alert_dialog, null, false)
        dialog.setContentView(dialogView)
        dialog.setCancelable(true)

        try {
            val mSimpleDateFormatter = SimpleDateFormat("yyyy-MM-dd", Locale.US)

            // set default date range for the data set
            mMinDate = mSimpleDateFormatter.parse("1998-01-01")
            mMaxDate = mSimpleDateFormatter.parse("1998-05-31")
        } catch (e: ParseException) {
            Log.e(TAG, "Error in date format: " + e.message)
        }
        dialogView.fromDateText.setOnClickListener {
            showCalendar(InputCalendar.From, dialogView)
        }
        dialogView.toDateText.setOnClickListener {
            showCalendar(InputCalendar.To, dialogView)
        }

        // ifbutton is clicked, close the custom dialog dialogView.analyzeButton.setOnClickListener { analyzeHotspots( dialogView.fromDateText.text.toString(), DialogView. ToDateText. Text. The toString ()) dialog. Dismiss ()} dialog. The show ()} / * * * display the date selector dialog, */ Private Fun showCalendar(inputCalendar: inputCalendar, dialogView: View) {// create a dateset listener
        val onDateSetListener =
            DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth ->
                // build the correct date format for the query
                val date = StringBuilder()
                    .append(year)
                    .append("-")
                    .append(month + 1)
                    .append("-")
                    .append(dayOfMonth)
                // set the date to correct text view
                if (inputCalendar === InputCalendar.From) {
                    dialogView.fromDateText.setText(date)
                    try {
                        // limit the min date to after from date
                        val mSimpleDateFormatter = SimpleDateFormat("yyyy-MM-dd", Locale.US)
                        mMinDate = mSimpleDateFormatter.parse(date.toString())
                    } catch (e: ParseException) {
                        e.printStackTrace()
                    }

                } else if (inputCalendar === InputCalendar.To) {
                    dialogView.toDateText.setText(date)
                    try {
                        // limit the maximum date to before the to date
                        val mSimpleDateFormatter = SimpleDateFormat("yyyy-MM-dd", Locale.US)
                        mMaxDate = mSimpleDateFormatter.parse(date.toString())
                    } catch (e: ParseException) {
                        e.printStackTrace()
                    }

                }
            }

        // define the date picker dialog
        val calendar = Calendar.getInstance()
        val datePickerDialog = DatePickerDialog(
            this,
            onDateSetListener,
            calendar.get(Calendar.YEAR),
            calendar.get(Calendar.MONTH),
            calendar.get(Calendar.DAY_OF_MONTH)
        )
        datePickerDialog.datePicker.minDate = mMinDate.time
        datePickerDialog.datePicker.maxDate = mMaxDate.time
        if(inputCalendar === InputCalendar.From) { // start from calendar from min date datePickerDialog.updateDate(1998, 0, 1)} datepickerdialog.show ()} /** * After the work is completed, load the generated ArcGISMapImageLayer onto the map and reset the viewpoint of the MapView. */ private fun analyzeHotspots(from: String, to: String) {// Cancel mGeoprocessingJob last request mgeoProcessingJob.cancel () // Result generates a map image layer. Remove any of the layer is added to the map before mapView. Map. OperationalLayers. The clear () / /set canceled flag to false
        canceled = false

        // parameters
        val paramsFuture = mGeoprocessingTask.createDefaultParametersAsync()
        paramsFuture.addDoneListener {
            val geoprocessingParameters = paramsFuture.get()
            geoprocessingParameters.processSpatialReference = mapView.spatialReference
            geoprocessingParameters.outputSpatialReference = mapView.spatialReference

            val queryString = StringBuilder("(\"DATE\" > date '")
                .append(from)
                .append(" 00:00:00' AND \"DATE\" < date '")
                .append(to)
                .append("00:00:00)")

            geoprocessingParameters.inputs["Query"] = GeoprocessingString(queryString.toString())
            // create job
            mGeoprocessingJob = mGeoprocessingTask.createJob(geoprocessingParameters)

            // start job
            mGeoprocessingJob.start()

            // create a dialog to show progress of the geoprocessing job
            val progressDialog = ProgressDialog(this)
            progressDialog.setTitle("Geographic information processing.")
            progressDialog.isIndeterminate = false
            progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
            progressDialog.max = 100
            progressDialog.setCancelable(false)
            progressDialog.setButton(
                DialogInterface.BUTTON_NEGATIVE, "Cancel") { dialog, _ -> dialog? .dismiss() //set canceled flag to true
                canceled = true
                mGeoprocessingJob.cancel()
            }
            progressDialog.show()
            // update progress
            mGeoprocessingJob.addProgressChangedListener {
                progressDialog.progress = mGeoprocessingJob.progress
            }
            mGeoprocessingJob.addJobDoneListener {
                progressDialog.dismiss()
                if (mGeoprocessingJob.status == Job.Status.SUCCEEDED) {
                    Log.i(TAG, "Job succeeded.") val hotspotMapImageLayer = mGeoprocessingJob.result? .mapImageLayer // add the new layer to the map mapView.map.operationalLayers.add(hotspotMapImageLayer) hotspotMapImageLayer? .addDoneLoadingListener { //set the map viewpoint to the MapImageLayer, once loaded
                        mapView.setViewpointGeometryAsync(hotspotMapImageLayer.fullExtent)
                    }
                } else if (canceled) {
                    EasyToast.DEFAULT.show("Job canceled")
                    Log.i(TAG, "Job cancelled.")}else {
                    EasyToast.DEFAULT.show("Job did not succeed!")
                    Log.e(TAG, "Job did not succeed!")
                }
            }
        }
    }


    override fun onResume() {
        mapView.resume()
        super.onResume()
    }

    override fun onPause() {
        mapView.pause()
        super.onPause()
    }

    override fun onDestroy() {
        mapView.dispose()
        super.onDestroy()
    }
}

enum class InputCalendar {
    From,
    To
}

Copy the code

Because this part of the content has not been practiced in the project, it can only be simple to find the official example to view the specific use and relevant source notes, if you have this aspect of the use of experience, welcome to leave a message to exchange!


The source code

portal

Reference:

The official documentation

The API is introduced

arcgis-runtime-samples-android

ArcGIS for Android 100.3.0