ArcGIS Runtime Android 開發總結

最近在項目中使用到了ArcgisRuntime Android,所以打算作一份總結,既加深了本身對該部分知識的印象,也能夠方便不熟悉的同窗參考。本文使用的版本是arcgis-android:100.6.0',示例部分既有參照官方文檔API介紹,也有參考 總有刁民想殺寡人的專欄,固然更多的仍是本身使用的一些心得。下面開始正文介紹html

  • Arcgis入門示例
  • 定位相關
  • 編輯地圖
  • 經常使用接口
  • 天地地圖接入
  • 三維地圖
  • 熱像圖

1、Arcgis入門示例

標準地圖

佈局java

<?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>
複製代碼

代碼android

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,//精度
            levelOfDetail//縮放級別(只能設置,不能獲取,且必須大於0)
        )
        mapView.map = map
    }
    override fun onResume() {
        mapView.resume()
        super.onResume()
    }
    override fun onPause() {
        mapView.pause()
        super.onPause()
    }
    override fun onDestroy() {
        mapView.dispose()
        super.onDestroy()
    }
}
複製代碼

設置地圖背景顏色

val mainBackgroundGrid = BackgroundGrid()
// 設置背景顏色
mainBackgroundGrid.color = -0x1
// 設置背景格子線顏色
mainBackgroundGrid.gridLineColor = -0x1
// // 設置背景格子線寬度 單位(dp)
mainBackgroundGrid.gridLineWidth = 0f

mapView.backgroundGrid = mainBackgroundGrid
複製代碼

顯示Arcgis基礎地圖

val levelOfDetail = 16
        val map = ArcGISMap(
            Basemap.Type.TOPOGRAPHIC, 30.671475859566514, //緯度
            104.07567785156248,//精度
            levelOfDetail//縮放級別(只能設置,不能獲取,且必須大於0)
        )
        mapView.map = map
複製代碼

加載基礎地圖的圖層

val url = "https://www.arcgis.com/home/item.html?id=7675d44bb1e4428aa2c30a9b68f97822"
map.basemap.baseLayers.add(ArcGISTiledLayer(url))
複製代碼

添加地圖繪製監聽

// 添加地圖狀態改變監聽
        mapView.addDrawStatusChangedListener {
            // 地圖繪製完成 另外一種就是繪製中 DrawStatus.IN_PROGRESS
            if (it.drawStatus == DrawStatus.COMPLETED) {
                // 開始定位
                initLocal()
            }
        }
複製代碼

地圖放大縮小

注意,此操做是異步。git

// 放大地圖
iv_add.setOnClickListener {
    mapView.setViewpointScaleAsync(mapView.mapScale * 0.5)
}
// 縮小地圖
iv_reduction.setOnClickListener {
    mapView.setViewpointScaleAsync(mapView.mapScale * 2)
}
複製代碼

2、定位相關

既然是地圖,那麼必定會涉及到定位,接下來的內容就是關於定位的相關內容。github

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        mapView.map = map
        initLocal()
    }
    
private fun initLocal() {
    // 申請運行時權限(此處申請定位便可)
    EasyPermissions.create(
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.ACCESS_FINE_LOCATION)
        .callback {
            if(it){
                // 開始定位
                val mLocationDisplay = mapView.locationDisplay
                // 定位顯示的模式
                mLocationDisplay.autoPanMode = LocationDisplay.AutoPanMode.RECENTER
                // 開始異步定位
                mLocationDisplay.startAsync()
            }else{
                EasyToast.DEFAULT.show("請打開相關所須要的權限,供後續測試")
            }
        }
        .request(this)
}
複製代碼

默認定位
其中定位顯示模式有以下四種:

  • COMPASS_NAVIGATION 步行導航

當用戶步行時,位置符號被固定在屏幕上的特定位置,而且老是指向設備的頂部邊緣,最適合於waypoint導航。web

  • NAVIGATION 車載導航

最適合車內導航,位置符號固定在屏幕上的特定位置,而且始終指向屏幕頂部。api

  • OFF

用戶位置符號會隨位置變化而移動,但地圖不會動bash

  • RECENTER

當位置符號移動到「漂移範圍」以外時,經過從新調整位置符號的中心,將位置符號保留在屏幕上。(第三方解釋:當用戶位置處於當前地圖範圍內時候,用戶位置符號會隨位置變化而移動,但地圖不會動;當用戶位置處於地圖邊緣時候,地圖會自動平移是用戶的當前位置從新居於顯示地圖中心。)網絡

若是接下來須要獲取當前定位點的經緯度信息,還要修改marker呢?接下來再看:app

val mLocationDisplay = mapView.locationDisplay
mLocationDisplay.autoPanMode = LocationDisplay.AutoPanMode.RECENTER
mLocationDisplay.startAsync()
val pinStarBlueDrawable =
    ContextCompat.getDrawable(this, R.mipmap.icon_marker_blue) as BitmapDrawable?
