N_GIS 核心知识 N_ArcGis全家桶 N_Android

ArcGIS Runtime for Android

官方文档 ArcGIS Maps SDK for Kotlin api-reference 官方-示例 GitHub库

Key features

Key features

关键对象

MapView

MapView

MapView 是一种用户界面控件,用于在应用程序中显示单个地图。它包含内置功能,允许用户通过放大和缩小、重新定位地图显示或获取有关地图中元素的其他信息来浏览地图。它还管理一个或多个图形覆盖集合中的图形。由地图视图管理的图形始终显示在地图中显示的所有图层上方。

MapView 的创建有两种方法,一种是在 Layout 文件中直接写控件。一种是实例化

MapView mapView = new MapView(Context context);

<com.esri.android.map.MapView
	android:id="@+id/map_view"
	android:layout_width="match_parent"
	android:layout_height="match_parent" />

get started

get-started

项目配置

display-a-map

增加 arcgis https://esri.jfrog.io/artifactory/arcgis仓库源 settings.gradle.kts

pluginManagement {
    repositories {
	    // arcgis sdk 源
        maven {
            setUrl("https://esri.jfrog.io/artifactory/arcgis")
        }
        ...
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        maven {
            setUrl("https://esri.jfrog.io/artifactory/arcgis")
        }
	  ...
    }
}
 
rootProject.name = "Application"
include(":app")

引入依赖 build.gradle.kts

dependencies {  
	implementation("androidx.core:core-ktx:1.10.1")  
	....
	implementation("com.esri.arcgisruntime:arcgis-android:100.15.0")
}

配置 资源忽略, 不然就是:

Execution failed for task ':reference:mergeDebugJavaResource'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction

build.gradle.kts

  
android {
....
	packaging {  
		resources {  
		excludes += "/META-INF/{AL2.0,LGPL2.1}" 
		//增加 
		excludes += "/META-INF/DEPENDENCIES"  
		}  
	}
}

离线地图 (mmpk)

display-a-map-from-a-mobile-map-package open-mobile-map-package/ MobileMapPackage API

package cn.simae.spxxf.aar.activity
 
 
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import cn.simae.spxxf.R
import com.arcgismaps.mapping.MobileMapPackage
import com.arcgismaps.mapping.view.MapView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
 
class MapActivity : AppCompatActivity() {
 
    val TAG = "MapActivity"
 
    private lateinit var mapView: MapView
    private lateinit var mapPackage: MobileMapPackage
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_map)
        // create and add the MapView to the lifecycle
        mapView = findViewById<MapView>(R.id.mapView)
        lifecycle.addObserver(mapView)//注意, 这行必须
        // get the file path of the (.mmpk) file
        val outputDirectory = getExternalFilesDir(null)
        val mmpkfile =  File(outputDirectory, "simple_anno_mmpk.mmpk")
        Log.i(TAG, "onCreate, 加载文件: "+ mmpkfile)
        loadMobileMapPackage(mmpkfile.absolutePath)
    }
 
    // load your mobile map package here. You call this method from onCreate()
    private fun loadMobileMapPackage(mmpkFilePath: String) {
        // create the mobile map package
        mapPackage = MobileMapPackage(mmpkFilePath)
        lifecycleScope.launch(Dispatchers.Main) {
            mapPackage.load()
				.onSuccess {
					mapView.map = mapPackage.maps.first()
					Log.i(TAG,"MapActivity 成功, 图层数量: ${mapView.map!!.operationalLayers.size}")
				}
				.onFailure {
					Log.i(TAG,"MapActivity 失败")
					showError(it.message.toString(), mapView)
				}
        }
    }
 
    override fun onDestroy() {
        super.onDestroy()
    }
 
 
    private fun showError(message: String, view: View) {
        Log.e(localClassName, message)
        Toast.makeText(this, message, Toast.LENGTH_LONG).show()
    }
}
 
 
 

添加覆盖物

构造 GraphicsOverlay Add graphics to a map view

https://developers.arcgis.com/kotlin/api-reference/arcgis-maps-kotlin/com.arcgismaps.mapping.view/-graphic/index.html

添加图形

// create a graphics overlay
val graphicsOverlay = GraphicsOverlay()
//坐标
val point = Point(114.10727402, 22.54862306, SpatialReference.wgs84())
//圆形
val redCircleSymbol = SimpleMarkerSymbol(SimpleMarkerSymbolStyle.Circle, Color.red, 10.0f)
val pierGraphic = Graphic(point, redCircleSymbol)
pierGraphic.attributes["type"] = "pier"
graphicsOverlay.graphics.add(pierGraphic)
 
