/** * 绘制管理器 * 职责:管理测区的交互绘制流程(点/线/面)、尺规与标签生成、完成时回调 */ export class DrawManager { /** * 构造函数 * @param {Cesium.Viewer} viewer Cesium Viewer 实例 * @param {EntityFactory} entityFactory 实体工厂 * @param {LabelManager} labelManager 标签管理器 * @param {EventHub} eventHub 事件中心 */ constructor(viewer, entityFactory, labelManager, eventHub) { this.viewer = viewer this.entityFactory = entityFactory this.labelManager = labelManager this.eventHub = eventHub /** * 内部状态 * - activeShapePoints: 已确认的顶点集合 * - floatingPoint: 鼠标移动中的浮动点 * - activeShape: 绘制完成后的面实体 * - polylineEntity: 绘制中的折线实体 * - polygonPositionList: 面的四个顶点 * - labels: 距离与角度标签的实体引用 * - angletext: 当前角度文本 * - centerPoint: 面中心点(预留) * - formatter: 距离格式化方法 */ this.state = { activeShapePoints: [], floatingPoint: null, activeShape: null, polylineEntity: null, polygonPositionList: [], labels: { d01: null, d12: null, d23: null, d34: null, angle: null }, angletext: 0, centerPoint: null, formatter: (m) => `${m} m` } } /** * 设置距离文本格式化函数 * @param {(m:number)=>string} fn 格式化方法 */ setFormatter (fn) { if (typeof fn === 'function') this.state.formatter = fn } /** * 绕 B 点按给定角度旋转 A 点(局部 ENU 坐标系) * @param {Cesium.Cartesian3} position_A 原点 A * @param {Cesium.Cartesian3} position_B 旋转中心 B * @param {number} angle 角度(度) * @returns {Cesium.Cartesian3} 旋转后的点 */ rotatedPointByAngle (position_A, position_B, angle) { const localToWorld_Matrix = Cesium.Transforms.eastNorthUpToFixedFrame(position_B) const worldToLocal_Matrix = Cesium.Matrix4.inverse(localToWorld_Matrix, new Cesium.Matrix4()) const localPosition_B = Cesium.Matrix4.multiplyByPoint(worldToLocal_Matrix, position_B, new Cesium.Cartesian3()) const localPosition_A = Cesium.Matrix4.multiplyByPoint(worldToLocal_Matrix, position_A, new Cesium.Cartesian3()) const new_x = localPosition_A.x * Math.cos(Cesium.Math.toRadians(angle)) + localPosition_A.y * Math.sin(Cesium.Math.toRadians(angle)) const new_y = localPosition_A.y * Math.cos(Cesium.Math.toRadians(angle)) - localPosition_A.x * Math.sin(Cesium.Math.toRadians(angle)) const new_z = localPosition_A.z return Cesium.Matrix4.multiplyByPoint(localToWorld_Matrix, new Cesium.Cartesian3(new_x, new_y, new_z), new Cesium.Cartesian3()) } /** * 创建绘制阶段的量尺与刻度(基于前两点) * @param {Cesium.Cartesian3} p0 第一个点 * @param {Cesium.Cartesian3} p1 第二个点 */ createAngleRuler (p0, p1) { const viewer = this.viewer const vector01 = Cesium.Cartesian3.subtract(p1, p0, new Cesium.Cartesian3()) const normalize = Cesium.Cartesian3.normalize(vector01, new Cesium.Cartesian3()) const radius = Cesium.Cartesian3.distance(p0, p1) / 4 const centerVectorZ = Cesium.Cartesian3.add(Cesium.Cartesian3.multiplyByScalar(normalize, radius, new Cesium.Cartesian3()), p1, new Cesium.Cartesian3()) const rotate = this.rotatedPointByAngle(centerVectorZ, p1, -90) const radius1 = Cesium.Cartesian3.distance(p0, p1) / 4.3 const centerVectorZ1 = Cesium.Cartesian3.add(Cesium.Cartesian3.multiplyByScalar(normalize, radius1, new Cesium.Cartesian3()), p1, new Cesium.Cartesian3()) const rotate1 = this.rotatedPointByAngle(centerVectorZ1, p1, -90) const radius2 = Cesium.Cartesian3.distance(p0, p1) / 4 / 3.5 * 2.5 const centerVectorZ2 = Cesium.Cartesian3.add(Cesium.Cartesian3.multiplyByScalar(normalize, radius2, new Cesium.Cartesian3()), p1, new Cesium.Cartesian3()) const rotate2 = this.rotatedPointByAngle(centerVectorZ2, p1, -90) const panSpinZoom = (normal, angle, rotater) => { const axis = Cesium.Quaternion.fromAxisAngle(normal, Cesium.Math.toRadians(angle)) const quater = Cesium.Matrix3.fromQuaternion(axis) const fromRotater = Cesium.Matrix4.fromRotationTranslation(quater) const subtract = Cesium.Cartesian3.subtract(p1, rotater, new Cesium.Cartesian3()) const matrix4 = Cesium.Matrix4.multiplyByPoint(fromRotater, subtract, new Cesium.Cartesian3()) return Cesium.Cartesian3.add(matrix4, p1, new Cesium.Cartesian3()) } let angleList = [] for (let angle = 180; angle <= 360; angle += 5) angleList.push(panSpinZoom(normalize, angle, rotate)) viewer.entities.add({ id: 'ruler1', polyline: { positions: angleList, width: 1, arcType: Cesium.ArcType.NONE, material: Cesium.Color.fromCssColorString('#aeff00') } }) for (let angle = 180; angle <= 360; angle += 15) { const tick = (angle - 180) / 5 viewer.entities.add({ id: 'tick' + tick, polyline: { positions: [panSpinZoom(normalize, angle, rotate1), angleList[tick]], width: 1, arcType: Cesium.ArcType.NONE, material: Cesium.Color.fromCssColorString('#aeff00') } }) } const vector02 = Cesium.Cartesian3.subtract(angleList[0], angleList[angleList.length - 1], new Cesium.Cartesian3()) const cross = Cesium.Cartesian3.cross(vector01, vector02, new Cesium.Cartesian3()) const normali = Cesium.Cartesian3.normalize(cross, new Cesium.Cartesian3()) const plane = Cesium.Plane.fromPointNormal(p1, normali) let angleList1 = [] for (let angle = 180; angle <= 260; angle += 5) angleList1.push(panSpinZoom(normalize, angle, rotate2)) const projectedPoint = Cesium.Plane.projectPointOntoPlane(plane, angleList1[angleList1.length - 1], new Cesium.Cartesian3()) angleList1.push(projectedPoint) this.viewer.entities.add({ id: 'ruler2', polyline: { positions: angleList1, width: 1, arcType: Cesium.ArcType.NONE, material: Cesium.Color.fromCssColorString('#aeff00') } }) let angleList2 = [] for (let angle = 280; angle <= 360; angle += 5) angleList2.push(panSpinZoom(normalize, angle, rotate2)) const projectedPoint1 = Cesium.Plane.projectPointOntoPlane(plane, angleList2[0], new Cesium.Cartesian3()) angleList2.unshift(projectedPoint1) this.viewer.entities.add({ id: 'ruler3', polyline: { positions: angleList2, width: 1, arcType: Cesium.ArcType.NONE, material: Cesium.Color.fromCssColorString('#aeff00') } }) } /** * 清理当前绘制中的所有标签引用并重置状态 */ clearLabels () { const { d01, d12, d23, d34, angle } = this.state.labels ;[d01, d12, d23, d34, angle].forEach(l => { if (l) this.viewer.entities.remove(l) }) this.state.labels = { d01: null, d12: null, d23: null, d34: null, angle: null } } /** * 刷新绘制过程中的距离与角度标签 */ updateLabels () { this.clearLabels() const { activeShapePoints, floatingPoint, formatter } = this.state const label = this.labelManager const activePoints = [...activeShapePoints, floatingPoint] if (activePoints.length >= 2) { const middle01 = Cesium.Cartesian3.midpoint(activePoints[0], activePoints[1], new Cesium.Cartesian3()) const distance01 = Cesium.Cartesian3.distance(activePoints[0], activePoints[1]) this.state.labels.d01 = label.createLabel(middle01, `${formatter(distance01)}`, false, 'distanceLabel01') } if (activePoints.length >= 3) { const middle12 = Cesium.Cartesian3.midpoint(activePoints[1], activePoints[2], new Cesium.Cartesian3()) const distance12 = Cesium.Cartesian3.distance(activePoints[1], activePoints[2]) this.state.labels.d12 = label.createLabel(middle12, `${formatter(distance12)}`, false, 'distanceLabel12') const rotate = this.rotatedPointByAngle(activePoints[0], activePoints[1], 90) const vector21 = Cesium.Cartesian3.subtract(activePoints[2], activePoints[1], new Cesium.Cartesian3()) const vectorRotate1 = Cesium.Cartesian3.subtract(rotate, activePoints[1], new Cesium.Cartesian3()) const angle = Cesium.Math.toDegrees(Cesium.Cartesian3.angleBetween(vectorRotate1, vector21)) const c1 = Cesium.Cartographic.fromCartesian(activePoints[1]) const c2 = Cesium.Cartographic.fromCartesian(activePoints[2]) if (c1.height < c2.height) { this.state.angletext = angle; this.state.polylineEntity.polyline.material = Cesium.Color.fromCssColorString('#2987f0') } else { this.state.polylineEntity.polyline.material = new Cesium.PolylineDashMaterialProperty({ color: Cesium.Color.fromCssColorString('#2987f0') }); this.state.angletext = 360 - angle } this.state.labels.angle = label.createLabel(activePoints[2], `${this.state.angletext.toFixed(1)}°`, true, 'angleLabel') } if (activePoints.length >= 4) { const middle23 = Cesium.Cartesian3.midpoint(this.state.polygonPositionList[2], this.state.polygonPositionList[3], new Cesium.Cartesian3()) const distance23 = Cesium.Cartesian3.distance(this.state.polygonPositionList[2], this.state.polygonPositionList[3]) this.state.labels.d23 = label.createLabel(middle23, `${formatter(distance23)}`, false, 'distanceLabel23') const middle34 = Cesium.Cartesian3.midpoint(this.state.polygonPositionList[3], this.state.polygonPositionList[0], new Cesium.Cartesian3()) const distance34 = Cesium.Cartesian3.distance(this.state.polygonPositionList[3], this.state.polygonPositionList[0]) this.state.labels.d34 = label.createLabel(middle34, `${formatter(distance34)}`, false, 'distanceLabel34') } } /** * 开始绘制交互(注册 LEFT_CLICK / MOUSE_MOVE),完成后回调 onComplete * @param {(polygon:Cesium.Cartesian3[],activeShape:Cesium.Entity)=>void} onComplete 完成回调 */ start (onComplete) { const viewer = this.viewer const drawHandler = this.eventHub.get('draw') drawHandler.setInputAction((event) => { const s = this.state if (!s._isDraw) s._isDraw = true if (!s._isDraw) return const earthPosition = viewer.scene.pickPosition(event.position) if (!Cesium.defined(earthPosition)) return if (s.activeShapePoints.length === 0) { s.floatingPoint = earthPosition s.polylineEntity = this.entityFactory.drawLine(new Cesium.CallbackProperty(() => { if (!Cesium.defined(s.floatingPoint)) return [] const activePoints = [...s.activeShapePoints, s.floatingPoint] if (activePoints.length >= 3) { const [p0, p1, p2] = activePoints const vector21 = Cesium.Cartesian3.subtract(p2, p1, new Cesium.Cartesian3()) const p3 = Cesium.Cartesian3.add(p0, vector21, new Cesium.Cartesian3()) return [p0, p1, p2, p3, p0] } return activePoints }, false)) } const activePoints = [...s.activeShapePoints, s.floatingPoint] if (activePoints.length === 2) this.createAngleRuler(activePoints[0], activePoints[1]) if (activePoints.length >= 3) { const [p0, p1, p2] = activePoints const vector21 = Cesium.Cartesian3.subtract(p2, p1, new Cesium.Cartesian3()) const p3 = Cesium.Cartesian3.add(p0, vector21, new Cesium.Cartesian3()) viewer.entities.remove(s.polylineEntity) s.polygonPositionList = [p0, p1, p2, p3] s.activeShape = this.entityFactory.drawShape(s.polygonPositionList) this.entityFactory.removeEntityByPrefix('ruler') this.entityFactory.removeEntityByPrefix('tick') this.eventHub.destroy('draw') if (typeof onComplete === 'function') onComplete(s.polygonPositionList, s.activeShape) s._isDraw = false } s.activeShapePoints.push(earthPosition) this.updateLabels() if (activePoints.length >= 3) { this.labelManager.removeLabelById('angleLabel') } }, Cesium.ScreenSpaceEventType.LEFT_CLICK) drawHandler.setInputAction((event) => { const s = this.state if (!s.floatingPoint) return const newPosition = viewer.scene.pickPosition(event.endPosition) if (!Cesium.defined(newPosition)) return s.floatingPoint = newPosition if (s.activeShapePoints.length >= 2) { const ray = viewer.camera.getPickRay(event.endPosition) if (ray) { const [p0, p1] = s.activeShapePoints const planeNormal = Cesium.Cartesian3.subtract(p1, p0, new Cesium.Cartesian3()) const plane = Cesium.Plane.fromPointNormal(p1, Cesium.Cartesian3.normalize(planeNormal, new Cesium.Cartesian3())) const intersection = Cesium.IntersectionTests.rayPlane(ray, plane) if (intersection) s.floatingPoint = intersection } } this.updateLabels() }, Cesium.ScreenSpaceEventType.MOUSE_MOVE) } }