import * as Cesium from 'cesium' import * as turf from '@turf/turf' import store from '@/store' export const getLngLatDistance = (lat1, lng1, lat2, lng2) => { const radLat1 = (lat1 * Math.PI) / 180.0 const radLat2 = (lat2 * Math.PI) / 180.0 const a = radLat1 - radLat2 const b = (lng1 * Math.PI) / 180.0 - (lng2 * Math.PI) / 180.0 let s = 2 * Math.asin( Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)) ) s = s * 6378.137 // EARTH_RADIUS; s = Math.round(s * 10000) / 10000 return s * 1000 } // 获取限polyline长度 export const getPolylineLength = entity => { let length = 0 // 获取Polyline的所有顶点位置 const positions = entity.polyline.positions.getValue() for (let i = 0; i < positions.length - 1; ++i) { const startPosition = positions[i] const endPosition = positions[i + 1] // 使用Cesium提供的distanceBetween函数计算两个顶点之间的距离 const distance = Cesium.Cartesian3.distance(startPosition, endPosition) // 将每个顶点之间的距离相加得到总长度 length += distance } return length } // 创建三角广告牌 export const createTriangleMarker = (title, color) => { // 创建canvas绘制广告牌 const billboard = document.createElement('canvas') billboard.width = 30 billboard.height = 30 const ctx = billboard.getContext('2d') ctx.beginPath() ctx.moveTo(0, 0) ctx.lineTo(30, 0) ctx.lineTo(15, 22) ctx.fillStyle = color ctx.fill() ctx.font = '18px serif' ctx.fillStyle = '#ffffff' ctx.fillText(title, 10, 15) ctx.closePath() return billboard } export const createCircleBillboard = (title, color) => { const billboard = document.createElement('canvas') const ctx = billboard.getContext('2d') ctx.beginPath() ctx.ellipse(150, 90, 11, 11, 0, 0, Math.PI * 2) ctx.fillStyle = color ctx.fill() ctx.font = 'bold 15px serif' ctx.textAlign = 'center' ctx.fillStyle = '#ffffff' ctx.fillText(title, 150, 95) ctx.closePath() return billboard } // 获取当前经纬度海拔 export const getHaeHeight = (start, end) => { const ellipsoid = Cesium.Ellipsoid.WGS84 // 选择合适的椭圆体模型 // const { longitude, latitude, height } = start // const { longitude: endLng, latitude: endLat, height: endHeight } = end // const startC3Position = new Cesium.Cartographic(longitude, latitude, height) // const endC3Position = new Cesium.Cartographic(endLng, endLat, endHeight) // const startCartesianPosition = ellipsoid.cartographicToCartesian(startC3Position) // const endCartesianPosition = ellipsoid.cartographicToCartesian(endC3Position) // // 计算两个位置之间的距离 // const distance = new Cesium.EllipsoidGeodesic(startCartesianPosition, endCartesianPosition, ellipsoid) // return distance const { longitude, latitude, height } = end const cartographic = Cesium.Cartographic.fromDegrees(longitude, latitude, height) const cartesianPosition = ellipsoid.cartographicToCartesian(cartographic) const haeHeight = ellipsoid.cartesianToCartographic(cartesianPosition).height return haeHeight } // Cesium Cartesian3 转正常经纬度 export const cartesian3Convert = (cartesian3Position, viewer) => { // 转换为经纬度 const ellipsoid = viewer.scene.globe.ellipsoid const cartographicPosition = Cesium.Cartographic.fromCartesian(cartesian3Position, ellipsoid) const longitude = Cesium.Math.toDegrees(cartographicPosition.longitude) const latitude = Cesium.Math.toDegrees(cartographicPosition.latitude) const height = cartographicPosition.height // console.log('经度: ' + longitude); // console.log('纬度: ' + latitude); // console.log('高度: ' + height); return { longitude, latitude, height, } } // Cesium Cartesian2 转正常经纬度 export const cartesian2Convert = (position, viewer) => { const ellipsoid = viewer.scene.globe.ellipsoid const c3Position = viewer.scene.globe.pick(viewer.camera.getPickRay(position), viewer.scene) const c2Postion = ellipsoid.cartesianToCartographic(c3Position) const longitude = Cesium.Math.toDegrees(c2Postion.longitude) const latitude = Cesium.Math.toDegrees(c2Postion.latitude) return { longitude, latitude } } // 根据经纬度获取距离 export const getLnglatDist = (lng1, lat1, lng2, lat2) => { const start = new Cesium.Cartesian3.fromDegrees(Number(lng1), Number(lat1), 0) const end = new Cesium.Cartesian3.fromDegrees(Number(lng2), Number(lat2), 0) return Cesium.Cartesian3.distance(start, end) } /** * 地点坐标计算中心点 * @param coordinateList {Array>} [[{lat, lng}]] * @return { Object } {lat lng} */ export const getCenterPoint = coordinateList => { coordinateList = coordinateList.map(item => { const [lng, lat] = item return [{ lat, lng }] }) const geoCoordinateListFlat = coordinateList.reduce((s, v) => { return (s = s.concat(v)) }, []) const total = geoCoordinateListFlat.length let X = 0 let Y = 0 let Z = 0 for (const g of geoCoordinateListFlat) { const lat = (g.lat * Math.PI) / 180 const lon = (g.lng * Math.PI) / 180 const x = Math.cos(lat) * Math.cos(lon) const y = Math.cos(lat) * Math.sin(lon) const z = Math.sin(lat) X += x Y += y Z += z } X = X / total Y = Y / total Z = Z / total const Lon = Math.atan2(Y, X) const Hyp = Math.sqrt(X * X + Y * Y) const Lat = Math.atan2(Z, Hyp) return { lng: (Lon * 180) / Math.PI, lat: (Lat * 180) / Math.PI } } /** * @description: 计算两组经纬度中距离最短的两个点 * @param {*} lnglat1 [[xxx,xxx]] * @param {*} lnglat2 [[xxx,xxx]] * @return {*} { index: 1 } */ export const getShortestDistance = (lnglat1, lnglat2) => { const arr = [] lnglat1.forEach((v, i) => { const [lng, lat] = v lnglat2.forEach((s, i2) => { const [vLng, vLat] = s const distance = getLnglatDist(lng, lat, vLng, vLat) arr.push({ distance, index: [i, i2], lnglat: [v, s], }) }) }) 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) => { return new Promise((resolve, reject) => { // 假设 viewer 已经初始化并且 terrainProvider 是有效的 const terrainProvider = viewer?.terrainProvider // 创建 Cartographic 对象 const cartographic = Cesium.Cartographic.fromDegrees(longitude, latitude) // 获取地形数据的Promise const promise = Cesium.sampleTerrainMostDetailed(terrainProvider, [cartographic]) // 使用 Cesium.when 处理 Promise promise .then(function (updatedPositions) { // updatedPositions 是一个数组,包含更新后的 Cartographic 对象 const updatedPosition = updatedPositions[0] const height = updatedPosition.height const cartesian3 = Cesium.Cartesian3.fromDegrees(longitude, latitude, height) resolve({ longitude, latitude, height, cartesian3, }) // 在这里,你可以使用 height 值进行后续操作 }) .catch(function (error) { console.error('获取高程时发生错误:', error) reject(error) }) }) } // 批量获取航线经纬度对应高度信息 export const getPositionsHeight = (data, viewer, droneHeight = null) => { 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?.['Point']?.['coordinates']?.['#text'].split(',') return Cesium.Cartographic.fromDegrees(Number(lng), Number(lat)) }) // 获取地形数据的Promise const promise = Cesium.sampleTerrainMostDetailed(terrainProvider, cartographics) // 使用 Cesium.when 处理 Promise promise .then(function (updatedPositions) { // updatedPositions 是一个数组,包含更新后的 Cartographic 对象 const newPosition = updatedPositions.map((item, index) => { const longitude = Cesium.Math.toDegrees(item.longitude) const latitude = Cesium.Math.toDegrees(item.latitude) let FinalHeight = Number(data[index]?.['ellipsoidHeight']?.['#text']) + Number(item.height) if (droneHeight) { FinalHeight = Number(data[index]?.['ellipsoidHeight']?.['#text']) + droneHeight } return { ...data[index], longitude, latitude, FinalHeight, customHeight: Number(item.height), } }) resolve(newPosition) // 在这里,你可以使用 height 值进行后续操作 }) .catch(function (error) { // console.error('获取高程时发生错误:', error); reject(error) }) }) } // 批量获取点数组经纬度对应高度信息 export const getPointPositionsHeight = (data, viewer, droneHeight = null) => { 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) { // 是一个数组,包含更新后的 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, latitude, ASL: Number(item.height), customHeight: Number(item.height), } if (droneHeight) pointData.TH = Number(item.height) + Number(droneHeight) return pointData }) resolve(newPosition) }) .catch(function (error) { console.log('获取高程时发生错误:', error) resolve(data.map(item => ({ ...item, ASL: 0, customHeight: 0, longitude: item.lng, latitude: item.lat }))) }) }) } // 获取面中最高点 export const getPolygonMaxHeight = (turfPolygon, viewer) => { return new Promise((resolve, reject) => { const turfExtent = turf.bbox(turfPolygon) const turfSamplePoints = turf.pointGrid(turfExtent, 0.01, { units: 'kilometers', mask: turfPolygon, }) // 假设 viewer 已经初始化并且 terrainProvider 是有效的 const terrainProvider = viewer?.terrainProvider const cesiumSamplePoints = [] for (let i = 0; i < turfSamplePoints.features.length; i++) { const coord = turfSamplePoints.features[i].geometry.coordinates cesiumSamplePoints.push(Cesium.Cartographic.fromDegrees(coord[0], coord[1])) } // 获取地形数据的Promise const promise = Cesium.sampleTerrainMostDetailed(terrainProvider, cesiumSamplePoints) promise .then(function (updatedPositions) { const newPosition = updatedPositions.map(item => Number(item.height)) resolve(Math.max(...newPosition)) }) .catch(function (error) { reject(error) }) }) } // 展示当前的高度 export const getWaylineShowHeight = positions => { const { lng, lat } = getCenterPoint(positions) let maxDist = 0 positions.forEach(item => { const [lng1, lat1] = item let pointsDist = getLnglatDist(lng1, lat1, lng, lat) if (pointsDist > maxDist) { maxDist = pointsDist } }) return { lng, lat, showHeight: maxDist * 8, } } // 获取当前面是否有交叉点 export const isIntersection = coordinates => { const polyDikuai = turf.polygon([coordinates]) const result = turf.kinks(polyDikuai) const resultFeatures = result.features if (resultFeatures.length === 0) { //无相交 return false } else { //有相交 return true } } // 获取视口地图中心点 export function getMapCenterPoint(viewer) { const centerResult = viewer.camera.pickEllipsoid( new Cesium.Cartesian2(viewer.canvas.clientWidth / 2, viewer.canvas.clientHeight / 2) ) const curPosition = Cesium.Ellipsoid.WGS84.cartesianToCartographic(centerResult) const longitude = (curPosition.longitude * 180) / Math.PI const latitude = (curPosition.latitude * 180) / Math.PI return { longitude, latitude } } // 获取屏幕四个角经纬度 export const getScreenCorner = viewer => { const camera = viewer.scene.camera const canvas = viewer.canvas // 获取屏幕左下角和右上角的世界坐标 const leftBottom = new Cesium.Cartesian2(0, canvas.clientHeight) const rightTop = new Cesium.Cartesian2(canvas.clientWidth, 0) // 将屏幕坐标转为世界坐标 const leftBottomWorld = camera.pickEllipsoid(leftBottom, viewer.scene.globe.ellipsoid) const rightTopWorld = camera.pickEllipsoid(rightTop, viewer.scene.globe.ellipsoid) // 将世界坐标转为经纬度 const leftBottomCartographic = viewer.scene.globe.ellipsoid.cartesianToCartographic(leftBottomWorld) const rightTopCartographic = viewer.scene.globe.ellipsoid.cartesianToCartographic(rightTopWorld) // 获取经纬度 const leftBottomLonLat = [ Cesium.Math.toDegrees(leftBottomCartographic.longitude), Cesium.Math.toDegrees(leftBottomCartographic.latitude), ] const rightTopLonLat = [ Cesium.Math.toDegrees(rightTopCartographic.longitude), Cesium.Math.toDegrees(rightTopCartographic.latitude), ] // 返回四个角的经纬度 return { lb: leftBottomLonLat, rb: [rightTopLonLat[0], leftBottomLonLat[1]], // 右下角 rt: rightTopLonLat, lt: [leftBottomLonLat[0], rightTopLonLat[1]], // 左上角 } } // areaCode转换为目录结构, 返回数组, export function areaCodeToArr(code) { const codeStr = code.toString() const provinceCode = codeStr.slice(0, 2) + '0000' const cityCode = codeStr.slice(0, 4) + '00' if (codeStr.slice(2, 4) === '00' && codeStr.slice(4, 6) === '00') { return [provinceCode] } else if (codeStr.slice(4, 6) === '00') { return [provinceCode, cityCode] } return [provinceCode, cityCode, codeStr] } // 辅助函数:创建旋转文字的画布 export function createRotatedTextCanvas(time) { const prefix = '预计到达' const suffix = '分钟' const canvas = document.createElement('canvas') const context = canvas.getContext('2d') // 设置字体 context.font = 'bold 16px Source Han Sans CN' // 测量各部分文本宽度 const prefixWidth = context.measureText(prefix).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 // 估算高度 // 设置画布大小(增加边距) canvas.width = totalWidth + 30 canvas.height = textHeight * 3 // 重置上下文 context.font = 'bold 16px Source Han Sans CN' context.textBaseline = 'middle' context.textAlign = 'left' // 改为左对齐,方便分段绘制 // 清除画布 context.clearRect(0, 0, canvas.width, canvas.height) // 绘制“预计到达”(白色) context.fillStyle = '#FFFFFF' context.fillText(prefix, 10, 15) // 绘制时间(蓝色) context.font = 'bold 24px Source Han Sans CN' context.fillStyle = '#1EE7E7' // 亮蓝色 // 设置宽度 context.textBaseline = 'middle' context.fillText(time, 10 + prefixWidth, 14) // 绘制“分钟”(白色) context.font = 'bold 16px Source Han Sans CN' context.fillStyle = '#FFFFFF' 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), } }