一些技术路线测试,增加git,方便代码还原
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/**
 * 绘制管理器
 * 职责:管理测区的交互绘制流程(点/线/面)、尺规与标签生成、完成时回调
 */
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)
  }
}