@mesh3d/cesium-vectortile-gl 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.gitattributes +11 -0
  2. package/.gitconfig +3 -0
  3. package/.husky/pre-commit +1 -0
  4. package/.prettierignore +5 -0
  5. package/.vscode/settings.json +25 -0
  6. package/LICENSE.md +203 -203
  7. package/README.md +202 -167
  8. package/Source/Cesium.d.ts +2692 -2691
  9. package/Source/VectorTileLOD.js +720 -532
  10. package/Source/VectorTileRenderList.js +70 -70
  11. package/Source/VectorTileset.js +473 -447
  12. package/Source/layers/BackgroundRenderLayer.js +91 -89
  13. package/Source/layers/FillRenderLayer.js +18 -18
  14. package/Source/layers/IRenderLayer.js +160 -152
  15. package/Source/layers/LineRenderLayer.js +104 -94
  16. package/Source/layers/SymbolRenderLayer.js +30 -31
  17. package/Source/layers/index.js +23 -16
  18. package/Source/layers/registerRenderLayer.js +24 -24
  19. package/Source/layers/visualizers/FillLayerVisualizer.js +542 -426
  20. package/Source/layers/visualizers/ILayerVisualizer.js +90 -94
  21. package/Source/layers/visualizers/LineLayerVisualizer.js +702 -571
  22. package/Source/layers/visualizers/SymbolLayerVisualizer.js +514 -244
  23. package/Source/sources/GeoJSONSource.js +53 -46
  24. package/Source/sources/ISource.js +39 -39
  25. package/Source/sources/VectorSource.js +94 -52
  26. package/Source/sources/granularitySettings.js +23 -20
  27. package/Source/sources/index.js +6 -11
  28. package/Source/sources/registerSource.js +17 -19
  29. package/Source/style/StyleLayer.js +43 -43
  30. package/Source/style/StyleLayerProperties.js +44 -43
  31. package/Source/style/index.js +2 -2
  32. package/Source/symbol/SymbolPlacements.js +117 -88
  33. package/Source/workers/VectorTileWorker.js +41 -0
  34. package/Source/workers/ellipsoid.js +47 -0
  35. package/Source/workers/processTileTask.js +329 -0
  36. package/Source/workers/styleEvaluator.js +168 -0
  37. package/benchmark.html +148 -0
  38. package/dist/cvt-gl-worker.js +9274 -0
  39. package/dist/cvt-gl-worker.js.map +1 -0
  40. package/dist/cvt-gl.js +2570 -2001
  41. package/dist/cvt-gl.js.map +1 -1
  42. package/dist/cvt-gl.min.js +3 -3
  43. package/dist/cvt-gl.min.js.map +1 -1
  44. package/eslint.config.mjs +58 -0
  45. package/index.js +9 -6
  46. package/mlt.html +26 -25
  47. package/package.json +64 -41
  48. package/prettier.config.mjs +30 -0
  49. package/vite.config.mjs +43 -0
  50. package/vite.worker.config.mjs +31 -0
  51. package/worker.html +26 -0
