[6.5.0][Opt]优化行程概览卡片位置显示

This commit is contained in:
chenfufeng
2024-07-11 19:39:43 +08:00
parent 5e0d6e11a6
commit 41a63d92f6
18 changed files with 420 additions and 48 deletions

View File

@@ -12,4 +12,10 @@ data class DeviceInfoBean(
var deviceIp: String?,
var lon: Double,
var lat: Double
)
) {
/**
* 该点相对于最近轨迹线段AB的位置
* 0:左1:上2:右3:下
*/
var orientation: Int = 0
}

View File

@@ -8,6 +8,13 @@ data class EventBean(
var centerJwdLine: List<GeoCoord>?,// 拥堵的路段
var polygon: List<GeoCoord>?,// 施工、交通事故(延后50m的)
var origPolygon: List<GeoCoord>?// 施工、交通事故(识别的)
)
) {
/**
* 该点相对于最近轨迹线段AB的位置
* 0:左1:上2:右3:下
*/
var orientation: Int = 0
var title: String? = ""
}
data class GeoCoord(var lon: Double, var lat: Double)

View File

@@ -18,6 +18,8 @@ import com.mogo.eagle.core.network.MoGoRetrofitFactory
import com.mogo.eagle.core.network.apiCall
import com.mogo.eagle.core.network.apiResponseCall
import com.mogo.eagle.core.network.request
import com.mogo.eagle.core.utilcode.util.DrivingDirectionUtils
import com.mogo.eagle.core.utilcode.util.LocationUtils
import com.mogo.eagle.core.utilcode.util.Md5Util
import me.jessyan.autosize.utils.AutoSizeUtils
import java.util.Locale
@@ -132,7 +134,7 @@ class TravelRealityModel private constructor() {
onFailed: ((LatLng) -> Unit)
) {
if (url.isNullOrEmpty()) return
val radiusPx = AutoSizeUtils.dp2px(context, 15f).toFloat()
val radiusPx = AutoSizeUtils.dp2px(context, 24f).toFloat()
val options = RequestOptions().transform(
GranularRoundedCorners(radiusPx, radiusPx,0f,0f)
)
@@ -153,4 +155,114 @@ class TravelRealityModel private constructor() {
}
})
}
/**
* 计算Marker相对最近线段ab应该摆放的位置
* 0:左1:上2:右3:下
*/
fun calculateOrientation(pLon: Double, pLat: Double, aLon: Double, aLat: Double, bLon: Double, bLat: Double): Int {
// 0:左1:上2:右3:下
var orientation = 0
// 线段与正北方向的夹角,范围[0,360]
val abAngle = DrivingDirectionUtils.getLineAngle(aLon, aLat, bLon, bLat)
if (abAngle in 70.0..110.0 || abAngle in 250.0..290.0) {// 接近水平方向
// 先比较纬度,然后比较向量叉积
if (pLat > aLat && pLat > bLat) {
orientation = 1
} else if (pLat < aLat && pLat < bLat) {
orientation = 3
} else {
// 0:在线段上1:左侧2:右侧3:上方4:下方(逆时针方向看左、上)
when (LocationUtils.checkLocation(pLon, pLat, aLon, aLat, bLon, bLat)) {
1 -> {
orientation = if (abAngle > 180) {// 地图上从右往左的左侧
3
} else {// 地图上从左往右的左侧
1
}
}
2 -> {
orientation = if (abAngle > 180) {
1
} else {
3
}
}
else -> {// 点p在线段ab所在的直线上
orientation = 1// 未计算后面线段,写死上方
}
}
}
} else {
when (LocationUtils.checkLocation(pLon, pLat, aLon, aLat, bLon, bLat)) {
1 -> {// 逆时针方向为左侧
orientation = when {
abAngle in 0.0..90.0 -> {
0// 地图上的左侧
}
abAngle > 90.0 && abAngle <= 180 -> {
2// 地图上的右侧
}
abAngle > 180.0 && abAngle <= 270.0 -> {
2// 地图上的右侧
}
else -> {
0// 地图上的左侧
}
}
}
2 -> {// 顺时针方向为右侧
orientation = when {
abAngle in 0.0..90.0 -> {
2// 地图上的右侧
}
abAngle > 90.0 && abAngle <= 180 -> {
0// 地图上的左侧
}
abAngle > 180.0 && abAngle <= 270.0 -> {
0// 地图上的左侧
}
else -> {
2// 地图上的右侧
}
}
}
else -> {// 点p在线段ab所在的直线上
orientation = 0// 未计算后面线段,写死左侧
}
}
}
return orientation
}
/**
* 锚点和EventVideoView相关联的
*/
fun calculateAnchor(orientation: Int): Pair<Float, Float> {
// 0:左1:上2:右3:下
return when (orientation) {
0 -> {
Pair(0.94f, 0.48f)
}
1 -> {
Pair(0.49f, 0.94f)
}
2 -> {
Pair(0.05f, 0.47f)
}
else -> {
Pair(0.50f, 0.04f)
}
}
}
}

