You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
574 lines
18 KiB
574 lines
18 KiB
package com.gyf.csams.activity.model
|
|
|
|
import android.app.Activity
|
|
import android.app.Application
|
|
import android.content.Context
|
|
import android.graphics.BitmapFactory
|
|
import androidx.lifecycle.AndroidViewModel
|
|
import androidx.lifecycle.LiveData
|
|
import androidx.lifecycle.MutableLiveData
|
|
import com.baidu.mapapi.map.*
|
|
import com.baidu.mapapi.model.LatLng
|
|
import com.baidu.mapapi.model.LatLngBounds
|
|
import com.baidu.mapapi.search.core.PoiInfo
|
|
import com.baidu.mapapi.search.core.SearchResult
|
|
import com.baidu.mapapi.search.geocode.*
|
|
import com.baidu.mapapi.search.poi.*
|
|
import com.baidu.mapapi.search.sug.SuggestionResult
|
|
import com.baidu.mapapi.search.sug.SuggestionSearch
|
|
import com.baidu.mapapi.search.sug.SuggestionSearchOption
|
|
import com.gyf.csams.MyLocationListener
|
|
import com.gyf.csams.NOT_IMPL_TIP
|
|
import com.gyf.csams.R
|
|
import com.gyf.csams.util.ContextUtil
|
|
import com.gyf.lib.uikit.ScaffoldModel
|
|
import com.gyf.lib.uikit.StringForm
|
|
import com.gyf.lib.util.DATETIME_FORMAT
|
|
import com.orhanobut.logger.Logger
|
|
import kotlinx.coroutines.GlobalScope
|
|
import kotlinx.coroutines.launch
|
|
import java.util.*
|
|
|
|
|
|
class SizeForm(val application: Application) :
|
|
StringForm(formDesc = application.getString(R.string.activity_size), textLength = 2) {
|
|
|
|
val _formError = MutableLiveData<String>()
|
|
val formError: LiveData<String> = _formError
|
|
val maxActivitySize = application.resources.getInteger(R.integer.activity_size)
|
|
val rangeError = application.getString(R.string.activity_size_error, 1, maxActivitySize)
|
|
|
|
|
|
override fun onChange(value: String) {
|
|
if (value.length > textLength) {
|
|
_formError.value = "${formDesc}不能超过最大长度$textLength"
|
|
_formValue.value = value.slice(IntRange(0, textLength - 1))
|
|
} else if (value.matches(Regex("\\d+")) && value.toInt() !in 1..maxActivitySize) {
|
|
Logger.i("活动人数:${value}不合法")
|
|
_formError.value = rangeError
|
|
} else {
|
|
_formValue.value = value
|
|
_formError.value = ""
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 申请活动数据状态管理
|
|
*
|
|
* @constructor
|
|
*
|
|
*
|
|
* @param application
|
|
*/
|
|
class ApplyActViewModel(application: Application) : AndroidViewModel(application) {
|
|
val activityName =
|
|
StringForm(formDesc = application.getString(R.string.activity_name), textLength = 10)
|
|
val activityTime = StringForm(
|
|
formDesc = application.getString(R.string.activity_time),
|
|
textLength = DATETIME_FORMAT.length
|
|
)
|
|
val activityAddress =
|
|
StringForm(formDesc = application.getString(R.string.activity_address), textLength = 30)
|
|
val activityDesc =
|
|
StringForm(formDesc = application.getString(R.string.activity_desc), textLength = 50)
|
|
|
|
val activitySize = SizeForm(application = application)
|
|
|
|
val city =
|
|
object : StringForm(formDesc = application.getString(R.string.city), textLength = 4) {
|
|
override val formPlaceholder = ""
|
|
}
|
|
|
|
// 默认逆地理编码半径范围
|
|
private val sDefaultRGCRadius = 500
|
|
|
|
private val mGeoCoder: GeoCoder = GeoCoder.newInstance().apply {
|
|
setOnGetGeoCodeResultListener(object : OnGetGeoCoderResultListener {
|
|
override fun onGetGeoCodeResult(p0: GeoCodeResult?) {
|
|
|
|
}
|
|
|
|
override fun onGetReverseGeoCodeResult(reverseGeoCodeResult: ReverseGeoCodeResult?) {
|
|
reverseGeoCodeResult?.let {
|
|
GlobalScope.launch {
|
|
updateUI(it)
|
|
}
|
|
}
|
|
|
|
}
|
|
})
|
|
}
|
|
|
|
lateinit var scaffoldModel: ScaffoldModel
|
|
|
|
private val _mapView = MutableLiveData<MapView>()
|
|
|
|
val mapView: LiveData<MapView> = _mapView
|
|
|
|
/**
|
|
* 更新UI
|
|
*
|
|
* @param reverseGeoCodeResult
|
|
*/
|
|
private fun updateUI(reverseGeoCodeResult: ReverseGeoCodeResult) {
|
|
Logger.i("逆编码更新地点信息")
|
|
var poiInfo = reverseGeoCodeResult.poiList
|
|
val curAddressPoiInfo = PoiInfo()
|
|
curAddressPoiInfo.address = reverseGeoCodeResult.address
|
|
curAddressPoiInfo.location = reverseGeoCodeResult.location
|
|
if (null == poiInfo) {
|
|
poiInfo = ArrayList(2)
|
|
}
|
|
poiInfo.add(0, curAddressPoiInfo)
|
|
|
|
_poInfo.postValue(poiInfo)
|
|
_selectPoi.postValue(null)
|
|
}
|
|
|
|
|
|
fun searchPoiInCity() {
|
|
if (city.formValue.value?.isEmpty() == true ||
|
|
address.formValue.value?.isEmpty() == true
|
|
) {
|
|
Logger.i("city=${city.formValue.value},keyword=${address.formValue.value}")
|
|
return
|
|
} else {
|
|
Logger.i("在${city.formValue.value}市内找${address.formValue.value}")
|
|
mPoiSearch.searchInCity(
|
|
PoiCitySearchOption()
|
|
.city(city.formValue.value)
|
|
.keyword(address.formValue.value)
|
|
.pageNum(0) // 分页编号
|
|
.cityLimit(true)
|
|
.scope(1)
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 在地图上定位poi
|
|
*
|
|
* @param suggestInfo
|
|
*/
|
|
fun locateSuggestPoi(suggestInfo: SuggestionResult.SuggestionInfo) {
|
|
// _sugResult.value?.clear()
|
|
_sugResult.value = mutableListOf()
|
|
_mapView.value?.apply {
|
|
val latLng = suggestInfo.getPt()
|
|
|
|
_mStatusChangeByItemClick.value = false
|
|
// 将地图平移到 latLng 位置
|
|
val mapStatusUpdate = MapStatusUpdateFactory.newLatLng(latLng)
|
|
map.setMapStatus(mapStatusUpdate)
|
|
|
|
// 清除之前的
|
|
// clearData()
|
|
|
|
// 显示当前的
|
|
// if (!showSuggestMarker(latLng)) {
|
|
// searchPoiInCity()
|
|
// }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 显示定位点
|
|
*
|
|
* @param latLng
|
|
*/
|
|
private fun showSuggestMarker(latLng: LatLng?): Boolean {
|
|
if (null == latLng) {
|
|
return false
|
|
}
|
|
_mapView.value?.apply {
|
|
val markerOptions = MarkerOptions()
|
|
.position(latLng)
|
|
.icon(mBitmapDescWaterDrop)
|
|
.scaleX(1.5f)
|
|
.scaleY(1.5f)
|
|
.clickable(true)
|
|
map.addOverlay(markerOptions)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
val mSuggestionSearch: SuggestionSearch = SuggestionSearch.newInstance().apply {
|
|
setOnGetSuggestionResultListener { it ->
|
|
if (it == null
|
|
|| it.error == SearchResult.ERRORNO.RESULT_NOT_FOUND
|
|
) {
|
|
scaffoldModel.update(
|
|
message = application.getString(
|
|
R.string.search_null
|
|
)
|
|
)
|
|
Logger.i("sug检索不到地点")
|
|
} else {
|
|
it.allSuggestions?.let {
|
|
Logger.i("sug检索到${it.size}个地点")
|
|
_sugResult.value = it
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private val mPoiSearch: PoiSearch = PoiSearch.newInstance().apply {
|
|
setOnGetPoiSearchResultListener(
|
|
object : OnGetPoiSearchResultListener {
|
|
override fun onGetPoiResult(
|
|
poiResult: PoiResult?
|
|
) {
|
|
if (poiResult == null || poiResult.error == SearchResult.ERRORNO.RESULT_NOT_FOUND) {
|
|
scaffoldModel.update(
|
|
message = application.getString(
|
|
R.string.search_null
|
|
)
|
|
)
|
|
Logger.i("Poi检索不到地点")
|
|
} else {
|
|
poiResult.allPoi?.let {
|
|
Logger.i("Poi检索到${it.size}个地点")
|
|
setPoiResult(it)
|
|
}
|
|
}
|
|
}
|
|
|
|
@Suppress("DEPRECATION")
|
|
override fun onGetPoiDetailResult(p0: PoiDetailResult?) {
|
|
Logger.i("$p0")
|
|
}
|
|
|
|
override fun onGetPoiDetailResult(p0: PoiDetailSearchResult?) {
|
|
Logger.i("$p0")
|
|
}
|
|
|
|
override fun onGetPoiIndoorResult(p0: PoiIndoorResult?) {
|
|
Logger.i("$p0")
|
|
}
|
|
})
|
|
}
|
|
|
|
private val mBitmapDescWaterDrop = BitmapDescriptorFactory.fromBitmap(
|
|
BitmapFactory.decodeResource(
|
|
application.resources,
|
|
R.drawable.icon_binding_point
|
|
)
|
|
)
|
|
|
|
|
|
private val mMarkerPoiInfo = mutableMapOf<Marker, PoiInfo>()
|
|
|
|
private var mPreSelectMarker: Marker? = null
|
|
|
|
private fun showPoiMarker(poiInfo: PoiInfo?, i: Int) {
|
|
poiInfo?.apply {
|
|
_mapView.value?.apply {
|
|
val markerOptions = MarkerOptions()
|
|
.position(location)
|
|
.icon(mBitmapDescWaterDrop)
|
|
|
|
// 第一个poi放大显示
|
|
// if (0 == i) {
|
|
// val infoWindow: InfoWindow = getPoiInfoWindow(poiInfo)
|
|
// markerOptions.scaleX(1.5f).scaleY(1.5f).infoWindow(infoWindow)
|
|
// }
|
|
val marker = map.addOverlay(markerOptions) as Marker
|
|
mMarkerPoiInfo[marker] = poiInfo
|
|
if (0 == i) {
|
|
mPreSelectMarker = marker
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
private fun setPoiResult(poiInfos: List<PoiInfo>?) {
|
|
_mapView.value?.apply {
|
|
if (null == poiInfos || poiInfos.isEmpty()) {
|
|
return
|
|
}
|
|
clearData()
|
|
|
|
// 将地图平移到 latLng 位置
|
|
val latLng = poiInfos[0].getLocation()
|
|
map.setMapStatus(mapStatus(latLng))
|
|
val itr: Iterator<*> = poiInfos.iterator()
|
|
val latLngs: MutableList<LatLng> = ArrayList()
|
|
var poiInfo: PoiInfo?
|
|
var i = 0
|
|
while (itr.hasNext()) {
|
|
poiInfo = itr.next() as PoiInfo?
|
|
if (null == poiInfo) {
|
|
continue
|
|
}
|
|
showPoiMarker(poiInfo, i)
|
|
latLngs.add(poiInfo.getLocation())
|
|
i++
|
|
}
|
|
setBounds(latLngs)
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* 最佳视野内显示所有点标记
|
|
*/
|
|
private fun setBounds(latLngs: List<LatLng>?) {
|
|
if (null == latLngs || latLngs.isEmpty()) {
|
|
return
|
|
}
|
|
val horizontalPadding = 80
|
|
val verticalPaddingBottom = 400
|
|
|
|
// 构造地理范围对象
|
|
val builder = LatLngBounds.Builder()
|
|
// 让该地理范围包含一组地理位置坐标
|
|
builder.include(latLngs)
|
|
|
|
_mapView.value?.apply {
|
|
// 设置显示在指定相对于MapView的padding中的地图地理范围
|
|
val mapStatusUpdate = MapStatusUpdateFactory.newLatLngBounds(
|
|
builder.build(),
|
|
horizontalPadding,
|
|
verticalPaddingBottom,
|
|
horizontalPadding,
|
|
verticalPaddingBottom
|
|
)
|
|
// 更新地图
|
|
map.setMapStatus(mapStatusUpdate)
|
|
// 设置地图上控件与地图边界的距离,包含比例尺、缩放控件、logo、指南针的位置
|
|
map.setViewPadding(
|
|
0,
|
|
0,
|
|
0,
|
|
verticalPaddingBottom
|
|
)
|
|
}
|
|
|
|
}
|
|
|
|
private fun clearData() {
|
|
Logger.i("清空地图数据")
|
|
_mapView.value?.apply {
|
|
map.clear()
|
|
mMarkerPoiInfo.clear()
|
|
mPreSelectMarker = null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 创建地图中心点marker
|
|
*/
|
|
private fun createCenterMarker(mBaiduMap: BaiduMap, mCenter: LatLng) {
|
|
Logger.i("标记坐标${mCenter}为地图中心点")
|
|
val projection: Projection = mBaiduMap.projection ?: return
|
|
val point = projection.toScreenLocation(mCenter)
|
|
val markerOptions = MarkerOptions()
|
|
.position(mCenter)
|
|
.icon(mBitmapDescWaterDrop)
|
|
.flat(false)
|
|
.fixedScreenPosition(point)
|
|
marker = markerOptions
|
|
mBaiduMap.addOverlay(markerOptions)
|
|
}
|
|
|
|
fun isLatlngEqual(latLng0: LatLng, latLng1: LatLng): Boolean {
|
|
return (latLng0.latitude == latLng1.latitude
|
|
&& latLng0.longitude == latLng1.longitude)
|
|
}
|
|
|
|
/**
|
|
* 逆地理编码请求
|
|
*
|
|
* @param latLng
|
|
*/
|
|
private fun reverseRequest(latLng: LatLng?) {
|
|
Logger.i("逆地理编码请求")
|
|
latLng?.let {
|
|
val reverseGeoCodeOption = ReverseGeoCodeOption().location(it)
|
|
.newVersion(1) // 建议请求新版数据
|
|
.radius(sDefaultRGCRadius)
|
|
mGeoCoder.reverseGeoCode(reverseGeoCodeOption)
|
|
}
|
|
|
|
}
|
|
|
|
private fun mapStatus(mCenter: LatLng): MapStatusUpdate {
|
|
return MapStatusUpdateFactory.newLatLngZoom(
|
|
mCenter,
|
|
16f
|
|
)
|
|
}
|
|
|
|
fun updateMapStatus(mapStatus: MapStatus?) {
|
|
mapStatus?.target?.let {
|
|
// 如果是点击poi item导致的地图状态更新,则不用做后面的逆地理请求,
|
|
if (_mStatusChangeByItemClick.value == true) {
|
|
if (!isLatlngEqual(
|
|
mCenter,
|
|
it
|
|
)
|
|
) {
|
|
mCenter = it
|
|
}
|
|
_mStatusChangeByItemClick.postValue(false)
|
|
return
|
|
}
|
|
|
|
if (!isLatlngEqual(mCenter, it)) {
|
|
mCenter = it
|
|
reverseRequest(mCenter)
|
|
}
|
|
}
|
|
}
|
|
|
|
private lateinit var mCenter: LatLng
|
|
|
|
private lateinit var marker: MarkerOptions
|
|
|
|
|
|
fun createMapView(context: Context): MapView {
|
|
val mapView = MapView(context).apply {
|
|
|
|
MyLocationListener.location.value?.let { _ ->
|
|
// 设置初始中心点为用户附近
|
|
// mCenter = LatLng(it.latitude, it.longitude)
|
|
mCenter = LatLng(39.963175, 116.400244)
|
|
|
|
map.setMapStatus(mapStatus(mCenter = mCenter))
|
|
|
|
map.setOnMapTouchListener {
|
|
ContextUtil.hideKeyBoard(context as Activity)
|
|
}
|
|
|
|
map.setOnMarkerClickListener {
|
|
scaffoldModel.update(message = "确定选中此地址", actionLabel = "确定") {
|
|
val poi = (_selectPoi.value ?: poiInfo.value?.get(0))
|
|
val address = poi?.address ?: poi?.name ?: "地址获取失败,请再尝试一次"
|
|
if (address.isNotEmpty()) {
|
|
activityAddress.onChange(address)
|
|
} else {
|
|
Logger.w("地址获取失败")
|
|
}
|
|
|
|
closeMap()
|
|
}
|
|
true
|
|
}
|
|
|
|
map.removeMarkerClickListener {
|
|
Logger.i("删除标记")
|
|
true
|
|
}
|
|
|
|
map.setOnMapLoadedCallback {
|
|
Logger.i("地图加载完成!")
|
|
// createCenterMarker(map, mCenter)
|
|
map.addOverlay(MarkerOptions().position(mCenter).icon(mBitmapDescWaterDrop))
|
|
reverseRequest(mCenter)
|
|
}
|
|
|
|
map.setOnMapStatusChangeListener(object :
|
|
BaiduMap.OnMapStatusChangeListener {
|
|
override fun onMapStatusChangeStart(p0: MapStatus?) {
|
|
Logger.i("状态开始改变:$p0")
|
|
}
|
|
|
|
override fun onMapStatusChangeStart(
|
|
p0: MapStatus?,
|
|
p1: Int
|
|
) {
|
|
Logger.i("状态改变:$p0,p1=$p1")
|
|
}
|
|
|
|
override fun onMapStatusChange(mapStatus: MapStatus?) {
|
|
Logger.i("状态改变:$mapStatus,_mStatusChangeByItemClick.value=${_mStatusChangeByItemClick.value}")
|
|
updateMapStatus(mapStatus = mapStatus)
|
|
map.clear()
|
|
map.addOverlay(MarkerOptions().position(mCenter).icon(mBitmapDescWaterDrop))
|
|
}
|
|
|
|
override fun onMapStatusChangeFinish(
|
|
mapStatus: MapStatus?
|
|
) {
|
|
Logger.i("状态改变完成:$mapStatus")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
_mapView.value = mapView
|
|
return mapView
|
|
}
|
|
|
|
|
|
val address = object : StringForm(formDesc = "", textLength = 5) {
|
|
override val formPlaceholder = ""
|
|
|
|
override fun onChange(value: String) {
|
|
super.onChange(value)
|
|
Logger.i("Sug检索,城市:${city.formValue.value},地点关键词:${value}")
|
|
// _sugResult.value?.clear()
|
|
_sugResult.value = mutableListOf()
|
|
mSuggestionSearch.requestSuggestion(
|
|
SuggestionSearchOption()
|
|
.city(city.formValue.value)
|
|
.keyword(value)
|
|
.citylimit(true)
|
|
)
|
|
}
|
|
}
|
|
|
|
private val _showMap = MutableLiveData<Boolean>()
|
|
val showMap: LiveData<Boolean> = _showMap
|
|
|
|
private val _mStatusChangeByItemClick = MutableLiveData(false)
|
|
|
|
private val _poInfo = MutableLiveData<List<PoiInfo>>()
|
|
val poiInfo: LiveData<List<PoiInfo>> = _poInfo
|
|
|
|
private val _selectPoi = MutableLiveData<PoiInfo?>()
|
|
val selectPoi: LiveData<PoiInfo?> = _selectPoi
|
|
|
|
private val _sugResult = MutableLiveData<MutableList<SuggestionResult.SuggestionInfo>>()
|
|
val sugResult: LiveData<MutableList<SuggestionResult.SuggestionInfo>> = _sugResult
|
|
|
|
fun updateSelectPoi(poiInfo: PoiInfo?) {
|
|
_selectPoi.value = poiInfo
|
|
}
|
|
|
|
fun updateStatusChangeByItemClick(flag: Boolean) {
|
|
_mStatusChangeByItemClick.value = flag
|
|
}
|
|
|
|
fun openMap() {
|
|
_showMap.value = true
|
|
}
|
|
|
|
private fun closeMap() {
|
|
_showMap.value = false
|
|
|
|
clearData()
|
|
}
|
|
|
|
private fun destroy() {
|
|
mPoiSearch.destroy()
|
|
|
|
mSuggestionSearch.destroy()
|
|
|
|
mapView.value?.onDestroy()
|
|
|
|
mBitmapDescWaterDrop?.recycle()
|
|
}
|
|
|
|
/**
|
|
* TODO 提交申请
|
|
*
|
|
*/
|
|
fun apply(callback: (message: String) -> Unit) {
|
|
callback(NOT_IMPL_TIP)
|
|
}
|
|
} |