// 地圖圖形圖像
val campsiteSymbol = PictureMarkerSymbol.createAsync(pinStarBlueDrawable).get()
mLocationDisplay.addLocationChangedListener {event ->
    // 查看返回的定位信息
    EasyLog.DEFAULT.e(event.location.position.toString())
    // 修改默認圖標
    mLocationDisplay.defaultSymbol = campsiteSymbol
    // mLocationDisplay.isShowLocation = false//隱藏符號
    mLocationDisplay.isShowPingAnimation = false//隱藏位置更新的符號動畫
}
複製代碼

看看地址信息:

定位日誌

咱們看到的是定位是每三秒一次定位,這個頻率仍是挺高的。若是咱們只須要定位一次呢?那在定位成功之後關閉定位便可:

if (mLocationDisplay.isStarted)
    mLocationDisplay.stop()
複製代碼

注意:

通過屢次測試,發現關閉定位是不受控制的:在紅米手機測試時,定位成功之後沒法關閉,使用OFF定位模式之後,發現直接定位失敗;使用紅米4X測試時,發現OFFRECENTER下測試都能成功定位,可是始終沒法關閉定位;小米9測試結果和紅米4X一致。最終在自定義位置顯示marker的指望沒有實現,且最終發生了下面的異常:

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:
2019-09-29 19:59:29.027 2795-8055/com.vincent.arcgisdemo A/art: art/runtime/indirect_reference_table.cc:137] please find table dump in dropbox: 2795-weak global reference-table-overflow-dump
...
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)
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$initLocal$1$1.onLocationChanged(MainActivity.kt:95)
複製代碼