// add graphics overlay to the geo view
mapView.graphicsOverlays.add(graphicsOverlay)

添加图形+文本

val graphicsOverlay = GraphicsOverlay()
//140.85064697265625,\"lng\":114.107398
val point = Point(114.10727402, 22.54862306, SpatialReference.wgs84())
val redCircleSymbol = SimpleMarkerSymbol(SimpleMarkerSymbolStyle.Circle, Color.red, 20.0f)
val redCircle = Graphic(point, redCircleSymbol)
redCircle.attributes["ydbh"] = it.ydbh//Graphic 可以存自定义属性
graphicsOverlay.graphics.add(redCircle)
 
val textSymbol = TextSymbol()
textSymbol.text = "文本文本"
val text = Graphic(point, textSymbol)
text.attributes["ydbh"] = it.ydbh//Graphic 可以存自定义属性
graphicsOverlay.graphics.add(text)
 
Log.i(TAG, "loadingPoints: "+graphicsOverlay)
mapView.graphicsOverlays.add(graphicsOverlay)

自定义覆盖物

https://developers.arcgis.com/kotlin/api-reference/arcgis-maps-kotlin/com.arcgismaps.mapping.symbology/-marker-symbol/index.html

关键是 MarkerSymbol 及其子类 : PictureMarkerSymbol 它可以绘制 BitmapDrawable, 所以可以将安卓View 转为BitmapDrawable 再给它 SimpleMarkerSymbol TextSymbol

 
fun createBitmapFromView(context: Context, view: View): Bitmap {
	view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
	view.layout(0, 0, view.measuredWidth, view.measuredHeight)
	val bitmap = Bitmap.createBitmap(view.measuredWidth, view.measuredHeight, Bitmap.Config.ARGB_8888)
	val canvas = Canvas(bitmap)
	view.draw(canvas)
	return bitmap
}
fun getSampleDetailBitmap(context: Context): BitmapDrawable {
	val inflater = LayoutInflater.from(context)
	val view = inflater.inflate(R.layout.map_sample_detail, null)
	//        将 View 转为 Bitmap
	val bitmap = createBitmapFromView(context, view)
	
	//        将 Bitmap 转为 BitmapDrawable
	val bd = BitmapDrawable(resources, bitmap)
	return bd;
}
////////////////
var sampleDetail = getSampleDetailBitmap(this);
// 给定 BitmapDrawable创建 PictureMarkerSymbol 
val symbol = PictureMarkerSymbol.createWithImage( sampleDetail)
//添加到 graphicsOverlay 中
val point = Point(lng.toDouble(), lat.toDouble(), SpatialReference.wgs84())
val graphics = Graphic(point, symbol)
XLog.i("$TAG, 添加 sampleDetail = "+graphics)
graphicsOverlay.graphics.add(graphics)

事件监听

https://developers.arcgis.com/kotlin/api-reference/arcgis-maps-kotlin/com.arcgismaps.mapping.view/-geo-view/identify-graphics-overlays.html 覆盖物本身不能添加事件监听, 只有mapView全局的事件

//1. 监听全局点击 事件
mapView.onSingleTapConfirmed.collect{tapEvent ->
	run {
		// 2. 通过点击事件的位置, 再去匹配 graphicsOverlay 中 的元素
			//参数说明: 
			//graphicsOverlay: 你希望识别图形的 GraphicsOverlay 对象。
			//screenPoint: 用户点击或触摸的屏幕点。
			//tolerance: 像素容差,用于确定识别附近图形的范围。
			//returnPopupsOnly: 是否只返回包含弹出窗口的图形。
			//maximumResults: 最大返回的图形数量。
		val identifyResult: Result<com.arcgismaps.mapping.view.IdentifyGraphicsOverlayResult> = mapView.identifyGraphicsOverlay(
			graphicsOverlay,
			tapEvent.screenCoordinate,
			1.0 ,
			false, 1
		)
		identifyResult.onSuccess {
			XLog.i(TAG + ", 点击目标 graphics " +it.graphics[0].attributes["ydbh"])
		}
		XLog.i(TAG + ", 点击事件 " + tapEvent)
 
	}
}

查询 Query

https://developers.arcgis.com/kotlin/query/

Relevant classes and members in the API ref

The following API is used to identify features in layers. The identify layers methods are defined in GeoView.

The following API is used to identify graphics in graphic overlays. The identify graphics overlay methods are defined in GeoView.

实例

单个图层查询

