最近在項目中使用到了ArcgisRuntime Android
,所以打算作一份總結,既加深了本身對該部分知識的印象,也能夠方便不熟悉的同窗參考。本文使用的版本是arcgis-android:100.6.0'
,示例部分既有參照官方文檔和API介紹,也有參考 總有刁民想殺寡人的專欄,固然更多的仍是本身使用的一些心得。下面開始正文介紹html
Arcgis
入門示例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)
}
複製代碼
既然是地圖,那麼必定會涉及到定位,接下來的內容就是關於定位的相關內容。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
測試時,發現OFF
和RECENTER
下測試都能成功定位,可是始終沒法關閉定位;小米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()
}
}
複製代碼
基本操做指的是地圖中經常使用的操做,下面介紹其中八種:
marker
邏輯很簡單,就是在地圖上點擊一下繪製一個點,所以須要對地圖設置一個點擊事件,可是地圖不是普通的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)
}
}
複製代碼
繪製直線和繪製點的邏輯相似,只是將點串成一條線 ,代碼如圖:
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)
}
複製代碼
繪製曲線和繪製直線的邏輯徹底一致,不一致的地方在於點更密集。既然須要採集比繪製直線更多的點,那麼直線這個收集點的方法確定不行了,但幸運的是這個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)
}
}
複製代碼
從代碼能夠看出,咱們是將點採集更多了,而後將全部的點仍是使用普通的方式繪製的直線,接下來看看效果:
繪製多邊形其實就是將線條合成一個面,可是面的合成也和線條形狀同樣有多種,因爲是面,此處經過圖案來演示:
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)
}
}
複製代碼
效果預覽:
其實繪製圓和繪製多邊形類型,不一樣的是繪製圓是由圓心和半徑來確認圓的位置的。而咱們用圓規畫圓的時候,經過兩個針尖所在的點就能夠確認一個圓的位置,此處也是相似的。第一個點肯定圓心的位置,第二個點肯定半徑,根據這個推理,咱們可獲得下面的代碼:
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
和普通的多邊形都是同樣的,接下來預覽一下效果:
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)
}
}
複製代碼
繪製文本標記和繪製圖片標記同樣很簡答,直接看代碼吧:
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)
}
}
複製代碼
效果預覽
雖然上面能夠支持添加圖片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
了!
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
複製代碼
在前面的實例中咱們已經看到有經過點擊事件獲取點擊點的經緯度,那麼爲何咱們點擊事件的方法不是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
方法不同
以上就是我在項目中使用到的關於地圖的接口回調了。
這個就比較簡單了,去天地地圖官網註冊一個帳號,而後申請一個應用,接着將獲得的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
...
}
複製代碼
SceneView
示例
arcgisruntime
是用了一個GeoView
類做爲地圖的基類直接繼承於ViewGroup
,而後MapView
和SceneView
分別做爲二維和三維地圖的容器繼承於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
}
複製代碼
三維經過接近真實世界的角度來可視化數據信息,三維場景的使用相似於
MapView
和ArcGISMap
,二維數據皆可加入三維場景,三維場景不一樣於二維,其具有高程表面(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)
}
複製代碼
ArcGISTiledElevationSource
、RasterElevationSource
)
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)
}
複製代碼
使用高層表面和不使用高層表面的體驗來看,有點相似高度有沒有變化同樣的感受,有興趣的同窗能夠本身嘗試一下。
其他部分能夠直接參考文檔:
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
}
複製代碼
因爲這部份內容沒有在項目中實踐過,只能簡單的找到官方示例查看具體使用和相關源碼註釋,若是你們有這方面的使用心得,歡迎各位同窗留言交流!