源碼:

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.Type.TOPOGRAPHIC, 30.671475859566514, //緯度
            104.07567785156248,//精度
            levelOfDetail//縮放級別(只能設置,不能獲取,且必須大於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
        // 添加地圖狀態改變監聽
        mapView.addDrawStatusChangedListener {
            // 繪製完成 另外一種就是繪製中 DrawStatus.IN_PROGRESS
            if (it.drawStatus == DrawStatus.COMPLETED) {
                initLocal()
            }
        }


        mapView.graphicsOverlays.add(mGraphicsOverlay)


    }

    /**
     * 開始監聽
     * 官方文檔是須要下面兩種權限,示例中是三種權限,區別在於 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 ->
                    // 查看返回的定位信息
                    EasyLog.DEFAULT.e(event.location.position.toString())
                    mLocationDisplay.defaultSymbol = campsiteSymbol
//                    mLocationDisplay.isShowLocation = false//隱藏符號
//                    mLocationDisplay.isShowPingAnimation = false//隱藏位置更新的符號動畫
                    if (mLocationDisplay.isStarted) {
                        mLocationDisplay.stop()
                    }

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


                }
            } else {
                EasyToast.DEFAULT.show("請打開相關所須要的權限,供後續測試")
            }
        }
        .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()
    }
}
複製代碼

3、編輯地圖

基本操做

基本操做指的是地圖中經常使用的操做,下面介紹其中八種:

  • 繪製點
  • 繪製直線(折線段)
  • 繪製曲線(直接根據手指軌跡進行繪製)
  • 繪製多邊形
  • 繪製圓
  • 繪製圖片marker
  • 繪製文字
  • 繪製自定義標註

1.繪製點

邏輯很簡單,就是在地圖上點擊一下繪製一個點,所以須要對地圖設置一個點擊事件,可是地圖不是普通的View,具體示例見代碼:

mapView.onTouchListener = object : DefaultMapViewOnTouchListener(this,mapView){
            override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
                e?:super.onSingleTapConfirmed(e)
                if(drawType != -1){
                     e?:return false
                    val clickPoint = mMapView.screenToLocation( android.graphics.Point(e.x.roundToInt(),e.y.roundToInt()))
                    when(drawType){
                        0 ->  MapUtil.drawPoint(clickPoint)
                    }
                }

                return super.onSingleTapConfirmed(e)
            }
        }
        
// 地圖工具類
object MapUtil {
    val mGraphicsOverlay = GraphicsOverlay()


    // 繪製點
    fun drawPoint(p: Point) {
        //SimpleMarkerSymbol.Style有以下六個值,分別表明不一樣形狀
        // SimpleMarkerSymbol.Style.CIRCLE 圓
        // SimpleMarkerSymbol.Style.CROSS  十字符號
        // SimpleMarkerSymbol.Style.DIAMOND 鑽石
        // SimpleMarkerSymbol.Style.SQUARE 方形
        // SimpleMarkerSymbol.Style.TRIANGLE 三角形
        // SimpleMarkerSymbol.Style.X       X形狀
        val simpleMarkerSymbol = SimpleMarkerSymbol(SimpleMarkerSymbol.Style.CIRCLE, Color.RED, 20f)
        val graphic = Graphic(p, simpleMarkerSymbol)
        //清除上一個點
        mGraphicsOverlay.graphics.clear()
        mGraphicsOverlay.graphics.add(graphic)
    }
}
複製代碼

2.繪製直線

繪製直線和繪製點的邏輯相似,只是將點串成一條線 ,代碼如圖:

when(drawType){
        0 ->  MapUtil.drawPoint(clickPoint)
        1 -> MapUtil.drawLine(clickPoint)
    
    }
    
// 繪製直線
fun drawLine(p: Point) {
    // 保存點
    mPointCollection.add(p)
    val polyline = Polyline(mPointCollection)

    //點 可不繪製
    drawPoint(p)

    //線
    //SimpleLineSymbol.Style 線段形狀
    // SimpleLineSymbol.Style.DASH  - - - -
    // SimpleLineSymbol.Style.DASH_DOT--·--·--
    // SimpleLineSymbol.Style.DASH_DOT_DOT --··--·----··--·--
    // SimpleLineSymbol.Style.DOT ..........
    // SimpleLineSymbol.Style.NULL 不顯示
    // SimpleLineSymbol.Style.SOLID  直線
    val simpleLineSymbol = SimpleLineSymbol(SimpleLineSymbol.Style.SOLID , Color.BLUE, 3f);
    val graphic = Graphic(polyline, simpleLineSymbol)
    mGraphicsOverlay.graphics.add(graphic)
}
複製代碼

3.繪製曲線

繪製曲線和繪製直線的邏輯徹底一致,不一致的地方在於點更密集。既然須要採集比繪製直線更多的點,那麼直線這個收集點的方法確定不行了,但幸運的是這個DefaultMapViewOnTouchListener還提供了一個onScroll方法,就是地圖滾動事件的回調方法,咱們看看源碼:

mapView.onTouchListener = object : DefaultMapViewOnTouchListener(this,mapView){
            override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
                e?:super.onSingleTapConfirmed(e)
                if(drawType != -1){
                    e?:return false
                    val clickPoint = mMapView.screenToLocation( android.graphics.Point(e.x.roundToInt(),e.y.roundToInt()))
                    when(drawType){
                        0 ->  MapUtil.drawPoint(clickPoint)
                        1 -> MapUtil.drawLine(clickPoint)
                    }
                }

                return super.onSingleTapConfirmed(e)
            }

            override fun onScroll(
                e1: MotionEvent?,
                e2: MotionEvent?,
                distanceX: Float,
                distanceY: Float
            ): Boolean {
                if(drawType == 2){
                    e1?:return false
                    e2?:return false
                    val p1 = mMapView.screenToLocation( android.graphics.Point(e1.x.roundToInt(),e1.y.roundToInt()))
                    val p2 = mMapView.screenToLocation( android.graphics.Point(e2.x.roundToInt(),e2.y.roundToInt()))
                    MapUtil.drawCurves(p1, p2)
                    // 返回true 地圖將不在滑動的時候滾動
                    return true
                }
                return super.onScroll(e1, e2, distanceX, distanceY)
            }
        }
// 地圖工具類
object MapUtil {
    // 繪製圖層
    val mGraphicsOverlay = GraphicsOverlay()
    // 點集合
    private val mPointCollection = PointCollection(SpatialReferences.getWebMercator())

     // 繪製曲線
    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)
    }
}
複製代碼

從代碼能夠看出,咱們是將點採集更多了,而後將全部的點仍是使用普通的方式繪製的直線,接下來看看效果:

4.繪製多邊形

繪製多邊形其實就是將線條合成一個面,可是面的合成也和線條形狀同樣有多種,因爲是面,此處經過圖案來演示:

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

  • SimpleFillSymbol.Style.VERTICAL

  • SimpleFillSymbol.Style.CROSS
  • SimpleFillSymbol.Style.SOLID 顏色填充

  • SimpleFillSymbol.Style.NULL 無背景 知道了這幾種背景填充,接下來看看多邊形是如何實現繪製的:
when(drawType){
        0 ->  MapUtil.drawPoint(clickPoint)
        1 -> MapUtil.drawLine(clickPoint)
        3 -> MapUtil.drawPolygon(clickPoint)
    }
    
// 地圖工具類
object MapUtil {
   

   // 繪製多邊形
    fun drawPolygon(p: Point) {
        mGraphicsOverlay.graphics.clear()
        mPointCollection.add(p)
        // 一個點沒法構成一個面
        if (mPointCollection.size == 1) {
            drawPoint(p)
            return
        }
        val polygon = Polygon(mPointCollection)
        val lineSymbol = SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.GREEN, 3.0f)
        
        val simpleFillSymbol =
            SimpleFillSymbol(SimpleFillSymbol.Style.SOLID, Color.YELLOW, lineSymbol)
        val graphic = Graphic(polygon, simpleFillSymbol)
        mGraphicsOverlay.graphics.add(graphic)
    }
}
複製代碼

效果預覽:

5.繪製圓

其實繪製圓和繪製多邊形類型,不一樣的是繪製圓是由圓心和半徑來確認圓的位置的。而咱們用圓規畫圓的時候,經過兩個針尖所在的點就能夠確認一個圓的位置,此處也是相似的。第一個點肯定圓心的位置,第二個點肯定半徑,根據這個推理,咱們可獲得下面的代碼:

when(drawType){
        0 ->  MapUtil.drawPoint(clickPoint)
        1 -> MapUtil.drawLine(clickPoint)
        3 -> MapUtil.drawPolygon(clickPoint)
        4 -> MapUtil.drawCircle(clickPoint)
    }
    
// 地圖工具類
object MapUtil {
   

       // 繪製圓
    fun drawCircle(p: Point) {
        mGraphicsOverlay.graphics.clear()
        if (mPointCollection.size == 50) mPointCollection.clear()
        mPointCollection.add(p)
        // 只能肯定圓心
        if (mPointCollection.size == 1) {
            drawPoint(p)
            return
        }
        // 根據勾三股四玄五的三角函數獲得兩個點之間的距離做爲半徑
        val x = mPointCollection[0].x - mPointCollection[1].x
        val y = mPointCollection[0].y - mPointCollection[1].y
        val radius = sqrt(x.pow(2.0) + y.pow(2.0))
        
        val center = mPointCollection[0]
        mPointCollection.clear()
        // 根據圓心和半徑獲取圓周的點
        for (point in getPoints(center, radius)) {
            mPointCollection.add(point)
        }
        val polygon = Polygon(mPointCollection)
        // 邊線
        val lineSymbol = SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.GREEN, 3.0f)
        // 填充風格 填充顏色 填充邊框
        val simpleFillSymbol =
            SimpleFillSymbol(SimpleFillSymbol.Style.SOLID, Color.YELLOW, lineSymbol)
        val graphic = Graphic(polygon, simpleFillSymbol)
        mGraphicsOverlay.graphics.add(graphic)
    }

    /**
     * 經過中心點和半徑計算得出圓形的邊線點集合
     *
     * @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: Double
        for (i in 0..49) {
            sin = kotlin.math.sin(Math.PI * 2.0 * i / 50)
            cos = kotlin.math.cos(Math.PI * 2.0 * i / 50)
            x = center.x + radius * sin
            y = center.y + radius * cos
            points[i] = Point(x, y)
        }
        return points
    }
}
複製代碼

繪製圓在計算點之後,繪製圓和具體APi和普通的多邊形都是同樣的,接下來預覽一下效果:

6.繪製圖片marker

繪製圖片marker相似於給地圖固定點添加一個圖標,實現也很簡單:

when(drawType){
        0 ->  MapUtil.drawPoint(clickPoint)
        1 -> MapUtil.drawLine(clickPoint)
        3 -> MapUtil.drawPolygon(clickPoint)
        4 -> MapUtil.drawCircle(clickPoint)
        5 -> MapUtil.drawMarker(clickPoint,this@MainActivity)
    }
    
// 地圖工具類
object MapUtil {
   

// 繪製圖片marker
    fun drawMarker(p: Point, context: Context) {
        // 獲取 drawable 資源
        val pinStarBlueDrawable =
            ContextCompat.getDrawable(context, R.mipmap.icon_marker_red) as BitmapDrawable?
        // 生成圖片標記符號
        // val campsiteSymbol = PictureMarkerSymbol("圖片網絡地址")
        val campsiteSymbol = PictureMarkerSymbol.createAsync(pinStarBlueDrawable).get()
        // 異步加載
        campsiteSymbol.loadAsync()
        val attributes = HashMap<String, Any>()
        // 生成圖畫內容
        val pointGraphic =
            Graphic(p, attributes, campsiteSymbol)
        // 添加到圖層
        mGraphicsOverlay.graphics.add(pointGraphic)
    }
}
複製代碼

6.繪製文本標記

繪製文本標記和繪製圖片標記同樣很簡答,直接看代碼吧:

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)
    }
    
// 地圖工具類
object MapUtil {
   

    // 繪製文字
    fun drawText(p: Point) {
        // 水平方向有左 中 右
        // 水平左 TextSymbol.HorizontalAlignment.LEFT 
        // 水平中 TextSymbol.HorizontalAlignment.CENTER 
        // 水平右 TextSymbol.HorizontalAlignment.RIGHT
        // 垂直方向支持上中下
        // 垂直上 TextSymbol.VerticalAlignment.TOP
        // 垂直中 TextSymbol.VerticalAlignment.MIDDLE
        // 垂直下 TextSymbol.VerticalAlignment.BOTTOM
        val textSymbol = TextSymbol(
            20f, "標記文字", Color.RED,
            TextSymbol.HorizontalAlignment.CENTER, TextSymbol.VerticalAlignment.MIDDLE
        )
        // 生成繪畫內容
        val graphic = Graphic(p, textSymbol)
        // 清除以前的內容
        mGraphicsOverlay.graphics.clear()
        // 添加到圖層
        mGraphicsOverlay.graphics.add(graphic)
    }
}
複製代碼

效果預覽

7.繪製自定義標註

雖然上面能夠支持添加圖片marker,也支持文本內容,可是若是想要添加一個在圖層上面包含圖片和文本的標註應該怎麼辦呢?因而就有了下面的自定義標註。簡單一句話歸納,就是經過自定義View來顯示標註信息,具體示例以下:

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)
    }
    
// 地圖工具類
object MapUtil {
   

    fun drawCallout(p: Point,mapView: MapView,context: Context) {
        val callout = mapView.callout
        if(callout.isShowing){
            callout.dismiss()
        }
        val view = LayoutInflater.from(context).inflate(R.layout.callout_delete_layout, null, false)
        view.setOnClickListener {
            callout.dismiss()
            EasyToast.DEFAULT.show("關閉標記")
        }
        callout.location = p
        callout.content = view
        callout.show()
    }
}
複製代碼

效果預覽(注意尖角和圓角是地圖設置的):

草圖編輯器——SketchEditor

草圖編輯器除了不支持繪製圓,其它圖形都是沒有問題的,並且使用方法也很是簡單,直接看註釋:

// 草圖編輯器
private val mSketchEditor = SketchEditor()


實例化地圖
... 
// 設置草圖編輯器幾何體的透明度
mSketchEditor.opacity = 0.5f
// 將草圖編輯器添加到地圖中
mapView.sketchEditor = mSketchEditor
val builder2 = XPopup.Builder(this).watchView(btn_sketch)
btn_sketch.setOnClickListener {
    builder2.asAttachList(
        arrayOf("單點", "多點", "折線", "多邊形", "徒手畫線", "徒手畫多邊形", "上一步", "下一步"), 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 {
        // 第一種繪製的重置
        if (drawType != -1) {
            drawType = -1
            MapUtil.restDrawStatus()
        } else {
            // 是否繪製成功(有沒有圖形)
            if (!mSketchEditor.isSketchValid) {
                // 重置
                mSketchEditor.stop()
                return@setOnClickListener
            }
            // 從草圖編輯器得到幾何圖形
            val sketchGeometry = mSketchEditor.geometry
            mSketchEditor.stop()
            if (sketchGeometry != null) {

                //從草圖編輯器建立一個圖形
                val graphic = Graphic(sketchGeometry)

                // 根據幾何類型分配符號
                if (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, 20f
                    )
                    graphic.symbol = mPointSymbol
                }

                // 將圖形添加到圖形覆蓋層
                mGraphicsOverlay.graphics.add(graphic)
            }
        }

    }
複製代碼

預覽:

座標計算

上面只是簡單的繪製,可是對於開發來講這明顯是不足的,由於咱們沒有獲得繪製點的具體經緯度.對於經緯度,下面的解釋比較貼切:

因爲目前世界上只有美國纔有全球定位系統(GPS),當咱們實際作項目時,獲得的座標數據每每都是爲GPS全球定位系統使用而創建的座標系統,即咱們所說的84座標。而基於我國國情,這些真實座標都是已經進行人爲的加偏處理事後,才能進行出版和發佈。因此,後臺返回的是84座標,想要在地圖上顯示正確的位置,就須要進行座標轉換。原文

上面第一種方法中,咱們拿到的座標是屏幕座標android.graphics.Point,接下來轉爲了投影座標(固然也支持地圖座標轉爲屏幕座標的),這個時候這個座標的經緯度值依然還不對,咱們使用的時候還須要將投影座標轉爲空間座標,這裏就須要用到本節的主角GeometryEngine了!

1.GeometryEngine座標轉換

  • 根據投影座標獲取默認地圖座標
Point wgsPoint = (Point) GeometryEngine.project(dp, mMapView.getSpatialReference(), null);
複製代碼
  • J02座標轉爲G84座標
val projectedPoint = GeometryEngine.project(clickPoint,SpatialReference.create(4236)) as Point
// SpatialReference.create(4236) = SpatialReferences.getWgs84()
複製代碼
  • G84座標轉爲J02座標

GCJ02:又稱火星座標系,是由中國國度測繪局制定的地輿座標系統,是由WGS84加密後得到的座標系。

因爲GcJo2是一個加密後的結果,所以沒有WSG84同樣規範的WID,可是同樣有辦法實現。

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>文件描述:座標轉換工具類<p>
 * <p>@author 烤魚<p>
 * <p>@date 2019/10/1 0001 <p>
 * <p>@update 2019/10/1 0001<p>
 * <p>版本號:1<p>
 *
 */
