@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.
- package/.gitattributes +11 -0
- package/.gitconfig +3 -0
- package/.husky/pre-commit +1 -0
- package/.prettierignore +5 -0
- package/.vscode/settings.json +25 -0
- package/LICENSE.md +203 -203
- package/README.md +202 -167
- package/Source/Cesium.d.ts +2692 -2691
- package/Source/VectorTileLOD.js +720 -532
- package/Source/VectorTileRenderList.js +70 -70
- package/Source/VectorTileset.js +473 -447
- package/Source/layers/BackgroundRenderLayer.js +91 -89
- package/Source/layers/FillRenderLayer.js +18 -18
- package/Source/layers/IRenderLayer.js +160 -152
- package/Source/layers/LineRenderLayer.js +104 -94
- package/Source/layers/SymbolRenderLayer.js +30 -31
- package/Source/layers/index.js +23 -16
- package/Source/layers/registerRenderLayer.js +24 -24
- package/Source/layers/visualizers/FillLayerVisualizer.js +542 -426
- package/Source/layers/visualizers/ILayerVisualizer.js +90 -94
- package/Source/layers/visualizers/LineLayerVisualizer.js +702 -571
- package/Source/layers/visualizers/SymbolLayerVisualizer.js +514 -244
- package/Source/sources/GeoJSONSource.js +53 -46
- package/Source/sources/ISource.js +39 -39
- package/Source/sources/VectorSource.js +94 -52
- package/Source/sources/granularitySettings.js +23 -20
- package/Source/sources/index.js +6 -11
- package/Source/sources/registerSource.js +17 -19
- package/Source/style/StyleLayer.js +43 -43
- package/Source/style/StyleLayerProperties.js +44 -43
- package/Source/style/index.js +2 -2
- package/Source/symbol/SymbolPlacements.js +117 -88
- package/Source/workers/VectorTileWorker.js +41 -0
- package/Source/workers/ellipsoid.js +47 -0
- package/Source/workers/processTileTask.js +329 -0
- package/Source/workers/styleEvaluator.js +168 -0
- package/benchmark.html +148 -0
- package/dist/cvt-gl-worker.js +9274 -0
- package/dist/cvt-gl-worker.js.map +1 -0
- package/dist/cvt-gl.js +2570 -2001
- package/dist/cvt-gl.js.map +1 -1
- package/dist/cvt-gl.min.js +3 -3
- package/dist/cvt-gl.min.js.map +1 -1
- package/eslint.config.mjs +58 -0
- package/index.js +9 -6
- package/mlt.html +26 -25
- package/package.json +64 -41
- package/prettier.config.mjs +30 -0
- package/vite.config.mjs +43 -0
- package/vite.worker.config.mjs +31 -0
- package/worker.html +26 -0
|
@@ -1,88 +1,117 @@
|
|
|
1
|
-
import { GridIndex } from 'maplibre-gl/src/symbol/grid_index'
|
|
2
|
-
import { IRenderLayer } from '../layers/IRenderLayer'
|
|
3
|
-
|
|
4
|
-
//参考 maplibre-gl/src/symbol/CollisionIndex.ts
|
|
5
|
-
|
|
6
|
-
// When a symbol crosses the edge that causes it to be included in
|
|
7
|
-
// collision detection, it will cause changes in the symbols around
|
|
8
|
-
// it. This constant specifies how many pixels to pad the edge of
|
|
9
|
-
// the viewport for collision detection so that the bulk of the changes
|
|
10
|
-
// occur offscreen. Making this constant greater increases label
|
|
11
|
-
// stability, but it's expensive.
|
|
12
|
-
const viewportPadding = 100
|
|
13
|
-
let scratchScreenSpacePosition = null
|
|
14
|
-
let scratchScreenSpaceBoundingBox = null
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* 基于 maplibre-gl GridIndex 实现符号碰撞检测(自动避让)。文字碰撞检测结果存放在 label 对象的扩展属性 vtPlaceable,该值为true表示文字可以显示到屏幕
|
|
18
|
-
*/
|
|
19
|
-
export class SymbolPlacements {
|
|
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
|
-
|
|
1
|
+
import { GridIndex } from 'maplibre-gl/src/symbol/grid_index'
|
|
2
|
+
import { IRenderLayer } from '../layers/IRenderLayer'
|
|
3
|
+
|
|
4
|
+
//参考 maplibre-gl/src/symbol/CollisionIndex.ts
|
|
5
|
+
|
|
6
|
+
// When a symbol crosses the edge that causes it to be included in
|
|
7
|
+
// collision detection, it will cause changes in the symbols around
|
|
8
|
+
// it. This constant specifies how many pixels to pad the edge of
|
|
9
|
+
// the viewport for collision detection so that the bulk of the changes
|
|
10
|
+
// occur offscreen. Making this constant greater increases label
|
|
11
|
+
// stability, but it's expensive.
|
|
12
|
+
const viewportPadding = 100
|
|
13
|
+
let scratchScreenSpacePosition = null
|
|
14
|
+
let scratchScreenSpaceBoundingBox = null
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 基于 maplibre-gl GridIndex 实现符号碰撞检测(自动避让)。文字碰撞检测结果存放在 label 对象的扩展属性 vtPlaceable,该值为true表示文字可以显示到屏幕
|
|
18
|
+
*/
|
|
19
|
+
export class SymbolPlacements {
|
|
20
|
+
constructor() {
|
|
21
|
+
scratchScreenSpacePosition = new Cesium.Cartesian2()
|
|
22
|
+
scratchScreenSpaceBoundingBox = new Cesium.BoundingRectangle()
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* @param {Cesium.FrameState} frameState
|
|
26
|
+
* @param {IRenderLayer[]} orderedRenderLayers
|
|
27
|
+
* @param {number} zoom
|
|
28
|
+
*/
|
|
29
|
+
update(frameState, orderedRenderLayers, zoom) {
|
|
30
|
+
const width = frameState.context.drawingBufferWidth / frameState.pixelRatio
|
|
31
|
+
const height =
|
|
32
|
+
frameState.context.drawingBufferHeight / frameState.pixelRatio
|
|
33
|
+
const scene = frameState.camera._scene
|
|
34
|
+
|
|
35
|
+
const grid = new GridIndex(
|
|
36
|
+
width + 2 * viewportPadding,
|
|
37
|
+
height + 2 * viewportPadding,
|
|
38
|
+
25
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
for (const layer of orderedRenderLayers) {
|
|
42
|
+
const style = layer.style
|
|
43
|
+
/**@type {Cesium.Label[]} */
|
|
44
|
+
const labels = layer.labels
|
|
45
|
+
if (
|
|
46
|
+
layer.type !== 'symbol' ||
|
|
47
|
+
layer.visibility === 'none' ||
|
|
48
|
+
zoom < style.minzoom ||
|
|
49
|
+
zoom >= style.maxzoom ||
|
|
50
|
+
!Cesium.defined(labels) ||
|
|
51
|
+
!labels.length
|
|
52
|
+
) {
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//layout属性取值时 getDataConstValue 的 zoom 参数取瓦片层级,而不是全局缩放层级
|
|
57
|
+
const textAllowOverlap = style.layout.getDataConstValue(
|
|
58
|
+
'text-allow-overlap',
|
|
59
|
+
layer.tile.z
|
|
60
|
+
)
|
|
61
|
+
const textOverlap = style.layout.getDataConstValue(
|
|
62
|
+
'text-overlap',
|
|
63
|
+
layer.tile.z
|
|
64
|
+
)
|
|
65
|
+
const textOverlapMode = getOverlapMode(textOverlap, textAllowOverlap)
|
|
66
|
+
const textPadding = style.layout.getDataConstValue(
|
|
67
|
+
'text-padding',
|
|
68
|
+
layer.tile.z
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
for (const label of labels) {
|
|
72
|
+
const position = label.computeScreenSpacePosition(
|
|
73
|
+
scene,
|
|
74
|
+
scratchScreenSpacePosition
|
|
75
|
+
)
|
|
76
|
+
if (!position) {
|
|
77
|
+
//可能未准备就绪,先标记为可以摆放,确保Cesium能生成文字
|
|
78
|
+
label.vtPlaceable = true
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
/**@type {Cesium.BoundingRectangle} */
|
|
82
|
+
const box = Cesium.Label.getScreenSpaceBoundingBox(
|
|
83
|
+
label,
|
|
84
|
+
position,
|
|
85
|
+
scratchScreenSpaceBoundingBox
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
const tlX = box.x - textPadding,
|
|
89
|
+
tlY = box.y - textPadding,
|
|
90
|
+
brX = tlX + box.width + textPadding,
|
|
91
|
+
brY = tlY + box.height + textPadding
|
|
92
|
+
if (!grid.hitTest(tlX, tlY, brX, brY, textOverlapMode, null)) {
|
|
93
|
+
//二维包围盒碰撞检测
|
|
94
|
+
const textKey = { overlapMode: textOverlapMode }
|
|
95
|
+
grid.insert(textKey, tlX, tlY, brX, brY)
|
|
96
|
+
label.vtPlaceable = true
|
|
97
|
+
} else {
|
|
98
|
+
label.vtPlaceable = false
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getOverlapMode(overlap, allowOverlap) {
|
|
106
|
+
let result = 'never'
|
|
107
|
+
|
|
108
|
+
if (overlap) {
|
|
109
|
+
// if -overlap is set, use it
|
|
110
|
+
result = overlap
|
|
111
|
+
} else if (allowOverlap) {
|
|
112
|
+
// fall back to -allow-overlap, with false='never', true='always'
|
|
113
|
+
result = 'always'
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return result
|
|
117
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector tile Web Worker: 解析、过滤、坐标转换、几何数据构建。
|
|
3
|
+
* 遵循 Cesium TaskProcessor 协议:收到 { id, parameters },回复 { id, result }, transferList。
|
|
4
|
+
*/
|
|
5
|
+
import { processTileTask } from './processTileTask.js'
|
|
6
|
+
|
|
7
|
+
self.onmessage = function (e) {
|
|
8
|
+
const { id, parameters } = e.data
|
|
9
|
+
const transferList = []
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const result = processTileTask(parameters)
|
|
13
|
+
collectTransferables(result, transferList)
|
|
14
|
+
self.postMessage({ id, result }, transferList)
|
|
15
|
+
} catch (err) {
|
|
16
|
+
self.postMessage({ id, result: { error: String(err.message || err) } }, [])
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 从 result 中收集可 transfer 的 ArrayBuffer,用于 postMessage 的 transferList
|
|
22
|
+
* @param {object} result - { fill: [{ batches: [...] }], line: [{ batches: [...] }], symbol: [...] }
|
|
23
|
+
* @param {ArrayBuffer[]} transferList
|
|
24
|
+
*/
|
|
25
|
+
function collectTransferables(result, transferList) {
|
|
26
|
+
if (!result) return
|
|
27
|
+
for (const key of ['fill', 'line']) {
|
|
28
|
+
const arr = result[key]
|
|
29
|
+
if (!Array.isArray(arr)) continue
|
|
30
|
+
for (const layer of arr) {
|
|
31
|
+
const batches = layer.batches
|
|
32
|
+
if (!Array.isArray(batches)) continue
|
|
33
|
+
for (const batch of batches) {
|
|
34
|
+
if (batch.positions?.buffer) transferList.push(batch.positions.buffer)
|
|
35
|
+
if (batch.normals?.buffer) transferList.push(batch.normals.buffer)
|
|
36
|
+
if (batch.st?.buffer) transferList.push(batch.st.buffer)
|
|
37
|
+
if (batch.indices?.buffer) transferList.push(batch.indices.buffer)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WGS84 椭球经纬度 → 世界坐标(不依赖 Cesium),供 Worker 内几何构建使用。
|
|
3
|
+
*/
|
|
4
|
+
const A = 6378137
|
|
5
|
+
const E2 = 6.694379990141316e-2 // 2*f - f*f, f = 1/298.257223563
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 经纬度(度) + 高度 → 笛卡尔坐标 [x, y, z]
|
|
9
|
+
* @param {number} lonDeg - 经度(度)
|
|
10
|
+
* @param {number} latDeg - 纬度(度)
|
|
11
|
+
* @param {number} height - 高度(米)
|
|
12
|
+
* @param {Float64Array} [out] - 可选,长度至少 3
|
|
13
|
+
* @returns {Float64Array}
|
|
14
|
+
*/
|
|
15
|
+
export function fromDegrees(lonDeg, latDeg, height, out) {
|
|
16
|
+
const lon = (lonDeg * Math.PI) / 180
|
|
17
|
+
const lat = (latDeg * Math.PI) / 180
|
|
18
|
+
const sinLat = Math.sin(lat)
|
|
19
|
+
const cosLat = Math.cos(lat)
|
|
20
|
+
const n = A / Math.sqrt(1 - E2 * sinLat * sinLat)
|
|
21
|
+
const x = (n + height) * cosLat * Math.cos(lon)
|
|
22
|
+
const y = (n + height) * cosLat * Math.sin(lon)
|
|
23
|
+
const z = (n * (1 - E2) + height) * sinLat
|
|
24
|
+
const result = out || new Float64Array(3)
|
|
25
|
+
result[0] = x
|
|
26
|
+
result[1] = y
|
|
27
|
+
result[2] = z
|
|
28
|
+
return result
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 向量归一化,写回原数组
|
|
33
|
+
* @param {Float64Array} v - 长度至少 3
|
|
34
|
+
* @returns {Float64Array}
|
|
35
|
+
*/
|
|
36
|
+
export function normalize(v) {
|
|
37
|
+
const x = v[0]
|
|
38
|
+
const y = v[1]
|
|
39
|
+
const z = v[2]
|
|
40
|
+
const len = Math.sqrt(x * x + y * y + z * z)
|
|
41
|
+
if (len > 0) {
|
|
42
|
+
v[0] = x / len
|
|
43
|
+
v[1] = y / len
|
|
44
|
+
v[2] = z / len
|
|
45
|
+
}
|
|
46
|
+
return v
|
|
47
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker 内瓦片任务:解析 PBF/MLT、按样式过滤要素、构建几何数据。
|
|
3
|
+
* 不依赖 Cesium/WebGL,仅使用 @mapbox/vector-tile、maplibre 纯 JS。
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
VectorTile,
|
|
7
|
+
VectorTileFeature,
|
|
8
|
+
classifyRings
|
|
9
|
+
} from '@mapbox/vector-tile'
|
|
10
|
+
import Pbf from 'pbf'
|
|
11
|
+
import { MLTVectorTile } from 'maplibre-gl/src/source/vector_tile_mlt'
|
|
12
|
+
import { featureFilter } from '@maplibre/maplibre-gl-style-spec'
|
|
13
|
+
import { EXTENT } from 'maplibre-gl/src/data/extent'
|
|
14
|
+
import { loadGeometry } from 'maplibre-gl/src/data/load_geometry'
|
|
15
|
+
import { subdividePolygon } from 'maplibre-gl/src/render/subdivision'
|
|
16
|
+
import { subdivideVertexLine } from 'maplibre-gl/src/render/subdivision'
|
|
17
|
+
import { granularitySettings } from '../sources/granularitySettings.js'
|
|
18
|
+
import { fromDegrees, normalize } from './ellipsoid.js'
|
|
19
|
+
import {
|
|
20
|
+
evaluateFillPaint,
|
|
21
|
+
evaluateLinePaint,
|
|
22
|
+
evaluateSymbolLayout,
|
|
23
|
+
evaluateSymbolPaint,
|
|
24
|
+
colorToBytes
|
|
25
|
+
} from './styleEvaluator.js'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 解析瓦片 buffer 为 VectorTile 或 MLTVectorTile
|
|
29
|
+
* @param {ArrayBuffer} buffer
|
|
30
|
+
* @param {string} encoding - 'mvt' | 'mlt'
|
|
31
|
+
* @returns {import('@mapbox/vector-tile').VectorTile|import('maplibre-gl/src/source/vector_tile_mlt').MLTVectorTile}
|
|
32
|
+
*/
|
|
33
|
+
function parseTile(buffer, encoding) {
|
|
34
|
+
if (encoding === 'mlt') {
|
|
35
|
+
return new MLTVectorTile(buffer)
|
|
36
|
+
}
|
|
37
|
+
return new VectorTile(new Pbf(buffer))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 处理单瓦片任务:解析 + 过滤;后续步骤在此扩展几何构建。
|
|
42
|
+
* @param {object} parameters
|
|
43
|
+
* @param {Record<string,{buffer:ArrayBuffer,encoding:string}>} parameters.sources
|
|
44
|
+
* @param {number} parameters.x
|
|
45
|
+
* @param {number} parameters.y
|
|
46
|
+
* @param {number} parameters.z
|
|
47
|
+
* @param {number} [parameters.extent]
|
|
48
|
+
* @param {Array<{id:string,type:string,source:string,sourceLayer?:string,filter?:object,paint?:object,layout?:object}>} parameters.styleLayers
|
|
49
|
+
* @returns {{ fill: Array, line: Array, symbol: Array, parsedSources?: object }}
|
|
50
|
+
*/
|
|
51
|
+
export function processTileTask(parameters) {
|
|
52
|
+
const {
|
|
53
|
+
sources = {},
|
|
54
|
+
x,
|
|
55
|
+
y,
|
|
56
|
+
z,
|
|
57
|
+
extent = EXTENT,
|
|
58
|
+
styleLayers = []
|
|
59
|
+
} = parameters
|
|
60
|
+
|
|
61
|
+
const parsedSources = {}
|
|
62
|
+
for (const sourceId in sources) {
|
|
63
|
+
const { buffer, encoding } = sources[sourceId]
|
|
64
|
+
if (buffer) {
|
|
65
|
+
parsedSources[sourceId] = parseTile(buffer, encoding || 'mvt')
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const result = { fill: [], line: [], symbol: [] }
|
|
70
|
+
|
|
71
|
+
for (const layerSpec of styleLayers) {
|
|
72
|
+
if (layerSpec.type === 'background') continue
|
|
73
|
+
const vt = parsedSources[layerSpec.source]
|
|
74
|
+
if (!vt) continue
|
|
75
|
+
const layerName = layerSpec.sourceLayer ?? layerSpec['source-layer']
|
|
76
|
+
const vectorLayer = vt.layers[layerName]
|
|
77
|
+
if (!vectorLayer) continue
|
|
78
|
+
|
|
79
|
+
const filter = layerSpec.filter ? featureFilter(layerSpec.filter) : null
|
|
80
|
+
const features = []
|
|
81
|
+
const featureCount = vectorLayer.length
|
|
82
|
+
for (let i = 0; i < featureCount; i++) {
|
|
83
|
+
const feature = vectorLayer.feature(i)
|
|
84
|
+
if (filter && !filter.filter({ zoom: z }, feature)) continue
|
|
85
|
+
features.push({ index: i, feature, layerSpec })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const type = layerSpec.type
|
|
89
|
+
if (type === 'fill' || type === 'line' || type === 'symbol') {
|
|
90
|
+
result[type].push({
|
|
91
|
+
layerId: layerSpec.id,
|
|
92
|
+
source: layerSpec.source,
|
|
93
|
+
sourceLayer: layerName,
|
|
94
|
+
styleLayer: layerSpec,
|
|
95
|
+
extent,
|
|
96
|
+
x,
|
|
97
|
+
y,
|
|
98
|
+
z,
|
|
99
|
+
features
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 几何构建在 Worker 内完成,只把可序列化数据 + transferable buffers 传回主线程;
|
|
105
|
+
// 此处先做解析+过滤,几何构建在 buildGeometryInWorker 中扩展
|
|
106
|
+
return buildGeometryResult(result, extent, x, y, z)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 瓦片坐标 → 经纬度(度),与 VectorTileLOD.transformPoint 一致
|
|
111
|
+
*/
|
|
112
|
+
function transformPoint(x, y, size, x0, y0, out) {
|
|
113
|
+
out[0] = ((x + x0) * 360) / size - 180
|
|
114
|
+
out[1] =
|
|
115
|
+
(360 / Math.PI) *
|
|
116
|
+
Math.atan(Math.exp((1 - ((y + y0) * 2) / size) * Math.PI)) -
|
|
117
|
+
90
|
|
118
|
+
return out
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 根据解析+过滤结果构建几何数据(positions/normals/indices 等),仅返回可 transfer 的数据
|
|
123
|
+
*/
|
|
124
|
+
function buildGeometryResult(layerResult, extent, x, y, z) {
|
|
125
|
+
const out = { fill: [], line: [], symbol: [] }
|
|
126
|
+
const size = extent * Math.pow(2, z)
|
|
127
|
+
const x0 = extent * x
|
|
128
|
+
const y0 = extent * y
|
|
129
|
+
const canonical = { x, y, z }
|
|
130
|
+
|
|
131
|
+
for (const item of layerResult.fill || []) {
|
|
132
|
+
const batches = buildFillBatches(item, extent, size, x0, y0, canonical)
|
|
133
|
+
out.fill.push({
|
|
134
|
+
layerId: item.layerId,
|
|
135
|
+
source: item.source,
|
|
136
|
+
sourceLayer: item.sourceLayer,
|
|
137
|
+
styleLayer: item.styleLayer,
|
|
138
|
+
batches,
|
|
139
|
+
firstBatchId: 0,
|
|
140
|
+
lastBatchId: batches.length - 1
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const item of layerResult.line || []) {
|
|
145
|
+
const batches = buildLineBatches(item, extent, size, x0, y0)
|
|
146
|
+
out.line.push({
|
|
147
|
+
layerId: item.layerId,
|
|
148
|
+
source: item.source,
|
|
149
|
+
sourceLayer: item.sourceLayer,
|
|
150
|
+
styleLayer: item.styleLayer,
|
|
151
|
+
batches,
|
|
152
|
+
firstBatchId: 0,
|
|
153
|
+
lastBatchId: batches.length - 1
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for (const item of layerResult.symbol || []) {
|
|
158
|
+
const placements = buildSymbolPlacements(item, extent, size, x0, y0)
|
|
159
|
+
out.symbol.push({
|
|
160
|
+
layerId: item.layerId,
|
|
161
|
+
source: item.source,
|
|
162
|
+
sourceLayer: item.sourceLayer,
|
|
163
|
+
styleLayer: item.styleLayer,
|
|
164
|
+
placements,
|
|
165
|
+
firstBatchId: 0,
|
|
166
|
+
lastBatchId: 0
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return out
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Symbol 图层:输出每个符号的 placement(coord, text, style 等),主线程用其创建 Cesium.Label
|
|
175
|
+
*/
|
|
176
|
+
function buildSymbolPlacements(item, extent, size, x0, y0) {
|
|
177
|
+
const { features, styleLayer, z } = item
|
|
178
|
+
const coordDeg = [0, 0]
|
|
179
|
+
const placements = []
|
|
180
|
+
|
|
181
|
+
for (const { feature } of features) {
|
|
182
|
+
const type = VectorTileFeature.types[feature.type]
|
|
183
|
+
if (type !== 'Point' && type !== 'Unknown') continue
|
|
184
|
+
const vtCoords = loadGeometry(feature)
|
|
185
|
+
if (!vtCoords.length || !vtCoords[0].length) continue
|
|
186
|
+
const layout = evaluateSymbolLayout(styleLayer, z, feature)
|
|
187
|
+
if (!layout.text) continue
|
|
188
|
+
const paint = evaluateSymbolPaint(styleLayer, z, feature)
|
|
189
|
+
const textColorBytes = colorToBytes(paint.textColor)
|
|
190
|
+
const outlineColorBytes = colorToBytes(paint.outlineColor)
|
|
191
|
+
const firstRing = vtCoords[0]
|
|
192
|
+
for (let pi = 0; pi < firstRing.length; pi++) {
|
|
193
|
+
const p = firstRing[pi]
|
|
194
|
+
transformPoint(p.x, p.y, size, x0, y0, coordDeg)
|
|
195
|
+
placements.push({
|
|
196
|
+
coord: [coordDeg[0], coordDeg[1]],
|
|
197
|
+
text: layout.text,
|
|
198
|
+
font: layout.font,
|
|
199
|
+
textSize: layout.textSize,
|
|
200
|
+
textColorBytes: Array.from(textColorBytes),
|
|
201
|
+
outlineWidth: paint.outlineWidth,
|
|
202
|
+
outlineColorBytes: Array.from(outlineColorBytes),
|
|
203
|
+
textOffset: layout.textOffset,
|
|
204
|
+
textAnchor: layout.textAnchor,
|
|
205
|
+
id: feature.id ?? feature.properties?.id ?? null,
|
|
206
|
+
properties: feature.properties || {}
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return placements
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Fill 图层几何:每个 polygon 一个 batch(positions, normals, st, indices, colorBytes)
|
|
215
|
+
*/
|
|
216
|
+
function buildFillBatches(item, extent, size, x0, y0, canonical) {
|
|
217
|
+
const { features, styleLayer, z } = item
|
|
218
|
+
const granularity =
|
|
219
|
+
granularitySettings.globe.line.getGranularityForZoomLevel(z) / 2
|
|
220
|
+
const coordDeg = [0, 0]
|
|
221
|
+
const posScratch = new Float64Array(3)
|
|
222
|
+
const batches = []
|
|
223
|
+
let batchId = 0
|
|
224
|
+
|
|
225
|
+
for (const { feature } of features) {
|
|
226
|
+
if (VectorTileFeature.types[feature.type] !== 'Polygon') continue
|
|
227
|
+
const paint = evaluateFillPaint(styleLayer, z, feature)
|
|
228
|
+
const fillColor = paint.fillColor
|
|
229
|
+
const fillOpacity = paint.fillOpacity
|
|
230
|
+
const colorBytes = colorToBytes(fillColor, fillOpacity)
|
|
231
|
+
|
|
232
|
+
const vtCoords = loadGeometry(feature)
|
|
233
|
+
const polygons = classifyRings(vtCoords)
|
|
234
|
+
for (const coordinates of polygons) {
|
|
235
|
+
if (coordinates.some(ring => ring.length < 3)) continue
|
|
236
|
+
const subdivisionRes = subdividePolygon(
|
|
237
|
+
coordinates,
|
|
238
|
+
canonical,
|
|
239
|
+
granularity,
|
|
240
|
+
false
|
|
241
|
+
)
|
|
242
|
+
const verticesFlattened = subdivisionRes.verticesFlattened
|
|
243
|
+
const vertCount = verticesFlattened.length / 2
|
|
244
|
+
const positions = new Float64Array(vertCount * 3)
|
|
245
|
+
const normals = new Float32Array(vertCount * 3)
|
|
246
|
+
const st = new Float32Array(vertCount * 2)
|
|
247
|
+
|
|
248
|
+
for (let i = 0, j = 0; i < verticesFlattened.length; i += 2, j++) {
|
|
249
|
+
const vx = verticesFlattened[i]
|
|
250
|
+
const vy = verticesFlattened[i + 1]
|
|
251
|
+
transformPoint(vx, vy, size, x0, y0, coordDeg)
|
|
252
|
+
fromDegrees(coordDeg[0], coordDeg[1], 0, posScratch)
|
|
253
|
+
positions[j * 3] = posScratch[0]
|
|
254
|
+
positions[j * 3 + 1] = posScratch[1]
|
|
255
|
+
positions[j * 3 + 2] = posScratch[2]
|
|
256
|
+
normalize(posScratch)
|
|
257
|
+
normals[j * 3] = posScratch[0]
|
|
258
|
+
normals[j * 3 + 1] = posScratch[1]
|
|
259
|
+
normals[j * 3 + 2] = posScratch[2]
|
|
260
|
+
st[j * 2] = vx / extent
|
|
261
|
+
st[j * 2 + 1] = vy / extent
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const indices = new (
|
|
265
|
+
vertCount > 65535
|
|
266
|
+
? Uint32Array
|
|
267
|
+
: vertCount > 255
|
|
268
|
+
? Uint16Array
|
|
269
|
+
: Uint8Array
|
|
270
|
+
)(subdivisionRes.indicesTriangles)
|
|
271
|
+
|
|
272
|
+
batches.push({
|
|
273
|
+
batchId: batchId++,
|
|
274
|
+
positions,
|
|
275
|
+
normals,
|
|
276
|
+
st,
|
|
277
|
+
indices,
|
|
278
|
+
colorBytes,
|
|
279
|
+
id: feature.id ?? feature.properties?.id ?? null,
|
|
280
|
+
properties: feature.properties || {}
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return batches
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Line 图层几何:每条线一个 batch(positions, colorBytes, lineWidth)
|
|
289
|
+
*/
|
|
290
|
+
function buildLineBatches(item, extent, size, x0, y0) {
|
|
291
|
+
const { features, styleLayer, z } = item
|
|
292
|
+
const granularity =
|
|
293
|
+
granularitySettings.globe.line.getGranularityForZoomLevel(z)
|
|
294
|
+
const coordDeg = [0, 0]
|
|
295
|
+
const posScratch = new Float64Array(3)
|
|
296
|
+
const batches = []
|
|
297
|
+
let batchId = 0
|
|
298
|
+
|
|
299
|
+
for (const { feature } of features) {
|
|
300
|
+
const featureType = VectorTileFeature.types[feature.type]
|
|
301
|
+
if (featureType === 'Point' || featureType === 'Unknown') continue
|
|
302
|
+
const paint = evaluateLinePaint(styleLayer, z, feature)
|
|
303
|
+
const colorBytes = colorToBytes(paint.lineColor, paint.lineOpacity)
|
|
304
|
+
|
|
305
|
+
const vtCoords = loadGeometry(feature)
|
|
306
|
+
for (let ri = 0; ri < vtCoords.length; ri++) {
|
|
307
|
+
const ring = subdivideVertexLine(vtCoords[ri], granularity, false)
|
|
308
|
+
if (ring.length < 2) continue
|
|
309
|
+
const positions = new Float64Array(ring.length * 3)
|
|
310
|
+
for (let i = 0; i < ring.length; i++) {
|
|
311
|
+
const p = ring[i]
|
|
312
|
+
transformPoint(p.x, p.y, size, x0, y0, coordDeg)
|
|
313
|
+
fromDegrees(coordDeg[0], coordDeg[1], 0, posScratch)
|
|
314
|
+
positions[i * 3] = posScratch[0]
|
|
315
|
+
positions[i * 3 + 1] = posScratch[1]
|
|
316
|
+
positions[i * 3 + 2] = posScratch[2]
|
|
317
|
+
}
|
|
318
|
+
batches.push({
|
|
319
|
+
batchId: batchId++,
|
|
320
|
+
positions,
|
|
321
|
+
colorBytes,
|
|
322
|
+
lineWidth: paint.lineWidth,
|
|
323
|
+
id: feature.id ?? feature.properties?.id ?? null,
|
|
324
|
+
properties: feature.properties || {}
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return batches
|
|
329
|
+
}
|