@@ -1,244 +1,514 @@
1
- import { VectorTileFeature } from "@mapbox/vector-tile"
2
- import { ILayerVisualizer } from "./ILayerVisualizer"
3
- import { SymbolRenderLayer } from "../SymbolRenderLayer"
4
- import { warnOnce } from "maplibre-gl/src/util/util"
5
-
6
- export class SymbolFeature {
7
- constructor() {
8
- this.featureId = 0
9
- this.textColor = Cesium.Color.BLACK.clone()
10
- this.textSize = 12
11
- this.coordinates = []
12
- }
13
- }
14
-
15
- /**@type {Cesium.Cartesian3} */
16
- let scratchDirectionToEye = null
17
- /**@type {Cesium.Cartesian3} */
18
- let scratchSurfaceNormal = null
19
-
20
- export class SymbolLayerVisualizer extends ILayerVisualizer {
21
- constructor(layers, tile) {
22
- if (scratchDirectionToEye === null) {
23
- scratchDirectionToEye = new Cesium.Cartesian3()
24
- scratchSurfaceNormal = new Cesium.Cartesian3()
25
- }
26
-
27
- super(layers, tile)
28
-
29
- /**@type {Cesium.Label[]} */
30
- this.labels = []
31
- this.primitive = null
32
- this.dotCutOff = 0.0035
33
- }
34
-
35
- /**
36
- * 对符号进行地平线剔除
37
- * @param {Cesium.Cartesian3} positionWC
38
- * @param {Cesium.Cartesian3} cameraPositionWC
39
- */
40
- isOccluded(cameraPositionWC, positionWC) {
41
- /*
42
- 如下图,o为符号锚点,up为椭球面过锚点o的法线,tangent为过锚点o的切线,eye为相机位置。
43
- 可见,当锚点在地平线以下时,eye方向与up方向夹角小于90°。
44
- up
45
- ^
46
- |
47
- o —— —— > tangent
48
- \
49
- \
50
- eye
51
- */
52
- const eyeDir = Cesium.Cartesian3.subtract(cameraPositionWC, positionWC, scratchDirectionToEye)
53
- Cesium.Cartesian3.normalize(eyeDir, eyeDir)
54
- const up = Cesium.Cartesian3.normalize(positionWC, scratchSurfaceNormal)
55
- return Cesium.Cartesian3.dot(eyeDir, up) < this.dotCutOff
56
- }
57
-
58
- /**
59
- * @param {VectorTileFeature[]} features
60
- * @param {SymbolRenderLayer} layer
61
- * @param {Cesium.frameState} frameState
62
- * @param {VectorTileset} tileset
63
- */
64
- addLayer(features, layer, frameState, tileset) {
65
- const style = layer.style
66
- const { tile, labels } = this
67
- const rectangle = tile.rectangle
68
-
69
- function addText(coord, text, font, textSize, textColor, outlineWidth, outlineColor) {
70
- if (!Cesium.Rectangle.contains(rectangle, Cesium.Cartographic.fromDegrees(coord[0], coord[1]))) {
71
- return
72
- }
73
- const label = new Cesium.Label({
74
- position: Cesium.Cartesian3.fromDegrees(coord[0], coord[1]),
75
- text,
76
- font: textSize + 'px ' + font,
77
- fillColor: textColor,
78
- style: outlineWidth && Cesium.LabelStyle.FILL_AND_OUTLINE,
79
- outlineWidth: outlineWidth * textSize,
80
- outlineColor,
81
- //禁用深度测试
82
- disableDepthTestDistance: Infinity
83
- })
84
- label.batchId = labels.length
85
- labels.push(label)
86
- layer.labels.push(label)
87
- }
88
-
89
- for (const sourceFeature of features) {
90
- const feature = sourceFeature.toGeoJSON(tile.x, tile.y, tile.z)
91
- if (!feature.geometry) continue
92
- const properties = sourceFeature.properties
93
-
94
- //读取图层样式属性
95
- const iconImage = style.layout.getDataValue('icon-image', tile.z, sourceFeature)
96
- const textField = style.layout.getDataValue('text-field', tile.z, sourceFeature)
97
- let text = textField
98
- if (typeof text === 'string') {
99
- text = style.layout.resolveTokens(properties, textField)
100
- }
101
- else if (text && text.sections) {
102
- for (const section of text.sections) {
103
- section.text = style.layout.resolveTokens(properties, section.text)
104
- }
105
- text = text.toString()
106
- }
107
- if (iconImage) {
108
- warnOnce('symbol图层:不支持图标')
109
- continue
110
- }
111
- if (!text) {
112
- continue
113
- }
114
- const maxWidth = style.layout.getDataValue('text-max-width', tile.z, sourceFeature) * 3
115
- const textRotationAlignment = style.layout.getDataValue('text-rotation-alignment', tile.z, sourceFeature)
116
- const textPitchAlignment = style.layout.getDataValue('text-pitch-alignment', tile.z, sourceFeature)
117
- if (text.length > maxWidth) {
118
- warnOnce('symbol图层: 不支持 text-max-width,无自动换行效果')
119
- }
120
- if (textRotationAlignment === 'map') {
121
- warnOnce('symbol图层:text-rotation-alignment 仅支持 viewport')
122
- }
123
- if (textPitchAlignment === 'map') {
124
- warnOnce('symbol图层:text-pitch-alignment 仅支持 viewport')
125
- }
126
-
127
- const font = style.layout.getDataValue('text-font', tile.z, sourceFeature)
128
- const textSize = style.layout.getDataValue('text-size', tile.z, sourceFeature)
129
- const textColor = style.convertColor(style.paint.getDataValue('text-color', tile.z, sourceFeature))
130
- const outlineColor = style.convertColor(style.paint.getDataValue('text-halo-color', tile.z, sourceFeature))
131
- const outlineWidth = style.paint.getDataValue('text-halo-width', tile.z, sourceFeature)
132
-
133
- const geometryType = feature.geometry.type
134
- const coordinates = feature.geometry.coordinates
135
- if (geometryType == 'Point') {
136
- addText(coordinates, text, font, textSize, textColor, outlineWidth, outlineColor)
137
- }
138
- else if (geometryType == 'MultiPoint') {
139
- coordinates.forEach(coord => {
140
- addText(coord, text, font, textSize, textColor, outlineWidth, outlineColor)
141
- })
142
- }
143
- else {
144
- warnOnce('symbol图层:不支持符号沿线布局');
145
- }
146
- }
147
-
148
- this.layers.push(layer)
149
- }
150
-
151
- createPrimitive() {
152
- //所有图层的文字共用一个LabelCollection
153
- //注意:这样文字就没有了“图层”的特征了,渲染顺序可能和样式配置的不一致
154
- //优化:参考 maplibre-gl 的符号系统实现,但工作量巨大,如有需求建议使用商业版(Mesh-3D矢量地图引擎)
155
-
156
- const primitive = new Cesium.LabelCollection()
157
- for (let i = 0; i < this.labels.length; i++) {
158
- this.labels[i] = primitive.add(this.labels[i])
159
- }
160
-
161
- /**@type {SymbolRenderLayer[]} */
162
- const layers = this.layers
163
- for (const layer of layers) {
164
- for (let i = 0; i < layer.labels.length; i++) {
165
- layer.labels[i] = this.labels[layer.labels[i].batchId]
166
- }
167
- }
168
-
169
- this.primitive = primitive
170
- }
171
-
172
- update(frameState, tileset) {
173
- if (this.state !== 'none') return
174
-
175
- if (!this.primitive && this.labels?.length) {
176
- this.createPrimitive()
177
- }
178
-
179
- //性能优化:这里应该进行自动避让处理
180
-
181
- if (this.primitive) {
182
- this.commandList.length = 0
183
- const preCommandList = frameState.commandList
184
- frameState.commandList = this.commandList
185
- this.primitive.update(frameState)
186
- frameState.commandList = preCommandList
187
-
188
- if (this.state === 'none' && preCommandList.length > 0) {
189
- this.setState('done')
190
- }
191
- if (this.primitive._state === Cesium.PrimitiveState.FAILED) {
192
- this.setState('error')
193
- }
194
- }
195
- else if (this.state === 'none' && this.labels.length === 0) {
196
- this.setState('done')
197
- }
198
- }
199
-
200
- render(frameState, tileset) {
201
- if (this.state !== 'done') return
202
-
203
- const cameraPositionWC = frameState.camera.positionWC
204
-
205
- /**@type {SymbolRenderLayer[]} */
206
- const layers = this.layers
207
- for (const layer of layers) {
208
- for (let i = 0; i < layer.labels.length; i++) {
209
- const style = layer.style, zoom = tileset.zoom
210
- if (layer.visibility === 'none' || zoom < style.minzoom || zoom >= style.maxzoom) {
211
- layer.labels[i].show = false
212
- }
213
- else {
214
- //vtPlaceable 为碰撞检测结果,true 表示可以摆放到屏幕
215
- //碰撞检测在 symbol/SymbolPlacements.js 里完成
216
- layer.labels[i].show = !!layer.labels[i].vtPlaceable
217
- }
218
- }
219
- }
220
-
221
- for (const label of this.labels) {
222
- if (label.show) label.show = !this.isOccluded(cameraPositionWC, label.position)
223
- }
224
-
225
- if (this.primitive) {
226
- this.commandList.length = 0
227
- const preCommandList = frameState.commandList
228
- frameState.commandList = this.commandList
229
- this.primitive.update(frameState)
230
- frameState.commandList = preCommandList
231
- }
232
-
233
- super.render(frameState)
234
- }
235
-
236
- destroy() {
237
- this.primitive = this.primitive && this.primitive.destroy()
238
- super.destroy()
239
- }
240
-
241
- isDestroyed() {
242
- return false
243
- }
244
- }
1
+ import { VectorTileFeature } from '@mapbox/vector-tile'
2
+ import { ILayerVisualizer } from './ILayerVisualizer'
3
+ import { SymbolRenderLayer } from '../SymbolRenderLayer'
4
+ import { warnOnce } from 'maplibre-gl/src/util/util'
5
+
6
+ export class SymbolFeature {
7
+ constructor() {
8
+ this.featureId = 0
9
+ this.textColor = Cesium.Color.BLACK.clone()
10
+ this.textSize = 12
11
+ this.coordinates = []
12
+ }
13
+ }
14
+
15
+ /**@type {Cesium.Cartesian3} */
16
+ let scratchDirectionToEye = null
17
+ /**@type {Cesium.Cartesian3} */
18
+ let scratchSurfaceNormal = null
19
+
20
+ /** 淡入淡出每帧向目标透明度的插值系数,约 0.10 ~10帧完成过渡,越小越慢 */
21
+ const FADE_SPEED = 0.1
22
+
23
+ export class SymbolLayerVisualizer extends ILayerVisualizer {
24
+ constructor(layers, tile) {
25
+ if (scratchDirectionToEye === null) {
26
+ scratchDirectionToEye = new Cesium.Cartesian3()
27
+ scratchSurfaceNormal = new Cesium.Cartesian3()
28
+ }
29
+
30
+ super(layers, tile)
31
+
32
+ /**@type {Cesium.Label[]} */
33
+ this.labels = []
34
+ this.primitive = null
35
+ this.dotCutOff = 0.0035
36
+ }
37
+
38
+ /**
39
+ * 对符号进行地平线剔除
40
+ * @param {Cesium.Cartesian3} positionWC
41
+ * @param {Cesium.Cartesian3} cameraPositionWC
42
+ */
43
+ isOccluded(cameraPositionWC, positionWC) {
44
+ /*
45
+ 如下图,o为符号锚点,up为椭球面过锚点o的法线,tangent为过锚点o的切线,eye为相机位置。
46
+ 可见,当锚点在地平线以下时,eye方向与up方向夹角小于90°。
47
+ up
48
+ ^
49
+ |
50
+ o —— —— > tangent
51
+ \
52
+ \
53
+ eye
54
+ */
55
+ const eyeDir = Cesium.Cartesian3.subtract(
56
+ cameraPositionWC,
57
+ positionWC,
58
+ scratchDirectionToEye
59
+ )
60
+ Cesium.Cartesian3.normalize(eyeDir, eyeDir)
61
+ const up = Cesium.Cartesian3.normalize(positionWC, scratchSurfaceNormal)
62
+ return Cesium.Cartesian3.dot(eyeDir, up) < this.dotCutOff
63
+ }
64
+
65
+ /**
66
+ * @param {VectorTileFeature[]} features
67
+ * @param {SymbolRenderLayer} layer
68
+ * @param {Cesium.frameState} frameState
69
+ * @param {VectorTileset} tileset
70
+ */
71
+ addLayer(features, layer, frameState, tileset) {
72
+ const style = layer.style
73
+ const { tile, labels } = this
74
+ const rectangle = tile.rectangle
75
+
76
+ function addText(
77
+ coord,
78
+ text,
79
+ font,
80
+ textSize,
81
+ textColor,
82
+ outlineWidth,
83
+ outlineColor,
84
+ textOffset,
85
+ textOrigin
86
+ ) {
87
+ if (
88
+ !Cesium.Rectangle.contains(
89
+ rectangle,
90
+ Cesium.Cartographic.fromDegrees(coord[0], coord[1])
91
+ ) ||
92
+ !text
93
+ ) {
94
+ return
95
+ }
96
+ const label = new Cesium.Label({
97
+ position: Cesium.Cartesian3.fromDegrees(coord[0], coord[1]),
98
+ text,
99
+ font: textSize + 'px ' + font,
100
+ fillColor: textColor,
101
+ style: outlineWidth && Cesium.LabelStyle.FILL_AND_OUTLINE,
102
+ outlineWidth: outlineWidth * textSize,
103
+ outlineColor,
104
+ //禁用深度测试
105
+ disableDepthTestDistance: Infinity,
106
+ pixelOffset: new Cesium.Cartesian2(
107
+ textOffset[0] * textSize,
108
+ textOffset[1] * textSize
109
+ ),
110
+ horizontalOrigin: textOrigin.horizontal,
111
+ verticalOrigin: textOrigin.vertical
112
+ })
113
+ label._baseFillColor = label.fillColor.clone()
114
+ label._baseOutlineColor = label.outlineColor.clone()
115
+ label.vtAlpha = 0
116
+ label.batchId = labels.length
117
+ labels.push(label)
118
+ layer.labels.push(label)
119
+ }
120
+
121
+ for (const sourceFeature of features) {
122
+ const feature = sourceFeature.toGeoJSON(tile.x, tile.y, tile.z)
123
+ if (!feature.geometry) continue
124
+ const properties = sourceFeature.properties
125
+
126
+ //读取图层样式属性
127
+ const iconImage = style.layout.getDataValue(
128
+ 'icon-image',
129
+ tile.z,
130
+ sourceFeature
131
+ )
132
+ const textField = style.layout.getDataValue(
133
+ 'text-field',
134
+ tile.z,
135
+ sourceFeature
136
+ )
137
+ let text = textField
138
+ if (typeof text === 'string') {
139
+ text = style.layout.resolveTokens(properties, textField)
140
+ } else if (text && text.sections) {
141
+ for (const section of text.sections) {
142
+ section.text = style.layout.resolveTokens(properties, section.text)
143
+ }
144
+ text = text.toString()
145
+ }
146
+ if (iconImage) {
147
+ warnOnce('symbol图层:不支持图标')
148
+ continue
149
+ }
150
+ if (!text) {
151
+ continue
152
+ }
153
+
154
+ const textTransform = style.layout.getDataValue(
155
+ 'text-transform',
156
+ tile.z,
157
+ sourceFeature
158
+ )
159
+ if (textTransform === 'uppercase') {
160
+ text = String(text).toUpperCase()
161
+ } else if (textTransform === 'lowercase') {
162
+ text = String(text).toLowerCase()
163
+ }
164
+
165
+ const maxWidth =
166
+ style.layout.getDataValue('text-max-width', tile.z, sourceFeature) * 3
167
+ const textRotationAlignment = style.layout.getDataValue(
168
+ 'text-rotation-alignment',
169
+ tile.z,
170
+ sourceFeature
171
+ )
172
+ const textPitchAlignment = style.layout.getDataValue(
173
+ 'text-pitch-alignment',
174
+ tile.z,
175
+ sourceFeature
176
+ )
177
+ if (text.length > maxWidth) {
178
+ warnOnce('symbol图层: 不支持 text-max-width,无自动换行效果')
179
+ }
180
+ if (textRotationAlignment === 'map') {
181
+ warnOnce('symbol图层:text-rotation-alignment 仅支持 viewport')
182
+ }
183
+ if (textPitchAlignment === 'map') {
184
+ warnOnce('symbol图层:text-pitch-alignment 仅支持 viewport')
185
+ }
186
+
187
+ const font = style.layout.getDataValue('text-font', tile.z, sourceFeature)
188
+ const textSize = style.layout.getDataValue(
189
+ 'text-size',
190
+ tile.z,
191
+ sourceFeature
192
+ )
193
+ const textAnchor = style.layout.getDataValue(
194
+ 'text-anchor',
195
+ tile.z,
196
+ sourceFeature
197
+ )
198
+ const textOrigin = getOrigin(textAnchor)
199
+ const textOffset = style.layout.getDataValue(
200
+ 'text-offset',
201
+ tile.z,
202
+ sourceFeature
203
+ )
204
+ const textColor = style.convertColor(
205
+ style.paint.getDataValue('text-color', tile.z, sourceFeature)
206
+ )
207
+ const outlineColor = style.convertColor(
208
+ style.paint.getDataValue('text-halo-color', tile.z, sourceFeature)
209
+ )
210
+ const outlineWidth = style.paint.getDataValue(
211
+ 'text-halo-width',
212
+ tile.z,
213
+ sourceFeature
214
+ )
215
+
216
+ if (!textSize || !isFinite(textSize) || Number(textSize) <= 0) {
217
+ continue
218
+ }
219
+
220
+ const geometryType = feature.geometry.type
221
+ const coordinates = feature.geometry.coordinates
222
+ if (geometryType == 'Point') {
223
+ addText(
224
+ coordinates,
225
+ text,
226
+ font,
227
+ textSize,
228
+ textColor,
229
+ outlineWidth,
230
+ outlineColor,
231
+ textOffset,
232
+ textOrigin
233
+ )
234
+ } else if (geometryType == 'MultiPoint') {
235
+ coordinates.forEach(coord => {
236
+ addText(
237
+ coord,
238
+ text,
239
+ font,
240
+ textSize,
241
+ textColor,
242
+ outlineWidth,
243
+ outlineColor,
244
+ textOffset,
245
+ textOrigin
246
+ )
247
+ })
248
+ } else {
249
+ warnOnce('symbol图层:不支持符号沿线布局')
250
+ }
251
+ }
252
+
253
+ this.layers.push(layer)
254
+ }
255
+
256
+ /**
257
+ * 从 Web Worker 结果构建符号图层(placements 已由 Worker 算好)
258
+ * @param {object} workerLayerData - { layerId, source, sourceLayer, styleLayer, placements }
259
+ * @param {SymbolRenderLayer} layer
260
+ * @param {Cesium.FrameState} frameState
261
+ * @param {VectorTileset} tileset
262
+ */
263
+ addLayerFromWorkerResult(workerLayerData, layer, frameState, tileset) {
264
+ const { placements } = workerLayerData
265
+ const { labels } = this
266
+ const rectangle = this.tile.rectangle
267
+
268
+ for (const p of placements || []) {
269
+ if (
270
+ !Cesium.Rectangle.contains(
271
+ rectangle,
272
+ Cesium.Cartographic.fromDegrees(p.coord[0], p.coord[1])
273
+ )
274
+ ) {
275
+ continue
276
+ }
277
+ const textColor = Cesium.Color.fromBytes(
278
+ p.textColorBytes[0],
279
+ p.textColorBytes[1],
280
+ p.textColorBytes[2],
281
+ p.textColorBytes[3]
282
+ )
283
+ const outlineColor = Cesium.Color.fromBytes(
284
+ p.outlineColorBytes[0],
285
+ p.outlineColorBytes[1],
286
+ p.outlineColorBytes[2],
287
+ p.outlineColorBytes[3]
288
+ )
289
+ const textOrigin = getOrigin(p.textAnchor)
290
+ const label = new Cesium.Label({
291
+ position: Cesium.Cartesian3.fromDegrees(p.coord[0], p.coord[1]),
292
+ text: p.text,
293
+ font: p.textSize + 'px ' + p.font,
294
+ fillColor: textColor,
295
+ style:
296
+ p.outlineWidth > 0
297
+ ? Cesium.LabelStyle.FILL_AND_OUTLINE
298
+ : Cesium.LabelStyle.FILL,
299
+ outlineWidth: p.outlineWidth * p.textSize,
300
+ outlineColor,
301
+ disableDepthTestDistance: Infinity,
302
+ pixelOffset: new Cesium.Cartesian2(
303
+ (p.textOffset[0] || 0) * p.textSize,
304
+ (p.textOffset[1] || 0) * p.textSize
305
+ ),
306
+ horizontalOrigin: textOrigin.horizontal,
307
+ verticalOrigin: textOrigin.vertical
308
+ })
309
+ label._baseFillColor = label.fillColor.clone()
310
+ label._baseOutlineColor = label.outlineColor.clone()
311
+ label.vtAlpha = 0
312
+ label.batchId = labels.length
313
+ labels.push(label)
314
+ layer.labels.push(label)
315
+ }
316
+
317
+ this.layers.push(layer)
318
+ }
319
+
320
+ createPrimitive() {
321
+ //所有图层的文字共用一个LabelCollection
322
+ //注意:这样文字就没有了“图层”的特征了,渲染顺序可能和样式配置的不一致
323
+ //优化:参考 maplibre-gl 的符号系统实现,但工作量巨大,如有需求建议使用商业版(Mesh-3D矢量地图引擎)
324
+
325
+ const primitive = new Cesium.LabelCollection()
326
+ for (let i = 0; i < this.labels.length; i++) {
327
+ this.labels[i] = primitive.add(this.labels[i])
328
+ }
329
+
330
+ /**@type {SymbolRenderLayer[]} */
331
+ const layers = this.layers
332
+ for (const layer of layers) {
333
+ for (let i = 0; i < layer.labels.length; i++) {
334
+ layer.labels[i] = this.labels[layer.labels[i].batchId]
335
+ }
336
+ }
337
+
338
+ this.primitive = primitive
339
+ }
340
+
341
+ update(frameState, tileset) {
342
+ if (this.state !== 'none') return
343
+
344
+ if (!this.primitive && this.labels?.length) {
345
+ this.createPrimitive()
346
+ }
347
+
348
+ //性能优化:这里应该进行自动避让处理
349
+
350
+ if (this.primitive) {
351
+ this.commandList.length = 0
352
+ const preCommandList = frameState.commandList
353
+ frameState.commandList = this.commandList
354
+ this.primitive.update(frameState)
355
+ frameState.commandList = preCommandList
356
+
357
+ //解决Cesium特殊字符支持问题:生成的字形图纹理宽高为0的情况下,dimensions的height或者width为NaN,
358
+ //导致drawCommand的boundingVolume.radius为NaN,
359
+ //进而引发 View.createPotentiallyVisibleSet drawCommand按视锥分组失败的致命问题
360
+ for (const label of this.labels) {
361
+ const glyphs = label._glyphs
362
+ if (glyphs) {
363
+ let text = label.text,
364
+ newGlyphs = [],
365
+ newText = [],
366
+ glyphsChanged = false
367
+ for (let glyphIndex = 0; glyphIndex < glyphs.length; glyphIndex++) {
368
+ const glyph = glyphs[glyphIndex]
369
+ const dimensions = glyph.dimensions
370
+ if (
371
+ dimensions &&
372
+ isFinite(dimensions.width) &&
373
+ isFinite(dimensions.height)
374
+ ) {
375
+ newText.push(text[glyphIndex])
376
+ newGlyphs.push(glyph)
377
+ } else {
378
+ glyphsChanged = true
379
+ }
380
+ }
381
+ if (glyphsChanged) {
382
+ label._glyphs = newGlyphs
383
+ label.text = newText.join('')
384
+ }
385
+ }
386
+ }
387
+
388
+ if (this.state === 'none' && preCommandList.length > 0) {
389
+ this.setState('done')
390
+ }
391
+ if (this.primitive._state === Cesium.PrimitiveState.FAILED) {
392
+ this.setState('error')
393
+ }
394
+ } else if (this.state === 'none' && this.labels.length === 0) {
395
+ this.setState('done')
396
+ }
397
+ }
398
+
399
+ render(frameState, tileset) {
400
+ if (this.state !== 'done') return
401
+
402
+ const cameraPositionWC = frameState.camera.positionWC
403
+
404
+ /**@type {SymbolRenderLayer[]} */
405
+ const layers = this.layers
406
+ for (const layer of layers) {
407
+ for (let i = 0; i < layer.labels.length; i++) {
408
+ const style = layer.style,
409
+ zoom = tileset.zoom
410
+ if (
411
+ layer.visibility === 'none' ||
412
+ zoom < style.minzoom ||
413
+ zoom >= style.maxzoom
414
+ ) {
415
+ layer.labels[i].show = false
416
+ } else {
417
+ // 淡入淡出:根据 vtPlaceable 插值 vtAlpha,用 style 透明度控制,alpha 为 0 时才彻底隐藏
418
+ const label = layer.labels[i]
419
+ if (!label._baseFillColor) {
420
+ label._baseFillColor = label.fillColor.clone()
421
+ label._baseOutlineColor = label.outlineColor.clone()
422
+ if (label.vtAlpha == null) label.vtAlpha = label.vtPlaceable ? 1 : 0
423
+ }
424
+ const targetAlpha = label.vtPlaceable ? 1 : 0
425
+ label.vtAlpha = Cesium.Math.lerp(
426
+ label.vtAlpha ?? 0,
427
+ targetAlpha,
428
+ FADE_SPEED
429
+ )
430
+ if (label.vtAlpha < 0.001) {
431
+ label.vtAlpha = 0
432
+ label.show = false
433
+ } else {
434
+ label.show = true
435
+ label.fillColor = label._baseFillColor.withAlpha(
436
+ label._baseFillColor.alpha * label.vtAlpha
437
+ )
438
+ label.outlineColor = label._baseOutlineColor.withAlpha(
439
+ label._baseOutlineColor.alpha * label.vtAlpha
440
+ )
441
+ }
442
+ }
443
+ }
444
+ }
445
+
446
+ for (const label of this.labels) {
447
+ if (label.show)
448
+ label.show = !this.isOccluded(cameraPositionWC, label.position)
449
+ }
450
+
451
+ if (this.primitive) {
452
+ this.commandList.length = 0
453
+ const preCommandList = frameState.commandList
454
+ frameState.commandList = this.commandList
455
+ this.primitive.update(frameState)
456
+ frameState.commandList = preCommandList
457
+ }
458
+
459
+ super.render(frameState)
460
+ }
461
+
462
+ destroy() {
463
+ this.primitive = this.primitive && this.primitive.destroy()
464
+ super.destroy()
465
+ }
466
+
467
+ isDestroyed() {
468
+ return false
469
+ }
470
+ }
471
+
472
+ function getOrigin(textAnchor) {
473
+ let horizontal = Cesium.HorizontalOrigin.CENTER
474
+ let vertical = Cesium.VerticalOrigin.CENTER
475
+
476
+ switch (textAnchor) {
477
+ case 'left':
478
+ horizontal = Cesium.HorizontalOrigin.LEFT
479
+ break
480
+ case 'right':
481
+ horizontal = Cesium.HorizontalOrigin.RIGHT
482
+ break
483
+ case 'top':
484
+ vertical = Cesium.VerticalOrigin.TOP
485
+ break
486
+ case 'bottom':
487
+ vertical = Cesium.VerticalOrigin.BOTTOM
488
+ break
489
+ case 'top-left':
490
+ vertical = Cesium.VerticalOrigin.TOP
491
+ horizontal = Cesium.HorizontalOrigin.LEFT
492
+ break
493
+ case 'top-right':
494
+ vertical = Cesium.VerticalOrigin.TOP
495
+ horizontal = Cesium.HorizontalOrigin.RIGHT
496
+ break
497
+ case 'bottom-left':
498
+ vertical = Cesium.VerticalOrigin.BOTTOM
499
+ horizontal = Cesium.HorizontalOrigin.LEFT
500
+ break
501
+ case 'bottom-right':
502
+ vertical = Cesium.VerticalOrigin.BOTTOM
503
+ horizontal = Cesium.HorizontalOrigin.RIGHT
504
+ break
505
+ case 'center':
506
+ default:
507
+ break
508
+ }
509
+
510
+ return {
511
+ horizontal,
512
+ vertical
513
+ }
514
+ }