object TransformUtil {

    /**
     * 是否在國內
     */
    private fun outOfChina(lat: Double, lng: Double): Boolean {
        if (lng < 72.004 || lng > 137.8347) {
            return true
        }
        return lat < 0.8293 || lat > 55.8271
    }

    // 解密緯度
    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.0
        return ret
    }
    // 解密經度
    private 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 * 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.0
        return ret
    }

    /**
     * 測算經緯度差值
     * @param lat 緯度
     * @param lng 經度
     * @return delta[0] 是緯度差,delta[1]是經度差
     */
    private fun delta(lat: Double, lng: Double): DoubleArray {
        val delta = DoubleArray(2)
        val a = 6378137.0
        val ee = 0.00669342162296594323
        val dLat = transformLat(lng - 105.0, lat - 35.0)
        val dLng = transformLon(lng - 105.0, lat - 35.0)
        val radLat = lat / 180.0 * Math.PI
        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)
        return delta
    }

    /**
     * WSG84 轉 GCJ02 座標
     * @param lat 緯度
     * @param lng 經度
     */
    fun  wsG84toGCJ02Point(latitude:Double,longitude:Double) : Point {
        if (outOfChina(latitude, longitude)) {
            return Point(latitude, longitude)
        }
        val delta = delta(latitude, longitude)
          return Point(latitude + delta[0], longitude + delta[1])
    }

    /**
     * GCJo2 轉 WGS84 座標
     * @param lat 緯度
     * @param lng 經度
     */
    fun  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])
    }

}
複製代碼

