[8.0.2]优化规划与决策功能

This commit is contained in:
chenfufeng
2025-06-12 17:25:37 +08:00
parent 7dab0e3686
commit d1af40b2c7
16 changed files with 1043 additions and 148 deletions

View File

@@ -3,21 +3,19 @@ package com.mogo.eagle.core.function
import android.content.Context
import com.alibaba.android.arouter.facade.annotation.Route
import com.mogo.commons.AbsMogoApplication
import com.mogo.eagle.core.data.config.FunctionBuildConfig
import com.mogo.eagle.core.data.constants.MogoServicePaths
import com.mogo.eagle.core.function.api.base.IMoGoFunctionServerProvider
import com.mogo.eagle.core.function.api.map.roma.IMogoRoma
import com.mogo.eagle.core.function.business.MapPointCloudSubscriber
import com.mogo.eagle.core.function.business.SpeedLimitDataManager
import com.mogo.eagle.core.function.business.ai.AiCloudIdentifyDataManager.Companion.aiCloudIdentifyDataManager
import com.mogo.eagle.core.function.business.ai.RomaManager
import com.mogo.eagle.core.function.business.ai.RomaManager.Companion.romaManager
import com.mogo.eagle.core.function.business.identify.MapIdentifySubscriber
import com.mogo.eagle.core.function.business.roadcross.RoadCrossCameraManager
import com.mogo.eagle.core.function.business.routeoverlay.DecisionDataManager
import com.mogo.eagle.core.function.business.routeoverlay.MogoRouteOverlayManager
import com.mogo.eagle.core.function.business.routeoverlay.PredictionDataManager
import com.mogo.eagle.core.function.business.trajectoryoverlay.MogoTrajectoryOverlayManager
import com.mogo.eagle.core.function.call.map.CallerVisualAngleManager
import com.mogo.eagle.core.utilcode.mogo.AppIdentityModeUtils
import com.mogo.eagle.core.utilcode.util.DeviceUtils
import com.mogo.map.MapDataWrapper
@@ -31,6 +29,8 @@ class MapBizProvider :IMoGoFunctionServerProvider, IMogoRoma {
MapDataWrapper.init()
MapIdentifySubscriber.instance
MogoRouteOverlayManager.getInstance().init()
DecisionDataManager.getInstance()
PredictionDataManager.getInstance()
MogoTrajectoryOverlayManager.getInstance().init()
MapPointCloudSubscriber.instance
RoadCrossCameraManager.instance.init(context)

View File

@@ -7,6 +7,7 @@ import com.mogo.eagle.core.data.traffic.TrafficData
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotIdentifyListener
import com.mogo.eagle.core.function.api.base.IMoGoSubscriber
import com.mogo.eagle.core.function.api.datacenter.obu.IMoGoObuStatusListener
import com.mogo.eagle.core.function.business.routeoverlay.PredictionDataManager
import com.mogo.eagle.core.function.business.routeoverlay.PredictionOverlayDrawer
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotIdentifyListenerManager
import com.mogo.eagle.core.function.call.autopilot.CallerChassisLocationWGS84ListenerManager.getLocationHeading
@@ -108,24 +109,15 @@ class MapIdentifySubscriber private constructor() : IMoGoSubscriber,
if (preObj.predictionTrajectoryList == null || preObj.predictionTrajectoryList.size < 2) return@forEach
carPoiList1 = preObj.predictionTrajectoryList[0].trajectoryPointsList
carPoiList2 = preObj.predictionTrajectoryList[1].trajectoryPointsList
val largeType: Int
location1 = LocationUtils.generateLocation(carPoiList1!![0].x, carPoiList1!![0].y, getLocationHeading())
location2 = LocationUtils.generateLocation(carPoiList2!![0].x, carPoiList2!![0].y, getLocationHeading())
if (location1 == null || location2 == null) return@forEach
probability1 = preObj.predictionTrajectoryList[0].predictionProbability
probability2 = preObj.predictionTrajectoryList[1].predictionProbability
CallerAutopilotIdentifyListenerManager.invokeProbabilityChanged(probability1, probability2)
// if (probability1 >= probability2) {
//// MogoIdentifyManager.getInstance().updateGps(location1!!, MogoMap.SMALL_PRED_MAP)
// largeType = 2
// } else {
//// MogoIdentifyManager.getInstance().updateGps(location2!!, MogoMap.SMALL_PRED_MAP)
// largeType = 3
// }
// PredictionOverlayDrawer2.getInstance().drawPredictionList(carPoiList1, getLocationHeading(), false, 2, largeType)
// PredictionOverlayDrawer3.getInstance().drawPredictionList(carPoiList2, getLocationHeading(), false, 3, largeType)
// MogoIdentifyManager.getInstance().updateGps(location1!!, MogoMap.SMALL_PRED_MAP2)
// MogoIdentifyManager.getInstance().updateGps(location2!!, MogoMap.SMALL_PRED_MAP3)
PredictionDataManager.getInstance()?.updateData(carPoiList1!!, 0)
PredictionDataManager.getInstance()?.updateData(carPoiList2!!, 2)
} else {
if (preObj.predictionTrajectoryList.isNullOrEmpty() || isUnKnownType(preObj.classtype) || mogoMap == null) return@forEach
point = preObj.predictionTrajectoryList[0].trajectoryPointsList[0]

View File

@@ -0,0 +1,116 @@
package com.mogo.eagle.core.function.business.routeoverlay
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.os.Message
import android.util.Log
import com.mogo.eagle.core.function.api.autopilot.IMoGoPlanningTrajectoryListener
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotIdentifyListenerManager
import com.mogo.eagle.core.function.call.autopilot.CallerPlanningTrajectoryListenerManager
import com.mogo.eagle.core.utilcode.util.ThreadUtils
import com.mogo.map.MogoMap
import com.mogo.map.MogoMap.Companion.mapInstance
import mogo.telematics.pad.MessagePad
class DecisionDataManager private constructor() : IMoGoPlanningTrajectoryListener {
companion object {
private const val TAG = "DecisionDataManager"
private const val MSG_DATA_POINTS = 0
private const val MSG_CHECK = 1
@Volatile
private var sInstance: DecisionDataManager? = null
fun getInstance(): DecisionDataManager? {
if (sInstance == null) {
synchronized(DecisionDataManager::class.java) {
if (sInstance == null) {
sInstance = DecisionDataManager()
}
}
}
return sInstance
}
}
private var frequentHandler: FrequentHandler? = null
private var lastUpdateTime = 0L
init {
val frequentThread = HandlerThread("decision_thread")
frequentThread.start()
frequentHandler = FrequentHandler(frequentThread.looper)
frequentHandler?.sendEmptyMessageDelayed(MSG_CHECK, 1000)
CallerPlanningTrajectoryListenerManager.addListener(TAG, this)
}
private fun updateData(data: FloatArray) {
frequentHandler?.removeMessages(MSG_DATA_POINTS)
val message = Message.obtain()
message.what = MSG_DATA_POINTS
message.obj = data
frequentHandler?.sendMessage(message)
}
fun release() {
}
private inner class FrequentHandler(looper: Looper) : Handler(looper) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
MSG_DATA_POINTS -> {
lastUpdateTime = System.currentTimeMillis()
CallerAutopilotIdentifyListenerManager.invokeScreenPointsChanged(msg.obj as FloatArray)
}
MSG_CHECK -> {
val time = System.currentTimeMillis()
if (lastUpdateTime > 0 && time - lastUpdateTime >= 1000) {
CallerAutopilotIdentifyListenerManager.invokeScreenPointsChanged(floatArrayOf())
}
sendEmptyMessageDelayed(MSG_CHECK, 1000)
}
}
}
}
override fun onAutopilotTrajectory(trajectoryInfos: MutableList<MessagePad.TrajectoryPoint>) {
ThreadUtils.getCpuPool().execute {
val mogoMap = mapInstance.getMogoMap(MogoMap.DEFAULT)
mogoMap?.let { map ->
val lonLatList = ArrayList<android.graphics.Point>()
trajectoryInfos.forEach {
map.toScreenLocation(it.longitude, it.latitude)?.let { point ->
lonLatList.add(point)
Log.d(TAG, "转换后的屏幕坐标为:(${point.x},${point.y})")
}
}
if (lonLatList.size > 0) {
val points = FloatArray(lonLatList.size * 2)
var x = 0f
var y = 0f
var offset = 0f
for (i in 0 until lonLatList.size) {
x = lonLatList[i].x * 238 / 808.toFloat()
points[i * 2] = x
// TODO:需要根据UI布局而变化成比例缩放(高精地图返回的y轴是镜像的需要在y轴方向上翻转一下)
// float yB = heightB - (yA * (heightB / heightA));
// yB = 480 - (yA * 480 / 1300)
y = (458 + 14 - lonLatList[i].y * 458 / 1300).toFloat()
if (i == 0) {
offset = 357 - y// carBitmapTop等于height * 0.78offset = carBitmapTop - 1stY
}
points[i * 2 + 1] = y + offset
Log.d(TAG, "转换后的屏幕坐标为:(${x},${y})")
}
updateData(points)
} else {
updateData(floatArrayOf())
}
}
}
}
}

