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