From 70b6d2a40dcbc91938dc2eafde02d86c8f1c1276 Mon Sep 17 00:00:00 2001 From: lijia Date: Mon, 1 Apr 2024 16:32:03 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=90=E5=8D=95=E8=B5=B7=E5=A7=8B=E7=82=B9?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E8=A7=84=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dahe/gldriver/callback/MyOnRouteSerach.kt | 31 ++ .../waybill/activity/WaybillLoadActivity.kt | 13 +- .../activity/WaybillUnlLoadActivity.kt | 30 +- .../com/dahe/gldriver/utils/LocationUtils.kt | 103 +++++- .../java/com/dahe/gldriver/utils/NaviUtils.kt | 15 + .../com/dahe/gldriver/utils/OrderUtils.kt | 2 +- .../weight/overlay/CustomAmapRouteActivity.kt | 28 ++ .../weight/overlay/DrivingRouteOverlay.kt | 312 ++++++++++++++++++ .../gldriver/weight/overlay/RouteOverlay.kt | 206 ++++++++++++ .../res/drawable-xxhdpi/icon_loca_unload.png | Bin 0 -> 7207 bytes .../res/drawable-xxhdpi/icon_roud_type.png | Bin 0 -> 1276 bytes .../res/layout/activity_waybill_unload.xml | 1 + 12 files changed, 721 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/com/dahe/gldriver/callback/MyOnRouteSerach.kt create mode 100644 app/src/main/java/com/dahe/gldriver/weight/overlay/CustomAmapRouteActivity.kt create mode 100644 app/src/main/java/com/dahe/gldriver/weight/overlay/DrivingRouteOverlay.kt create mode 100644 app/src/main/java/com/dahe/gldriver/weight/overlay/RouteOverlay.kt create mode 100644 app/src/main/res/drawable-xxhdpi/icon_loca_unload.png create mode 100644 app/src/main/res/drawable-xxhdpi/icon_roud_type.png diff --git a/app/src/main/java/com/dahe/gldriver/callback/MyOnRouteSerach.kt b/app/src/main/java/com/dahe/gldriver/callback/MyOnRouteSerach.kt new file mode 100644 index 0000000..a9f541e --- /dev/null +++ b/app/src/main/java/com/dahe/gldriver/callback/MyOnRouteSerach.kt @@ -0,0 +1,31 @@ +package com.dahe.gldriver.callback + +import android.view.View +import com.amap.api.navi.INaviInfoCallback +import com.amap.api.navi.model.AMapNaviLocation +import com.amap.api.services.route.BusRouteResult +import com.amap.api.services.route.DriveRouteResult +import com.amap.api.services.route.RideRouteResult +import com.amap.api.services.route.RouteSearch +import com.amap.api.services.route.RouteSearchV2.OnRouteSearchListener +import com.amap.api.services.route.WalkRouteResult + +/** + * @ClassName MyOnInitNaviFailure + * @Author john + * @Date 2024/3/21 16:12 + * @Description TODO + */ +open class MyOnRouteSerach : RouteSearch.OnRouteSearchListener { + override fun onBusRouteSearched(p0: BusRouteResult?, p1: Int) { + } + + override fun onDriveRouteSearched(p0: DriveRouteResult?, p1: Int) { + } + + override fun onWalkRouteSearched(p0: WalkRouteResult?, p1: Int) { + } + + override fun onRideRouteSearched(p0: RideRouteResult?, p1: Int) { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dahe/gldriver/ui/waybill/activity/WaybillLoadActivity.kt b/app/src/main/java/com/dahe/gldriver/ui/waybill/activity/WaybillLoadActivity.kt index 7a9aeb7..5cdb3aa 100644 --- a/app/src/main/java/com/dahe/gldriver/ui/waybill/activity/WaybillLoadActivity.kt +++ b/app/src/main/java/com/dahe/gldriver/ui/waybill/activity/WaybillLoadActivity.kt @@ -25,6 +25,7 @@ import com.dahe.gldriver.net.DataManager import com.dahe.gldriver.net.RxHttpCallBack import com.dahe.gldriver.utils.GDLocationUtils import com.dahe.gldriver.utils.LocationUtils +import com.dahe.gldriver.utils.NaviUtils import com.dahe.gldriver.utils.OrderUtils import com.dahe.mylibrary.base.BaseActivity import com.dahe.mylibrary.net.CommonResponseBean @@ -73,14 +74,12 @@ class WaybillLoadActivity : BaseActivity() { uiSettings.run { isZoomControlsEnabled = false } } llGoStart.setOnClickListener { - if (!::loadPos.isInitialized) + if (!::loadPos.isInitialized){ showToast("请等待定位成功") return@setOnClickListener - AmapNaviPage.getInstance().showRouteActivity( - applicationContext, - AmapNaviParams(null, null, Poi("天安门", loadPos, ""), AmapNaviType.DRIVER), - object : MyINaviInfoCallback() {} - ) + } + + NaviUtils.getInstance().goNextPoint(mContext,loadPos,loadAddress) } btnRight.setOnClickListener { @@ -109,6 +108,7 @@ class WaybillLoadActivity : BaseActivity() { val load = orderBean.orderChildList.first { it.type == "1" } var distance = "" loadPos = LatLng(load.latitude.toDouble(), load.longitude.toDouble()) + loadAddress = load.address LocationUtils.getInstance().changeCamera(loadPos, aMap!!) if (::gdLatLng.isInitialized) { distance = LocationUtils.getInstance().getDistance( @@ -225,6 +225,7 @@ class WaybillLoadActivity : BaseActivity() { lateinit var gdLatLng: LatLng lateinit var loadPos: LatLng + lateinit var loadAddress: String fun getLocation() { GDLocationUtils.instance.getLocation(mContext) { //errCode等于0代表定位成功,其他的为定位失败,具体的可以参照官网定位错误码说明 diff --git a/app/src/main/java/com/dahe/gldriver/ui/waybill/activity/WaybillUnlLoadActivity.kt b/app/src/main/java/com/dahe/gldriver/ui/waybill/activity/WaybillUnlLoadActivity.kt index e461b75..03bd55a 100644 --- a/app/src/main/java/com/dahe/gldriver/ui/waybill/activity/WaybillUnlLoadActivity.kt +++ b/app/src/main/java/com/dahe/gldriver/ui/waybill/activity/WaybillUnlLoadActivity.kt @@ -1,27 +1,18 @@ package com.dahe.gldriver.ui.waybill.activity import android.Manifest -import android.content.res.Resources import android.graphics.Color import android.os.Bundle -import android.view.View import android.widget.LinearLayout import android.widget.Toast import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.amap.api.maps.AMap import com.amap.api.maps.model.LatLng -import com.amap.api.maps.model.Poi -import com.amap.api.navi.AmapNaviPage -import com.amap.api.navi.AmapNaviParams -import com.amap.api.navi.AmapNaviType -import com.amap.api.navi.INaviInfoCallback -import com.amap.api.navi.model.AMapNaviLocation import com.dahe.gldriver.R import com.dahe.gldriver.adapter.WaybillNodeAdapter import com.dahe.gldriver.base.AppConfig import com.dahe.gldriver.bean.OrderDetailBean -import com.dahe.gldriver.callback.MyINaviInfoCallback import com.dahe.gldriver.databinding.ActivityWaybillUnloadBinding import com.dahe.gldriver.net.BaseObserver import com.dahe.gldriver.net.DataManager @@ -79,7 +70,16 @@ class WaybillUnlLoadActivity : BaseActivity() { return@setOnClickListener } - NaviUtils.getInstance().goNextPoint(mContext,loadPos) + NaviUtils.getInstance().goNextPoint(mContext,loadPos,loadAddress) + } + + llRoutes.setOnClickListener { + + if (!::unLoadPos.isInitialized||!::loadPos.isInitialized) { + showToast("缺少起始点定位信息,请返回重试") + return@setOnClickListener + } + NaviUtils.getInstance().getRoute(mContext,loadPos,unLoadPos,loadAddress,unLoadAddress) } @@ -105,9 +105,14 @@ class WaybillUnlLoadActivity : BaseActivity() { super.onSuccess(t) orderBean = t.data val load = orderBean.orderChildList.first { it.type == "1" } + val unLoad = orderBean.orderChildList.last { it.type == "2" } var distance = "" loadPos = LatLng(load.latitude.toDouble(), load.longitude.toDouble()) - LocationUtils.getInstance().changeCamera(loadPos, aMap!!) + unLoadPos = LatLng(unLoad.latitude.toDouble(), unLoad.longitude.toDouble()) + loadAddress = load.address + unLoadAddress = unLoad.address + + LocationUtils.getInstance().searchRouteResult(mContext,loadPos,unLoadPos,aMap) if (::gdLatLng.isInitialized) { distance = LocationUtils.getInstance().getDistance( gdLatLng, @@ -219,6 +224,9 @@ class WaybillUnlLoadActivity : BaseActivity() { lateinit var gdLatLng: LatLng lateinit var loadPos: LatLng + lateinit var unLoadPos: LatLng + lateinit var loadAddress: String + lateinit var unLoadAddress: String fun getLocation() { GDLocationUtils.instance.getLocation(mContext) { //errCode等于0代表定位成功,其他的为定位失败,具体的可以参照官网定位错误码说明 diff --git a/app/src/main/java/com/dahe/gldriver/utils/LocationUtils.kt b/app/src/main/java/com/dahe/gldriver/utils/LocationUtils.kt index 69650c2..164ac90 100644 --- a/app/src/main/java/com/dahe/gldriver/utils/LocationUtils.kt +++ b/app/src/main/java/com/dahe/gldriver/utils/LocationUtils.kt @@ -1,5 +1,6 @@ package com.dahe.gldriver.utils +import android.content.Context import com.amap.api.maps.AMap import com.amap.api.maps.AMapException import com.amap.api.maps.CameraUpdateFactory @@ -7,7 +8,19 @@ import com.amap.api.maps.model.BitmapDescriptorFactory import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng import com.amap.api.maps.model.MarkerOptions +import com.amap.api.maps.model.Poi +import com.amap.api.navi.AmapNaviPage +import com.amap.api.navi.AmapNaviParams +import com.amap.api.navi.AmapNaviType +import com.amap.api.services.core.AMapException.CODE_AMAP_SUCCESS +import com.amap.api.services.core.LatLonPoint +import com.amap.api.services.route.DrivePath +import com.amap.api.services.route.DriveRouteResult +import com.amap.api.services.route.RouteSearch import com.dahe.gldriver.R +import com.dahe.gldriver.callback.MyINaviInfoCallback +import com.dahe.gldriver.callback.MyOnRouteSerach +import com.dahe.gldriver.weight.overlay.DrivingRouteOverlay import com.dahe.mylibrary.base.SingletonNoPHolder /** @@ -71,8 +84,23 @@ class LocationUtils private constructor() { } } - fun changeCamera(latLng: LatLng,aMap: AMap){ - aMap.moveCamera(CameraUpdateFactory.newCameraPosition(CameraPosition(latLng,12f,30f,30f))) + + /** + * 改变地图中心点 + * @param latLng LatLng + * @param aMap AMap + */ + fun changeCamera(latLng: LatLng, aMap: AMap) { + aMap.moveCamera( + CameraUpdateFactory.newCameraPosition( + CameraPosition( + latLng, + 12f, + 30f, + 30f + ) + ) + ) aMap.clear() aMap.addMarker( MarkerOptions().position(latLng) @@ -81,4 +109,75 @@ class LocationUtils private constructor() { ) ) } + + + /** + * 开始搜索路径规划方案 + * @param context Context + * @param load LatLng + * @param unload LatLng + * @param aMap AMap + */ + fun searchRouteResult(context: Context, load: LatLng, unload: LatLng, aMap: AMap) { + try { + var mRouteSearch = RouteSearch(context) + mRouteSearch.setRouteSearchListener(object : MyOnRouteSerach() { + override fun onDriveRouteSearched(p0: DriveRouteResult?, p1: Int) { + super.onDriveRouteSearched(p0, p1) + if (p1 == CODE_AMAP_SUCCESS) { + if (p0 != null && p0.getPaths() != null && p0.paths.size > 0) { + val drivePath: DrivePath = p0.paths[0] ?: return + + val drivingRouteOverlay = DrivingRouteOverlay( + context, aMap, drivePath, + p0.startPos, + p0.targetPos, null + ).apply { + setNodeIconVisibility(false) //设置节点marker是否显示 + setIsColorfulline(true) //是否用颜色展示交通拥堵情况,默认true + removeFromMap() + addToMap() + zoomToSpan() + } + } + } + } + }) + + + val fromAndTo = RouteSearch.FromAndTo( + LatLonPoint(load.latitude, load.longitude), + LatLonPoint(unload.latitude, unload.longitude) + ) + val query = RouteSearch.DriveRouteQuery( + fromAndTo, RouteSearch.DrivingDefault, null, + null, "" + ) // 第一个参数表示路径规划的起点和终点,第二个参数表示驾车模式,第三个参数表示途经点,第四个参数表示避让区域,第五个参数表示避让道路 + mRouteSearch.calculateDriveRouteAsyn(query) // 异步路径规划驾车模式查询 + + } catch (e: Exception) { + + } + + } + + /** + * 规划路径 + * @param context Context + * @param load LatLng + * @param unload LatLng + * @param startAddress String + * @param endAddress String + */ + fun getRoutes(context: Context, load: LatLng, unload: LatLng, startAddress: String = "", endAddress: String = "") { + val params = AmapNaviParams( + Poi(startAddress, load, ""), + null, + Poi(endAddress, unload, ""), + AmapNaviType.DRIVER + ) + params.setUseInnerVoice(true) + AmapNaviPage.getInstance() + .showRouteActivity(context, params, object : MyINaviInfoCallback() {}) + } } \ No newline at end of file diff --git a/app/src/main/java/com/dahe/gldriver/utils/NaviUtils.kt b/app/src/main/java/com/dahe/gldriver/utils/NaviUtils.kt index b7b58f0..29bc1e5 100644 --- a/app/src/main/java/com/dahe/gldriver/utils/NaviUtils.kt +++ b/app/src/main/java/com/dahe/gldriver/utils/NaviUtils.kt @@ -7,6 +7,7 @@ import com.amap.api.navi.AmapNaviPage import com.amap.api.navi.AmapNaviParams import com.amap.api.navi.AmapNaviType import com.dahe.gldriver.callback.MyINaviInfoCallback +import com.dahe.gldriver.weight.overlay.CustomAmapRouteActivity import com.dahe.mylibrary.base.SingletonNoPHolder /** @@ -24,6 +25,20 @@ class NaviUtils private constructor(){ AmapNaviParams(null, null, Poi(startName, latLng, ""), AmapNaviType.DRIVER), object : MyINaviInfoCallback() {} ) + + +// val params = AmapNaviParams( +// Poi("北京站", latLng, ""), +// null, +// Poi("故宫博物院", LatLng(39.917337, 116.397056), ""), +// AmapNaviType.DRIVER +// ) +// params.setUseInnerVoice(true) +// AmapNaviPage.getInstance().showRouteActivity( +// context, params, object : MyINaviInfoCallback() {}, +// CustomAmapRouteActivity::class.java +// ) + } fun getRoute(context: Context,startLatLng: LatLng,endLatLng: LatLng,startName:String = "",endName:String = ""){ diff --git a/app/src/main/java/com/dahe/gldriver/utils/OrderUtils.kt b/app/src/main/java/com/dahe/gldriver/utils/OrderUtils.kt index 58b1027..0c95d71 100644 --- a/app/src/main/java/com/dahe/gldriver/utils/OrderUtils.kt +++ b/app/src/main/java/com/dahe/gldriver/utils/OrderUtils.kt @@ -164,7 +164,7 @@ class OrderUtils private constructor() { .subscribe(BaseObserver(context, object : RxHttpCallBack() { override fun onSuccess(t: CommonResponseBean) { super.onSuccess(t) - if (t.data != null) { + if (t.data != null&&!t.data.orderId.isNullOrEmpty()) { BaseSPUtils.put(context, NEED_UP_ORDER, Gson().toJson(t.data)) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(Intent(context, UpLocationService2::class.java)) diff --git a/app/src/main/java/com/dahe/gldriver/weight/overlay/CustomAmapRouteActivity.kt b/app/src/main/java/com/dahe/gldriver/weight/overlay/CustomAmapRouteActivity.kt new file mode 100644 index 0000000..2a9fd47 --- /dev/null +++ b/app/src/main/java/com/dahe/gldriver/weight/overlay/CustomAmapRouteActivity.kt @@ -0,0 +1,28 @@ +package com.dahe.gldriver.weight.overlay + +import android.os.Bundle +import android.os.PersistableBundle +import com.amap.api.navi.AmapRouteActivity + +/** + * + * @author liqi + * @date 2019/2/22 + */ +class CustomAmapRouteActivity : AmapRouteActivity() { + override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { + super.onCreate(savedInstanceState, persistentState) + } + + override fun onResume() { + super.onResume() + } + + override fun onPause() { + super.onPause() + } + + override fun onDestroy() { + super.onDestroy() + } +} diff --git a/app/src/main/java/com/dahe/gldriver/weight/overlay/DrivingRouteOverlay.kt b/app/src/main/java/com/dahe/gldriver/weight/overlay/DrivingRouteOverlay.kt new file mode 100644 index 0000000..94d3623 --- /dev/null +++ b/app/src/main/java/com/dahe/gldriver/weight/overlay/DrivingRouteOverlay.kt @@ -0,0 +1,312 @@ +package com.dahe.gldriver.weight.overlay + +import android.content.Context +import android.graphics.Color +import com.amap.api.maps.AMap +import com.amap.api.maps.model.BitmapDescriptorFactory +import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.LatLngBounds +import com.amap.api.maps.model.Marker +import com.amap.api.maps.model.PolylineOptions +import com.amap.api.services.core.LatLonPoint +import com.amap.api.services.route.DrivePath +import com.amap.api.services.route.TMC +import com.dahe.gldriver.R + +/** + * 导航路线图层类。 + */ +class DrivingRouteOverlay( + private val mContext: Context, amap: AMap, path: DrivePath?, + start: LatLonPoint, end: LatLonPoint, throughPointList: List? +) : RouteOverlay( + mContext +) { + private val drivePath: DrivePath? + private val throughPointList: List? + private val throughPointMarkerList: MutableList? = ArrayList() + private var throughPointMarkerVisible = true + private var tmcs: MutableList? = null + private var mPolylineOptions: PolylineOptions? = null + private var mPolylineOptionscolor: PolylineOptions? = null + private var isColorfulline = true + + /** + * 设置路线宽度 + * + * @param mWidth 路线宽度,取值范围:大于0 + */ + override var routeWidth = 25f + private var mLatLngsOfPath: MutableList? = null + fun setIsColorfulline(iscolorfulline: Boolean) { + isColorfulline = iscolorfulline + } + + /** + * 根据给定的参数,构造一个导航路线图层类对象。 + * + * @param amap 地图对象。 + * @param path 导航路线规划方案。 + * @param context 当前的activity对象。 + */ + init { + mAMap = amap + drivePath = path + startPoint = LatLng(start.latitude, start.longitude) + endPoint = LatLng(end.latitude, end.longitude) + this.throughPointList = throughPointList + } + + /** + * 添加驾车路线添加到地图上显示。 + */ + fun addToMap() { + initPolylineOptions() + try { + if (mAMap == null) { + return + } + if (routeWidth == 0f || drivePath == null) { + return + } + mLatLngsOfPath = ArrayList() + tmcs = ArrayList() + val drivePaths = drivePath.steps + for (step in drivePaths) { + val latlonPoints = step.polyline + val tmclist = step.tmCs + tmcs?.addAll(tmclist) + // addDrivingStationMarkers(step, convertToLatLng(latlonPoints.get(0))); + for (latlonpoint in latlonPoints) { + mPolylineOptions!!.add(convertToLatLng(latlonpoint)) + mLatLngsOfPath?.add(convertToLatLng(latlonpoint)) + } + } + if (startMarker != null) { + startMarker!!.remove() + startMarker = null + } + if (endMarker != null) { + endMarker!!.remove() + endMarker = null + } + addStartAndEndMarker() + // addThroughPointMarker(); + if (isColorfulline && tmcs?.size!! > 0) { + colorWayUpdate(tmcs) + showcolorPolyline() + } else { + showPolyline() + } + } catch (e: Throwable) { + e.printStackTrace() + } + } + + /** + * 初始化线段属性 + */ + private fun initPolylineOptions() { + mPolylineOptions = null + mPolylineOptions = PolylineOptions() + mPolylineOptions!! + .color(driveColor) + .setCustomTexture(BitmapDescriptorFactory.fromResource(R.drawable.icon_roud_type)) + .width(routeWidth) + } + + private fun showPolyline() { + addPolyLine(mPolylineOptions) + } + + private fun showcolorPolyline() { + addPolyLine(mPolylineOptionscolor) + } + + /** + * 根据不同的路段拥堵情况展示不同的颜色 + * + * @param tmcSection + */ + private fun colorWayUpdate(tmcSection: List?) { + if (mAMap == null) { + return + } + if (tmcSection == null || tmcSection.size <= 0) { + return + } + var segmentTrafficStatus: TMC + mPolylineOptionscolor = null + mPolylineOptionscolor = PolylineOptions() + mPolylineOptionscolor!!.width(routeWidth) + val colorList: MutableList = ArrayList() + val start = tmcSection[0].polyline[0] + mPolylineOptionscolor!!.add(LatLng(start.latitude, start.longitude)) + colorList.add(driveColor) + for (i in tmcSection.indices) { + segmentTrafficStatus = tmcSection[i] + val color = getcolor(segmentTrafficStatus.status) + val mployline = segmentTrafficStatus.polyline + for (j in 1 until mployline.size) { + mPolylineOptionscolor!!.add(LatLng(mployline[j].latitude, mployline[j].longitude)) + colorList.add(color) + } + } + colorList.add(driveColor) + mPolylineOptionscolor!!.colorValues(colorList) + } + + private fun getcolor(status: String): Int { + return if (status == "畅通") { + Color.GREEN + } else if (status == "缓行") { + Color.YELLOW + } else if (status == "拥堵") { + Color.RED + } else if (status == "严重拥堵") { + Color.parseColor("#990033") + } else { + Color.parseColor("#537edc") + } + } + + fun convertToLatLng(point: LatLonPoint): LatLng { + return LatLng(point.latitude, point.longitude) + } + + override val latLngBounds: LatLngBounds + // /** + protected get() { + val b = LatLngBounds.builder() + b.include(LatLng(startPoint!!.latitude, startPoint!!.longitude)) + b.include(LatLng(endPoint!!.latitude, endPoint!!.longitude)) + if (throughPointList != null && throughPointList.size > 0) { + for (i in throughPointList.indices) { + b.include( + LatLng( + throughPointList[i].latitude, + throughPointList[i].longitude + ) + ) + } + } + return b.build() + } + + fun setThroughPointIconVisibility(visible: Boolean) { + try { + throughPointMarkerVisible = visible + if (throughPointMarkerList != null + && throughPointMarkerList.size > 0 + ) { + for (i in throughPointMarkerList.indices) { + throughPointMarkerList[i].isVisible = visible + } + } + } catch (e: Throwable) { + e.printStackTrace() + } + } + + /** + * 去掉DriveLineOverlay上的线段和标记。 + */ + override fun removeFromMap() { + try { + super.removeFromMap() + if (throughPointMarkerList != null + && throughPointMarkerList.size > 0 + ) { + for (i in throughPointMarkerList.indices) { + throughPointMarkerList[i].remove() + } + throughPointMarkerList.clear() + } + } catch (e: Throwable) { + e.printStackTrace() + } + } + + companion object { + /** + * 途径点 + */ + // private void addThroughPointMarker() { + // if (this.throughPointList != null && this.throughPointList.size() > 0) { + // LatLonPoint latLonPoint = null; + // for (int i = 0; i < this.throughPointList.size(); i++) { + // latLonPoint = this.throughPointList.get(i); + // if (latLonPoint != null) { + // throughPointMarkerList.add(mAMap + // .addMarker((new MarkerOptions()) + // .position( + // new LatLng(latLonPoint + // .getLatitude(), latLonPoint + // .getLongitude())) + // .visible(throughPointMarkerVisible) + // .icon(getThroughPointBitDes()) + // .title("\u9014\u7ECF\u70B9"))); + // } + // } + // } + // } + /** + * 途径点 + * @return + */ + // private BitmapDescriptor getThroughPointBitDes() { + // return BitmapDescriptorFactory.fromResource(R.drawable.amap_through); + // + // } + /** + * 获取两点间距离 + * + * @param start + * @param end + * @return + */ + fun calculateDistance(start: LatLng, end: LatLng): Int { + val x1 = start.longitude + val y1 = start.latitude + val x2 = end.longitude + val y2 = end.latitude + return calculateDistance(x1, y1, x2, y2) + } + + fun calculateDistance(x1: Double, y1: Double, x2: Double, y2: Double): Int { + var x1 = x1 + var y1 = y1 + var x2 = x2 + var y2 = y2 + val NF_pi = 0.01745329251994329 // 弧度 PI/180 + x1 *= NF_pi + y1 *= NF_pi + x2 *= NF_pi + y2 *= NF_pi + val sinx1 = Math.sin(x1) + val siny1 = Math.sin(y1) + val cosx1 = Math.cos(x1) + val cosy1 = Math.cos(y1) + val sinx2 = Math.sin(x2) + val siny2 = Math.sin(y2) + val cosx2 = Math.cos(x2) + val cosy2 = Math.cos(y2) + val v1 = DoubleArray(3) + v1[0] = cosy1 * cosx1 - cosy2 * cosx2 + v1[1] = cosy1 * sinx1 - cosy2 * sinx2 + v1[2] = siny1 - siny2 + val dist = Math.sqrt(v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2]) + return (Math.asin(dist / 2) * 12742001.5798544).toInt() + } + + //获取指定两点之间固定距离点 + fun getPointForDis(sPt: LatLng, ePt: LatLng, dis: Double): LatLng { + val lSegLength = calculateDistance(sPt, ePt).toDouble() + val preResult = dis / lSegLength + return LatLng( + (ePt.latitude - sPt.latitude) * preResult + sPt.latitude, + (ePt.longitude - sPt.longitude) * preResult + sPt.longitude + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dahe/gldriver/weight/overlay/RouteOverlay.kt b/app/src/main/java/com/dahe/gldriver/weight/overlay/RouteOverlay.kt new file mode 100644 index 0000000..4b382c2 --- /dev/null +++ b/app/src/main/java/com/dahe/gldriver/weight/overlay/RouteOverlay.kt @@ -0,0 +1,206 @@ +package com.dahe.gldriver.weight.overlay + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Color +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.BitmapDescriptor +import com.amap.api.maps.model.BitmapDescriptorFactory +import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.LatLngBounds +import com.amap.api.maps.model.Marker +import com.amap.api.maps.model.MarkerOptions +import com.amap.api.maps.model.Polyline +import com.amap.api.maps.model.PolylineOptions +import com.dahe.gldriver.R + +open class RouteOverlay(private val mContext: Context) { + protected var stationMarkers: MutableList? = ArrayList() + protected var allPolyLines: MutableList = ArrayList() + protected var startMarker: Marker? = null + protected var endMarker: Marker? = null + protected var startPoint: LatLng? = null + protected var endPoint: LatLng? = null + protected var mAMap: AMap? = null + private var startBit: Bitmap? = null + private var endBit: Bitmap? = null + private var busBit: Bitmap? = null + private var walkBit: Bitmap? = null + private var driveBit: Bitmap? = null + protected var nodeIconVisible = true + + /** + * 去掉BusRouteOverlay上所有的Marker。 + * @since V2.1.0 + */ + open fun removeFromMap() { + if (startMarker != null) { + startMarker!!.remove() + } + if (endMarker != null) { + endMarker!!.remove() + } + for (marker in stationMarkers!!) { + marker.remove() + } + for (line in allPolyLines) { + line.remove() + } + destroyBit() + } + + private fun destroyBit() { + if (startBit != null) { + startBit!!.recycle() + startBit = null + } + if (endBit != null) { + endBit!!.recycle() + endBit = null + } + if (busBit != null) { + busBit!!.recycle() + busBit = null + } + if (walkBit != null) { + walkBit!!.recycle() + walkBit = null + } + if (driveBit != null) { + driveBit!!.recycle() + driveBit = null + } + } + + protected val startBitmapDescriptor: BitmapDescriptor + /** + * 给起点Marker设置图标,并返回更换图标的图片。如不用默认图片,需要重写此方法。 + * @return 更换的Marker图片。 + * @since V2.1.0 + */ + protected get() = BitmapDescriptorFactory.fromResource(R.drawable.icon_loca_load) + protected val endBitmapDescriptor: BitmapDescriptor + /** + * 给终点Marker设置图标,并返回更换图标的图片。如不用默认图片,需要重写此方法。 + * @return 更换的Marker图片。 + * @since V2.1.0 + */ + protected get() = BitmapDescriptorFactory.fromResource(R.drawable.icon_loca_unload) + /** + * 给公交Marker设置图标,并返回更换图标的图片。如不用默认图片,需要重写此方法。 + * @return 更换的Marker图片。 + * @since V2.1.0 + */ + // protected BitmapDescriptor getBusBitmapDescriptor() { + // return BitmapDescriptorFactory.fromResource(R.drawable.amap_bus); + // } + /** + * 给步行Marker设置图标,并返回更换图标的图片。如不用默认图片,需要重写此方法。 + * @return 更换的Marker图片。 + * @since V2.1.0 + */ + // protected BitmapDescriptor getWalkBitmapDescriptor() { + // return BitmapDescriptorFactory.fromResource(R.drawable.amap_man); + // } + // protected BitmapDescriptor getDriveBitmapDescriptor() { + // return BitmapDescriptorFactory.fromResource(R.drawable.amap_car); + // } + protected fun addStartAndEndMarker() { + startMarker = mAMap!!.addMarker( + MarkerOptions() + .position(startPoint).icon(startBitmapDescriptor) + .title("\u8D77\u70B9") + ) + // startMarker.showInfoWindow(); + endMarker = mAMap!!.addMarker( + MarkerOptions().position(endPoint) + .icon(endBitmapDescriptor).title("\u7EC8\u70B9") + ) + // mAMap.moveCamera(CameraUpdateFactory.newLatLngZoom(startPoint, + // getShowRouteZoom())); + } + + /** + * 移动镜头到当前的视角。 + * @since V2.1.0 + */ + fun zoomToSpan() { + if (startPoint != null) { + if (mAMap == null) { + return + } + try { + val bounds = latLngBounds + mAMap!!.animateCamera( + CameraUpdateFactory + .newLatLngBounds(bounds, 100) + ) + } catch (e: Throwable) { + e.printStackTrace() + } + } + } + + protected open val latLngBounds: LatLngBounds + protected get() { + val b = LatLngBounds.builder() + b.include(LatLng(startPoint!!.latitude, startPoint!!.longitude)) + b.include(LatLng(endPoint!!.latitude, endPoint!!.longitude)) + return b.build() + } + + /** + * 路段节点图标控制显示接口。 + * @param visible true为显示节点图标,false为不显示。 + * @since V2.3.1 + */ + fun setNodeIconVisibility(visible: Boolean) { + try { + nodeIconVisible = visible + if (stationMarkers != null && stationMarkers!!.size > 0) { + for (i in stationMarkers!!.indices) { + stationMarkers!![i].isVisible = visible + } + } + } catch (e: Throwable) { + e.printStackTrace() + } + } + + protected fun addStationMarker(options: MarkerOptions?) { + if (options == null) { + return + } + val marker = mAMap!!.addMarker(options) + if (marker != null) { + stationMarkers!!.add(marker) + } + } + + protected fun addPolyLine(options: PolylineOptions?) { + if (options == null) { + return + } + val polyline = mAMap!!.addPolyline(options) + if (polyline != null) { + allPolyLines.add(polyline) + } + } + + protected open val routeWidth: Float + protected get() = 18f + protected val walkColor: Int + protected get() = Color.parseColor("#6db74d") + protected val busColor: Int + /** + * 自定义路线颜色。 + * return 自定义路线颜色。 + * @since V2.2.1 + */ + protected get() = Color.parseColor("#537edc") + protected val driveColor: Int + protected get() = Color.parseColor("#537edc") // protected int getShowRouteZoom() { + // return 15; + // } +} diff --git a/app/src/main/res/drawable-xxhdpi/icon_loca_unload.png b/app/src/main/res/drawable-xxhdpi/icon_loca_unload.png new file mode 100644 index 0000000000000000000000000000000000000000..6f0312e83332948d0d1953bd1ab321fc63b3fe4d GIT binary patch literal 7207 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/icon_roud_type.png b/app/src/main/res/drawable-xxhdpi/icon_roud_type.png new file mode 100644 index 0000000000000000000000000000000000000000..94fbba256f42bd3e319269854de5db0a8483e1c6 GIT binary patch literal 1276 zcmeAS@N?(olHy`uVBq!ia0vp^B0#Lb!2~4dGMs-1q$EpRBT9nv(@M${i&7aJQ}UBi z6+Ckj(^G>|6H_V+Po~;1Ffc1-hD4M^`1)8S=jZArg4F0$^BXQ!4Z zB&DWj=GiK}-@RW+Av48RDcsc8z_-9TH6zobswg$M$}c3jDm&RSMakYy!KT6rXh3di zNuokUZcbjYRfVk**jy_h8zii+qySb@l5ML5aa4qFfP!;=QL2Keo|$g4p^1fpxuu?= znTdh1nT~>yfuW_ofrY-Iv95ukm4TU+sks6aC;@FNN=dT{a&d#&1?1T(Wt5Z@Sn2DR zmzV368|&p4rRy77S^~{5(l;{F1**_3uFNY*tkBIXR)(42l3JWxlvz-cnV+WsGBGhJ zzqG_w3FuOY7jg^W#^x16g9#X5`W1-<`i6Q2ATPlb`ugHmnwtw(U0f2Bif}JhgNs8d z3sUuiQj7CTi;{s6m!=G|s=&&*C^fMpzbGU>KL-|y0U7xv`NbLe1;8LPGEfN5%uC5H zF9s|3^|kWMD=taQOHTE4u~h;ZpqH7MVrArH>11l^=IH9?=4fc>>T2m|W@%w;YU~0G zF9QQ-1DIZy{N&Qy)Vvay-V}shC!BggDIvE2XtPUdQD#|cid#{Bt^(M{R++fnVu8~< zsNNLZZn4CvS0CsYeNen2#WPF@n0P=;c&Y_*;ORa!5181CfT?==#4BD542*@IE{-7; zw~|7%%@ZD_Ju`WluE1Jj|M%nl>-&9pL>dCmE}o@%DMvYL-Wrjk$BwjlGji*3igWJP zYpzl_>~uusYltAH8S~qm>+2gg^tOKa-7{;$t$x?dYV&WYJT^;ndv@La{<^9yAMFp< zvm3rPIxBPN+%>@qb8_ymrR@3h(dt{px7qXa>;L|iT`VA^^7`-d|MTa)zTBV^W4L)k zFN?CY@M;tG35yRc-f^OcOWgSR?fLJ8{GRRo^-)Tr;?G%0Juy97MqSxD-a4~=%Ip1^ zSpVDn{`voVeM_2Y+*8T4yLWVA9CprlJkx)lZS(DdPiG@#>wGQqGIQ&A<;3z96l%;8 z4G|5GIM3u&^Y5?f+merK&zE1^v-$7OIMX;$S2@cC!ZRdh_Ql(AUg6>0P;@qC!B+>j zcQ;R+mS30H+qBAiZF~HD1|y-VN`Zg=#Qosb=k~umb)L6H3Dd#*|Le=`?&}{>EBJEu r