From efb3b5e84ed4493f82f30ed3c3dcdff0fd5a8083 Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Fri, 16 Jan 2026 10:55:55 +0800
Subject: [PATCH] feat:任务工单后台地图
---
applications/task-work-order/src/utils/cesium/mapUtil.js | 207 +++++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 181 insertions(+), 26 deletions(-)
diff --git a/applications/task-work-order/src/utils/cesium/mapUtil.js b/applications/task-work-order/src/utils/cesium/mapUtil.js
index eabf803..89bf787 100644
--- a/applications/task-work-order/src/utils/cesium/mapUtil.js
+++ b/applications/task-work-order/src/utils/cesium/mapUtil.js
@@ -1,7 +1,8 @@
import * as Cesium from 'cesium'
import * as turf from '@turf/turf'
-const { VITE_APP_BASE, VITE_APP_ENV, VITE_APP_REGION_URL } = import.meta.env
+import store from '@/store'
+const { VITE_APP_BASE, VITE_APP_ENV, VITE_APP_REGION_URL } = import.meta.env
export const getLngLatDistance = (lat1, lng1, lat2, lng2) => {
const radLat1 = (lat1 * Math.PI) / 180.0
const radLat2 = (lat2 * Math.PI) / 180.0
@@ -190,6 +191,66 @@
const newArr = arr.sort((a, b) => a.distance - b.distance)
return newArr[0]
}
+// 获取多边形内部数据
+export const samplePolygonInterior = (positions, samplingDistance) => {
+ if (!positions.length) return []
+ let minLng = Infinity,
+ maxLng = -Infinity
+ let minLat = Infinity,
+ maxLat = -Infinity
+ positions.forEach(pos => {
+ minLng = Math.min(minLng, pos.lng)
+ maxLng = Math.max(maxLng, pos.lng)
+ minLat = Math.min(minLat, pos.lat)
+ maxLat = Math.max(maxLat, pos.lat)
+ })
+
+ // 将 Cesium 多边形格式转为 Turf 格式
+ const turfPolygonCoords = positions.map(pos => [pos.lng, pos.lat])
+ turfPolygonCoords.push([positions[0].lng, positions[0].lat]) // 闭合多边形
+ const turfPolygon = turf.polygon([turfPolygonCoords])
+
+ // 生成网格点并判断是否在多边形内
+ const sampledPoints = []
+ for (let lng = minLng; lng <= maxLng; lng += samplingDistance) {
+ for (let lat = minLat; lat <= maxLat; lat += samplingDistance) {
+ // 创建 Turf 点
+ const turfPoint = turf.point([lng, lat])
+ // Turf 原生方法判断点是否在多边形内
+ if (turf.booleanPointInPolygon(turfPoint, turfPolygon)) {
+ sampledPoints.push({
+ lng: Number(lng.toFixed(6)),
+ lat: Number(lat.toFixed(6)),
+ height: 0,
+ })
+ }
+ }
+ }
+
+ return sampledPoints
+}
+// 获取多边形面内的最高点(相对地形高度)
+export const getPolygonHighestPoint = async (positions, viewer, samplingDistance = 0.0002) => {
+ try {
+ const interiorPoints = samplePolygonInterior(positions, samplingDistance, viewer)
+ const allPoints = [...positions, ...interiorPoints]
+ // 批量获取所有点的地形高度
+ const pointsWithTerrain = await getPointPositionsHeight(allPoints, viewer)
+ // console.log('所有点的地形高度', pointsWithTerrain)
+
+ // 地形高度(ASL)最高的点
+ const highestTerrainPoint = pointsWithTerrain.reduce(
+ (max, curr) => (curr.ASL > max.ASL ? curr : max),
+ pointsWithTerrain[0]
+ )
+
+ // console.log('地形最高点信息:', highestTerrainPoint)
+ return highestTerrainPoint
+ } catch (error) {
+ console.error('获取地形最高点失败:', error)
+ return null
+ }
+}
// 获取当前经纬度地形数据
export const getLnglatAltitude = (longitude, latitude, viewer) => {
@@ -285,31 +346,24 @@
return new Promise((resolve, reject) => {
if (!data || !data.length) {
resolve([])
-
return
}
-
// 假设 viewer 已经初始化并且 terrainProvider 是有效的
const terrainProvider = viewer?.terrainProvider
-
// 创建 Cartographic 对象
const cartographics = data.map(item => {
const { lng, lat } = item
-
return Cesium.Cartographic.fromDegrees(Number(lng), Number(lat))
})
-
// 获取地形数据的Promise
const promise = Cesium.sampleTerrainMostDetailed(terrainProvider, cartographics)
-
// 使用 Cesium.when 处理 Promise
promise
.then(function (updatedPositions) {
- // updatedPositions 是一个数组,包含更新后的 Cartographic 对象
+ // 是一个数组,包含更新后的 Cartographic 对象
const newPosition = updatedPositions.map((item, index) => {
const longitude = Cesium.Math.toDegrees(item.longitude)
const latitude = Cesium.Math.toDegrees(item.latitude)
-
let pointData = {
...data[index],
longitude,
@@ -317,18 +371,14 @@
ASL: Number(item.height),
customHeight: Number(item.height),
}
-
if (droneHeight) pointData.TH = Number(item.height) + Number(droneHeight)
-
return pointData
})
-
resolve(newPosition)
- // 在这里,你可以使用 height 值进行后续操作
})
.catch(function (error) {
- // console.error('获取高程时发生错误:', error);
- reject(error)
+ console.log('获取高程时发生错误:', error)
+ resolve(data.map(item => ({ ...item, ASL: 0, customHeight: 0, longitude: item.lng, latitude: item.lat })))
})
})
}
@@ -410,11 +460,15 @@
if (!Array.isArray(positionsData) || positionsData.length === 0) return
// 如果是一个点,加两个点方便后续生成外包围盒
- if (positionsData.length === 1){
- const [lon,lat,height] = positionsData[0]
- positionsData = [[lon+0.001,lat+0.001,height],[lon-0.001,lat-0.001,height],[lon,lat,height]]
+ if (positionsData.length === 1) {
+ const [lon, lat, height = 0] = positionsData[0]
+ positionsData = [
+ [lon + 0.001, lat + 0.001, height],
+ [lon - 0.001, lat - 0.001, height],
+ [lon, lat, height],
+ ]
}
- const positions = positionsData.map(([lon, lat, height]) =>
+ const positions = positionsData.map(([lon, lat, height = 0]) =>
Cesium.Cartesian3.fromDegrees(Number(lon), Number(lat), Number(height || 0))
)
@@ -439,7 +493,7 @@
range: boundingSphere.radius * multiple,
},
complete: () => {
- // 飞行完成后检查相机高度是否小于最高点,如果小于直接飞到最高点
+ // 飞行完成后检查相机高度是否小于最高点
const cameraHeight = Cesium.Cartographic.fromCartesian(viewer.camera.position).height
if (cameraHeight < maxHeight) {
viewer.camera.flyTo({
@@ -529,7 +583,7 @@
// 辅助函数:创建旋转文字的画布
export function createRotatedTextCanvas(time) {
- const prefix = '预计'
+ const prefix = '预计到达'
const suffix = '分钟'
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
@@ -537,7 +591,9 @@
context.font = 'bold 16px Source Han Sans CN'
// 测量各部分文本宽度
const prefixWidth = context.measureText(prefix).width
- const timeWidth = context.measureText(time).width
+ let timeWidth = context.measureText(time).width
+ const timeWidthMore = `${time >= 10 ? 6 : 0}`
+ timeWidth = timeWidth + Number(timeWidthMore)
const suffixWidth = context.measureText(suffix).width
const totalWidth = prefixWidth + timeWidth + suffixWidth
const textHeight = 20 // 估算高度
@@ -550,17 +606,116 @@
context.textAlign = 'left' // 改为左对齐,方便分段绘制
// 清除画布
context.clearRect(0, 0, canvas.width, canvas.height)
- // 绘制“预计”(白色)
+ // 绘制“预计到达”(白色)
context.fillStyle = '#FFFFFF'
context.fillText(prefix, 10, 15)
// 绘制时间(蓝色)
- context.font = 'bold 30px Source Han Sans CN'
+ context.font = 'bold 24px Source Han Sans CN'
context.fillStyle = '#1EE7E7' // 亮蓝色
- context.fillText(time, 10 + prefixWidth, 12)
+ // 设置宽度
+ context.textBaseline = 'middle'
+ context.fillText(time, 10 + prefixWidth, 14)
// 绘制“分钟”(白色)
context.font = 'bold 16px Source Han Sans CN'
context.fillStyle = '#FFFFFF'
- context.fillText(suffix, 20 + prefixWidth + timeWidth, 15)
+ context.fillText(suffix, 14 + prefixWidth + timeWidth, 15)
return canvas
}
+
+
+export const saveCurrentCameraPosition = viewer => {
+ const position = viewer?.camera.position.clone() // 相机位置(Cartesian3)
+ const heading = viewer?.camera.heading // 航向角(弧度)
+ const pitch = viewer?.camera.pitch // 俯仰角(弧度)
+ const roll = viewer?.camera.roll // 翻滚角(弧度)
+ store.commit('setCameraPosition', {
+ duration: 0,
+ destination: position,
+ orientation: { heading, pitch, roll },
+ })
+}
+
+// 连接地面线
+export function getDockPolyLine(data, viewer) {
+ if (!data.longitude || !data.latitude) return {}
+ return {
+ positions: new Cesium.CallbackProperty(() => {
+ const pointPosition = Cesium.Cartesian3.fromDegrees(+data.longitude, +data.latitude, +data.height)
+ if (!pointPosition) return []
+ // 获取点的位置
+ const cartographic = Cesium.Cartographic.fromCartesian(pointPosition)
+ // 获取地形高度
+ const terrainHeight = viewer.scene.globe.getHeight(cartographic) || 0
+ // 创建地面点位置
+ const groundPosition = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, terrainHeight)
+ return [pointPosition, groundPosition]
+ }, false),
+ width: 0.5,
+ material: Cesium.Color.WHITE,
+ }
+}
+
+// 飞向一个机巢中心
+export const flyDockCenter = (viewer, info) => {
+ const { longitude = 115, latitude = 28, height = 0 } = info
+ const position = Cesium.Cartesian3.fromDegrees(+longitude, +latitude, +height)
+ const droneEntity = viewer?.entities.add({ position: position, point: { pixelSize: 0 } })
+ viewer?.flyTo(droneEntity, {
+ offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-60), 22000),
+ duration: 0.5,
+ complete: () => {
+ viewer?.entities.remove(droneEntity)
+ },
+ })
+}
+
+// 在固定的经度上获得高度
+export function getHeightOnFixedLonLat(ray, pointParams, ellipsoid = Cesium.Ellipsoid.WGS84) {
+ const { longitude: lon, latitude: lat } = pointParams
+ // 1. 地表点(高度=0)
+ const p0 = Cesium.Cartesian3.fromDegrees(lon, lat, 0, ellipsoid)
+
+ // 2. 该点处的法线方向(up 向量,单位向量)
+ const up = ellipsoid.geodeticSurfaceNormal(p0, new Cesium.Cartesian3())
+
+ // 3. 射线起点和方向
+ const r0 = ray.origin // 相机位置
+ const rd = ray.direction // 射线方向(已归一化?不一定,但不影响)
+
+ // 4. 解:求直线 L(t) = p0 + t * up 与射线 R(s) = r0 + s * rd 的最近点
+ // 使用向量公式求两条直线的最近点参数 t
+ const w0 = Cesium.Cartesian3.subtract(p0, r0, new Cesium.Cartesian3())
+
+ const a = Cesium.Cartesian3.dot(up, up) // = 1(因为 up 是单位向量)
+ const b = Cesium.Cartesian3.dot(up, rd)
+ const c = Cesium.Cartesian3.dot(rd, rd) // = |rd|^2
+ const d = Cesium.Cartesian3.dot(up, w0)
+ const e = Cesium.Cartesian3.dot(rd, w0)
+
+ const denom = a * c - b * b
+ let t
+
+ if (Math.abs(denom) < Cesium.Math.EPSILON10) {
+ // 两直线平行,取 t = 0
+ t = 0
+ } else {
+ t = (b * e - c * d) / denom
+ }
+
+ // 5. 得到高度线上对应的点
+ const resultPoint = Cesium.Cartesian3.add(
+ p0,
+ Cesium.Cartesian3.multiplyByScalar(up, t, new Cesium.Cartesian3()),
+ new Cesium.Cartesian3()
+ )
+
+ // 6. 转回 Cartographic 获取精确高度
+ const carto = Cesium.Cartographic.fromCartesian(resultPoint, ellipsoid)
+ return {
+ cartesian: resultPoint,
+ height: carto.height,
+ longitude: Cesium.Math.toDegrees(carto.longitude),
+ latitude: Cesium.Math.toDegrees(carto.latitude),
+ }
+}
--
Gitblit v1.9.3