@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
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Worker 内样式求值:paint 表达式求值、颜色转 [r,g,b,a] 字节。
3
+ * 不依赖 Cesium,仅使用 @maplibre/maplibre-gl-style-spec。
4
+ */
5
+ import { expression, latest } from '@maplibre/maplibre-gl-style-spec'
6
+
7
+ const paintFillRef = latest.paint_fill
8
+ const paintLineRef = latest.paint_line
9
+ const layoutSymbolRef = latest.layout_symbol
10
+ const paintSymbolRef = latest.paint_symbol
11
+
12
+ /**
13
+ * 构建图层 paint 的表达式 Map(仅包含需要的 key)
14
+ * @param {object} paint - 原始 paint 对象
15
+ * @param {object} groupRef - latest.paint_fill 或 paint_line
16
+ * @returns {Map<string, object>}
17
+ */
18
+ function buildPaintProps(paint, groupRef) {
19
+ const props = new Map()
20
+ if (!paint || !groupRef) return props
21
+ for (const key of Object.keys(groupRef)) {
22
+ const reference = groupRef[key]
23
+ const value = paint[key]
24
+ const property = expression.normalizePropertyExpression(
25
+ value === undefined ? reference.default : value,
26
+ reference
27
+ )
28
+ props.set(key, property)
29
+ }
30
+ return props
31
+ }
32
+
33
+ /**
34
+ * 求值 paint 属性
35
+ * @param {Map<string, object>} props
36
+ * @param {string} name
37
+ * @param {number} zoom
38
+ * @param {object} feature
39
+ * @returns {*}
40
+ */
41
+ function getDataValue(props, name, zoom, feature) {
42
+ const expr = props.get(name)
43
+ return expr ? expr.evaluate({ zoom }, feature) : undefined
44
+ }
45
+
46
+ /**
47
+ * Fill 图层 paint 求值(fill-color, fill-opacity)
48
+ * @param {object} styleLayer - 可序列化的 styleLayer(含 paint)
49
+ * @param {number} zoom
50
+ * @param {object} feature
51
+ * @returns {{ fillColor: { r, g, b, a }, fillOpacity: number }}
52
+ */
53
+ export function evaluateFillPaint(styleLayer, zoom, feature) {
54
+ const props = buildPaintProps(styleLayer.paint, paintFillRef)
55
+ const fillColor = getDataValue(props, 'fill-color', zoom, feature)
56
+ const fillOpacity = getDataValue(props, 'fill-opacity', zoom, feature) ?? 1
57
+ return {
58
+ fillColor: fillColor || { r: 0, g: 0, b: 0, a: 1 },
59
+ fillOpacity
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Line 图层 paint 求值(line-width, line-color, line-opacity)
65
+ * @param {object} styleLayer
66
+ * @param {number} zoom
67
+ * @param {object} feature
68
+ * @returns {{ lineWidth: number, lineColor: object, lineOpacity: number }}
69
+ */
70
+ export function evaluateLinePaint(styleLayer, zoom, feature) {
71
+ const props = buildPaintProps(styleLayer.paint, paintLineRef)
72
+ const lineWidth = getDataValue(props, 'line-width', zoom, feature) ?? 1
73
+ const lineColor = getDataValue(props, 'line-color', zoom, feature) || {
74
+ r: 0,
75
+ g: 0,
76
+ b: 0,
77
+ a: 1
78
+ }
79
+ const lineOpacity = getDataValue(props, 'line-opacity', zoom, feature) ?? 1
80
+ return { lineWidth, lineColor, lineOpacity }
81
+ }
82
+
83
+ /**
84
+ * maplibre Color (r,g,b,a 0-1) → Uint8Array [r,g,b,a],与主线程 StyleLayer.convertColor 一致(premultiply 逆处理)
85
+ * @param {object} c - { r, g, b, a }
86
+ * @param {number} [opacity=1] - 额外透明度(如 fillOpacity/lineOpacity)
87
+ * @returns {Uint8Array}
88
+ */
89
+ export function colorToBytes(c, opacity = 1) {
90
+ const a = c.a != null ? c.a : 1
91
+ const alphaScalar = a > 0 ? 1 / a : 1
92
+ const out = new Uint8Array(4)
93
+ out[0] = Math.round((c.r != null ? c.r : 0) * alphaScalar * 255)
94
+ out[1] = Math.round((c.g != null ? c.g : 0) * alphaScalar * 255)
95
+ out[2] = Math.round((c.b != null ? c.b : 0) * alphaScalar * 255)
96
+ out[3] = Math.floor(a * opacity * 255)
97
+ return out
98
+ }
99
+
100
+ /**
101
+ * 构建 layout/paint 的表达式 Map(symbol 用)
102
+ */
103
+ function buildProps(styleProperties, groupRef) {
104
+ const props = new Map()
105
+ if (!styleProperties || !groupRef) return props
106
+ for (const key of Object.keys(groupRef)) {
107
+ const reference = groupRef[key]
108
+ const value = styleProperties[key]
109
+ const property = expression.normalizePropertyExpression(
110
+ value === undefined ? reference.default : value,
111
+ reference
112
+ )
113
+ props.set(key, property)
114
+ }
115
+ return props
116
+ }
117
+
118
+ /**
119
+ * Symbol 图层 layout 求值(text-field, text-font, text-size, text-anchor, text-offset 等)
120
+ */
121
+ export function evaluateSymbolLayout(styleLayer, zoom, feature) {
122
+ const layout = styleLayer.layout || {}
123
+ const props = buildProps(layout, layoutSymbolRef)
124
+ const get = name => getDataValue(props, name, zoom, feature)
125
+ const textField = get('text-field')
126
+ let text = textField
127
+ if (typeof text === 'string') {
128
+ text = resolveTokens(feature.properties || {}, text)
129
+ } else if (text && text.sections) {
130
+ for (const section of text.sections) {
131
+ section.text = resolveTokens(feature.properties || {}, section.text)
132
+ }
133
+ text = text.toString()
134
+ }
135
+ const textTransform = get('text-transform')
136
+ if (textTransform === 'uppercase') text = String(text).toUpperCase()
137
+ else if (textTransform === 'lowercase') text = String(text).toLowerCase()
138
+ return {
139
+ text: text || '',
140
+ font: get('text-font') || 'Open Sans Regular, Arial Unicode MS Regular',
141
+ textSize: get('text-size') ?? 16,
142
+ textAnchor: get('text-anchor') || 'center',
143
+ textOffset: get('text-offset') || [0, 0]
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Symbol 图层 paint 求值(text-color, text-halo-color, text-halo-width)
149
+ */
150
+ export function evaluateSymbolPaint(styleLayer, zoom, feature) {
151
+ const paint = styleLayer.paint || {}
152
+ const props = buildProps(paint, paintSymbolRef)
153
+ const get = name => getDataValue(props, name, zoom, feature)
154
+ const textColor = get('text-color') || { r: 0, g: 0, b: 0, a: 1 }
155
+ const haloColor = get('text-halo-color') || { r: 0, g: 0, b: 0, a: 1 }
156
+ const haloWidth = get('text-halo-width') ?? 0
157
+ return {
158
+ textColor,
159
+ outlineColor: haloColor,
160
+ outlineWidth: haloWidth
161
+ }
162
+ }
163
+
164
+ function resolveTokens(properties, text) {
165
+ return String(text).replace(/\{([^{}]+)\}/g, (match, key) =>
166
+ properties && key in properties ? String(properties[key]) : ''
167
+ )
168
+ }
package/benchmark.html ADDED
@@ -0,0 +1,148 @@
1
+ <html lang="zh-cn">
2
+ <head>
3
+ <meta charset="UTF-8" />
4
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
5
+ <title>Worker 与主线程性能对比</title>
6
+ <link
7
+ rel="stylesheet"
8
+ href="./node_modules/cesium/Build/Cesium/Widgets/widgets.css"
9
+ />
10
+ <script src="./node_modules/cesium/Build/CesiumUnminified/Cesium.js"></script>
11
+ <style>
12
+ html,
13
+ body {
14
+ margin: 0;
15
+ padding: 0;
16
+ font-family: sans-serif;
17
+ }
18
+ #toolbar {
19
+ position: fixed;
20
+ top: 0;
21
+ left: 0;
22
+ right: 0;
23
+ padding: 8px 12px;
24
+ background: #1a1a2e;
25
+ color: #eee;
26
+ z-index: 100;
27
+ display: flex;
28
+ align-items: center;
29
+ gap: 12px;
30
+ }
31
+ #toolbar button {
32
+ padding: 8px 16px;
33
+ background: #16213e;
34
+ border: 1px solid #0f3460;
35
+ color: #eee;
36
+ cursor: pointer;
37
+ border-radius: 4px;
38
+ }
39
+ #toolbar button:hover {
40
+ background: #0f3460;
41
+ }
42
+ #bench-row {
43
+ position: fixed;
44
+ top: 48px;
45
+ left: 0;
46
+ right: 0;
47
+ bottom: 200px;
48
+ display: flex;
49
+ gap: 4px;
50
+ }
51
+ .viewer-half {
52
+ flex: 1;
53
+ position: relative;
54
+ min-width: 0;
55
+ }
56
+ .viewer-half .label {
57
+ position: absolute;
58
+ top: 8px;
59
+ left: 8px;
60
+ padding: 4px 8px;
61
+ background: rgba(0, 0, 0, 0.7);
62
+ color: #fff;
63
+ font-size: 12px;
64
+ z-index: 10;
65
+ border-radius: 4px;
66
+ }
67
+ #viewer-main,
68
+ #viewer-worker {
69
+ width: 100%;
70
+ height: 100%;
71
+ }
72
+ #report {
73
+ position: fixed;
74
+ left: 0;
75
+ right: 0;
76
+ bottom: 0;
77
+ height: 200px;
78
+ padding: 12px;
79
+ overflow: auto;
80
+ background: #0d1117;
81
+ color: #c9d1d9;
82
+ font-size: 13px;
83
+ white-space: pre-wrap;
84
+ }
85
+ #report pre {
86
+ margin: 0;
87
+ }
88
+ </style>
89
+ </head>
90
+
91
+ <body>
92
+ <div id="toolbar">
93
+ <button type="button" id="run">运行测试</button>
94
+ <label
95
+ style="display: flex; align-items: center; gap: 6px; cursor: pointer"
96
+ >
97
+ <input type="checkbox" id="swapMode" />
98
+ <span>调换左右模式</span>
99
+ </label>
100
+ <span id="status"
101
+ >左侧=主线程,右侧=Worker。15 秒内各 100 个 Box(边长
102
+ 50000)加载与路径漫游。</span
103
+ >
104
+ </div>
105
+ <div id="bench-row">
106
+ <div class="viewer-half">
107
+ <span class="label" id="label-left">主线程</span>
108
+ <div id="viewer-main"></div>
109
+ </div>
110
+ <div class="viewer-half">
111
+ <span class="label" id="label-right">Worker</span>
112
+ <div id="viewer-worker"></div>
113
+ </div>
114
+ </div>
115
+ <div id="report" role="log">
116
+ 运行后此处显示左侧主线程与右侧 Worker 的帧时间(分开)及对比结果。
117
+ </div>
118
+ <script type="module">
119
+ import { runBenchmark } from './examples/benchmark.js'
120
+ const runBtn = document.getElementById('run')
121
+ const swapCheckbox = document.getElementById('swapMode')
122
+ const labelLeft = document.getElementById('label-left')
123
+ const labelRight = document.getElementById('label-right')
124
+ const status = document.getElementById('status')
125
+ const reportEl = document.getElementById('report')
126
+
127
+ function updateLabels() {
128
+ const swap = swapCheckbox.checked
129
+ labelLeft.textContent = swap ? 'Worker' : '主线程'
130
+ labelRight.textContent = swap ? '主线程' : 'Worker'
131
+ }
132
+ swapCheckbox.addEventListener('change', updateLabels)
133
+ updateLabels()
134
+
135
+ runBtn.addEventListener('click', async () => {
136
+ runBtn.disabled = true
137
+ const swap = swapCheckbox.checked
138
+ status.textContent = swap
139
+ ? '运行中:先左侧 Worker 15 秒,再右侧主线程 15 秒,结果分开并对比…'
140
+ : '运行中:先左侧主线程 15 秒,再右侧 Worker 15 秒,结果分开并对比…'
141
+ reportEl.textContent = ''
142
+ await runBenchmark(swap)
143
+ status.textContent = '测试完成。'
144
+ runBtn.disabled = false
145
+ })
146
+ </script>
147
+ </body>
148
+ </html>