| | |
| | | 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 |
| | |
| | | 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) |
| | | ) |
| | | 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 |
| | |
| | | longitude, |
| | | latitude, |
| | | height, |
| | | cartesian3 |
| | | cartesian3, |
| | | }) |
| | | // 在这里,你可以使用 height 值进行后续操作 |
| | | }) |
| | |
| | | const longitude = Cesium.Math.toDegrees(item.longitude) |
| | | const latitude = Cesium.Math.toDegrees(item.latitude) |
| | | |
| | | let FinalHeight = |
| | | Number(data[index]?.['ellipsoidHeight']?.['#text']) + Number(item.height) |
| | | let FinalHeight = Number(data[index]?.['ellipsoidHeight']?.['#text']) + Number(item.height) |
| | | |
| | | if (droneHeight) { |
| | | FinalHeight = Number(data[index]?.['ellipsoidHeight']?.['#text']) + droneHeight |
| | |
| | | |
| | | /** |
| | | * 飞到中心点并且所有点在可视范围 |
| | | * @param lngLatArr |
| | | * @param viewer |
| | | * @param rangeMultiple 范围倍数越大飞的越高默认3 |
| | | * @param positionsData 二维数组,每项为 [lon, lat] 或 三维数组 [lon, lat, height] |
| | | * @param viewer Cesium.Viewer 实例 |
| | | * @param multiple 缩放倍数-默认为4 |
| | | * @param pitch 俯仰角-默认为-90 |
| | | */ |
| | | export function flyVisual (lngLatArr, viewer, rangeMultiple = 3) { |
| | | if (!Array.isArray(lngLatArr) || lngLatArr.length === 0) return |
| | | const positions = lngLatArr.map(([lon, lat]) => |
| | | Cesium.Cartesian3.fromDegrees(Number(lon), Number(lat)) |
| | | 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)) |
| | | ) |
| | | // 计算包围盒 BoundingSphere(所有点的外接球) |
| | | |
| | | // 计算最高高度和中心点经纬度 |
| | | 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) |
| | | viewer.camera.flyToBoundingSphere(boundingSphere, { |
| | | // 暂时飞向 BoundingSphere |
| | | viewer.camera.flyToBoundingSphere(Cesium.BoundingSphere.fromPoints(positions), { |
| | | duration: 0, |
| | | offset: new Cesium.HeadingPitchRange(0, -90, boundingSphere.radius * rangeMultiple), |
| | | 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) { |
| | | export function getMapCenterPoint(viewer) { |
| | | const centerResult = viewer.camera.pickEllipsoid( |
| | | new Cesium.Cartesian2( |
| | | viewer.canvas.clientWidth / 2, |
| | | viewer.canvas.clientHeight / 2 |
| | | ) |
| | | 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 |
| | |
| | | } |
| | | |
| | | // 获取屏幕四个角经纬度 |
| | | export const getScreenCorner = (viewer) => { |
| | | export const getScreenCorner = viewer => { |
| | | const camera = viewer.scene.camera |
| | | const canvas = viewer.canvas |
| | | |
| | |
| | | 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 |
| | | } |