計算長度

  • 計算給定折線的長度
GeometryEngine.length ( polyline:Polyline):Double
複製代碼
  • 計算幾何的大地長度

注意LinearUnit——返回值的計量單位。若是爲null,則默認爲Id的線性單位METERS

GeometryEngine.lengthGeodetic ( geometry:Geometry,  lengthUnit:LinearUnit, curveType:GeodeticCurveType):Double
複製代碼

計算面積

GeometryEngine.area ( polygon:Polygon):Double

GeometryEngine.area ( envelope:Envelope):Double

GeometryEngine.areaGeodetic ( geometry:Geometry,  areaUnit:AreaUnit,  curveType:GeodeticCurveType):Double
複製代碼

計算給定幾何的邊界

GeometryEngine.boundary (geometry:Geometry):Geometry
複製代碼

合併兩個給定幾何的範圍

GeometryEngine.combineExtents (geometry1:Geometry, geometry2:Geometry):Envelope 
複製代碼

合併幾何集合的範圍

GeometryEngine.contains ( container:Geometry, within:Geometry):Boolean
複製代碼

經過在幾何圖形中現有頂點之間繪製點來強化給定的幾何圖形。

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

GeometryEngine.densifyGeodetic ( geometry:Geometry,  maxSegmentLength:Double,lengthUnit:LinearUnit,  curveType:GeodeticCurveType):Geometry
複製代碼

