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 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 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) { // 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) // 在这里,你可以使用 height 值进行后续操作 }) .catch(function (error) { // console.error('获取高程时发生错误:', error); reject(error) }) }) } // 获取面中最高点 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 } } /** * 飞到中心点并且所有点在可视范围 * @param positionsData 二维数组,每项为 [lon, lat] 或 三维数组 [lon, lat, height] * @param viewer Cesium.Viewer 实例 * @param multiple 缩放倍数-默认为4 * @param pitch 俯仰角-默认为-90 */ export function flyVisual({ positionsData, viewer, multiple = 4, pitch = -90 }) { if (!Array.isArray(positionsData) || positionsData.length === 0) return const positions = positionsData.map(([lon, lat, height]) => Cesium.Cartesian3.fromDegrees(Number(lon), Number(lat), Number(height || 0)) ) // 计算最高高度和中心点经纬度 let maxHeight = -Infinity let sumLon = 0, sumLat = 0 for (const [lon, lat, height] of positionsData) { sumLon += Number(lon) sumLat += Number(lat) maxHeight = Math.max(maxHeight, Number(height || 0)) } const centerLon = sumLon / positionsData.length const centerLat = sumLat / positionsData.length const boundingSphere = Cesium.BoundingSphere.fromPoints(positions) // 暂时飞向 BoundingSphere viewer.camera.flyToBoundingSphere(Cesium.BoundingSphere.fromPoints(positions), { duration: 0, offset: { heading: Cesium.Math.toRadians(0), pitch: Cesium.Math.toRadians(pitch), range: boundingSphere.radius * multiple, }, complete: () => { // 飞行完成后检查相机高度是否小于最高点 const cameraHeight = Cesium.Cartographic.fromCartesian(viewer.camera.position).height if (cameraHeight < maxHeight) { viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(centerLon, centerLat, maxHeight + 100), // 加100米缓冲 duration: 1.5, orientation: { heading: viewer.camera.heading, pitch: viewer.camera.pitch, roll: viewer.camera.roll, }, }) } }, }) } // 获取视口地图中心点 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] } // 根据区域code获取轮廓 export const getContourByCode = async areaCode => { const defaultDir = `${VITE_APP_REGION_URL}/100000/` const hierarchy = areaCodeToArr(areaCode.slice(0, 6)) const jsonPathPre = hierarchy.slice(0, hierarchy.length - 1).join('/') const res = await fetch(`${defaultDir}${jsonPathPre}/index.json`) const parentGJson = await res.json() let features = parentGJson.features.find(item => item.properties.adcode === Number(hierarchy[hierarchy.length - 1])) return { type: 'FeatureCollection', features: [features] } } // 辅助函数:创建旋转文字的画布 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 const timeWidth = context.measureText(time).width 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 30px Source Han Sans CN' context.fillStyle = '#1EE7E7' // 亮蓝色 context.fillText(time, 10 + prefixWidth, 12) // 绘制“分钟”(白色) context.font = 'bold 16px Source Han Sans CN' context.fillStyle = '#FFFFFF' context.fillText(suffix, 20 + prefixWidth + timeWidth, 15) return canvas }