@mesh3d/cesium-vectortile-gl 0.4.6 → 0.5.1

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.
package/README.md CHANGED
@@ -13,6 +13,7 @@
13
13
  - ✅ **GPU 精准剔除**:利用 FBO + RTT 技术生成瓦片 ID 纹理,在 GPU 中高效剔除不可见矢量片段
14
14
  - ✅ **高度可扩展**:提供清晰接口,轻松接入新数据源(如 TopoJSON)或自定义图层类型(如热力图、流线)
15
15
  - ✅ **文字符号自动避让**:使用 `maplibre-gl` `GridIndex`类,结合 Cesium.Label 屏幕空间位置和包围盒计算API,实现符号碰撞检测(自动避让)
16
+ - ✅ **样式动态修改**:提供`setLayoutProperty`、`setPaintProperty`、`setFilter`等`样式修改API`,可动态控制图层`显隐`、`颜色`、`透明度`
16
17
 
17
18
  #### 🧠 技术栈亮点
18
19
 
@@ -83,6 +84,34 @@ const tileset = new VectorTileset({
83
84
  viewer.scene.primitives.add(tileset)
84
85
  ```
85
86
 
87
+ ### 样式修改示例
88
+
89
+ ```js
90
+ tileset.readyEvent.addEventListener(() => {
91
+ // 1、修改绘制属性(颜色、透明度),不会触发强制刷新,实时变化
92
+ tileset.setPaintProperty('background', 'background-color', 'skyblue')
93
+ tileset.setPaintProperty('geolines', 'line-color', 'green')
94
+ tileset.setPaintProperty('us_states:fill', 'fill-color', 'red')
95
+ tileset.setPaintProperty('countries-label', 'text-color', 'blue')
96
+
97
+ // 2、修改布局属性,除 visibility 外,都会触发强制刷新
98
+ // 2.1、布局属性修改:图层显隐,不会触发强制刷新
99
+ tileset.setLayoutProperty('coastline', 'visibility', 'none')
100
+ // 2.2、布局属性修改(非visibility):字体大小修改,会触发强制刷新
101
+ tileset.setLayoutProperty('countries-label', 'text-size', {
102
+ stops: [
103
+ [2, 12],
104
+ [4, 16],
105
+ [6, 20]
106
+ ]
107
+ })
108
+
109
+ // 3、修改过滤器。
110
+ //和setLayoutProperty一样(visibility例外)会触发强制刷新
111
+ tileset.setFilter('geolines', null)
112
+ })
113
+ ```
114
+
86
115
  ### 可选:Web Worker 多线程瓦片处理
87
116
 
88
117
  将瓦片解析、坐标转换、几何构建放到子线程,减轻主线程卡顿。不传 `workerUrl` 时仍走主线程。
@@ -33,6 +33,7 @@ export class VectorTileset {
33
33
  this._tilesToRender = []
34
34
  /**@type {StyleLayer[]} */
35
35
  this._styleLayers = []
36
+ this._styleLayerIndexMap = new Map()
36
37
  /**@type {VectorTileRenderList} */
37
38
  this._renderList = new VectorTileRenderList(this._styleLayers)
38
39
  this.numLoading = 0
@@ -95,6 +96,7 @@ export class VectorTileset {
95
96
  //初始化样式图层
96
97
  for (let i = 0; i < style.layers.length; i++) {
97
98
  this._styleLayers[i] = new StyleLayer(style.layers[i])
99
+ this._styleLayerIndexMap.set(style.layers[i].id, i)
98
100
  }
99
101
 
100
102
  //创建顶级瓦片LOD
@@ -257,6 +259,65 @@ export class VectorTileset {
257
259
  }
258
260
  }
259
261
 
262
+ //样式编辑API
263
+
264
+ setLayoutProperty(layerId, name, value) {
265
+ const styleLayerIndexMap = this._styleLayerIndexMap
266
+ if (!styleLayerIndexMap.has(layerId)) {
267
+ warnOnce(`不存在图层:${layerId}`)
268
+ return false
269
+ }
270
+ const layerIndex = styleLayerIndexMap.get(layerId)
271
+ const styleLayer = this._styleLayers[layerIndex]
272
+ const changed = styleLayer.setLayoutProperty(name, value)
273
+ //强制更新
274
+ if (changed && name !== 'visibility') {
275
+ this._forceUpdate()
276
+ }
277
+ return changed
278
+ }
279
+
280
+ setPaintProperty(layerId, name, value) {
281
+ const styleLayerIndexMap = this._styleLayerIndexMap
282
+ if (!styleLayerIndexMap.has(layerId)) {
283
+ warnOnce(`不存在图层:${layerId}`)
284
+ return false
285
+ }
286
+ const layerIndex = styleLayerIndexMap.get(layerId)
287
+ const styleLayer = this._styleLayers[layerIndex]
288
+ return styleLayer.setPaintProperty(name, value)
289
+ }
290
+
291
+ setFilter(layerId, filter) {
292
+ const styleLayerIndexMap = this._styleLayerIndexMap
293
+ if (!styleLayerIndexMap.has(layerId)) {
294
+ warnOnce(`不存在图层:${layerId}`)
295
+ return false
296
+ }
297
+ const layerIndex = styleLayerIndexMap.get(layerId)
298
+ const styleLayer = this._styleLayers[layerIndex]
299
+ const changed = styleLayer.setFilter(filter)
300
+ if (changed) {
301
+ this._forceUpdate()
302
+ }
303
+ return changed
304
+ }
305
+
306
+ //强制更新
307
+ _forceUpdate() {
308
+ for (const cacheTile of this._cacheTiles) {
309
+ cacheTile.unload()
310
+ }
311
+ for (const cacheTile of this._tilesToRender) {
312
+ cacheTile.unload()
313
+ }
314
+ for (const cacheTile of this._tilesToUpdate) {
315
+ cacheTile.unload()
316
+ }
317
+ this._tilesToRender.length = 0
318
+ this._tilesToUpdate.length = 0
319
+ }
320
+
260
321
  destroy() {
261
322
  const scene = this.scene
262
323
  const rootTiles = this._rootTiles
@@ -80,6 +80,21 @@ export class BackgroundRenderLayer extends IRenderLayer {
80
80
 
81
81
  frameState.commandList = preCommandList
82
82
  }
83
+ if (this.primitive && this.paintNeedsUpdate) {
84
+ const style = this.style,
85
+ tile = this.tile
86
+ const color = style.convertColor(
87
+ style.paint.getDataConstValue('background-color', tile.z)
88
+ )
89
+ const opacity = style.paint.getDataConstValue(
90
+ 'background-opacity',
91
+ tile.z
92
+ )
93
+ color.alpha *= opacity
94
+ this.primitive.appearance.material.uniforms.color = color
95
+
96
+ this.paintNeedsUpdate = false
97
+ }
83
98
  }
84
99
 
85
100
  destroy() {
@@ -10,7 +10,36 @@ export class FillRenderLayer extends IRenderLayer {
10
10
  */
11
11
  update(frameState, tileset) {
12
12
  //可以在这里实现同步样式,动态更新图层颜色等样式
13
+ if (this.paintNeedsUpdate) {
14
+ const style = this.style,
15
+ tile = this.tile,
16
+ batchTable = this._batchTable
13
17
 
18
+ for (const feature of this.features) {
19
+ const fillColor = style.convertColor(
20
+ style.paint.getDataValue('fill-color', tile.z, feature)
21
+ )
22
+ const fillOpacity = style.paint.getDataValue(
23
+ 'fill-opacity',
24
+ tile.z,
25
+ feature
26
+ )
27
+ feature.fillColor = fillColor
28
+ feature.fillOpacity = fillOpacity
29
+
30
+ const batchId = feature.batchId
31
+ const colorBytes = fillColor.toBytes()
32
+ colorBytes[3] = Math.floor(colorBytes[3] * fillOpacity)
33
+ batchTable.setBatchedAttribute(batchId, 0, {
34
+ x: colorBytes[0],
35
+ y: colorBytes[1],
36
+ z: colorBytes[2],
37
+ w: colorBytes[3]
38
+ })
39
+ }
40
+
41
+ this.paintNeedsUpdate = false
42
+ }
14
43
  super.update(frameState, tileset)
15
44
  }
16
45
  }
@@ -82,6 +82,10 @@ export class IRenderLayer {
82
82
  * @type {'none'|'done'|'error'}
83
83
  */
84
84
  this.state = 'none'
85
+ /**
86
+ * 绘制(paint)属性版本号,用于监听绘制属性变化
87
+ */
88
+ this.paintVersion = styleLayer.paintVersion
85
89
  }
86
90
 
87
91
  get id() {
@@ -92,6 +96,15 @@ export class IRenderLayer {
92
96
  return this.style.type
93
97
  }
94
98
 
99
+ get paintNeedsUpdate() {
100
+ return this.paintVersion !== this.style.paintVersion
101
+ }
102
+ set paintNeedsUpdate(val) {
103
+ if (!val) {
104
+ this.paintVersion = this.style.paintVersion
105
+ }
106
+ }
107
+
95
108
  /**
96
109
  * 更渲染图层,可在该方法内实现绘图命令构建、动态样式更新等图层渲染准备相关功能。该方法可以被子类重写或复用
97
110
  * @param {Cesium.FrameState} frameState
@@ -5,6 +5,7 @@ import { IRenderLayer } from './IRenderLayer'
5
5
  import { registerRenderLayer } from './registerRenderLayer'
6
6
  import { VectorTileLOD } from '../VectorTileLOD'
7
7
  import { LineLayerVisualizer } from './visualizers/LineLayerVisualizer'
8
+ import { warnOnce } from 'maplibre-gl/src/util/util'
8
9
 
9
10
  export class LineRenderLayer extends IRenderLayer {
10
11
  /**
@@ -91,7 +92,49 @@ export class LineRenderLayer extends IRenderLayer {
91
92
  // }
92
93
  // if (this.primitive && this.primitive.length) {
93
94
  // this.primitive.update(frameState)
95
+ // this._batchTable = primitive._batchTable
94
96
  // }
97
+ if (this.paintNeedsUpdate) {
98
+ const style = this.style,
99
+ tile = this.tile,
100
+ batchTable = this._batchTable
101
+
102
+ for (const feature of this.features) {
103
+ const lineWidth = style.paint.getDataValue(
104
+ 'line-width',
105
+ tile.z,
106
+ feature
107
+ )
108
+ const lineColor = style.convertColor(
109
+ style.paint.getDataValue('line-color', tile.z, feature)
110
+ )
111
+ const lineOpacity = style.paint.getDataValue(
112
+ 'line-opacity',
113
+ tile.z,
114
+ feature
115
+ )
116
+
117
+ if (lineWidth !== feature.lineWidth) {
118
+ warnOnce('不支持动态修改 line 图层样式属性:line-width')
119
+ }
120
+
121
+ feature.lineColor = lineColor
122
+ feature.lineOpacity = lineOpacity
123
+ feature.lineWidth = lineWidth
124
+
125
+ const batchId = feature.batchId
126
+ const colorBytes = lineColor.toBytes()
127
+ colorBytes[3] = Math.floor(colorBytes[3] * lineOpacity)
128
+ batchTable.setBatchedAttribute(batchId, 0, {
129
+ x: colorBytes[0],
130
+ y: colorBytes[1],
131
+ z: colorBytes[2],
132
+ w: colorBytes[3]
133
+ })
134
+ }
135
+
136
+ this.paintNeedsUpdate = false
137
+ }
95
138
  super.update(frameState, tileset)
96
139
  }
97
140
 
@@ -22,6 +22,41 @@ export class SymbolRenderLayer extends IRenderLayer {
22
22
  */
23
23
  update(frameState, tileset) {
24
24
  //TODO:动态更新符号样式
25
+ if (this.paintNeedsUpdate) {
26
+ const style = this.style,
27
+ tile = this.tile
28
+
29
+ for (let i = 0; i < this.features.length; i++) {
30
+ const feature = this.features[i]
31
+ const label = this.labels[i]
32
+
33
+ const textColor = style.convertColor(
34
+ style.paint.getDataValue('text-color', tile.z, feature)
35
+ )
36
+ const outlineColor = style.convertColor(
37
+ style.paint.getDataValue('text-halo-color', tile.z, feature)
38
+ )
39
+ const outlineWidth = style.paint.getDataValue(
40
+ 'text-halo-width',
41
+ tile.z,
42
+ feature
43
+ )
44
+
45
+ feature.textColor = textColor
46
+ feature.outlineColor = outlineColor
47
+ feature.outlineWidth = outlineWidth
48
+
49
+ label.fillColor = textColor
50
+ label.style = outlineWidth && Cesium.LabelStyle.FILL_AND_OUTLINE
51
+ label.outlineWidth = outlineWidth * feature.textSize
52
+ label.outlineColor = outlineColor
53
+
54
+ label._baseFillColor = label.fillColor.clone()
55
+ label._baseOutlineColor = label.outlineColor.clone()
56
+ }
57
+
58
+ this.paintNeedsUpdate = false
59
+ }
25
60
 
26
61
  super.update(frameState, tileset)
27
62
  }
@@ -92,6 +92,7 @@ export class FillLayerVisualizer extends ILayerVisualizer {
92
92
  }
93
93
 
94
94
  scope.addFeature(fillFeature, granularity)
95
+ layer.features.push(fillFeature)
95
96
 
96
97
  featureId++
97
98
  }
@@ -427,6 +428,9 @@ void main()
427
428
  */
428
429
  onBatchTableCreated(batchTable) {
429
430
  this._batchTable = batchTable
431
+ for (const layer of this.layers) {
432
+ layer._batchTable = batchTable
433
+ }
430
434
  }
431
435
 
432
436
  /**
@@ -511,6 +515,9 @@ void main()
511
515
  if (err.stack) console.trace(err.stack)
512
516
  else console.error(err)
513
517
  return
518
+ } finally {
519
+ //恢复系统的 commandList
520
+ frameState.commandList = preCommandList
514
521
  }
515
522
 
516
523
  //使用合批后的 drawCommand 创建副本,为渲染图层分配 drawCommand
@@ -522,10 +529,12 @@ void main()
522
529
  this.setState('error')
523
530
  }
524
531
 
525
- //恢复系统的 commandList
526
- frameState.commandList = preCommandList
527
532
  this.geometryInstances = []
528
533
  }
534
+
535
+ if (this._batchTable && this._batchTable._batchValuesDirty) {
536
+ this._batchTable.update(frameState)
537
+ }
529
538
  }
530
539
 
531
540
  destroy() {
@@ -92,6 +92,7 @@ export class LineLayerVisualizer extends ILayerVisualizer {
92
92
  }
93
93
 
94
94
  scope.addFeature(lineFeature)
95
+ layer.features.push(lineFeature)
95
96
 
96
97
  featureId++
97
98
  }
@@ -560,6 +561,9 @@ czm_material czm_getMaterial(czm_materialInput materialInput)
560
561
  */
561
562
  onBatchTableCreated(batchTable) {
562
563
  this._batchTable = batchTable
564
+ for (const layer of this.layers) {
565
+ layer._batchTable = batchTable
566
+ }
563
567
  }
564
568
 
565
569
  /**
@@ -667,6 +671,9 @@ czm_material czm_getMaterial(czm_materialInput materialInput)
667
671
  if (err.stack) console.trace(err.stack)
668
672
  else console.error(err)
669
673
  return
674
+ } finally {
675
+ //恢复系统的 commandList
676
+ frameState.commandList = preCommandList
670
677
  }
671
678
 
672
679
  //使用合批后的 drawCommand 创建副本,为渲染图层分配 drawCommand
@@ -678,14 +685,16 @@ czm_material czm_getMaterial(czm_materialInput materialInput)
678
685
  this.setState('error')
679
686
  }
680
687
 
681
- //恢复系统的 commandList
682
- frameState.commandList = preCommandList
683
688
  this.geometryInstances = []
684
689
  }
685
690
 
686
691
  if (this.primitive && frameState.camera.pitch > -1.309) {
687
692
  warnOnce('line图层:不支持透视,建议保持相机俯仰角(pitch)小于 -75 度')
688
693
  }
694
+
695
+ if (this._batchTable && this._batchTable._batchValuesDirty) {
696
+ this._batchTable.update(frameState)
697
+ }
689
698
  }
690
699
 
691
700
  destroy() {
@@ -82,7 +82,8 @@ export class SymbolLayerVisualizer extends ILayerVisualizer {
82
82
  outlineWidth,
83
83
  outlineColor,
84
84
  textOffset,
85
- textOrigin
85
+ textOrigin,
86
+ properties
86
87
  ) {
87
88
  if (
88
89
  !Cesium.Rectangle.contains(
@@ -116,6 +117,17 @@ export class SymbolLayerVisualizer extends ILayerVisualizer {
116
117
  label.batchId = labels.length
117
118
  labels.push(label)
118
119
  layer.labels.push(label)
120
+ layer.features.push({
121
+ text,
122
+ font,
123
+ textSize,
124
+ textColor,
125
+ outlineWidth,
126
+ outlineColor,
127
+ textOffset,
128
+ textOrigin,
129
+ properties
130
+ })
119
131
  }
120
132
 
121
133
  for (const sourceFeature of features) {
@@ -229,7 +241,8 @@ export class SymbolLayerVisualizer extends ILayerVisualizer {
229
241
  outlineWidth,
230
242
  outlineColor,
231
243
  textOffset,
232
- textOrigin
244
+ textOrigin,
245
+ properties
233
246
  )
234
247
  } else if (geometryType == 'MultiPoint') {
235
248
  coordinates.forEach(coord => {
@@ -242,7 +255,8 @@ export class SymbolLayerVisualizer extends ILayerVisualizer {
242
255
  outlineWidth,
243
256
  outlineColor,
244
257
  textOffset,
245
- textOrigin
258
+ textOrigin,
259
+ properties
246
260
  )
247
261
  })
248
262
  } else {
@@ -21,6 +21,33 @@ export class StyleLayer {
21
21
  if (layer.filter) {
22
22
  this.filter = featureFilter(layer.filter)
23
23
  }
24
+ this.paintVersion = 0
25
+ }
26
+
27
+ setLayoutProperty(name, value) {
28
+ return this.layout.setProperty(name, value)
29
+ }
30
+
31
+ setPaintProperty(name, value) {
32
+ const changed = this.paint.setProperty(name, value)
33
+ if (changed) {
34
+ this.paintVersion++
35
+ }
36
+ return changed
37
+ }
38
+
39
+ setFilter(filter) {
40
+ if (!filter) {
41
+ const changed = Cesium.defined(this.filter)
42
+ this.filter = null
43
+ delete this.data.filter
44
+ return changed
45
+ } else if (JSON.stringify(this.data.filter) !== JSON.stringify(filter)) {
46
+ this.data.filter = filter
47
+ this.filter = featureFilter(filter)
48
+ return true
49
+ }
50
+ return false
24
51
  }
25
52
 
26
53
  /**
@@ -1,10 +1,12 @@
1
1
  import { expression, latest } from '@maplibre/maplibre-gl-style-spec'
2
+ import { warnOnce } from 'maplibre-gl/src/util/util'
2
3
 
3
4
  export class StyleLayerProperties {
4
5
  constructor(groupName, styleProperties = {}) {
5
6
  this.data = styleProperties
6
7
  /**@type {Map<string,import('@maplibre/maplibre-gl-style-spec').StylePropertyExpression>} */
7
8
  this.props = new Map()
9
+ this.groupName = groupName
8
10
 
9
11
  const groupReference = latest[groupName]
10
12
  for (const key in groupReference) {
@@ -20,6 +22,26 @@ export class StyleLayerProperties {
20
22
  }
21
23
  }
22
24
 
25
+ setProperty(name, value) {
26
+ const groupReference = latest[this.groupName]
27
+ if (Object.hasOwnProperty.call(groupReference, name)) {
28
+ const reference = groupReference[name]
29
+ const oldValue = this.data[name]
30
+ if (oldValue === value) {
31
+ return false
32
+ }
33
+ const property = expression.normalizePropertyExpression(
34
+ !Cesium.defined(value) ? reference.default : value,
35
+ reference
36
+ )
37
+ this.props.set(name, property)
38
+ return true
39
+ } else {
40
+ warnOnce(`maplibre样式规范不支持属性:${this.groupName}.${name}`)
41
+ return false
42
+ }
43
+ }
44
+
23
45
  /**
24
46
  * Replace tokens in a string template with values in an object
25
47
  *
@@ -704,34 +704,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
704
704
 
705
705
  ---
706
706
 
707
- Name: earcut
708
- Version: 3.0.2
709
- License: ISC
710
- Private: false
711
- Description: The fastest and smallest JavaScript polygon triangulation library for your WebGL apps
712
- Repository: git://github.com/mapbox/earcut.git
713
- Author: Vladimir Agafonkin
714
- License Copyright:
715
- ===
716
-
717
- ISC License
718
-
719
- Copyright (c) 2024, Mapbox
720
-
721
- Permission to use, copy, modify, and/or distribute this software for any purpose
722
- with or without fee is hereby granted, provided that the above copyright notice
723
- and this permission notice appear in all copies.
724
-
725
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
726
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
727
- FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
728
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
729
- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
730
- TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
731
- THIS SOFTWARE.
732
-
733
- ---
734
-
735
707
  Name: @mapbox/unitbezier
736
708
  Version: 0.0.1
737
709
  License: BSD-2-Clause
@@ -769,4 +741,32 @@ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
769
741
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
770
742
 
771
743
  Ported from Webkit
772
- http://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/UnitBezier.h
744
+ http://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/UnitBezier.h
745
+
746
+ ---
747
+
748
+ Name: earcut
749
+ Version: 3.0.2
750
+ License: ISC
751
+ Private: false
752
+ Description: The fastest and smallest JavaScript polygon triangulation library for your WebGL apps
753
+ Repository: git://github.com/mapbox/earcut.git
754
+ Author: Vladimir Agafonkin
755
+ License Copyright:
756
+ ===
757
+
758
+ ISC License
759
+
760
+ Copyright (c) 2024, Mapbox
761
+
762
+ Permission to use, copy, modify, and/or distribute this software for any purpose
763
+ with or without fee is hereby granted, provided that the above copyright notice
764
+ and this permission notice appear in all copies.
765
+
766
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
767
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
768
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
769
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
770
+ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
771
+ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
772
+ THIS SOFTWARE.