兩個圖形是否包含

後者是否包含前者

GeometryEngine.within ( within:Geometry,  container:Geometry):Boolean
複製代碼

4、經常使用接口介紹

在前面的實例中咱們已經看到有經過點擊事件獲取點擊點的經緯度,那麼爲何咱們點擊事件的方法不是View.OnClickListener接口呢?接下來就給你們介紹一下包括DefaultMapViewOnTouchListener在內的接口吧!

  • MapScaleChangedListener

這個接口有一個回調方法mapScaleChanged,就是當地圖的比例尺修改時調用,那麼具體在何時使用這個接口呢?不知道是否還記得在初始化地圖的時候,咱們設置了地圖的默認縮放級別是16,那麼地圖的最大與最小縮放分別是多少呢?網上有說是0~20,可是我沒有找到相關文檔,在初始化的過程當中當中查看源碼也沒有發現檢查最大值(有不能小於0的判斷),由於最終實現方法是native方法。並且咱們最後也沒有發現獲取縮放級別的相關Api,所以能夠經過這個回調來處理比例尺與縮放級別的問題,從而實現限制縮放,防止地圖無限放大或者縮小。

注意:mapScaleChanged方法被調用頻率很高,且重置縮放的比例尺方法setViewpointScaleAsync是異步,這裏的交互效果就會顯得不那麼理想。 解決方案最後在DefaultMapViewOnTouchListener內。

  • MapRotationChangedListener

這個接口的回調方法也只有一個,就是mapRotationChanged方法。當地圖旋轉的時候調用,也是一個被調用很高頻的方法,若是有須要在地圖旋轉之後更新內容的需求,能夠參考這個接口。

  • DefaultMapViewOnTouchListener DefaultMapViewOnTouchListener比較複雜,是一個實現類,你們能夠先看看這張圖:

固然,咱們也沒有必要徹底去弄明白每個方法的用途,只須要了解本身須要的方法便可。

單擊 onSingleTapConfirmed 在項目經過這個方法實現了單擊回調,有興趣的同窗也能夠了解一下onSingleTapUp方法

長按 onLongPress 這個回調沒有異議,項目中使用暫時也尚未發現問題

雙擊 onDoubleTouchDrag 這個方法名稱有些歧義,由於這個方法只有雙擊回調,沒有雙指滑動的回調

開始縮放 onScaleEnd 上面說到的限制地圖無限制縮放的時候,能夠經過這個方法和onScaleBegin配合使用,在開始縮放的時候判斷當前縮放的尺寸,若是已經不支持縮放直接消費掉這次事件便可

結束縮放 onScaleBegin 上面說到的限制地圖無限制縮放的時候,能夠經過這個方法和onScaleEnd配合使用,在開始縮放的時候判斷當前縮放的尺寸,若是已經不支持縮放直接消費掉這次事件便可

手指滑動 onScroll 這個就是至關於默認滑動事件的MotionEvent.ACTION_MOVE,可是有一個奇特的地方在於當滑動結束的時候必定會回調onFling方法