View File

@@ -15,7 +15,8 @@ class EventVideoView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
private val title: String? = "全息路口"
val title: String? = "",
val orientation: Int = 0
) : ConstraintLayout (
context,
attrs,
@@ -23,7 +24,21 @@ class EventVideoView @JvmOverloads constructor(
) {
init {
LayoutInflater.from(context).inflate(R.layout.layout_event_video_maker, this, true)
// 0:左1:上2:右3:下
when (orientation) {
0 -> {
LayoutInflater.from(context).inflate(R.layout.layout_event_toward_right_maker, this, true)
}
1 -> {
LayoutInflater.from(context).inflate(R.layout.layout_event_toward_down_maker, this, true)
}
2 -> {
LayoutInflater.from(context).inflate(R.layout.layout_event_toward_left_maker, this, true)
}
else -> {
LayoutInflater.from(context).inflate(R.layout.layout_event_toward_up_maker, this, true)
}
}
initView()
}

View File

@@ -0,0 +1,5 @@
package com.mogo.eagle.core.function.business.travelreality.view
import com.amap.api.maps.model.LatLng
data class VideoMarkerEntity(var latLng: LatLng, var title: String? = "", var orientation: Int = 0)

View File

@@ -38,6 +38,7 @@ import com.mogo.eagle.core.function.business.travelreality.EventReqEntity
import com.mogo.eagle.core.function.business.travelreality.Point
import com.mogo.eagle.core.function.business.travelreality.TravelRealityModel.Companion.travelNetWorkModel
import com.mogo.eagle.core.function.business.travelreality.view.EventVideoView
import com.mogo.eagle.core.function.business.travelreality.view.VideoMarkerEntity
import com.mogo.eagle.core.function.call.autopilot.CallerChassisLocationGCJ02ListenerManager
import com.mogo.eagle.core.function.call.autopilot.CallerPlanningRottingListenerManager
import com.mogo.eagle.core.function.call.map.CallerMapUIServiceManager
@@ -175,6 +176,10 @@ class TravelRealityView @JvmOverloads constructor(
@Volatile
private var deviceList: List<CrossDeviceBean>? = null
/**
* 在轨迹点附近的路口设备
*/
@Volatile
private var deviceInPathList: List<CrossDeviceBean>? = null
@@ -184,11 +189,14 @@ class TravelRealityView @JvmOverloads constructor(
@Volatile
private var isRoadTrackReq = false
/**
* 在轨迹点附近的道路事件
*/
private val roadEventList by lazy {
ArrayList<EventBean>()
}
private val ipPointMap by lazy {
HashMap<String, LatLng>()
HashMap<String, VideoMarkerEntity>()
}
private var testTime = 0L
@@ -443,39 +451,46 @@ class TravelRealityView @JvmOverloads constructor(
) {
return@forEach
}
eventBean.orientation = travelNetWorkModel.calculateOrientation(eventBean.lon, eventBean.lat, globalPoint.lon,
globalPoint.lat, globalList[index + 1].lon, globalList[index + 1].lat,)
filterEventWithType(eventBean, eventOptionsList)
}
}
}
// 事件不足3个则用路口设备补齐
completeQuantity()
Log.d(TAG, "过滤掉的事件个数为:${eventList.size - eventOptionsList.size}")
// 绘制道路事件
drawMarkers(DRAW_ROAD_EVENT, eventOptionsList)
}
private fun completeQuantity() {
ipPointMap.clear()
// 全部展示事件,事件数不足则用路口设备补齐
if (roadEventList.size >= 3) {
val ipList = mutableListOf<String>()
roadEventList[0].ip?.let {
ipPointMap[it] =
coordinateConverterWgsToGcj(roadEventList[0].lat, roadEventList[0].lon)
ipList.add(it)
roadEventList[0].let {
it.ip?.let { ip ->
ipPointMap[ip] = VideoMarkerEntity(coordinateConverterWgsToGcj(it.lat, it.lon), it.title, it.orientation)
ipList.add(ip)
}
}
roadEventList[(roadEventList.size - 1) / 2].ip?.let {
ipPointMap[it] = coordinateConverterWgsToGcj(
roadEventList[(roadEventList.size - 1) / 2].lat,
roadEventList[(roadEventList.size - 1) / 2].lon
)
ipList.add(it)
roadEventList[(roadEventList.size - 1) / 2].let {
it.ip?.let { ip ->
ipPointMap[ip] = VideoMarkerEntity(coordinateConverterWgsToGcj(it.lat, it.lon), it.title, it.orientation)
ipList.add(ip)
}
}
roadEventList[roadEventList.size - 1].ip?.let {
ipPointMap[it] = coordinateConverterWgsToGcj(
roadEventList[roadEventList.size - 1].lat,
roadEventList[roadEventList.size - 1].lon
)
ipList.add(it)
roadEventList[roadEventList.size - 1].let {
it.ip?.let { ip ->
ipPointMap[ip] = VideoMarkerEntity(coordinateConverterWgsToGcj(it.lat, it.lon), it.title, it.orientation)
ipList.add(ip)
}
}
reqCrossLive(ipList)
} else {
retryGetCrossDevice()
}
Log.d(TAG, "过滤掉的事件个数为:${eventList.size - eventOptionsList.size}")
// 绘制道路事件
drawMarkers(DRAW_ROAD_EVENT, eventOptionsList)
}
private fun filterEventWithType(
@@ -491,6 +506,7 @@ class TravelRealityView @JvmOverloads constructor(
zIndex(0.8f)
icon(BitmapDescriptorFactory.fromResource(R.drawable.mogo_shigu_nor))
})
eventBean.title = "交通事故"
roadEventList.add(eventBean)
}
@@ -501,6 +517,7 @@ class TravelRealityView @JvmOverloads constructor(
zIndex(0.8f)
icon(BitmapDescriptorFactory.fromResource(R.drawable.mogo_shigong_image))
})
eventBean.title = "道路施工"
roadEventList.add(eventBean)
}
@@ -520,6 +537,7 @@ class TravelRealityView @JvmOverloads constructor(
zIndex(0.8f)
icon(BitmapDescriptorFactory.fromResource(R.drawable.mogo_jingzhi_nor))
})
eventBean.title = "静止事件"
roadEventList.add(eventBean)
}
@@ -530,11 +548,17 @@ class TravelRealityView @JvmOverloads constructor(
zIndex(0.8f)
icon(BitmapDescriptorFactory.fromResource(R.drawable.mogo_jingzhi_nor))
})
eventBean.title = "未知事件"
roadEventList.add(eventBean)
}
}
}
/**
* 等待路口设备数据请求结束
* handleRoadEvent -> 计算Marker方向 -> completeQuantity -> retryGetCrossDevice(等待缓存deviceInPathList结束) -> 获取deviceInPathList -> 获取Marker方向
* handleCrossDevices -> 计算Marker方向 -> 缓存deviceInPathList
*/
private fun retryGetCrossDevice() {
val crossDeviceListTmp = deviceInPathList
if (crossDeviceListTmp.isNullOrEmpty()) {
@@ -548,16 +572,19 @@ class TravelRealityView @JvmOverloads constructor(
if (maxSize <= 0) return
val ipList = mutableListOf<String>()
roadEventList.forEach {
ipList.add(it.ip ?: "")
it.ip?.let { ip ->
ipPointMap[ip] = VideoMarkerEntity(coordinateConverterWgsToGcj(it.lat, it.lon), it.title, it.orientation)
ipList.add(ip)
}
}
// 轨迹线附近的路口设备取几个进行卡片展示
for (i in crossDeviceListTmp.indices) {
if (maxSize > 0 && !crossDeviceListTmp[i].deviceInfoList.isNullOrEmpty()) {
crossDeviceListTmp[i].deviceInfoList!![0].deviceIp?.let {
ipPointMap[it] = coordinateConverterWgsToGcj(
ipPointMap[it] = VideoMarkerEntity(coordinateConverterWgsToGcj(
crossDeviceListTmp[i].deviceInfoList!![0].lat,
crossDeviceListTmp[i].deviceInfoList!![0].lon
)
), "全息路口", crossDeviceListTmp[i].deviceInfoList!![0].orientation)
ipList.add(it)
maxSize--
}
@@ -571,14 +598,14 @@ class TravelRealityView @JvmOverloads constructor(
* 先下载图片然后设置给View
* 再转换成BitmapDescriptor最后进行渲染
*/
private fun downloadImage(url: String, latLng: LatLng?) {
if (latLng == null) return
private fun downloadImage(url: String, latLng: LatLng?, title: String?, orientation: Int = 0) {
if (latLng == null || url.isEmpty()) return
travelNetWorkModel.downloadImage(context, url, latLng, onSuccess = { bitmap, latLng ->
val view = EventVideoView(context)
val view = EventVideoView(context, title = title, orientation = orientation)
view.setBitmap(bitmap)
updateVideoMarker(view, latLng)
}, onFailed = {
val view = EventVideoView(context)
val view = EventVideoView(context, title = title, orientation = orientation)
view.setPlaceHolder()
updateVideoMarker(view, latLng)
})
@@ -588,15 +615,31 @@ class TravelRealityView @JvmOverloads constructor(
ndeRoadCameraNetWorkModel.batchRequestCrossLive(ipList, onSuccess = {
val liveOptionsList = ArrayList<MarkerOptions>()
var markerOption: MarkerOptions
var pair: Pair<Float, Float>
it.forEach { roadCamera ->
roadCamera.ip?.let { ip ->
roadCamera.imageUrl?.let { url ->
downloadImage(url, ipPointMap[ip])
}
markerOption = MarkerOptions()
markerOption.position(ipPointMap[ip])
markerOption.anchor(0.5f, 0.5f)
markerOption.zIndex(0.9f)
if (roadCamera.imageUrl.isNullOrEmpty()) {
var view: EventVideoView
if (ipPointMap[ip] != null) {
markerOption.position(ipPointMap[ip]?.latLng)
view = EventVideoView(context, title = ipPointMap[ip]!!.title, orientation = ipPointMap[ip]!!.orientation)
pair = travelNetWorkModel.calculateAnchor(ipPointMap[ip]!!.orientation)
Log.d(TAG, "绘制时位置为:${ipPointMap[ip]?.latLng},标题为:${ipPointMap[ip]!!.title},方向为:${ipPointMap[ip]!!.orientation},锚点为:${pair}")
markerOption.anchor(pair.first, pair.second)
} else {
return@let
}
view.setPlaceHolder()
markerOption.icon(BitmapDescriptorFactory.fromView(view))
} else {
markerOption.icon(null)
markerOption.position(ipPointMap[ip]?.latLng)
pair = travelNetWorkModel.calculateAnchor(ipPointMap[ip]?.orientation ?: 0)
markerOption.anchor(pair.first, pair.second)
downloadImage(roadCamera.imageUrl!!, ipPointMap[ip]?.latLng, ipPointMap[ip]?.title, ipPointMap[ip]?.orientation ?: 0)
}
liveOptionsList.add(markerOption)
}
}
@@ -628,6 +671,9 @@ class TravelRealityView @JvmOverloads constructor(
) {
return@loop
}
// 计算事件Marker朝向
deviceBean.orientation = travelNetWorkModel.calculateOrientation(deviceBean.lon, deviceBean.lat,
globalPoint.lon, globalPoint.lat, globalList[index + 1].lon, globalList[index + 1].lat)
deviceInPaths.add(deviceBean)
crossOptionsList.add(MarkerOptions().apply {
position(
@@ -868,6 +914,7 @@ class TravelRealityView @JvmOverloads constructor(
val options = MarkerOptions()
options.position(latLng)
options.icon(BitmapDescriptorFactory.fromView(view))
Log.d(TAG, "更新时位置为:${latLng},标题为:${view.title}")
Message.obtain().apply {
what = UPDATE_VIDEO_MARKER
obj = options
@@ -1026,6 +1073,7 @@ class TravelRealityView @JvmOverloads constructor(
DRAW_LIVE_DETAIL -> {
removeMessages(DRAW_LIVE_DETAIL)
removeMessages(UPDATE_VIDEO_MARKER)
(msg.obj as ArrayList<MarkerOptions>).apply {
realDrawLiveMarkers(this)
}
@@ -1156,6 +1204,7 @@ class TravelRealityView @JvmOverloads constructor(
}
private fun realDrawLiveMarkers(liveOptionList: ArrayList<MarkerOptions>) {
Log.d(TAG, "realDrawLiveMarkers")
liveMarkerList.forEach {
it.destroy()
}
@@ -1166,6 +1215,7 @@ class TravelRealityView @JvmOverloads constructor(
}
private fun realUpdateVideoMarker(options: MarkerOptions) {
Log.d(TAG, "realUpdateVideoMarker")
liveMarkerList.forEach {
if (it.position == options.position) {
it.setIcon(options.icon)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="324dp"
android:layout_height="396dp"
android:background="@drawable/event_video_down_bg"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/videoImg"
android:layout_width="279dp"
android:layout_height="198dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="15dp"
/>
<TextView
android:id="@+id/tvTitle"
android:layout_width="182dp"
android:layout_height="36dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="center"
android:textStyle="bold"
android:layout_marginBottom="141dp"
tools:text="全息路口"
android:textColor="#132A54"
android:textSize="25dp"
/>
<ImageView
android:layout_width="88dp"
android:layout_height="17dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@drawable/event_logo_icon"
android:layout_marginBottom="116dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="396dp"
android:layout_height="324dp"
android:background="@drawable/event_video_left_bg"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/videoImg"
android:layout_width="279dp"
android:layout_height="198dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="15dp"
android:layout_marginEnd="21dp"
android:scaleType="fitXY"
tools:src="@drawable/mogo_test"
/>
<TextView
android:id="@+id/tvTitle"
android:layout_width="182dp"
android:layout_height="36dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="center"
android:textStyle="bold"
android:layout_marginStart="144dp"
android:layout_marginBottom="70dp"
tools:text="全息路口"
android:textColor="#132A54"
android:textSize="25dp"
/>
<ImageView
android:layout_width="88dp"
android:layout_height="17dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@drawable/event_logo_icon"
android:layout_marginStart="191dp"
android:layout_marginBottom="44dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="396dp"
android:layout_height="324dp"
android:background="@drawable/event_video_right_bg"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/videoImg"
android:layout_width="279dp"
android:layout_height="198dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="15dp"
android:layout_marginStart="20dp"
/>
<TextView
android:id="@+id/tvTitle"
android:layout_width="182dp"
android:layout_height="36dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="center"
android:textStyle="bold"
android:layout_marginStart="68dp"
android:layout_marginBottom="67dp"
tools:text="全息路口"
android:textColor="#132A54"
android:textSize="25dp"
/>
<ImageView
android:layout_width="88dp"
android:layout_height="17dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@drawable/event_logo_icon"
android:layout_marginStart="115dp"
android:layout_marginBottom="42dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,31 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="280dp"
android:layout_height="352dp"
android:background="@drawable/event_video_bg"
android:layout_width="324dp"
android:layout_height="396dp"
android:background="@drawable/event_video_up_bg"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/videoImg"
android:layout_width="240dp"
android:layout_height="200dp"
android:layout_width="279dp"
android:layout_height="198dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="66dp"
android:layout_marginTop="87dp"
/>
<TextView
android:id="@+id/tvTitle"
android:layout_width="152dp"
android:layout_width="182dp"
android:layout_height="36dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="center"
android:textStyle="bold"
android:layout_marginBottom="65dp"
android:layout_marginBottom="67dp"
tools:text="全息路口"
android:textColor="#132A54"
android:textSize="25dp"
@@ -33,12 +33,12 @@
<ImageView
android:layout_width="88dp"
android:layout_height="16dp"
android:layout_height="17dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@drawable/event_logo_icon"
android:layout_marginBottom="40dp"
android:layout_marginBottom="42dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -154,7 +154,7 @@ public class DrivingDirectionUtils {
}
/**
*
* 取值范围[0,360]
* @param dx1
* @param dy1
* @param dx2

View File

@@ -176,4 +176,47 @@ public class LocationUtils {
}
return difference;
}
/**
* 计算向量叉乘
* @return
*/
public static double crossProduct(double xa, double ya, double xb, double yb, double xp, double yp) {
return (xb - xa) * (yp - ya) - (yb - ya) * (xp - xa);
}
/**
*
* @return 点P是否在线段AB上
*/
public static boolean isOnSegment(double xa, double ya, double xb, double yb, double xp, double yp) {
return (xp >= Math.min(xa, xb) && xp <= Math.max(xa, xb) && yp >= Math.min(ya, yb) && yp <= Math.max(ya, yb));
}
/**
* 判断点P相对于点A和点B组成的线段的位置(逆时针方向看左、上)
* @return 0:在线段上1:左侧2:右侧3:上方4:下方
*/
public static int checkLocation(double xp, double yp, double xa, double ya, double xb, double yb) {
// 计算向量的叉乘
double cp = crossProduct(xa, ya, xb, yb, xp, yp);
if (cp > 0) {
return 1;
} else if (cp < 0) {
return 2;
} else {
// 叉乘为0时,点p在线段AB所在的直线上此时先判断是否在AB线段上
if (isOnSegment(xa, ya, xb, yb, xp, yp)) {
return 0;
} else {
double slopeAB = (yb - ya) / (xb - xa);
double slopeAP = (yp - ya) / (xp - xa);
if (slopeAP > slopeAB) {
return 3;
} else {
return 4;
}
}
}
}
}