/**
|
* 绘制管理器
|
* 职责:管理测区的交互绘制流程(点/线/面)、尺规与标签生成、完成时回调
|
*/
|
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)
|
}
|
}
|