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<Array<Object>>} [[{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
|
}
|