滑動結束 onFling 當滑動結束的時候被調用,與View中的fling方法不同

以上就是我在項目中使用到的關於地圖的接口回調了。

5、天地地圖接入

這個就比較簡單了,去天地地圖官網註冊一個帳號,而後申請一個應用,接着將獲得的key放到工具類便可使用(目前發現未校驗簽名)。 下面的效果圖使用的是天地地圖的數據,沒有使用Arcgis官網的地圖數據了。

代碼:

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_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,
        1128.4994333441375
    )
    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,
        0.298582141738970
    )

    private val RESOLUTIONS_2000 = doubleArrayOf(
        0.7031249999891485, 0.35156249999999994,
        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? = null
        if (mainIs2000) {
            for (i in minZoomLevel..maxZoomLevel) {
                val item = LevelOfDetail(i, RESOLUTIONS_2000[i - 1], SCALES[i - 1])
                mainLevelOfDetail.add(item)
            }
            mainEnvelope = ENVELOPE_2000
            mainOrigin = ORIGIN_2000
        } else {
            for (i in minZoomLevel..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 {
        return when (layerType) {
            // 天地圖矢量墨卡託投影地圖服務
            "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
            // 天地圖影像墨卡託投影地圖服務
            "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
            // 天地圖地形墨卡託投影地圖服務
            "TIANDITU_TERRAIN_MERCATOR" -> LayerType.TIANDITU_TERRAIN_MERCATOR
            // 天地圖地形墨卡託投影中文標註
            "TIANDITU_TERRAIN_ANNOTATION_CHINESE_MERCATOR" -> LayerType.TIANDITU_TERRAIN_ANNOTATION_CHINESE_MERCATOR
            // 天地圖矢量國家2000座標系地圖服務
            "TIANDITU_VECTOR_2000" -> LayerType.TIANDITU_VECTOR_2000
            // 天地圖矢量國家2000座標系中文標註
            "TIANDITU_VECTOR_ANNOTATION_CHINESE_2000" -> LayerType.TIANDITU_VECTOR_ANNOTATION_CHINESE_2000
            // 天地圖矢量國家2000座標系英文標註
            "TIANDITU_VECTOR_ANNOTATION_ENGLISH_2000" -> LayerType.TIANDITU_VECTOR_ANNOTATION_ENGLISH_2000
            // 天地圖影像國家2000座標系地圖服務
            "TIANDITU_IMAGE_2000" -> LayerType.TIANDITU_IMAGE_2000
            // 天地圖影像國家2000座標系中文標註
            "TIANDITU_IMAGE_ANNOTATION_CHINESE_2000" -> LayerType.TIANDITU_IMAGE_ANNOTATION_CHINESE_2000
            // 天地圖影像國家2000座標系英文標註
            "TIANDITU_IMAGE_ANNOTATION_ENGLISH_2000" -> LayerType.TIANDITU_IMAGE_ANNOTATION_ENGLISH_2000
            // 天地圖地形國家2000座標系地圖服務
            "TIANDITU_TERRAIN_2000" -> LayerType.TIANDITU_TERRAIN_2000
            // 天地圖地形國家2000座標系中文標註
            "TIANDITU_TERRAIN_ANNOTATION_CHINESE_2000" -> LayerType.TIANDITU_TERRAIN_ANNOTATION_CHINESE_2000
            else -> LayerType.TIANDITU_VECTOR_2000
        }

    }
}

enum class LayerType {
    /**
     * 天地圖矢量墨卡託投影地圖服務
     */
    TIANDITU_VECTOR_MERCATOR,
    /**
     * 天地圖矢量墨卡託中文標註
     */
    TIANDITU_VECTOR_ANNOTATION_CHINESE_MERCATOR,
    /**
     * 天地圖矢量墨卡託英文標註
     */
    TIANDITU_VECTOR_ANNOTATION_ENGLISH_MERCATOR,
    /**
     * 天地圖影像墨卡託投影地圖服務
     */
    TIANDITU_IMAGE_MERCATOR,
    /**
     * 天地圖影像墨卡託投影中文標註
     */
    TIANDITU_IMAGE_ANNOTATION_CHINESE_MERCATOR,
    /**
     * 天地圖影像墨卡託投影英文標註
     */
    TIANDITU_IMAGE_ANNOTATION_ENGLISH_MERCATOR,
    /**
     * 天地圖地形墨卡託投影地圖服務
     */
    TIANDITU_TERRAIN_MERCATOR,
    /**
     * 天地圖地形墨卡託投影中文標註
     */
    TIANDITU_TERRAIN_ANNOTATION_CHINESE_MERCATOR,
    /**
     * 天地圖矢量國家2000座標系地圖服務
     */
    TIANDITU_VECTOR_2000,
    /**
     * 天地圖矢量國家2000座標系中文標註
     */
    TIANDITU_VECTOR_ANNOTATION_CHINESE_2000,
    /**
     * 天地圖矢量國家2000座標系英文標註
     */
    TIANDITU_VECTOR_ANNOTATION_ENGLISH_2000,
    /**
     * 天地圖影像國家2000座標系地圖服務
     */
    TIANDITU_IMAGE_2000,
    /**
     * 天地圖影像國家2000座標系中文標註
     */
    TIANDITU_IMAGE_ANNOTATION_CHINESE_2000,
    /**
     * 天地圖影像國家2000座標系英文標註
     */
    TIANDITU_IMAGE_ANNOTATION_ENGLISH_2000,
    /**
     * 天地圖地形國家2000座標系地圖服務
     */
    TIANDITU_TERRAIN_2000,
    /**
     * 天地圖地形國家2000座標系中文標註
     */
    TIANDITU_TERRAIN_ANNOTATION_CHINESE_2000

}


// 地圖添加天地地圖數據:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...

        //注意:在100.2.0以後要設置RequestConfiguration
        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

        ...
    }

複製代碼

6、三維地圖

SceneView示例

arcgisruntime是用了一個GeoView類做爲地圖的基類直接繼承於ViewGroup,而後MapViewSceneView分別做爲二維和三維地圖的容器繼承於GeoView。其實把SceneView當作MapView,把ArcGISScene當作ArcGISMap就行.

代碼示例:

<?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 onDestroy() {
        super.onDestroy()
        sceneview.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


    }
       
複製代碼

展現三維場景

三維經過接近真實世界的角度來可視化數據信息,三維場景的使用相似於MapViewArcGISMap,二維數據皆可加入三維場景,三維場景不一樣於二維,其具有高程表面(elevation 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)
        // 設置三維場景視角鏡頭(camera)
        //latitude——緯度;多是負的
        //
        //longitude——經度;多是負的
        //
        //altitude-海拔;多是負的
        //
        //heading——鏡頭水平朝向;多是負的
        //
        //pitch——鏡頭垂直朝向;不必定是負的
        //
        //roll-轉動的角度
        val camera =  Camera(48.378, -4.494, 200.0, 345.0, 65.0, 0.0)
        sceneview.setViewpointCamera(camera)
    }
  