private suspend fun showLayerDetailDialog(tapEvent: SingleTapConfirmedEvent){
        val layers = mapView.map!!.operationalLayers
        layers.forEach {
            val identifyLayersFuture :Result<IdentifyLayerResult> = mapView.identifyLayer(it, tapEvent.screenCoordinate, 1.0, false)
            identifyLayersFuture.onSuccess { res ->
                run {
                    if (res.geoElements.isNotEmpty()) {
                        ///////////////
                        val sb = StringBuffer();
                        sb.append(("$TAG 图层: ${it.name}, 结果: " + res.geoElements.get(0).attributes))
                            .append("\n")
                            .append(" mapPoint x = ${tapEvent.mapPoint?.x} , = ${tapEvent.mapPoint?.y}")
 
                        val dialogBuilder = AlertDialog.Builder(this)
                        val textView = TextView(this)
                        textView.setText(sb.toString())
                        val alertDialog = dialogBuilder
                            .setView(textView)
                            .create()
                        alertDialog.show()
                    }
                }
            }
        }
    }

多个图层查询

val identifyLayersFuture:Result<List<IdentifyLayerResult>> = mapView.identifyLayers( tapEvent.screenCoordinate, 1.0, false )
        identifyLayersFuture.onSuccess { res ->
            run {
                if (res.isNotEmpty()) {
                    for (ilr in res){
                        val sb = StringBuffer();
                        sb.append(("$TAG 图层: ${ilr.layerContent.name}, 结果: " + ilr.geoElements.get(0).attributes))
                            .append("\n")
                            .append(" mapPoint x = ${tapEvent.mapPoint?.x} , = ${tapEvent.mapPoint?.y}")
                        val dialogBuilder = AlertDialog.Builder(this)
                        val textView = TextView(this)
                        textView.setText(sb.toString())
                        val alertDialog = dialogBuilder
                            .setView(textView)
                            .create()
                        alertDialog.show()
                    }
                }
            }
        }

三普

/**
     * 查询图层显示
     */
    @SuppressLint("SetTextI18n")
    private suspend fun showLayerDetail(tapEvent: SingleTapConfirmedEvent){
        val identifyLayersFuture:Result<List<IdentifyLayerResult>> = mapView.identifyLayers( tapEvent.screenCoordinate, 1.0, false )
        identifyLayersFuture.onSuccess { res ->
            run {
                if (res.isNotEmpty()) {
                    val builder = AlertDialog.Builder(this)
                    val layout = LinearLayout(this)// ui 弹窗布局
                    layout.orientation = LinearLayout.VERTICAL
                    layout.setPadding(20, 20, 20, 30) // 设置内边距
                    builder.setTitle("信息")
                    builder.setView(layout)
                    //
                    val mappKeys = mapOf<String,String>("poxiang" to "坡向", "poxing" to "坡型", "ZLDWMC" to "坐落单位"
                        , "XZQMC" to "乡镇名称", "DLMC" to "土地利用类型", "my" to "母岩")
                    XLog.i("$TAG ================查询=============")
                    for (ilr in res){
                        val layerText = TextView(this)
                        layerText.text = "图层: ${ilr.layerContent.name}"
                        val typeface = Typeface.defaultFromStyle(Typeface.BOLD)
                        layerText.typeface = typeface
                        layerText.setPadding(0, 30, 0, 0)
                        layout.addView(layerText)//ui 图层名称
                        var hasContext = false
                        if (ilr.geoElements.isNotEmpty()){
                            val sb = StringBuffer();
                            sb.append(("图层: ${ilr.layerContent.name}, length: ${ilr.geoElements.size}, 0结果: " + ilr.geoElements[0].attributes))
                            XLog.w("$TAG $sb")
                            //
                            val attributes = ilr.geoElements[0].attributes
                            for (attribute in attributes){
                                val mnName = mappKeys[attribute.key] ?: continue
                                hasContext = true
                                val rowLayout = LinearLayout(this)
                                rowLayout.orientation = LinearLayout.HORIZONTAL
                                rowLayout.weightSum = 2f // 分配权重,使按钮平均分布
 
                                val attrKey = TextView(this)
                                attrKey.text = "$mnName: "
                                rowLayout.addView(attrKey)//ui 属性中文
                                val attrVal = TextView(this)
                                attrVal.text = attribute.value.toString()//ui 属性值
                                rowLayout.addView(attrVal)
                                layout.addView(rowLayout)
                            }
 
                        }
                        if (!hasContext){//没有
                            layout.removeView(layerText )
                        }
                    }
                    val dialogBuilder = AlertDialog.Builder(this)
                    val alertDialog = dialogBuilder
                        .setView(layout)
                        .create()
                    alertDialog.show()
                }
            }
        }
 
    }

Reduce your APK size

https://developers.arcgis.com/android/license-and-deployment/reduce-your-apk-size/

https://developer.android.com/build/configure-apk-splits