View File

@@ -0,0 +1,116 @@
package com.mogo.eagle.core.function.business.routeoverlay
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.os.Message
import android.util.Log
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotIdentifyListenerManager
import com.mogo.map.MogoMap
import com.mogo.map.MogoMap.Companion.mapInstance
class PredictionDataManager private constructor() {
companion object {
private const val TAG = "PredictionDataManager"
private const val MSG_CHECK = 0
private const val MSG_DATA_POINTS_0 = 1
private const val MSG_DATA_POINTS_1 = 2
@Volatile
private var sInstance: PredictionDataManager? = null
fun getInstance(): PredictionDataManager? {
if (sInstance == null) {
synchronized(PredictionDataManager::class.java) {
if (sInstance == null) {
sInstance = PredictionDataManager()
}
}
}
return sInstance
}
}
private var frequentHandler: FrequentHandler? = null
private var lastUpdateTime = 0L
init {
val frequentThread = HandlerThread("prediction_thread")
frequentThread.start()
frequentHandler = FrequentHandler(frequentThread.looper)
frequentHandler?.sendEmptyMessageDelayed(MSG_CHECK, 1000)
}
fun updateData(dataList: List<geometry.Geometry.Point>, index: Int = 0) {
val type = if (index == 0) MSG_DATA_POINTS_0 else MSG_DATA_POINTS_1
frequentHandler?.removeMessages(type)
val message = Message.obtain()
message.what = type
message.obj = dataList
frequentHandler?.sendMessage(message)
}
fun release() {
}
private inner class FrequentHandler(looper: Looper) : Handler(looper) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
MSG_DATA_POINTS_0, MSG_DATA_POINTS_1 -> {
val list = getDataList(msg.obj)
lastUpdateTime = System.currentTimeMillis()
CallerAutopilotIdentifyListenerManager.invokePreScrPointsChanged(list, if (msg.what == MSG_DATA_POINTS_0) 0 else 2)
}
MSG_CHECK -> {
val time = System.currentTimeMillis()
if (lastUpdateTime > 0 && time - lastUpdateTime >= 1000) {
CallerAutopilotIdentifyListenerManager.invokePreScrPointsChanged(floatArrayOf(), 0)
CallerAutopilotIdentifyListenerManager.invokePreScrPointsChanged(floatArrayOf(), 2)
}
sendEmptyMessageDelayed(MSG_CHECK, 1000)
}
}
}
private fun getDataList(obj: Any): FloatArray {
val mogoMap = mapInstance.getMogoMap(MogoMap.DEFAULT) ?: return floatArrayOf()
val list = obj as List<geometry.Geometry.Point>
val lonLatList = ArrayList<android.graphics.Point>()
var arr: DoubleArray?
var point: android.graphics.Point?
list.forEach {
arr = mogoMap.switchData(it.x, it.y, false)// UTM转wgs84
if (arr == null || arr!!.size < 2) return@forEach
point = mogoMap.toScreenLocation(arr!![0], arr!![1])// wgs84转屏幕坐标
if (point == null) return@forEach
lonLatList.add(point!!)
Log.d(TAG, "预测数据的屏幕坐标为:(${point!!.x},${point!!.y})")
}
if (lonLatList.size > 0) {
val points = FloatArray(lonLatList.size * 2)
var x = 0f
var y = 0f
var offset = 0f
for (i in 0 until lonLatList.size) {
x = lonLatList[i].x * 238 / 808.toFloat()
points[i * 2] = x
// TODO:需要根据UI布局而变化成比例缩放(高精地图返回的y轴是镜像的需要在y轴方向上翻转一下)
// float yB = heightB - (yA * (heightB / heightA));
// yB = 480 - (yA * 480 / 1300)
y = (458 + 14 - lonLatList[i].y * 458 / 1300).toFloat()
if (i == 0) {
offset = 357 - y// carBitmapTop等于height * 0.78offset = carBitmapTop - 1stY
}
points[i * 2 + 1] = y + offset
Log.d(TAG, "预测数据转换后的屏幕坐标为:(${x},${y})")
}
return points
} else {
return floatArrayOf()
}
}
}
}