複製代碼

使用高程表面(ArcGISTiledElevationSourceRasterElevationSource

ArcGISTiledElevationSource:將在線服務做爲高程表面

RasterElevationSource:將本地DEM文件做爲高程表面

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)
        // 設置三維場景視角鏡頭(camera)
        //latitude——緯度;多是負的
        //
        //longitude——經度;多是負的
        //
        //altitude-海拔;多是負的
        //
        //heading——鏡頭水平朝向;多是負的
        //
        //pitch——鏡頭垂直朝向;不必定是負的
        //
        //roll-轉動的角度
        val camera = Camera(28.4, 83.9, 10010.0, 10.0, 80.0, 0.0)
        sceneview.setViewpointCamera(camera)
    }
複製代碼

使用高層表面和不使用高層表面的體驗來看,有點相似高度有沒有變化同樣的感受,有興趣的同窗能夠本身嘗試一下。

其他部分能夠直接參考文檔:

7、熱像圖

官方提供效果圖
根據官方 示例展現的效果,其中有幾個 API須要咱們掌握:

  • GeoprocessingJob 地理處理做業用於在服務上運行地理處理任務
  • GeoprocessingParameters 地理處理參數包含發送到目標地理處理任務的輸入參數
  • GeoprocessingResult 從服務返回的輸出參數
  • GeoprocessingTask 用於運行做爲web服務發佈的地理處理任務

具體能夠經過代碼來查看具體效果:

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()
    }

    /**
     * 選擇分析熱點的時間區間
     */
    private fun showDateRangeDialog() {
        // 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)
        }

        // if button is clicked, close the custom dialog
        dialogView.analyzeButton.setOnClickListener {
            analyzeHotspots(
                dialogView.fromDateText.text.toString(),
                dialogView.toDateText.text.toString()
            )
            dialog.dismiss()

        }

        dialog.show()
    }

    /**
     * 顯示日期選擇器對話框,並將選擇的日期寫入正確的可編輯文本
     */
    private fun showCalendar(inputCalendar: InputCalendar, dialogView: View) {
        // create a date set 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()
    }

    /**
     * 運行地理處理做業,在加載時更新進度。工做完成後,將生成的ArcGISMapImageLayer加載到地圖並重置MapView的視點。
     */
    private fun analyzeHotspots(from: String, to: String) {
        // 取消 mGeoprocessingJob 上一個請求
        mGeoprocessingJob.cancel()

        // 結果生成一個地圖圖像層。刪除以前添加到地圖的任何層
        mapView.map.operationalLayers.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("地理信息處理中")
            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
}

複製代碼

因爲這部份內容沒有在項目中實踐過,只能簡單的找到官方示例查看具體使用和相關源碼註釋,若是你們有這方面的使用心得,歡迎各位同窗留言交流!


源碼

傳送門

參考:

官方文檔

API介紹

arcgis-runtime-samples-android

ArcGIS for Android 100.3.0

相關文章
相關標籤/搜索