1.修改平面距离、面积测量功能的实现逻辑和显示效果;2.优化相关代码,修复bug;
This commit is contained in:
parent
eef0209e58
commit
0f43d4d08a
|
@ -54,8 +54,13 @@ export const useDrawTool = (viewer) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!assistLayer) {
|
if (!assistLayer) {
|
||||||
assistLayer = new CustomDataSource('__assist_edit')
|
const layers = viewer.dataSources.getByName('__assist_edit')
|
||||||
viewer.dataSources.add(assistLayer)
|
if(layers.length && layers.length > 0) {
|
||||||
|
assistLayer = layers[0]
|
||||||
|
} else {
|
||||||
|
assistLayer = new CustomDataSource('__assist_edit')
|
||||||
|
viewer.dataSources.add(assistLayer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助点,用于显示获取的实际位置
|
// 辅助点,用于显示获取的实际位置
|
||||||
|
|
|
@ -1,223 +0,0 @@
|
||||||
import * as Cesium from 'cesium'
|
|
||||||
import * as turf from '@turf/turf'
|
|
||||||
import { useDrawTool } from './useDrawTool'
|
|
||||||
|
|
||||||
// 存储测量内容的辅助图层
|
|
||||||
let measureLayer = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算折线长度
|
|
||||||
* @param {Cesium.Cartesian3[]} positions 位置列表
|
|
||||||
* @returns 折线长度,单位米
|
|
||||||
*/
|
|
||||||
const getDistance = (positions) => {
|
|
||||||
const line = turf.lineString(positions.map((pos) => {
|
|
||||||
const cartographic = Cesium.Cartographic.fromCartesian(pos)
|
|
||||||
return [Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude)]
|
|
||||||
}))
|
|
||||||
return turf.length(line, { units: 'kilometers' }) * 1000 // 返回米
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算多边形面积
|
|
||||||
* @param {Cesium.Cartesian3[]} positions 位置列表
|
|
||||||
* @returns 多边形面积,单位平方米
|
|
||||||
*/
|
|
||||||
const getArea = (positions) => {
|
|
||||||
const pts = [...positions, positions[0]] // 闭合多边形
|
|
||||||
const polygon = turf.polygon([pts.map((pos) => {
|
|
||||||
const cartographic = Cesium.Cartographic.fromCartesian(pos)
|
|
||||||
return [Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude)]
|
|
||||||
})])
|
|
||||||
return turf.area(polygon) // 返回平方米
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取多个点的中心位置,根据边界范围确定
|
|
||||||
* @param {Cesium.Cartesian3[]} positions 位置列表
|
|
||||||
* @param {number} height 返回位置的高度,默认为0
|
|
||||||
* @returns 根据边界范围确定的中心点位置
|
|
||||||
*/
|
|
||||||
const getBoundingCenter = (positions, height = 0) => {
|
|
||||||
const coord = getBoundingCenterCoordinate(positions)
|
|
||||||
return Cesium.Cartesian3.fromDegrees(coord[0], coord[1], height)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取多个点的中心位置坐标,根据边界范围确定
|
|
||||||
* @param {Cesium.Cartesian3[]} positions 位置列表
|
|
||||||
* @returns 根据边界范围确定的中心点位置坐标,格式为 [经度, 纬度]
|
|
||||||
*/
|
|
||||||
const getBoundingCenterCoordinate = (positions) => {
|
|
||||||
const polygon = turf.lineString(positions.map((pos) => {
|
|
||||||
const cartographic = Cesium.Cartographic.fromCartesian(pos)
|
|
||||||
return [Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude)]
|
|
||||||
}))
|
|
||||||
const centerFeature = turf.center(polygon)
|
|
||||||
return centerFeature.geometry.coordinates
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useMeasureTool = (viewer) => {
|
|
||||||
if (!viewer) {
|
|
||||||
throw new Error('获取 Viewer 实例失败,请确保在 Viewer 初始化后调用 useMeasureTool 方法!')
|
|
||||||
}
|
|
||||||
|
|
||||||
const editTool = useDrawTool(viewer)
|
|
||||||
if (!measureLayer) {
|
|
||||||
measureLayer = new Cesium.CustomDataSource('__assist_measure')
|
|
||||||
viewer.dataSources.add(measureLayer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 距离测量
|
|
||||||
const measureDistance = () => {
|
|
||||||
return editTool.drawPolyline().then((positions) => {
|
|
||||||
const style = {
|
|
||||||
width: 5,
|
|
||||||
material: Cesium.Color.fromCssColorString('#57e0e0'),
|
|
||||||
}
|
|
||||||
let arr = []
|
|
||||||
for (let i = 0; i < positions.length; i++) {
|
|
||||||
if (i + 1 < positions.length) {
|
|
||||||
if (!positions[i] || !positions[i + 1]) continue
|
|
||||||
arr.push([positions[i], positions[i + 1]])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(arr)
|
|
||||||
let result = 0
|
|
||||||
arr.forEach((item) => {
|
|
||||||
let start = Cesium.Cartographic.fromCartesian(item[0])
|
|
||||||
let end = Cesium.Cartographic.fromCartesian(item[1])
|
|
||||||
let startObj = { longitude: '', latitude: '' }
|
|
||||||
let endObj = { longitude: '', latitude: '' }
|
|
||||||
startObj.longitude = Cesium.Math.toDegrees(start.longitude)
|
|
||||||
startObj.latitude = Cesium.Math.toDegrees(start.latitude)
|
|
||||||
endObj.longitude = Cesium.Math.toDegrees(end.longitude)
|
|
||||||
endObj.latitude = Cesium.Math.toDegrees(end.latitude)
|
|
||||||
|
|
||||||
console.log(startObj)
|
|
||||||
console.log(endObj)
|
|
||||||
|
|
||||||
let from = turf.point([startObj.longitude, startObj.latitude])
|
|
||||||
let to = turf.point([endObj.longitude, endObj.latitude])
|
|
||||||
|
|
||||||
let distance = turf.distance(from, to, { units: 'kilometers' }) * 1000
|
|
||||||
|
|
||||||
result += distance
|
|
||||||
|
|
||||||
const tempPolyline = new Cesium.Entity({
|
|
||||||
name: '__measure_distance',
|
|
||||||
// position: [ {x: (item[0].x + item[1].x) / 2, y: (item[0].y + item[1].y) / 2, z: (item[0].z + item[1].z) / 2} ],
|
|
||||||
position: new Cesium.Cartesian3(
|
|
||||||
(item[0].x + item[1].x) / 2,
|
|
||||||
(item[0].y + item[1].y) / 2,
|
|
||||||
(item[0].z + item[1].z) / 2,
|
|
||||||
),
|
|
||||||
polyline: {
|
|
||||||
positions: [...item],
|
|
||||||
width: style.width,
|
|
||||||
material: style.material,
|
|
||||||
clampToGround: true,
|
|
||||||
},
|
|
||||||
label: new Cesium.LabelGraphics({
|
|
||||||
text: distance.toFixed(2) + '米',
|
|
||||||
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
|
||||||
pixelOffset: new Cesium.Cartesian2(10, 10),
|
|
||||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
measureLayer.entities.add(tempPolyline)
|
|
||||||
return positions
|
|
||||||
})
|
|
||||||
|
|
||||||
let len = positions.length - 1
|
|
||||||
if (len > 1) {
|
|
||||||
let resultLabel = new Cesium.Entity({
|
|
||||||
name: 'measure_distance_length',
|
|
||||||
position: new Cesium.Cartesian3(
|
|
||||||
(positions[0].x + positions[len].x) / 2,
|
|
||||||
(positions[0].y + positions[len].y) / 2,
|
|
||||||
(positions[0].z + positions[len].z) / 2,
|
|
||||||
),
|
|
||||||
label: new Cesium.LabelGraphics({
|
|
||||||
text: '总距离:' + result.toFixed(2) + '米',
|
|
||||||
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
|
||||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
||||||
fillColor: Cesium.Color.AQUAMARINE,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
measureLayer.entities.add(resultLabel)
|
|
||||||
}
|
|
||||||
return positions
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 面积测量
|
|
||||||
const measureArea = () => {
|
|
||||||
return editTool.drawPolygon().then((positions) => {
|
|
||||||
let polygon = []
|
|
||||||
positions.forEach((item) => {
|
|
||||||
let obj = Cesium.Cartographic.fromCartesian(item)
|
|
||||||
polygon.push([Cesium.Math.toDegrees(obj.longitude), Cesium.Math.toDegrees(obj.latitude)])
|
|
||||||
})
|
|
||||||
|
|
||||||
let features = turf.points(polygon)
|
|
||||||
let center = turf.center(features)
|
|
||||||
|
|
||||||
console.log(center)
|
|
||||||
|
|
||||||
let points0 = Cesium.Cartographic.fromCartesian(positions[0])
|
|
||||||
polygon.push([
|
|
||||||
Cesium.Math.toDegrees(points0.longitude),
|
|
||||||
Cesium.Math.toDegrees(points0.latitude),
|
|
||||||
])
|
|
||||||
let areaPolygon = turf.polygon([polygon])
|
|
||||||
let area = turf.area(areaPolygon)
|
|
||||||
|
|
||||||
const tempPolygon = new Cesium.Entity({
|
|
||||||
name: '__measure_area',
|
|
||||||
position: Cesium.Cartesian3.fromDegrees(
|
|
||||||
center.geometry.coordinates[0],
|
|
||||||
center.geometry.coordinates[1],
|
|
||||||
),
|
|
||||||
polygon: {
|
|
||||||
hierarchy: new Cesium.PolygonHierarchy(positions),
|
|
||||||
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
|
||||||
material: Cesium.Color.fromCssColorString('#ffff00').withAlpha(0.3),
|
|
||||||
},
|
|
||||||
polyline: {
|
|
||||||
positions: [...positions, positions[0]],
|
|
||||||
clampToGround: true,
|
|
||||||
width: 2,
|
|
||||||
material: Cesium.Color.fromCssColorString('#ffff00'),
|
|
||||||
},
|
|
||||||
label: new Cesium.LabelGraphics({
|
|
||||||
text: area.toFixed(2) + '平方米',
|
|
||||||
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
|
||||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
measureLayer.entities.add(tempPolygon)
|
|
||||||
return positions
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消当前测量
|
|
||||||
const abort = () => {
|
|
||||||
editTool.abort() // 调用编辑工具的取消方法
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除所有测量结果
|
|
||||||
const clear = () => {
|
|
||||||
measureLayer.entities.removeAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
distance: measureDistance,
|
|
||||||
area: measureArea,
|
|
||||||
abort,
|
|
||||||
clear,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { getDistance, getArea, getBoundingCenter, getBoundingCenterCoordinate }
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
import * as Cesium from 'cesium'
|
||||||
|
|
||||||
|
import { useEventBus, ScreenMode } from '../useEventBus'
|
||||||
|
import { useHoverPosition } from '../useHoverPosition'
|
||||||
|
import { getDistance, getArea, getBoundingCenter, getBoundingCenterCoordinate } from './utils'
|
||||||
|
import { measureDistance as _measureDistance } from './measureDistance'
|
||||||
|
import { measureArea as _measureArea } from './measureArea'
|
||||||
|
|
||||||
|
// 存储测量内容的辅助图层
|
||||||
|
let measureLayer = null
|
||||||
|
// 存储编辑内容的辅助图层
|
||||||
|
// let assistLayer = null
|
||||||
|
|
||||||
|
// 所有useMeasureTool实例共用一个绘制状态和取消回调
|
||||||
|
let abortCallback = null
|
||||||
|
|
||||||
|
export const useMeasureTool = (viewer) => {
|
||||||
|
if (!viewer) {
|
||||||
|
throw new Error('获取 Viewer 实例失败,请确保在 Viewer 初始化后调用 useMeasureTool 方法!')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取实时位置
|
||||||
|
const { isHover, position } = useHoverPosition(viewer)
|
||||||
|
// 获取事件总线
|
||||||
|
const bus = useEventBus(viewer)
|
||||||
|
|
||||||
|
// 判断辅助点是否显示
|
||||||
|
watch(
|
||||||
|
() => [bus.mode.value, isHover.value],
|
||||||
|
([nowStatus, hover]) => {
|
||||||
|
if (hover && nowStatus === ScreenMode.DRAW) {
|
||||||
|
assistPoint.show = true
|
||||||
|
} else {
|
||||||
|
assistPoint.show = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!measureLayer) {
|
||||||
|
const layers = viewer.dataSources.getByName('__assist_measure')
|
||||||
|
if(layers.length && layers.length > 0) {
|
||||||
|
measureLayer = layers[0]
|
||||||
|
} else {
|
||||||
|
measureLayer = new Cesium.CustomDataSource('__assist_measure')
|
||||||
|
viewer.dataSources.add(measureLayer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (!assistLayer) {
|
||||||
|
// const layers = viewer.dataSources.getByName('__assist_edit')
|
||||||
|
// if(layers.length && layers.length > 0) {
|
||||||
|
// assistLayer = layers[0]
|
||||||
|
// } else {
|
||||||
|
// assistLayer = new Cesium.CustomDataSource('__assist_edit')
|
||||||
|
// viewer.dataSources.add(assistLayer)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 辅助点,用于显示获取的实际位置
|
||||||
|
const assistPoint = new Cesium.Entity({
|
||||||
|
name: '__draw_icon',
|
||||||
|
show: false,
|
||||||
|
position: new Cesium.CallbackPositionProperty(() => {
|
||||||
|
return position.value || undefined
|
||||||
|
}, false),
|
||||||
|
point: {
|
||||||
|
pixelSize: 7,
|
||||||
|
color: Cesium.Color.BLUE,
|
||||||
|
outlineColor: Cesium.Color.WHITE,
|
||||||
|
outlineWidth: 1,
|
||||||
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
viewer.entities.add(assistPoint)
|
||||||
|
|
||||||
|
// 全局取消当前绘制操作的函数
|
||||||
|
const abort = () => {
|
||||||
|
if (abortCallback) {
|
||||||
|
abortCallback() // 调用当前注册的取消回调
|
||||||
|
abortCallback = null // 清空回调
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册当前绘制操作的取消回调函数
|
||||||
|
const registerAbort = (cb) => {
|
||||||
|
abortCallback = cb
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备传递给绘制函数的依赖对象
|
||||||
|
const drawDependencies = {
|
||||||
|
viewer,
|
||||||
|
bus,
|
||||||
|
ScreenMode,
|
||||||
|
position,
|
||||||
|
measureLayer,
|
||||||
|
registerAbort,
|
||||||
|
globalAbort: abort, // 将全局 abort 函数也传递进去,方便内部调用
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用拆分后的测量函数,并传递依赖
|
||||||
|
const distance = () => _measureDistance(drawDependencies)
|
||||||
|
|
||||||
|
const area = () => _measureArea(drawDependencies)
|
||||||
|
|
||||||
|
|
||||||
|
// 取消当前测量
|
||||||
|
// const abort = () => {
|
||||||
|
// editTool.abort() // 调用编辑工具的取消方法
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 清除所有测量结果
|
||||||
|
const clear = () => {
|
||||||
|
measureLayer.entities.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
distance,
|
||||||
|
area,
|
||||||
|
abort,
|
||||||
|
clear,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getDistance, getArea, getBoundingCenter, getBoundingCenterCoordinate }
|
|
@ -0,0 +1,166 @@
|
||||||
|
import * as Cesium from 'cesium'
|
||||||
|
import { getBoundingCenter, getArea } from './utils'
|
||||||
|
|
||||||
|
// 面积测量
|
||||||
|
export const measureArea = (deps) => {
|
||||||
|
const { viewer, bus, ScreenMode, position, measureLayer, registerAbort, globalAbort } = deps
|
||||||
|
|
||||||
|
// 如果在执行其它操作,则取消
|
||||||
|
globalAbort()
|
||||||
|
|
||||||
|
bus.mode.value = ScreenMode.DRAW
|
||||||
|
const featurePositions = []
|
||||||
|
|
||||||
|
// 临时多边形实体 (包含填充和边界线)
|
||||||
|
const tempPolygon = new Cesium.Entity({
|
||||||
|
name: '__draw_temp_polygon',
|
||||||
|
position: new Cesium.CallbackProperty(() => {
|
||||||
|
if (featurePositions.length < 2) return null
|
||||||
|
|
||||||
|
return getBoundingCenter([...featurePositions, position.value])
|
||||||
|
}, false),
|
||||||
|
label: {
|
||||||
|
text: new Cesium.CallbackProperty(() => {
|
||||||
|
if (featurePositions.length < 2) return null
|
||||||
|
|
||||||
|
return `${getArea([...featurePositions, position.value]).toFixed(2)}平方米`
|
||||||
|
}, false),
|
||||||
|
font: '14px sans-serif',
|
||||||
|
pixelOffset: new Cesium.Cartesian2(0, -6),
|
||||||
|
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
||||||
|
showBackground: true,
|
||||||
|
backgroundPadding: new Cesium.Cartesian2(8, 4),
|
||||||
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||||
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||||
|
},
|
||||||
|
polygon: {
|
||||||
|
hierarchy: new Cesium.CallbackProperty(() => {
|
||||||
|
const positions = [...featurePositions]
|
||||||
|
// 添加当前鼠标位置作为最后一个点,形成动态效果
|
||||||
|
if (
|
||||||
|
position.value &&
|
||||||
|
!position.value.equals(featurePositions[featurePositions.length - 1]) // 避免重复添加同一个点
|
||||||
|
) {
|
||||||
|
positions.push(position.value)
|
||||||
|
}
|
||||||
|
// 多边形需要至少3个点才能形成
|
||||||
|
if (positions.length < 3) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return new Cesium.PolygonHierarchy(positions)
|
||||||
|
}, false),
|
||||||
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||||
|
material: Cesium.Color.fromCssColorString('#ff0000').withAlpha(0.3),
|
||||||
|
},
|
||||||
|
// 绘制边界线,连接所有点和第一个点,形成闭合效果
|
||||||
|
polyline: {
|
||||||
|
positions: new Cesium.CallbackProperty(() => {
|
||||||
|
const positions = [...featurePositions]
|
||||||
|
// 添加当前鼠标位置作为最后一个点
|
||||||
|
if (
|
||||||
|
position.value &&
|
||||||
|
!position.value.equals(featurePositions[featurePositions.length - 1])
|
||||||
|
) {
|
||||||
|
positions.push(position.value)
|
||||||
|
}
|
||||||
|
// 如果有至少一个点,则连接到第一个点形成闭合线
|
||||||
|
if (featurePositions.length > 0 && featurePositions[0]) {
|
||||||
|
positions.push(featurePositions[0])
|
||||||
|
}
|
||||||
|
return positions
|
||||||
|
}, false),
|
||||||
|
// clampToGround: true, // 多边形边界线通常不需要 clampToGround,因为多边形本身已经处理了高度
|
||||||
|
width: 2,
|
||||||
|
material: Cesium.Color.fromCssColorString('#ff0000'),
|
||||||
|
clampToGround: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
viewer.entities.add(tempPolygon)
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 清理资源
|
||||||
|
const dispose = () => {
|
||||||
|
registerAbort(null) // 取消注册当前的 abort 回调
|
||||||
|
viewer.entities.remove(tempPolygon)
|
||||||
|
bus.offScreen(Cesium.ScreenSpaceEventType.LEFT_CLICK, handleAddPoint)
|
||||||
|
bus.offScreen(Cesium.ScreenSpaceEventType.RIGHT_CLICK, handleCancelDraw)
|
||||||
|
bus.offScreen(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK, handleEndDraw)
|
||||||
|
bus.mode.value = ScreenMode.VIEW
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消绘制
|
||||||
|
const abort = (info) => {
|
||||||
|
dispose()
|
||||||
|
reject(info || '取消多边形绘制!')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 左键加点
|
||||||
|
const handleAddPoint = () => {
|
||||||
|
if (position.value && !position.value.equals(featurePositions[featurePositions.length - 1])) {
|
||||||
|
featurePositions.push(position.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 左键双击完成绘制
|
||||||
|
const handleEndDraw = () => {
|
||||||
|
// 双击时,如果鼠标位置与最后一个点不同,先添加当前点
|
||||||
|
handleAddPoint()
|
||||||
|
if (featurePositions.length < 3) {
|
||||||
|
// TODO: 替换为实际的错误提示方式
|
||||||
|
console.error('请保多边形至少有三个点!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 绘制完成的面积测量实体
|
||||||
|
measureLayer.entities.add({
|
||||||
|
position: getBoundingCenter(featurePositions),
|
||||||
|
label: {
|
||||||
|
text: `${getArea(featurePositions).toFixed(2)}平方米`,
|
||||||
|
font: '14px sans-serif',
|
||||||
|
pixelOffset: new Cesium.Cartesian2(0, -6),
|
||||||
|
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
||||||
|
showBackground: true,
|
||||||
|
backgroundPadding: new Cesium.Cartesian2(8, 4),
|
||||||
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||||
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||||
|
},
|
||||||
|
polygon: {
|
||||||
|
hierarchy: [...featurePositions],
|
||||||
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||||
|
material: Cesium.Color.fromCssColorString('#ff0000').withAlpha(0.3),
|
||||||
|
},
|
||||||
|
// 绘制边界线,连接所有点和第一个点,形成闭合效果
|
||||||
|
polyline: {
|
||||||
|
positions: [...featurePositions, featurePositions[0]],
|
||||||
|
width: 2,
|
||||||
|
material: Cesium.Color.fromCssColorString('#ff0000'),
|
||||||
|
clampToGround: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
dispose()
|
||||||
|
// 使用 setTimeout 避免立即 resolve 可能导致的事件冲突或其他问题
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(featurePositions)
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右键取消绘制或删除最后一个点
|
||||||
|
const handleCancelDraw = () => {
|
||||||
|
if (featurePositions.length > 0) {
|
||||||
|
// 取消最后绘制的点
|
||||||
|
featurePositions.pop()
|
||||||
|
} else {
|
||||||
|
// 如果没有点,则取消整个绘制
|
||||||
|
abort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册当前的取消回调
|
||||||
|
registerAbort(abort)
|
||||||
|
|
||||||
|
// 绑定绘制相关事件
|
||||||
|
bus.onScreen(Cesium.ScreenSpaceEventType.LEFT_CLICK, handleAddPoint)
|
||||||
|
bus.onScreen(Cesium.ScreenSpaceEventType.RIGHT_CLICK, handleCancelDraw)
|
||||||
|
bus.onScreen(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK, handleEndDraw)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
import * as Cesium from 'cesium'
|
||||||
|
import { getBoundingCenter, getDistance } from './utils'
|
||||||
|
|
||||||
|
// 距离测量
|
||||||
|
export const measureDistance = (deps) => {
|
||||||
|
const { viewer, bus, ScreenMode, position, measureLayer, registerAbort, globalAbort } = deps
|
||||||
|
|
||||||
|
// 如果在执行其它操作,则取消
|
||||||
|
globalAbort();
|
||||||
|
|
||||||
|
bus.mode.value = ScreenMode.DRAW;
|
||||||
|
const featurePositions = [];
|
||||||
|
/**
|
||||||
|
* 测距分段折线存储结构,按绘制顺序排序
|
||||||
|
* {
|
||||||
|
* entity: 绘制完成对应折线部分的实体,
|
||||||
|
* distance: 绘制距离,用于计算总距离
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
const subDistance = []
|
||||||
|
|
||||||
|
// 临时折线实体
|
||||||
|
const tempPolyline = new Cesium.Entity({
|
||||||
|
name: "__draw_temp_polyline",
|
||||||
|
position: new Cesium.CallbackProperty(() => {
|
||||||
|
if (featurePositions.length === 0) return null
|
||||||
|
// 获取最后一个位置和当前鼠标位置的中点,作为标签显示位置
|
||||||
|
// return getBoundingCenter([featurePositions[featurePositions.length - 1], position.value])
|
||||||
|
return position.value
|
||||||
|
}, false),
|
||||||
|
label: {
|
||||||
|
text: new Cesium.CallbackProperty(() => {
|
||||||
|
if (featurePositions.length === 0) return null
|
||||||
|
// 获取两个点之间的距离
|
||||||
|
const distance = getDistance([featurePositions[featurePositions.length - 1], position.value])
|
||||||
|
return `${distance.toFixed(2)}米\n总距离:${(subDistance.reduce((total, d) => total + d.distance, 0) + distance).toFixed(2)}米`;
|
||||||
|
}, false),
|
||||||
|
font: '14px sans-serif',
|
||||||
|
pixelOffset: new Cesium.Cartesian2(0, -6),
|
||||||
|
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
||||||
|
showBackground: true,
|
||||||
|
backgroundPadding: new Cesium.Cartesian2(8, 4),
|
||||||
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||||
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||||
|
},
|
||||||
|
polyline: {
|
||||||
|
positions: new Cesium.CallbackProperty(() => {
|
||||||
|
if(featurePositions.length === 0) return null
|
||||||
|
|
||||||
|
const positions = [featurePositions[featurePositions.length - 1]]
|
||||||
|
// 添加当前鼠标位置作为最后一个点,形成动态效果
|
||||||
|
if (
|
||||||
|
position.value &&
|
||||||
|
!position.value.equals(
|
||||||
|
featurePositions[featurePositions.length - 1]
|
||||||
|
) // 避免重复添加同一个点
|
||||||
|
) {
|
||||||
|
positions.push(position.value);
|
||||||
|
}
|
||||||
|
if (positions.length < 2) return null
|
||||||
|
|
||||||
|
return positions;
|
||||||
|
}, false),
|
||||||
|
width: 2,
|
||||||
|
material: Cesium.Color.fromCssColorString("#FF0000"),
|
||||||
|
clampToGround: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
viewer.entities.add(tempPolyline);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 清理资源
|
||||||
|
const dispose = () => {
|
||||||
|
subDistance.length = 0
|
||||||
|
registerAbort(null); // 取消注册当前的 abort 回调
|
||||||
|
viewer.entities.remove(tempPolyline);
|
||||||
|
bus.offScreen(Cesium.ScreenSpaceEventType.LEFT_CLICK, handleAddPoint);
|
||||||
|
bus.offScreen(Cesium.ScreenSpaceEventType.RIGHT_CLICK, handleCancelDraw);
|
||||||
|
bus.offScreen(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK, handleEndDraw);
|
||||||
|
bus.mode.value = ScreenMode.VIEW;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消绘制
|
||||||
|
const abort = (info) => {
|
||||||
|
dispose();
|
||||||
|
reject(info || "取消折线绘制!");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 左键加点
|
||||||
|
const handleAddPoint = () => {
|
||||||
|
if (
|
||||||
|
position.value &&
|
||||||
|
!position.value.equals(featurePositions[featurePositions.length - 1])
|
||||||
|
) {
|
||||||
|
featurePositions.push(position.value);
|
||||||
|
if(featurePositions.length > 1) {
|
||||||
|
const positions = [
|
||||||
|
featurePositions[featurePositions.length - 2],
|
||||||
|
featurePositions[featurePositions.length - 1],
|
||||||
|
];
|
||||||
|
// 当至少有2个点时,添加最后2个点的折线
|
||||||
|
subDistance.push({
|
||||||
|
entity: measureLayer.entities.add(new Cesium.Entity({
|
||||||
|
position: getBoundingCenter(positions),
|
||||||
|
label: {
|
||||||
|
text: `${getDistance(positions).toFixed(2)}米`,
|
||||||
|
font: '14px sans-serif',
|
||||||
|
pixelOffset: new Cesium.Cartesian2(0, -6),
|
||||||
|
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
||||||
|
showBackground: true,
|
||||||
|
backgroundPadding: new Cesium.Cartesian2(8, 4),
|
||||||
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||||
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||||
|
},
|
||||||
|
polyline: {
|
||||||
|
positions,
|
||||||
|
width: 2,
|
||||||
|
material: Cesium.Color.fromCssColorString('#ff0000'),
|
||||||
|
clampToGround: true,
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
distance: getDistance(positions),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 左键双击完成绘制
|
||||||
|
const handleEndDraw = () => {
|
||||||
|
// 双击时,如果鼠标位置与最后一个点不同,先添加当前点
|
||||||
|
handleAddPoint();
|
||||||
|
if (featurePositions.length < 2) {
|
||||||
|
// TODO: 替换为实际的错误提示方式
|
||||||
|
console.error("请保证折线至少有两个点!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制总长标签
|
||||||
|
measureLayer.entities.add(new Cesium.Entity({
|
||||||
|
position: featurePositions[featurePositions.length - 1],
|
||||||
|
label: {
|
||||||
|
text: `总距离:${subDistance.reduce((total, d) => total + d.distance, 0).toFixed(2)}米`,
|
||||||
|
font: '14px sans-serif',
|
||||||
|
pixelOffset: new Cesium.Cartesian2(0, -6),
|
||||||
|
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
||||||
|
showBackground: true,
|
||||||
|
backgroundPadding: new Cesium.Cartesian2(8, 4),
|
||||||
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||||
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
dispose();
|
||||||
|
// 使用 setTimeout 避免立即 resolve 可能导致的事件冲突或其他问题
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(featurePositions);
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 右键取消绘制或删除最后一个点
|
||||||
|
const handleCancelDraw = () => {
|
||||||
|
if (featurePositions.length > 0) {
|
||||||
|
// 取消最后绘制的点
|
||||||
|
featurePositions.pop();
|
||||||
|
// 如果有已经绘制的测量线段,删除最后绘制的
|
||||||
|
if (subDistance.length > 0) {
|
||||||
|
measureLayer.entities.remove(subDistance.pop().entity);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有点,则取消整个绘制
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 注册当前的取消回调
|
||||||
|
registerAbort(abort);
|
||||||
|
|
||||||
|
// 绑定绘制相关事件
|
||||||
|
bus.onScreen(Cesium.ScreenSpaceEventType.LEFT_CLICK, handleAddPoint);
|
||||||
|
bus.onScreen(Cesium.ScreenSpaceEventType.RIGHT_CLICK, handleCancelDraw);
|
||||||
|
bus.onScreen(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK, handleEndDraw);
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,54 @@
|
||||||
|
import * as Cesium from 'cesium'
|
||||||
|
import * as turf from '@turf/turf'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算折线长度
|
||||||
|
* @param {Cesium.Cartesian3[]} positions 位置列表
|
||||||
|
* @returns 折线长度,单位米
|
||||||
|
*/
|
||||||
|
export const getDistance = (positions) => {
|
||||||
|
const line = turf.lineString(positions.map((pos) => {
|
||||||
|
const cartographic = Cesium.Cartographic.fromCartesian(pos)
|
||||||
|
return [Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude)]
|
||||||
|
}))
|
||||||
|
return turf.length(line, { units: 'kilometers' }) * 1000 // 返回米
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算多边形面积
|
||||||
|
* @param {Cesium.Cartesian3[]} positions 位置列表
|
||||||
|
* @returns 多边形面积,单位平方米
|
||||||
|
*/
|
||||||
|
export const getArea = (positions) => {
|
||||||
|
const pts = [...positions, positions[0]] // 闭合多边形
|
||||||
|
const polygon = turf.polygon([pts.map((pos) => {
|
||||||
|
const cartographic = Cesium.Cartographic.fromCartesian(pos)
|
||||||
|
return [Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude)]
|
||||||
|
})])
|
||||||
|
return turf.area(polygon) // 返回平方米
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取多个点的中心位置,根据边界范围确定
|
||||||
|
* @param {Cesium.Cartesian3[]} positions 位置列表
|
||||||
|
* @param {number} height 返回位置的高度,默认为0
|
||||||
|
* @returns 根据边界范围确定的中心点位置
|
||||||
|
*/
|
||||||
|
export const getBoundingCenter = (positions, height = 0) => {
|
||||||
|
const coord = getBoundingCenterCoordinate(positions)
|
||||||
|
return Cesium.Cartesian3.fromDegrees(coord[0], coord[1], height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取多个点的中心位置坐标,根据边界范围确定
|
||||||
|
* @param {Cesium.Cartesian3[]} positions 位置列表
|
||||||
|
* @returns 根据边界范围确定的中心点位置坐标,格式为 [经度, 纬度]
|
||||||
|
*/
|
||||||
|
export const getBoundingCenterCoordinate = (positions) => {
|
||||||
|
const polygon = turf.lineString(positions.map((pos) => {
|
||||||
|
const cartographic = Cesium.Cartographic.fromCartesian(pos)
|
||||||
|
return [Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude)]
|
||||||
|
}))
|
||||||
|
const centerFeature = turf.center(polygon)
|
||||||
|
return centerFeature.geometry.coordinates
|
||||||
|
}
|
Loading…
Reference in New Issue