View File

@@ -0,0 +1,713 @@
package com.mogo.eagle.core.function.view
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.DashPathEffect
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import android.util.AttributeSet
import android.util.Log
import android.view.SurfaceHolder
import android.view.SurfaceView
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotIdentifyListener
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotIdentifyListenerManager
import com.mogo.eagle.core.function.map.R
import me.jessyan.autosize.utils.AutoSizeUtils
import kotlin.math.min
class CoordinateAnimationView @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null
) :
SurfaceView(context, attrs), SurfaceHolder.Callback, IMoGoAutopilotIdentifyListener {
companion object {
private const val TAG = "CoordinateAnimationView"
private const val UPDATE_INTERVAL = 1000 // 数据更新间隔(ms)
private const val MAX_POINTS = 500 // 最大点数
private const val BUFFER_SIZE = MAX_POINTS * 2 // x,y坐标交替存储
private const val FPS = 25
}
private var surfaceHolder: SurfaceHolder? = null
private var animationThread: AnimationThread? = null
private var bufferA: Array<Point?>? = null
private var bufferB: Array<Point?>? = null
private var controlPoints: FloatArray? = null // 控制点缓冲区
private var activePointCount = 0
@Volatile
private var activeBufferIndex = 0
@Volatile
private var drawingBufferIndex = 0
private var lastUpdateTime: Long = 0
private var costTime: Long = 0
private var fpsInterval: Int = 1000 / FPS
@Volatile
private var dataChanged = false // 数据变化标志
@Volatile
private var isRunning = false
private var bezierPath: Path? = null // 贝塞尔曲线路径
private var curvePaint: Paint? = null
private var carPaint: Paint? = null
// private var circlePaint: Paint? = null
private var visibleRect: RectF? = null // 可见区域
private var strokeWidth = 22f
private var carBitmap: Bitmap? = null
// 虚线相关参数
private val DASH_LENGTH: Float = 46f // 虚线线段长度
private val DASH_GAP: Float = 80f // 虚线间隔
private val DASH_SPEED: Float = 8f // 虚线移动速度
private var dashOffsetY = 0f // 虚线Y轴偏移量
private var dashStrokeWidth = 3.6f
private var dashPaint: Paint? = null // 虚线画笔
private val leftPath by lazy {
Path()
}
private val rightPath by lazy {
Path()
}
private var index: Int = 1
init {
init(attrs)
}
private fun init(attrs: AttributeSet?) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CoordinateAnimationView)
index = typedArray.getInt(R.styleable.CoordinateAnimationView_map_index, 1)
typedArray.recycle()
Log.d(TAG, "初始化地图的index为:${index}")
surfaceHolder = holder
surfaceHolder!!.addCallback(this)
curvePaint = Paint()
carPaint = Paint()
carPaint!!.isFilterBitmap = false
carPaint!!.isAntiAlias = true
carBitmap = getOriginBitmap(
R.drawable.decision_car_icon,
AutoSizeUtils.dp2px(context, 128f),
AutoSizeUtils.dp2px(context, 280f)
)
// circlePaint = Paint()
// circlePaint!!.color = Color.RED
// circlePaint!!.style = Paint.Style.STROKE
// circlePaint!!.isAntiAlias = true
curvePaint!!.setARGB(102, 48, 163, 255)
// curvePaint!!.shader = LinearGradient(
// 50f, 100f, 750f, 100f,
// intArrayOf(0x0FFF0000, 0xFF00FF00.toInt(), 0x0F0000FF),
// floatArrayOf(0f, 0.5f, 1f),
// Shader.TileMode.CLAMP
// )
curvePaint!!.style = Paint.Style.STROKE
curvePaint!!.strokeWidth = strokeWidth
// 一定要设置抗锯齿,否则画贝塞尔曲线时会出现很多白色分割线
curvePaint!!.isAntiAlias = true
// 线段连接处为圆弧
curvePaint!!.strokeJoin = Paint.Join.ROUND
// 设置线冒样式
curvePaint!!.strokeCap = Paint.Cap.SQUARE
// 初始化虚线
dashPaint = Paint()
dashPaint!!.setColor(Color.WHITE)
dashPaint!!.style = Paint.Style.STROKE
dashPaint!!.strokeWidth = dashStrokeWidth
dashPaint!!.isAntiAlias = true
dashPaint!!.pathEffect = DashPathEffect(floatArrayOf(DASH_LENGTH, DASH_GAP), 0f)
bufferA = arrayOfNulls(MAX_POINTS)
bufferB = arrayOfNulls(MAX_POINTS)
for (i in 0 until MAX_POINTS) {
bufferA!![i] = Point()
bufferB!![i] = Point()
}
bezierPath = Path()
dataChanged = false
controlPoints = FloatArray(MAX_POINTS * 4)// 按照三阶贝塞尔曲线来创建
visibleRect = RectF()
// 启用硬件加速
setLayerType(LAYER_TYPE_HARDWARE, null)
}
private fun getOriginBitmap(resId: Int, desWidth: Int, desHeight: Int): Bitmap {
val bitmap = BitmapFactory.decodeResource(resources, resId)
val scaleWidth = (bitmap.width * 1.6).toInt()
val scaleHeight = (bitmap.height * 1.6).toInt()
Log.d(TAG, "$index-Bitmap width:$scaleWidth,height:$scaleHeight")
return Bitmap.createScaledBitmap(
bitmap, scaleWidth,
scaleHeight, true
)
// val options = BitmapFactory.Options()
// options.inJustDecodeBounds = true
// BitmapFactory.decodeResource(resources, resId, options)
// var inSampleSize = 1
// if (options.outHeight > desHeight || options.outWidth > desWidth) {
// while ((options.outHeight / 2 / inSampleSize) >= desHeight && (options.outWidth / inSampleSize) >= desWidth) {
// inSampleSize *= 2// 每次翻倍,保证是 2 的幂次
// }
// }
// options.inJustDecodeBounds = false
// options.inSampleSize = inSampleSize
// return BitmapFactory.decodeResource(resources, resId, options)
}
override fun surfaceCreated(holder: SurfaceHolder) {
animationThread = AnimationThread()
animationThread!!.start()
isRunning = true
CallerAutopilotIdentifyListenerManager.addListener("${TAG}${this.hashCode()}", this)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
val margin = strokeWidth * 2
// 处理Surface尺寸变化
visibleRect!!.set(margin, margin, width - margin, height - margin)
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
isRunning = false
var retry = true
while (retry) {
try {
animationThread!!.join()
retry = false
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
CallerAutopilotIdentifyListenerManager.removeListener("${TAG}${this.hashCode()}")
carBitmap?.recycle()
carBitmap = null
bufferA = null
bufferB = null
controlPoints = null
}
override fun screenPointsChanged(data: FloatArray, index: Int) {
if (index == this.index) {
updatePoints(data)
}
}
override fun preScrPointsChanged(data: FloatArray, index: Int) {
if (index == this.index) {
updatePoints(data)
}
}
private fun updatePoints(newPoints: FloatArray) {
if (!isRunning) {
Log.d(TAG, "$index-渲染线程未启动")
return
}
if (bufferA == null || bufferB == null) {
Log.d(TAG, "$index-updatePoints->缓冲区未初始化完成!")
return
}
// 获取可用缓冲区
val targetBuffer: Array<Point?> = if ((drawingBufferIndex == 0)) bufferB!! else bufferA!!
// 重置目标缓冲区
for (i in 0 until MAX_POINTS) {
targetBuffer[i]?.reset()
}
// 填充新数据(不能超过最大点数)
val count = min(newPoints.size / 2, MAX_POINTS)
var p: Point?
for (i in 0 until count) {
p = targetBuffer[i]
p?.set(newPoints[i * 2], newPoints[i * 2 + 1])
}
activePointCount = count
activeBufferIndex = if (drawingBufferIndex == 0) 1 else 0
dataChanged = true
Log.d(TAG, "$index-数据已更新!")
}
private fun drawMovingDashedPath(canvas: Canvas?) {
if (canvas == null) {
Log.d(TAG, "$index-画布为null!")
return
}
Log.d(TAG, "$index-开始绘制虚线!")
leftPath.rewind()
rightPath.rewind()
// 两条虚线的X坐标左右对称
val leftLineX = width * 0.2f
val rightLineX = width * 0.8f
// 计算当前虚线的偏移量(取模运算确保在一个周期内)
val offset = dashOffsetY % (DASH_LENGTH + DASH_GAP)
// 绘制多条虚线线段,确保覆盖整个屏幕
var y = offset
var endY = 0f
while (y < height + DASH_LENGTH + DASH_GAP) {
endY = min(y + DASH_LENGTH, height.toFloat())
leftPath.moveTo(leftLineX, y)
leftPath.lineTo(leftLineX, endY)
rightPath.moveTo(rightLineX, y)
rightPath.lineTo(rightLineX, endY)
y += DASH_LENGTH + DASH_GAP// 步长
}
canvas.drawPath(leftPath, dashPaint!!)
canvas.drawPath(rightPath, dashPaint!!)
// 更新虚线偏移量
dashOffsetY += DASH_SPEED
if (dashOffsetY > DASH_LENGTH + DASH_GAP) {
dashOffsetY -= (DASH_LENGTH + DASH_GAP)
}
}
private fun drawCar(canvas: Canvas?) {
if (canvas == null) {
Log.d(TAG, "$index-画布为null!")
return
}
canvas.drawBitmap(carBitmap!!, (width - carBitmap!!.width) / 2f, height * 0.78f, carPaint!!)
}
private fun drawPath(canvas: Canvas?) {
if (canvas == null) {
Log.d(TAG, "$index-画布为null!")
return
}
// 获取当前活跃的缓冲区
val currentBuffer = if (activeBufferIndex == 0) bufferA else bufferB
drawingBufferIndex = activeBufferIndex
// 绘制轨迹
if (dataChanged) {
bezierPath?.rewind()
if (activePointCount > 0) {
// 计算贝塞尔曲线的控制点(二阶贝塞尔曲线性能更好)
// calculateControlPoints(currentBuffer, activePointCount, controlPoints!!)
calculateControlPoints2ndKind(currentBuffer, activePointCount, controlPoints!!)
// 使用可见区域裁剪的贝塞尔曲线
// drawClippedBezierPath(currentBuffer, activePointCount, controlPoints!!)
drawClippedBezier2ndKindPath(currentBuffer, activePointCount, controlPoints!!)
}
dataChanged = false
} else {
Log.d(TAG, "$index-数据未更新:${dataChanged}或无有效的点:${activePointCount}")
}
// 绘制贝塞尔曲线
canvas.drawPath(bezierPath!!, curvePaint!!)
Log.d(TAG, "=====$index-渲染贝塞尔曲线完成!=====")
// if (currentBuffer == null) {
// Log.d(TAG, "currentBuffer未初始化完成!")
// return
// }
// canvas.drawCircle(currentBuffer[0]!!.x, currentBuffer[0]!!.y, 30f, circlePaint!!)
}
// 绘制可见区域内的贝塞尔曲线
private fun drawClippedBezierPath(
points: Array<Point?>?,
count: Int,
controlPoints: FloatArray
) {
if (points.isNullOrEmpty()) {
Log.d(TAG, "$index-贝塞尔曲线点的个数为空,返回!")
return
}
var isFirstPoint = true
var wasLastPointVisible = false
var p: Point?
var nextPoint: Point?
var currentPointVisible = false
var nextPointVisible = false
var controlIndex: Int
var x: Float
var y: Float
var nextX: Float
var nextY: Float
var intersection: FloatArray?
for (i in 0 until count - 1) {
p = points[i]
nextPoint = points[i + 1]
if (p == null || nextPoint == null) continue
x = p.x
y = p.y
nextX = nextPoint.x
nextY = nextPoint.y
// 检查当前点和下一个点是否在可见区域内
currentPointVisible = isPointVisible(x, y)
nextPointVisible = isPointVisible(nextX, nextY)
controlIndex = i * 2
// 处理可见性变化
if (currentPointVisible || nextPointVisible) {
// 如果是第一个可见点,移动到该点
if (isFirstPoint) {
bezierPath!!.moveTo(x, y)
isFirstPoint = false
}
// 如果当前点和下一个点都可见,直接连接
if (currentPointVisible && nextPointVisible) {
bezierPath!!.cubicTo(
controlPoints[controlIndex], controlPoints[controlIndex + 1],
controlPoints[controlIndex + 2], controlPoints[controlIndex + 3],
nextX, nextY
)
} else if (currentPointVisible && !nextPointVisible) {
// intersection = findIntersection(x, y, nextX, nextY)
// if (intersection != null) {
// bezierPath!!.lineTo(intersection[0], intersection[1])
// }
bezierPath!!.cubicTo(
controlPoints[controlIndex], controlPoints[controlIndex + 1],
controlPoints[controlIndex + 2], controlPoints[controlIndex + 3],
nextX, nextY
)
} else if (!currentPointVisible && nextPointVisible) {
// intersection = findIntersection(nextX, nextY, x, y)
// if (intersection != null) {
// bezierPath!!.moveTo(intersection[0], intersection[1])
// bezierPath!!.cubicTo(
// controlPoints[controlIndex], controlPoints[controlIndex + 1],
// controlPoints[controlIndex + 2], controlPoints[controlIndex + 3],
// nextX, nextY
// )
// }
bezierPath!!.cubicTo(
controlPoints[controlIndex], controlPoints[controlIndex + 1],
controlPoints[controlIndex + 2], controlPoints[controlIndex + 3],
nextX, nextY
)
}
wasLastPointVisible = true
} else {
// 如果连续不可见点,重置路径状态
if (wasLastPointVisible) {
isFirstPoint = true
}
wasLastPointVisible = false
}
}
}
// 计算贝塞尔曲线的控制点
private fun calculateControlPoints(
points: Array<Point?>?,
count: Int,
controlPoints: FloatArray
) {
if (count < 3 || points.isNullOrEmpty()) {
Log.d(TAG, "$index-点的个数小于3无法绘制贝塞尔曲线!")
return// 至少需要3个点才能计算控制点
}
var prev: Point?
var current: Point?
var next: Point?
var controlIndex: Int
// 使用相邻点的中点作为控制点
for (i in 1 until count - 1) {
prev = points[i - 1]
current = points[i]
next = points[i + 1]
controlIndex = i * 2
if (current == null || prev == null || next == null) continue
// 第一个控制点:当前点和前一个点的中点
controlPoints[controlIndex] = (current.x + prev.x) / 2
controlPoints[controlIndex + 1] = (current.y + prev.y) / 2
// 第二个控制点:当前点和下一个点的中点
controlPoints[controlIndex + 2] = (current.x + next.x) / 2
controlPoints[controlIndex + 3] = (current.y + next.y) / 2
}
if (points[0] == null || points[count - 2] == null) return
// 特殊处理第一个和最后一个控制点
controlPoints[0] = points[0]!!.x
controlPoints[1] = points[0]!!.y
val lastIndex = (count - 2) * 2
controlPoints[lastIndex] = points[count - 2]!!.x
controlPoints[lastIndex + 1] = points[count - 2]!!.y
}
// 计算二阶贝塞尔曲线的控制点
private fun calculateControlPoints2ndKind(
points: Array<Point?>?,
count: Int,
controlPoints: FloatArray
) {
if (count < 2 || points.isNullOrEmpty()) return // 至少需要2个点才能计算控制点
var current: Point?
var next: Point?
var controlIndex: Int
var dx: Float
var dy: Float
// 简单算法:使用相邻点的中点作为控制点
for (i in 0 until count - 1) {
current = points[i]
next = points[i + 1]
if (current == null || next == null) continue
controlIndex = i * 2
// 控制点:当前点和下一个点之间的中点,增加一定偏移使曲线更平滑
dx = next.x - current.x
dy = next.y - current.y
// 控制点位置 = 中点 + 一定比例的方向向量(使曲线更平滑)
controlPoints[controlIndex] = current.x + dx * 0.5f
controlPoints[controlIndex + 1] = current.y + dy * 0.5f
}
}
/**
* 绘制可见区域内的二阶贝塞尔曲线
*/
private fun drawClippedBezier2ndKindPath(
points: Array<Point?>?,
count: Int,
controlPoints: FloatArray
) {
if (points.isNullOrEmpty()) {
Log.d(TAG, "$index-贝塞尔曲线点的个数为空,返回!")
return
}
var isFirstPoint = true
var wasLastPointVisible = false
// var hasPath = false
var p: Point?
var nextPoint: Point?
for (i in 0 until count - 1) {
p = points[i]
nextPoint = points[i + 1]
if (p == null || nextPoint == null) continue
val x = p.x
val y = p.y
val nextX = nextPoint.x
val nextY = nextPoint.y
// 检查当前点和下一个点是否在可见区域内
val currentPointVisible = isPointVisible(x, y)
val nextPointVisible = isPointVisible(nextX, nextY)
// 处理可见性变化
if (currentPointVisible || nextPointVisible) {
// 如果是第一个可见点,移动到该点
if (isFirstPoint) {
// bezierPath!!.moveTo(x-strokeWidth/4, y)
bezierPath!!.moveTo(x, y)
isFirstPoint = false
// hasPath = true
}
// 如果当前点和下一个点都可见,直接连接
if (currentPointVisible && nextPointVisible) {
// 使用二阶贝塞尔曲线quadTo方法(控制点x, 控制点y, 终点x, 终点y)
bezierPath!!.quadTo(
controlPoints[i * 2], controlPoints[i * 2 + 1],
nextX, nextY
)
// hasPath = true
} else if (currentPointVisible && !nextPointVisible) {
// val intersection = findIntersection(x, y, nextX, nextY)
// if (intersection != null) {
// bezierPath!!.lineTo(intersection[0], intersection[1])
//// hasPath = true
// }
bezierPath!!.lineTo(
controlPoints[i * 2], controlPoints[i * 2 + 1],
)
} else if (!currentPointVisible && nextPointVisible) {
// val intersection = findIntersection(nextX, nextY, x, y)
// if (intersection != null) {
// bezierPath!!.moveTo(intersection[0], intersection[1])
// // 绘制二阶贝塞尔曲线
// val controlIndex = i * 2
// bezierPath!!.quadTo(
// controlPoints[controlIndex], controlPoints[controlIndex + 1],
// nextX, nextY
// )
//// hasPath = true
// }
bezierPath!!.lineTo(
controlPoints[i * 2], controlPoints[i * 2 + 1],
)
}
wasLastPointVisible = true
} else {
// 如果连续不可见点,重置路径状态
if (wasLastPointVisible) {
isFirstPoint = true
}
wasLastPointVisible = false
}
}
// if (count > 1 && hasPath) {
// val lastPoint = points[count - 1] ?: return
// val endX = lastPoint.x
// val endY = lastPoint.y
//
// // 添加一个微小的延伸,确保终点形状为方形
// if (isPointVisible(endX, endY)) {
// bezierPath!!.lineTo(endX + strokeWidth / 4, endY)
// }
// }
Log.d(TAG, "=======$index-绘制二阶贝塞尔曲线完成=======!")
}
/**
* 绘制二阶贝塞尔曲线
*/
private fun drawBezierPath2ndKind(count: Int) {
Log.d(TAG, "=======$index-绘制二阶贝塞尔曲线完成=======!")
}
// 检查点是否在可见区域内(考虑线宽)
private fun isPointVisible(x: Float, y: Float): Boolean {
val extraMargin = strokeWidth / 2
return visibleRect!!.contains(x, y) ||
visibleRect!!.let {
it.inset(-extraMargin, -extraMargin)// 扩大(使用负数)边界以考虑线宽
it.contains(x, y)
}
}
// 计算线段与可见区域的交点
private fun findIntersection(x1: Float, y1: Float, x2: Float, y2: Float): FloatArray? {
// // 简化版:只检查线段与可见区域边界的交点
// // 实际应用中可能需要更复杂的贝塞尔曲线与矩形的交点计算
//
// // 检查与左边界的交点
// if ((x1 < visibleRect!!.left && x2 >= visibleRect!!.left) ||
// (x1 >= visibleRect!!.left && x2 < visibleRect!!.left)
// ) {
// val t = (visibleRect!!.left - x1) / (x2 - x1)
// val y = y1 + t * (y2 - y1)
// if (y >= visibleRect!!.top && y <= visibleRect!!.bottom) {
// return floatArrayOf(visibleRect!!.left, y)
// }
// }
//
// // 检查与右边界的交点
// if ((x1 < visibleRect!!.right && x2 >= visibleRect!!.right) ||
// (x1 >= visibleRect!!.right && x2 < visibleRect!!.right)
// ) {
// val t = (visibleRect!!.right - x1) / (x2 - x1)
// val y = y1 + t * (y2 - y1)
// if (y >= visibleRect!!.top && y <= visibleRect!!.bottom) {
// return floatArrayOf(visibleRect!!.right, y)
// }
// }
//
// // 检查与上边界的交点
// if ((y1 < visibleRect!!.top && y2 >= visibleRect!!.top) ||
// (y1 >= visibleRect!!.top && y2 < visibleRect!!.top)
// ) {
// val t = (visibleRect!!.top - y1) / (y2 - y1)
// val x = x1 + t * (x2 - x1)
// if (x >= visibleRect!!.left && x <= visibleRect!!.right) {
// return floatArrayOf(x, visibleRect!!.top)
// }
// }
//
// // 检查与下边界的交点
// if ((y1 < visibleRect!!.bottom && y2 >= visibleRect!!.bottom) ||
// (y1 >= visibleRect!!.bottom && y2 < visibleRect!!.bottom)
// ) {
// val t = (visibleRect!!.bottom - y1) / (y2 - y1)
// val x = x1 + t * (x2 - x1)
// if (x >= visibleRect!!.left && x <= visibleRect!!.right) {
// return floatArrayOf(x, visibleRect!!.bottom)
// }
// }
return null // 没有交点
}
private inner class AnimationThread : Thread() {
override fun run() {
var canvas: Canvas?
lastUpdateTime = System.currentTimeMillis()
while (isRunning) {
canvas = null
try {
canvas = surfaceHolder!!.lockCanvas()
synchronized(surfaceHolder!!) {
// 绘制
Log.d(TAG, "$index-准备绘制!")
// 清屏
canvas?.drawColor(Color.rgb(231, 235, 238))
drawMovingDashedPath(canvas)
drawPath(canvas)
drawCar(canvas)
}
} finally {
if (canvas != null) {
surfaceHolder!!.unlockCanvasAndPost(canvas)
}
}
try {
costTime = fpsInterval - System.currentTimeMillis() + lastUpdateTime
if (costTime > 0) {
sleep(costTime)
}
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
}
// 轨迹点对象(可复用)
class Point {
var x: Float = 0f
var y: Float = 0f
var isActive: Boolean = false
fun set(x: Float, y: Float) {
this.x = x
this.y = y
this.isActive = true
}
fun reset() {
this.isActive = false
}
}
}

View File

@@ -10,9 +10,6 @@ import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotIdentifyListener
import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotIdentifyListenerManager
import com.mogo.eagle.core.function.map.R
import com.mogo.eagle.core.utilcode.util.ThreadUtils
import kotlinx.android.synthetic.main.layout_decision_container.view.decMapView
import kotlinx.android.synthetic.main.layout_decision_container.view.preDetailView2
import kotlinx.android.synthetic.main.layout_decision_container.view.preDetailView3
import kotlinx.android.synthetic.main.layout_decision_container.view.tvPre1
import kotlinx.android.synthetic.main.layout_decision_container.view.tvPre2
import kotlinx.android.synthetic.main.layout_decision_container.view.tvPre3
@@ -39,21 +36,12 @@ class DecisionLayout @JvmOverloads constructor(
}
fun onCreate(savedInstanceState: Bundle?) {
decMapView.onCreate(savedInstanceState)
preDetailView2.onCreate(savedInstanceState)
preDetailView3.onCreate(savedInstanceState)
}
fun onSaveInstanceState(outState: Bundle) {
decMapView.onSaveInstanceState(outState)
preDetailView2.onSaveInstanceState(outState)
preDetailView3.onSaveInstanceState(outState)
}
fun onResume() {
decMapView.onResume()
preDetailView2.onResume()
preDetailView3.onResume()
}
@SuppressLint("SetTextI18n")
@@ -81,20 +69,11 @@ class DecisionLayout @JvmOverloads constructor(
}
fun onLowMemory() {
decMapView.onLowMemory()
preDetailView2.onLowMemory()
preDetailView3.onLowMemory()
}
fun onPause() {
decMapView.onPause()
preDetailView2.onPause()
preDetailView3.onPause()
}
fun onDestroy() {
decMapView.onDestroy()
preDetailView2.onDestroy()
preDetailView3.onDestroy()
}
}

View File

@@ -6,7 +6,7 @@
android:layout_height="match_parent"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<com.mogo.eagle.core.function.view.PredictionMap2View
<com.mogo.eagle.core.function.view.CoordinateAnimationView
android:id="@+id/preDetailView2"
android:layout_width="238dp"
android:layout_height="458dp"
@@ -14,14 +14,10 @@
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="@dimen/dp_27"
android:layout_marginBottom="24dp"
app:carPosition="6"
app:isAutoLocation="false"
app:isDisplayAnim="false"
app:isWeatherEnable="false"
app:styleMode="MAP_STYLE_DAY_VR_AIP"
app:vrAngleMode="MAP_STYLE_VR_ANGLE_TOP" />
app:map_index="0"
/>
<com.mogo.eagle.core.function.view.DecisionMapView
<com.mogo.eagle.core.function.view.CoordinateAnimationView
android:id="@+id/decMapView"
android:layout_width="238dp"
android:layout_height="458dp"
@@ -29,16 +25,10 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="24dp"
android:focusable="false"
android:focusableInTouchMode="false"
app:carPosition="6"
app:isAutoLocation="false"
app:isDisplayAnim="false"
app:isWeatherEnable="false"
app:styleMode="MAP_STYLE_DAY_VR_AIP"
app:vrAngleMode="MAP_STYLE_VR_ANGLE_TOP" />
app:map_index="1"
/>
<com.mogo.eagle.core.function.view.PredictionMap3View
<com.mogo.eagle.core.function.view.CoordinateAnimationView
android:id="@+id/preDetailView3"
android:layout_width="238dp"
android:layout_height="458dp"
@@ -46,68 +36,8 @@
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="@dimen/dp_27"
android:layout_marginBottom="20dp"
app:carPosition="6"
app:isAutoLocation="false"
app:isDisplayAnim="false"
app:isWeatherEnable="false"
app:styleMode="MAP_STYLE_DAY_VR_AIP"
app:vrAngleMode="MAP_STYLE_VR_ANGLE_TOP" />
<!-- <androidx.cardview.widget.CardView-->
<!-- android:id="@+id/decContainer"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- app:cardBackgroundColor="#D7F1FF"-->
<!-- app:cardCornerRadius="20dp"-->
<!-- app:cardElevation="0dp"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- >-->
<!-- <com.mogo.eagle.core.function.view.PredictionMap2View-->
<!-- android:id="@+id/preDetailView2"-->
<!-- android:layout_width="238dp"-->
<!-- android:layout_height="458dp"-->
<!-- android:layout_gravity="start|bottom"-->
<!-- android:layout_marginStart="@dimen/dp_27"-->
<!-- android:layout_marginBottom="24dp"-->
<!-- app:carPosition="6"-->
<!-- app:isAutoLocation="false"-->
<!-- app:isDisplayAnim="false"-->
<!-- app:isWeatherEnable="false"-->
<!-- app:styleMode="MAP_STYLE_DAY_VR_AIP"-->
<!-- app:vrAngleMode="MAP_STYLE_VR_ANGLE_TOP" />-->
<!-- <com.mogo.eagle.core.function.view.DecisionMapView-->
<!-- android:id="@+id/decMapView"-->
<!-- android:layout_width="238dp"-->
<!-- android:layout_height="458dp"-->
<!-- android:layout_gravity="center_horizontal|bottom"-->
<!-- android:layout_marginBottom="24dp"-->
<!-- android:focusable="false"-->
<!-- android:focusableInTouchMode="false"-->
<!-- app:carPosition="6"-->
<!-- app:isAutoLocation="false"-->
<!-- app:isDisplayAnim="false"-->
<!-- app:isWeatherEnable="false"-->
<!-- app:styleMode="MAP_STYLE_DAY_VR_AIP"-->
<!-- app:vrAngleMode="MAP_STYLE_VR_ANGLE_TOP" />-->
<!-- <com.mogo.eagle.core.function.view.PredictionMap3View-->
<!-- android:id="@+id/preDetailView3"-->
<!-- android:layout_width="238dp"-->
<!-- android:layout_height="458dp"-->
<!-- android:layout_gravity="end|bottom"-->
<!-- android:layout_marginEnd="@dimen/dp_27"-->
<!-- android:layout_marginBottom="20dp"-->
<!-- app:carPosition="6"-->
<!-- app:isAutoLocation="false"-->
<!-- app:isDisplayAnim="false"-->
<!-- app:isWeatherEnable="false"-->
<!-- app:styleMode="MAP_STYLE_DAY_VR_AIP"-->
<!-- app:vrAngleMode="MAP_STYLE_VR_ANGLE_TOP" />-->
<!-- </androidx.cardview.widget.CardView>-->
app:map_index="2"
/>
<TextView
android:id="@+id/tvDecTitle"

View File

@@ -76,4 +76,8 @@
<!-- 是否是订单结束页显示 -->
<attr name="isOrderEnd" format="boolean" />
</declare-styleable>
<declare-styleable name="CoordinateAnimationView">
<attr name="map_index" format="integer" />
</declare-styleable>
</resources>