需求: 实现在高德地图上点亮城市
1. 获取行政区划数据
获取点亮城市列表,这里数据源是不带省份标识的,例如["广州市"]而不是["广东省广州市"] 这样会获取不到城市数据
关键代码:
- query.keywords = city // 使用当前遍历到的城市作为关键字
- query.isShowBoundary = true // 是否返回边界值 因为我需要这些边界点来绘制城市的形状
- search.setOnDistrictSearchListener //在回调函数中解析districtResult获取行政区划信息
val query = DistrictSearchQuery()
query.keywords = city // 使用当前遍历到的城市作为关键字
query.isShowBoundary = true // 是否返回边界值
query.isShowChild = false
search.query = query
// 由于搜索是异步的,我们需要为每个城市设置一个监听器来处理结果
search.setOnDistrictSearchListener { districtResult ->
if (districtResult != null && districtResult.aMapException.errorCode == 1000) {
// 成功获取到行政区划信息
val districtItems: List<DistrictItem>? = districtResult.district
}
}
2.成功获取返回的行政区划信息并解析出坐标点
成功获取到行政区划信息districtItems格式如下
遍历以字符串数组形式返回行政区划边界值
val boundaries = districtItem.districtBoundary()
遍历行政区划边界值并分割出具体的坐标点 然后创建一个PolygonOptions对象来定义多边形 将解析出的坐标点分割成经纬度,然后添加到 PolygonOptions对象里面 用来后续的绘制不规则多边形 具体代码如下
for (districtItem in districtItems) {
//boundaries :以字符串数组形式返回行政区划边界值。 字符串拆分规则: 经纬度,经度和纬度之间用","分隔,坐标点之间用";"分隔。例如:116.076498,40.115153;116.076603,40.115071;116.076333,40.115257;116.076498,40.115153。 字符串数组由来: 如果行政区包括的是群岛,则坐标点是各个岛屿的边界,各个岛屿之间的经纬度使用"|"分隔。一个字符串数组可包含多个封闭区域,一个字符串表示一个封闭区域。
val boundaries = districtItem.districtBoundary()
YzLog.e("IMineRunFootPrintImpl-》boundaries for : ${boundaries}")
for (boundaryStr in boundaries) {
val polygonOptions = withIO {
// 创建一个PolygonOptions对象来定义多边形
val polygonOptions = PolygonOptions()
// 如果字符串包含"|",说明它表示群岛,需要进一步分割
val islandBoundaries = boundaryStr.split("|")
// 遍历每个岛屿的边界字符串
for (islandBoundary in islandBoundaries) {
// 分割出每个经纬度点
val points = islandBoundary.split(";")
// 遍历每个点,并创建LatLng对象
for (point in points) {
// 分割经度和纬度
val coordinates = point.split(",")
if (coordinates.size == 2) {
// 创建LatLng对象并添加到列表
val latitude = coordinates[0].toDouble()
val longitude = coordinates[1].toDouble()
val latLng = LatLng(longitude, latitude)
polygonOptions.add(latLng)
}
}
}
// 设置多边形的样式
polygonOptions.fillColor(Color.parseColor("#80F65C15")) // 设置填充颜色为红色
polygonOptions.strokeColor(Color.parseColor("#FFF65C15")) // 设置边框颜色为黑色(可选)
polygonOptions.strokeWidth(4f) // 设置边框宽度(可选)
}
withMain {
// 将多边形添加到地图
getCurMap()?.addPolygon(polygonOptions)
}
}
addMarker(
districtItem.center.latitude,
districtItem.center.longitude,
districtItem.name
)
}
绘制不规则多边形
```// 设置多边形的样式
polygonOptions.fillColor(Color.parseColor("#80F65C15")) // 设置填充颜色为红色
polygonOptions.strokeColor(Color.parseColor("#FFF65C15")) // 设置边框颜色为黑色(可选)
polygonOptions.strokeWidth(4f) // 设置边框宽度(可选)
val latitude = coordinates[0].toDouble()
val longitude = coordinates[1].toDouble()
val latLng = LatLng(longitude, latitude)
// 将多边形添加到地图
getCurMap()?.addPolygon(polygonOptions)
```
4.给绘制出来的城市添加Marker标记点
-
latitude,longitude:为我们获取的行政区划信息对象districtItems的中心点districtItem.center.latitude,districtItem.center.longitude
-
RunMarkerView:是我们自定义的view\
-
anchor(0.2f, 0.8f): 是用来控制我们Marker的偏移量
val view = RunMarkerView(context) view.iniTitle(title) val latLng = LatLng(latitude, longitude) aMap?.addMarker( MarkerOptions() .position(latLng) //.icon( BitmapDescriptorFactory.fromResource(R.drawable.ic_run_map_point)) //.title(title) .icon(BitmapDescriptorFactory.fromView(view)) .anchor(0.2f, 0.8f) )
源码
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.os.Bundle
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import com.amap.api.maps.AMap
import com.amap.api.maps.CameraUpdateFactory
import com.amap.api.maps.model.BitmapDescriptorFactory
import com.amap.api.maps.model.CustomMapStyleOptions
import com.amap.api.maps.model.LatLng
import com.amap.api.maps.model.MarkerOptions
import com.amap.api.maps.model.PolygonOptions
import com.amap.api.services.district.DistrictItem
import com.amap.api.services.district.DistrictSearch
import com.amap.api.services.district.DistrictSearchQuery
import com.android.common.style.action.ToastAction
import com.android.common.utils.log.YzLog
import com.android.common.utils.scope.scopeViewLifeAction
import com.android.common.utils.scope.withIO
import com.android.common.utils.scope.withMain
import com.youth.common.style.ext.getLifecycleOwner
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import java.io.IOException
import java.io.InputStream
import java.util.LinkedList
class RunMapWithFootPrintView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : FrameLayout(context, attrs), LifecycleEventObserver, ToastAction {
private var mBinding: RunFootPrintViewBinding
private val searchQueue = LinkedList<String>() // 用于存储待搜索的城市列表
private var currentSearch: DistrictSearch? = null // 当前正在进行的搜索
private var curJob: CoroutineScope? = null
private var currentLevel: Float = 5f
private var curLocation: LatLng? = null
init {
mBinding = RunFootPrintViewBinding.inflate(LayoutInflater.from(context), this, true)
initView()
}
private fun initView() {
}
fun onSaveInstanceState(outState: Bundle?) {
mBinding.mapRunView.onSaveInstanceState(outState)
}
private fun getCurMap(): AMap? {
return mBinding.mapRunView?.map
}
fun bindLifecycle(savedInstanceState: Bundle?) {
mBinding.mapRunView.onCreate(savedInstanceState)
context.getLifecycleOwner()?.lifecycle?.addObserver(this)
val uiSettings = getCurMap()?.uiSettings
getCurMap()?.apply {
setOnMapLoadedListener {
this.mapType = AMap.MAP_TYPE_NORMAL
//设置是否显示地图上的文字标注(如地名、路名等
this.showMapText(false)
//设置是否显示正在建设中的道路
this.setConstructingRoadEnable(false)
//设置是否启用实时交通信息
this.isTrafficEnabled = false
this.showBuildings(false)
setCustomMap()
}
}
uiSettings?.isRotateGesturesEnabled = true
uiSettings?.isScaleControlsEnabled = false
uiSettings?.isZoomControlsEnabled = false
uiSettings?.isTiltGesturesEnabled = true
uiSettings?.setLogoBottomMargin(-150)//隐藏logo
}
private fun setCustomMap() {
val mapStyleOptions = CustomMapStyleOptions()
setMapCustomStyleFile(context,mapStyleOptions)
setMapCustomStyleExtraFile(context,mapStyleOptions)
// 设置自定义样式
mapStyleOptions.setEnable(true)
//mapStyleOptions.setStyleId("your id");
getCurMap()?.setCustomMapStyle(mapStyleOptions)
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
when (event) {
Lifecycle.Event.ON_RESUME -> {
onResume()
}
Lifecycle.Event.ON_PAUSE -> {
onPause()
}
Lifecycle.Event.ON_DESTROY -> {
onDestroy()
}
else -> {
}
}
}
fun onResume() {
mBinding.mapRunView.onResume()
}
fun onPause() {
mBinding.mapRunView.onPause()
}
fun onDestroy() {
mBinding.mapRunView.onDestroy()
}
// 初始化地图视图并开始搜索
fun initMapView(cityList: List<String>?) {
cityList?.forEach { city -> searchQueue.add(city) }
processSearchQueue()
}
// 处理搜索队列
private fun processSearchQueue() {
if (searchQueue.isNotEmpty()) {
val city = searchQueue.poll() // 取出队列中的第一个城市
performSearchForCity(city)
}
}
fun moveCamera(level: Float) {
currentLevel = level
getCurMap()?.moveCamera(
CameraUpdateFactory.newLatLngZoom(
curLocation,
level
)
)
}
private fun setMapCustomStyleExtraFile(context: Context,mapStyleOptions:CustomMapStyleOptions) {
val styleName = "style_extra.data"
var inputStream: InputStream? = null
try {
inputStream = context.assets.open(styleName)
val b = ByteArray(inputStream.available())
inputStream.read(b)
if (mapStyleOptions != null) {
// 设置自定义样式
mapStyleOptions.setStyleExtraData(b)
}
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
inputStream?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
private fun setMapCustomStyleFile(context: Context,mapStyleOptions:CustomMapStyleOptions) {
val styleName = "style.data"
var inputStream: InputStream? = null
try {
inputStream = context.assets.open(styleName)
val b = ByteArray(inputStream.available())
inputStream.read(b)
if (mapStyleOptions != null) {
// 设置自定义样式
mapStyleOptions.setStyleData(b)
}
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
inputStream?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
private fun performSearchForCity(city: String) {
val search = DistrictSearch(context)
val query = DistrictSearchQuery()
query.keywords = city // 使用当前遍历到的城市作为关键字
query.isShowBoundary = true // 是否返回边界值
query.isShowChild = false
search.query = query
// 由于搜索是异步的,我们需要为每个城市设置一个监听器来处理结果
search.setOnDistrictSearchListener { districtResult ->
if (districtResult != null && districtResult.aMapException.errorCode == 1000) {
// 成功获取到行政区划信息
val districtItems: List<DistrictItem>? = districtResult.district
val districtItem = districtItems?.firstOrNull()
val center = districtItem?.center
// 指定中心点并移动地图
if (center != null && center.longitude > 0) {
curLocation = LatLng(center.latitude, center.longitude)
getCurMap()?.moveCamera(
CameraUpdateFactory.newLatLngZoom(
LatLng(center.latitude, center.longitude),
currentLevel
)
)
}
// 记录日志
YzLog.e("IMineRunFootPrintImpl-》districtItems for $city: ${districtItems.toString()}")
// 绘制省份和标记
drawMap(districtItems)
} else {
// 处理查询失败的情况
YzLog.e("IMineRunFootPrintImpl-》DistrictSearch failed for $city: ${districtResult?.aMapException?.errorMessage}")
}
// 完成当前搜索后,继续处理队列中的下一个城市
processSearchQueue()
}
// 执行异步搜索
search.searchDistrictAsyn()
// 保存当前搜索实例,以便在需要时可以取消或重新配置
currentSearch = search
}
private fun drawMap(districtItems: List<DistrictItem>?) {
curJob?.cancel()
curJob = scopeViewLifeAction {
if (!districtItems.isNullOrEmpty()) {
// 遍历DistrictItem列表,为每个行政区划创建并添加多边形
for (districtItem in districtItems) {
//boundaries :以字符串数组形式返回行政区划边界值。 字符串拆分规则: 经纬度,经度和纬度之间用","分隔,坐标点之间用";"分隔。例如:116.076498,40.115153;116.076603,40.115071;116.076333,40.115257;116.076498,40.115153。 字符串数组由来: 如果行政区包括的是群岛,则坐标点是各个岛屿的边界,各个岛屿之间的经纬度使用"|"分隔。一个字符串数组可包含多个封闭区域,一个字符串表示一个封闭区域。
val boundaries = districtItem.districtBoundary()
YzLog.e("IMineRunFootPrintImpl-》boundaries for : ${boundaries}")
for (boundaryStr in boundaries) {
val polygonOptions = withIO {
// 创建一个PolygonOptions对象来定义多边形
val polygonOptions = PolygonOptions()
// 如果字符串包含"|",说明它表示群岛,需要进一步分割
val islandBoundaries = boundaryStr.split("|")
// 遍历每个岛屿的边界字符串
for (islandBoundary in islandBoundaries) {
// 分割出每个经纬度点
val points = islandBoundary.split(";")
// 遍历每个点,并创建LatLng对象
for (point in points) {
// 分割经度和纬度
val coordinates = point.split(",")
if (coordinates.size == 2) {
// 创建LatLng对象并添加到列表
val latitude = coordinates[0].toDouble()
val longitude = coordinates[1].toDouble()
val latLng = LatLng(longitude, latitude)
polygonOptions.add(latLng)
}
}
}
// 设置多边形的样式
polygonOptions.fillColor(Color.parseColor("#80F65C15")) // 设置填充颜色为红色
polygonOptions.strokeColor(Color.parseColor("#FFF65C15")) // 设置边框颜色为黑色(可选)
polygonOptions.strokeWidth(4f) // 设置边框宽度(可选)
}
withMain {
// 将多边形添加到地图
getCurMap()?.addPolygon(polygonOptions)
}
}
addMarker(
districtItem.center.latitude,
districtItem.center.longitude,
districtItem.name
)
}
}
}
}
fun setMapGesturesEnable(enable: Boolean) {
//禁止缩放、滑动、旋转,倾斜手势
getCurMap()?.uiSettings?.isZoomGesturesEnabled = enable
getCurMap()?.uiSettings?.isScrollGesturesEnabled = enable
getCurMap()?.uiSettings?.isRotateGesturesEnabled = enable
getCurMap()?.uiSettings?.isTiltGesturesEnabled = enable
}
private fun addMarker(
latitude: Double?,
longitude: Double?,
title: String,
resInt: Int? = null
) {
if (latitude == null || longitude == null || latitude == 0.0 || longitude == 0.0) return
val aMap = getCurMap()
RunLog.e(" showLocation aMap=$aMap latitude=${latitude} longitude=${longitude} title=${title}")
val view = RunMarkerView(context)
view.iniTitle(title)
val latLng = LatLng(latitude, longitude)
aMap?.addMarker(
MarkerOptions()
.position(latLng)
// .icon( BitmapDescriptorFactory.fromResource(R.drawable.ic_run_map_point))
// .title(title)
.icon(BitmapDescriptorFactory.fromView(view))
.anchor(0.2f, 0.8f)
)
}
fun getMapShot(listener: ((Bitmap?) -> Unit)?) {
getCurMap()?.getMapScreenShot(object : AMap.OnMapScreenShotListener {
override fun onMapScreenShot(p0: Bitmap?) {
}
override fun onMapScreenShot(p0: Bitmap?, p1: Int) {
listener?.invoke(p0)
}
})
}
}