From 0f43d4d08ab6bd68f77b2d9e134cee7f15ba37e7 Mon Sep 17 00:00:00 2001 From: zhangquan <2523589960@qq.com> Date: Sat, 2 Aug 2025 11:59:35 +0800 Subject: [PATCH] =?UTF-8?q?1.=E4=BF=AE=E6=94=B9=E5=B9=B3=E9=9D=A2=E8=B7=9D?= =?UTF-8?q?=E7=A6=BB=E3=80=81=E9=9D=A2=E7=A7=AF=E6=B5=8B=E9=87=8F=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E7=9A=84=E5=AE=9E=E7=8E=B0=E9=80=BB=E8=BE=91=E5=92=8C?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=95=88=E6=9E=9C=EF=BC=9B2.=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8Dbug=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CesiumMap/mixins/useDrawTool/index.js | 9 +- .../CesiumMap/mixins/useMeasureTool.js | 223 ------------------ .../CesiumMap/mixins/useMeasureTool/index.js | 124 ++++++++++ .../mixins/useMeasureTool/measureArea.js | 166 +++++++++++++ .../mixins/useMeasureTool/measureDistance.js | 183 ++++++++++++++ .../CesiumMap/mixins/useMeasureTool/utils.js | 54 +++++ 6 files changed, 534 insertions(+), 225 deletions(-) delete mode 100644 src/components/CesiumMap/mixins/useMeasureTool.js create mode 100644 src/components/CesiumMap/mixins/useMeasureTool/index.js create mode 100644 src/components/CesiumMap/mixins/useMeasureTool/measureArea.js create mode 100644 src/components/CesiumMap/mixins/useMeasureTool/measureDistance.js create mode 100644 src/components/CesiumMap/mixins/useMeasureTool/utils.js diff --git a/src/components/CesiumMap/mixins/useDrawTool/index.js b/src/components/CesiumMap/mixins/useDrawTool/index.js index db8a038..6494329 100644 --- a/src/components/CesiumMap/mixins/useDrawTool/index.js +++ b/src/components/CesiumMap/mixins/useDrawTool/index.js @@ -54,8 +54,13 @@ export const useDrawTool = (viewer) => { ) if (!assistLayer) { - assistLayer = new CustomDataSource('__assist_edit') - viewer.dataSources.add(assistLayer) + const layers = viewer.dataSources.getByName('__assist_edit') + if(layers.length && layers.length > 0) { + assistLayer = layers[0] + } else { + assistLayer = new CustomDataSource('__assist_edit') + viewer.dataSources.add(assistLayer) + } } // 辅助点,用于显示获取的实际位置 diff --git a/src/components/CesiumMap/mixins/useMeasureTool.js b/src/components/CesiumMap/mixins/useMeasureTool.js deleted file mode 100644 index 0ce60d6..0000000 --- a/src/components/CesiumMap/mixins/useMeasureTool.js +++ /dev/null @@ -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 } diff --git a/src/components/CesiumMap/mixins/useMeasureTool/index.js b/src/components/CesiumMap/mixins/useMeasureTool/index.js new file mode 100644 index 0000000..928aa6c --- /dev/null +++ b/src/components/CesiumMap/mixins/useMeasureTool/index.js @@ -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 } diff --git a/src/components/CesiumMap/mixins/useMeasureTool/measureArea.js b/src/components/CesiumMap/mixins/useMeasureTool/measureArea.js new file mode 100644 index 0000000..93cc91d --- /dev/null +++ b/src/components/CesiumMap/mixins/useMeasureTool/measureArea.js @@ -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) + }) +} \ No newline at end of file diff --git a/src/components/CesiumMap/mixins/useMeasureTool/measureDistance.js b/src/components/CesiumMap/mixins/useMeasureTool/measureDistance.js new file mode 100644 index 0000000..c7fde6a --- /dev/null +++ b/src/components/CesiumMap/mixins/useMeasureTool/measureDistance.js @@ -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); + }); +}; diff --git a/src/components/CesiumMap/mixins/useMeasureTool/utils.js b/src/components/CesiumMap/mixins/useMeasureTool/utils.js new file mode 100644 index 0000000..c3ba090 --- /dev/null +++ b/src/components/CesiumMap/mixins/useMeasureTool/utils.js @@ -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 +} \ No newline at end of file