最近在项目中使用到了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
}
复制代码
因为这部份内容没有在项目中实践过,只能简单的找到官方示例查看具体使用和相关源码注释,若是你们有这方面的使用心得,欢迎各位同窗留言交流!