map-zero 0.1.0 → 0.2.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/CHANGELOG.md +14 -0
- package/README.md +31 -2
- package/docs/cesium.md +24 -9
- package/package.json +4 -3
- package/packages/cesium/package.json +6 -3
- package/packages/cesium/src/imagery-worker.js +604 -0
- package/packages/cesium/src/imagery.js +434 -0
- package/packages/cesium/src/index.js +199 -35
- package/packages/ol/package.json +1 -1
- package/packages/ol/src/index.js +349 -16
- package/src/3dtiles/b3dm.js +18 -2
- package/src/3dtiles/clipper-surfaces.js +121 -20
- package/src/3dtiles/export.js +298 -25
- package/src/3dtiles/extrude.js +78 -23
- package/src/3dtiles/flat.js +8 -20
- package/src/3dtiles/glb.js +78 -27
- package/src/3dtiles/gpkg-features.js +4 -4
- package/src/3dtiles/precision.js +47 -0
- package/src/cli.js +100 -2
- package/src/export-pmtiles.js +17 -16
- package/src/from-bbox.js +335 -0
- package/src/gpkg-read.js +15 -2
- package/src/html.js +28 -17
- package/src/manifest.js +1 -8
- package/src/mvt.js +35 -3
- package/src/package.js +343 -0
- package/src/server.js +38 -10
- package/src/style-command.js +1 -1
- package/src/style-filters.js +2 -3
- package/styles/presets/neon-dark-3d.json +0 -90
package/packages/ol/src/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import MVT from 'ol/format/MVT.js';
|
|
2
|
+
import TileLayer from 'ol/layer/Tile.js';
|
|
2
3
|
import WebGLVectorTileLayer from 'ol/layer/WebGLVectorTile.js';
|
|
3
4
|
import WebGLVectorTileLayerRenderer from 'ol/renderer/webgl/VectorTileLayer.js';
|
|
5
|
+
import ImageTileSource from 'ol/source/ImageTile.js';
|
|
4
6
|
import VectorTileSource from 'ol/source/VectorTile.js';
|
|
5
7
|
import { createXYZ } from 'ol/tilegrid.js';
|
|
6
8
|
import { PMTiles } from 'pmtiles';
|
|
@@ -21,6 +23,12 @@ let autoInstanceCounter = 0;
|
|
|
21
23
|
* style?: string | Record<string, unknown>,
|
|
22
24
|
* visibleLayers?: string[] | Set<string>,
|
|
23
25
|
* source?: 'auto' | 'pmtiles' | 'dynamic',
|
|
26
|
+
* renderMode?: 'vector' | 'raster-worker',
|
|
27
|
+
* workerUrl?: string | URL,
|
|
28
|
+
* rasterWorkerUrl?: string | URL,
|
|
29
|
+
* rasterPixelRatio?: number,
|
|
30
|
+
* overzoomLevels?: number,
|
|
31
|
+
* edgeGuardPixels?: number,
|
|
24
32
|
* apiBaseUrl?: string,
|
|
25
33
|
* zIndexBase?: number,
|
|
26
34
|
* onTileLoadStart?: () => void,
|
|
@@ -69,8 +77,6 @@ export async function loadMapZeroStyle(styleUrl) {
|
|
|
69
77
|
* }>}
|
|
70
78
|
*/
|
|
71
79
|
export async function createMapZeroOpenLayersLayers(options) {
|
|
72
|
-
patchWebGlVectorTileRenderer();
|
|
73
|
-
|
|
74
80
|
const manifestUrl = resolveUrl(options.manifestUrl, documentBaseUrl());
|
|
75
81
|
const manifestBaseUrl = new URL('.', manifestUrl).href;
|
|
76
82
|
const manifest = options.manifest ?? await loadMapZeroManifest(manifestUrl);
|
|
@@ -95,6 +101,12 @@ export async function createMapZeroOpenLayersLayers(options) {
|
|
|
95
101
|
onTileLoadError: options.onTileLoadError
|
|
96
102
|
};
|
|
97
103
|
|
|
104
|
+
if (options.renderMode === 'raster-worker') {
|
|
105
|
+
return createRasterWorkerController(context, options);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
patchWebGlVectorTileRenderer();
|
|
109
|
+
|
|
98
110
|
const source = createTileSource(context);
|
|
99
111
|
const layer = new WebGLVectorTileLayer({
|
|
100
112
|
source,
|
|
@@ -119,7 +131,7 @@ export async function createMapZeroOpenLayersLayers(options) {
|
|
|
119
131
|
applyLayerZIndex(layer, orderedLayers, styleDocument, options.zIndexBase);
|
|
120
132
|
if (labelController) {
|
|
121
133
|
tagOpenLayersLayer(labelController.layer, instanceId, orderedLayers.map((item) => item.id), 'labels');
|
|
122
|
-
labelController.layer.setZIndex(layer.getZIndex() +
|
|
134
|
+
labelController.layer.setZIndex(layer.getZIndex() + 1);
|
|
123
135
|
}
|
|
124
136
|
|
|
125
137
|
const refresh = () => {
|
|
@@ -165,6 +177,209 @@ export async function createMapZeroOpenLayersLayers(options) {
|
|
|
165
177
|
};
|
|
166
178
|
}
|
|
167
179
|
|
|
180
|
+
/**
|
|
181
|
+
* @param {{
|
|
182
|
+
* instanceId: string,
|
|
183
|
+
* manifest: Record<string, unknown>,
|
|
184
|
+
* manifestUrl: string,
|
|
185
|
+
* styleDocument: Record<string, unknown>,
|
|
186
|
+
* orderedLayers: Array<{ id: string, type?: string, style?: string }>,
|
|
187
|
+
* layerVisibility: Map<string, boolean>,
|
|
188
|
+
* layerOpacity: Map<string, number>
|
|
189
|
+
* }} context
|
|
190
|
+
* @param {MapZeroOpenLayersOptions} options
|
|
191
|
+
* @returns {{
|
|
192
|
+
* id: string,
|
|
193
|
+
* manifest: Record<string, unknown>,
|
|
194
|
+
* style: Record<string, unknown>,
|
|
195
|
+
* layers: TileLayer[],
|
|
196
|
+
* setVisible: (layerId: string, visible: boolean) => void,
|
|
197
|
+
* setOpacity: (layerId: string, opacity: number) => void,
|
|
198
|
+
* destroy: () => void
|
|
199
|
+
* }}
|
|
200
|
+
*/
|
|
201
|
+
function createRasterWorkerController(context, options) {
|
|
202
|
+
if (!isPmtilesManifest(context.manifest)) {
|
|
203
|
+
throw new Error('raster-worker render mode requires vector PMTiles');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const worker = new MapZeroRasterTileWorker({
|
|
207
|
+
manifest: context.manifest,
|
|
208
|
+
manifestUrl: context.manifestUrl,
|
|
209
|
+
styleDocument: context.styleDocument,
|
|
210
|
+
layers: context.orderedLayers.map((layer) => layer.id),
|
|
211
|
+
workerUrl: options.rasterWorkerUrl ?? options.workerUrl ?? new URL('@map-zero/cesium/imagery-worker.js', import.meta.url),
|
|
212
|
+
rasterPixelRatio: options.rasterPixelRatio,
|
|
213
|
+
overzoomLevels: options.overzoomLevels,
|
|
214
|
+
edgeGuardPixels: options.edgeGuardPixels
|
|
215
|
+
});
|
|
216
|
+
worker.setVisibleLayers(context.layerVisibility);
|
|
217
|
+
const range = pmtilesZoomRange(context.manifest);
|
|
218
|
+
const maxZoom = range.maxZoom + clampInteger(options.overzoomLevels ?? 0, 0, 4);
|
|
219
|
+
const rasterPixelRatio = clampNumber(options.rasterPixelRatio ?? 2, 1, 2);
|
|
220
|
+
const source = new ImageTileSource({
|
|
221
|
+
minZoom: range.minZoom,
|
|
222
|
+
maxZoom,
|
|
223
|
+
tileGrid: createXYZ({
|
|
224
|
+
minZoom: range.minZoom,
|
|
225
|
+
maxZoom,
|
|
226
|
+
tileSize: 512
|
|
227
|
+
}),
|
|
228
|
+
tileSize: 512,
|
|
229
|
+
transition: 0,
|
|
230
|
+
interpolate: true,
|
|
231
|
+
zDirection: preferNearestZoomLevel,
|
|
232
|
+
wrapX: false,
|
|
233
|
+
loader: (z, x, y) => worker.render(z, x, y)
|
|
234
|
+
});
|
|
235
|
+
source.getTilePixelRatio = () => rasterPixelRatio;
|
|
236
|
+
const layer = new TileLayer({
|
|
237
|
+
source,
|
|
238
|
+
cacheSize: 4096,
|
|
239
|
+
preload: 0,
|
|
240
|
+
useInterimTilesOnError: false
|
|
241
|
+
});
|
|
242
|
+
tagOpenLayersLayer(layer, context.instanceId, context.orderedLayers.map((item) => item.id), 'raster');
|
|
243
|
+
applyLayerZIndex(layer, context.orderedLayers, context.styleDocument, options.zIndexBase);
|
|
244
|
+
|
|
245
|
+
const refresh = () => {
|
|
246
|
+
worker.setVisibleLayers(context.layerVisibility);
|
|
247
|
+
source.clear();
|
|
248
|
+
layer.changed();
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
id: context.instanceId,
|
|
253
|
+
manifest: context.manifest,
|
|
254
|
+
style: context.styleDocument,
|
|
255
|
+
layers: [layer],
|
|
256
|
+
setVisible(layerId, visible) {
|
|
257
|
+
if (!context.layerVisibility.has(layerId)) {
|
|
258
|
+
throw new Error(`unknown map-zero layer: ${layerId}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
context.layerVisibility.set(layerId, Boolean(visible));
|
|
262
|
+
refresh();
|
|
263
|
+
},
|
|
264
|
+
setOpacity(layerId, opacity) {
|
|
265
|
+
if (!context.layerOpacity?.has?.(layerId)) {
|
|
266
|
+
throw new Error(`unknown map-zero layer: ${layerId}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
layer.setOpacity(clamp(Number(opacity), 0, 1));
|
|
270
|
+
},
|
|
271
|
+
destroy() {
|
|
272
|
+
source.clear();
|
|
273
|
+
worker.destroy();
|
|
274
|
+
layer.dispose();
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
class MapZeroRasterTileWorker {
|
|
280
|
+
/**
|
|
281
|
+
* @param {{
|
|
282
|
+
* manifest: Record<string, unknown>,
|
|
283
|
+
* manifestUrl: string,
|
|
284
|
+
* styleDocument: Record<string, unknown>,
|
|
285
|
+
* layers: string[],
|
|
286
|
+
* workerUrl: string | URL,
|
|
287
|
+
* rasterPixelRatio?: number,
|
|
288
|
+
* overzoomLevels?: number,
|
|
289
|
+
* edgeGuardPixels?: number
|
|
290
|
+
* }} options
|
|
291
|
+
*/
|
|
292
|
+
constructor(options) {
|
|
293
|
+
assertRasterWorkerSupport();
|
|
294
|
+
this.#rasterPixelRatio = clampNumber(options.rasterPixelRatio ?? 2, 1, 2);
|
|
295
|
+
this.worker = new Worker(options.workerUrl, { type: 'module' });
|
|
296
|
+
this.worker.addEventListener('message', (event) => this.#handleMessage(event.data));
|
|
297
|
+
this.worker.addEventListener('error', (event) => this.#rejectAll(new Error(event.message || 'map-zero OpenLayers raster worker failed')));
|
|
298
|
+
this.worker.postMessage({
|
|
299
|
+
type: 'init',
|
|
300
|
+
options: {
|
|
301
|
+
manifest: options.manifest,
|
|
302
|
+
manifestUrl: resolveUrl(options.manifestUrl, documentBaseUrl()),
|
|
303
|
+
styleDocument: options.styleDocument,
|
|
304
|
+
layers: options.layers,
|
|
305
|
+
tileSize: 512,
|
|
306
|
+
pixelRatio: this.#rasterPixelRatio,
|
|
307
|
+
sourceMaximumLevel: pmtilesZoomRange(options.manifest).maxZoom,
|
|
308
|
+
overzoomLevels: clampInteger(options.overzoomLevels ?? 0, 0, 4),
|
|
309
|
+
edgeGuardPixels: clampInteger(options.edgeGuardPixels ?? 0, 0, 8),
|
|
310
|
+
source: String(pmtilesInfo(options.manifest).url ?? 'tiles.pmtiles')
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* @param {number} z
|
|
317
|
+
* @param {number} x
|
|
318
|
+
* @param {number} y
|
|
319
|
+
* @returns {Promise<ImageBitmap | HTMLCanvasElement>}
|
|
320
|
+
*/
|
|
321
|
+
render(z, x, y) {
|
|
322
|
+
const id = this.#nextId++;
|
|
323
|
+
return new Promise((resolve, reject) => {
|
|
324
|
+
this.#pending.set(id, { resolve, reject });
|
|
325
|
+
this.worker.postMessage({ type: 'render', id, x, y, z });
|
|
326
|
+
}).catch(() => emptyCanvas(512 * this.#rasterPixelRatio, 512 * this.#rasterPixelRatio));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* @param {Map<string, boolean>} visibility
|
|
331
|
+
*/
|
|
332
|
+
setVisibleLayers(visibility) {
|
|
333
|
+
for (const [layerId, visible] of visibility) {
|
|
334
|
+
this.worker.postMessage({ type: 'visibility', layerId, visible });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
destroy() {
|
|
339
|
+
this.#rejectAll(new Error('map-zero OpenLayers raster worker destroyed'));
|
|
340
|
+
this.worker.terminate();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* @param {any} message
|
|
345
|
+
*/
|
|
346
|
+
#handleMessage(message) {
|
|
347
|
+
if (message?.type !== 'tile') {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const pending = this.#pending.get(message.id);
|
|
352
|
+
if (!pending) {
|
|
353
|
+
message.image?.close?.();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
this.#pending.delete(message.id);
|
|
358
|
+
if (message.error) {
|
|
359
|
+
pending.reject(new Error(message.error));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
pending.resolve(message.image ?? emptyCanvas(512, 512));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* @param {Error} error
|
|
367
|
+
*/
|
|
368
|
+
#rejectAll(error) {
|
|
369
|
+
for (const pending of this.#pending.values()) {
|
|
370
|
+
pending.reject(error);
|
|
371
|
+
}
|
|
372
|
+
this.#pending.clear();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** @type {Worker} */
|
|
376
|
+
worker;
|
|
377
|
+
#rasterPixelRatio = 2;
|
|
378
|
+
#nextId = 1;
|
|
379
|
+
/** @type {Map<number, { resolve: (image: ImageBitmap | HTMLCanvasElement) => void, reject: (error: Error) => void }>} */
|
|
380
|
+
#pending = new Map();
|
|
381
|
+
}
|
|
382
|
+
|
|
168
383
|
/**
|
|
169
384
|
* Add map-zero layers to an existing OpenLayers map.
|
|
170
385
|
*
|
|
@@ -255,7 +470,7 @@ async function loadStyleDocument(manifest, manifestBaseUrl, style) {
|
|
|
255
470
|
*/
|
|
256
471
|
function orderManifestLayers(manifest, styleDocument) {
|
|
257
472
|
const layers = Array.isArray(manifest.layers)
|
|
258
|
-
? /** @type {
|
|
473
|
+
? /** @type {string[]} */ (manifest.layers).map(manifestLayer)
|
|
259
474
|
: [];
|
|
260
475
|
const drawOrder = Array.isArray(styleDocument.drawOrder)
|
|
261
476
|
? /** @type {string[]} */ (styleDocument.drawOrder)
|
|
@@ -270,6 +485,29 @@ function orderManifestLayers(manifest, styleDocument) {
|
|
|
270
485
|
});
|
|
271
486
|
}
|
|
272
487
|
|
|
488
|
+
/**
|
|
489
|
+
* @param {string} layerId
|
|
490
|
+
* @returns {{ id: string, type?: string, style?: string }}
|
|
491
|
+
*/
|
|
492
|
+
function manifestLayer(layerId) {
|
|
493
|
+
return {
|
|
494
|
+
id: layerId,
|
|
495
|
+
type: layerType(layerId),
|
|
496
|
+
style: layerId
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* @param {string} layerId
|
|
502
|
+
* @returns {string}
|
|
503
|
+
*/
|
|
504
|
+
function layerType(layerId) {
|
|
505
|
+
if (layerId === 'buildings' || layerId === 'water' || layerId === 'landuse') return 'polygon';
|
|
506
|
+
if (layerId === 'pois') return 'point';
|
|
507
|
+
if (layerId === 'aip' || layerId === 'aviation') return 'mixed';
|
|
508
|
+
return 'line';
|
|
509
|
+
}
|
|
510
|
+
|
|
273
511
|
/**
|
|
274
512
|
* @param {Array<{ id: string, type?: string, style?: string }>} orderedLayers
|
|
275
513
|
* @param {Record<string, unknown>} styleDocument
|
|
@@ -295,18 +533,14 @@ function createLayerVisibility(orderedLayers, styleDocument, visibleLayers) {
|
|
|
295
533
|
* @param {number | undefined} zIndexBase
|
|
296
534
|
*/
|
|
297
535
|
function applyLayerZIndex(layer, orderedLayers, styleDocument, zIndexBase) {
|
|
298
|
-
|
|
299
|
-
const order = Number(getLayerRule(styleDocument, item).order);
|
|
300
|
-
return Number.isFinite(order) ? Math.max(max, order) : max;
|
|
301
|
-
}, 0);
|
|
302
|
-
layer.setZIndex((Number.isFinite(Number(zIndexBase)) ? Number(zIndexBase) : 0) + maxOrder);
|
|
536
|
+
layer.setZIndex(Number.isFinite(Number(zIndexBase)) ? Number(zIndexBase) : 0);
|
|
303
537
|
}
|
|
304
538
|
|
|
305
539
|
/**
|
|
306
540
|
* @param {unknown} layer
|
|
307
541
|
* @param {string} instanceId
|
|
308
542
|
* @param {string[]} layerIds
|
|
309
|
-
* @param {'geometry' | 'labels'} role
|
|
543
|
+
* @param {'geometry' | 'labels' | 'raster'} role
|
|
310
544
|
*/
|
|
311
545
|
function tagOpenLayersLayer(layer, instanceId, layerIds, role) {
|
|
312
546
|
const namespacedLayerIds = layerIds.map((layerId) => namespaceLayerId(instanceId, layerId));
|
|
@@ -465,7 +699,6 @@ function createTileUrlFunction(context) {
|
|
|
465
699
|
*/
|
|
466
700
|
function createPmtilesTileUrlFunction(context) {
|
|
467
701
|
const { minZoom, maxZoom } = pmtilesZoomRange(context.manifest);
|
|
468
|
-
const packageBbox = normalizeBbox(context.manifest.bbox);
|
|
469
702
|
|
|
470
703
|
return (tileCoord) => {
|
|
471
704
|
if (!tileCoord) {
|
|
@@ -477,10 +710,6 @@ function createPmtilesTileUrlFunction(context) {
|
|
|
477
710
|
return undefined;
|
|
478
711
|
}
|
|
479
712
|
|
|
480
|
-
if (packageBbox && !bboxIntersects(tileToBbox(z, x, y), packageBbox)) {
|
|
481
|
-
return undefined;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
713
|
if (activeLayerIdsForZoom(context.orderedLayers, context.styleDocument, context.layerVisibility, z).length === 0) {
|
|
485
714
|
return undefined;
|
|
486
715
|
}
|
|
@@ -710,7 +939,9 @@ function createWebGlStyles(context) {
|
|
|
710
939
|
const filter = createLayerFilter(layer.id, rule);
|
|
711
940
|
const styleParts = layer.id === 'roads'
|
|
712
941
|
? createRoadStyleRules(filter, rule, context.layerOpacity)
|
|
713
|
-
: layer.id === 'boundaries'
|
|
942
|
+
: layer.id === 'boundaries'
|
|
943
|
+
? createBoundaryStyleRules(filter, rule, context.layerOpacity)
|
|
944
|
+
: isAipLayer(layer.id)
|
|
714
945
|
? createGeometryAwareStyleRules(filter, rule, layer.id, context.layerOpacity)
|
|
715
946
|
: createLayerStyleRules(filter, rule, layer.type || 'line', layer.id, context.layerOpacity);
|
|
716
947
|
for (const style of styleParts) {
|
|
@@ -1021,6 +1252,45 @@ function createPropertyVisibilityFilter(filter, rule) {
|
|
|
1021
1252
|
return hidden.length > 0 ? ['all', filter, ...hidden] : filter;
|
|
1022
1253
|
}
|
|
1023
1254
|
|
|
1255
|
+
/**
|
|
1256
|
+
* @param {unknown[]} filter
|
|
1257
|
+
* @param {Record<string, unknown>} rule
|
|
1258
|
+
* @param {Map<string, number>} layerOpacity
|
|
1259
|
+
* @returns {Array<{ filter: unknown[], style: Record<string, unknown> }>}
|
|
1260
|
+
*/
|
|
1261
|
+
function createBoundaryStyleRules(filter, rule, layerOpacity) {
|
|
1262
|
+
const polygonFilter = ['all', filter, ['==', ['geometry-type'], 'Polygon']];
|
|
1263
|
+
const lineFilter = ['all', filter, ['==', ['geometry-type'], 'LineString']];
|
|
1264
|
+
const polygonRule = {
|
|
1265
|
+
...rule,
|
|
1266
|
+
stroke: null,
|
|
1267
|
+
strokeOpacity: 0,
|
|
1268
|
+
strokeWidth: 0,
|
|
1269
|
+
glow: {
|
|
1270
|
+
...rule.glow,
|
|
1271
|
+
enabled: false
|
|
1272
|
+
},
|
|
1273
|
+
casing: {
|
|
1274
|
+
...rule.casing,
|
|
1275
|
+
enabled: false
|
|
1276
|
+
},
|
|
1277
|
+
centerLine: {
|
|
1278
|
+
...rule.centerLine,
|
|
1279
|
+
enabled: false
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
const lineRule = {
|
|
1283
|
+
...rule,
|
|
1284
|
+
fill: null,
|
|
1285
|
+
fillOpacity: 0
|
|
1286
|
+
};
|
|
1287
|
+
|
|
1288
|
+
return [
|
|
1289
|
+
...createLayerStyleRules(polygonFilter, polygonRule, 'polygon', 'boundaries', layerOpacity),
|
|
1290
|
+
...createLayerStyleRules(lineFilter, lineRule, 'line', 'boundaries', layerOpacity)
|
|
1291
|
+
];
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1024
1294
|
/**
|
|
1025
1295
|
* @param {unknown[]} filter
|
|
1026
1296
|
* @param {Record<string, unknown>} rule
|
|
@@ -1638,6 +1908,69 @@ function isValidTileCoord(z, x, y) {
|
|
|
1638
1908
|
return Number.isInteger(x) && Number.isInteger(y) && x >= 0 && y >= 0 && x < maxIndex && y < maxIndex;
|
|
1639
1909
|
}
|
|
1640
1910
|
|
|
1911
|
+
/**
|
|
1912
|
+
* @param {Record<string, unknown>} manifest
|
|
1913
|
+
* @returns {{ url?: string, minZoom?: unknown, maxZoom?: unknown }}
|
|
1914
|
+
*/
|
|
1915
|
+
function pmtilesInfo(manifest) {
|
|
1916
|
+
const tiles = manifest.tiles && typeof manifest.tiles === 'object' ? manifest.tiles : {};
|
|
1917
|
+
return /** @type {{ url?: string, minZoom?: unknown, maxZoom?: unknown }} */ (tiles);
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
/**
|
|
1921
|
+
* @param {number} width
|
|
1922
|
+
* @param {number} height
|
|
1923
|
+
* @returns {HTMLCanvasElement}
|
|
1924
|
+
*/
|
|
1925
|
+
function emptyCanvas(width, height) {
|
|
1926
|
+
const canvas = document.createElement('canvas');
|
|
1927
|
+
canvas.width = width;
|
|
1928
|
+
canvas.height = height;
|
|
1929
|
+
return canvas;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
function assertRasterWorkerSupport() {
|
|
1933
|
+
if (typeof Worker !== 'function' || typeof OffscreenCanvas !== 'function' || typeof createImageBitmap !== 'function') {
|
|
1934
|
+
throw new Error('map-zero OpenLayers raster-worker mode requires Worker, OffscreenCanvas, and createImageBitmap');
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
/**
|
|
1939
|
+
* Choose the raster tile zoom at the midpoint in zoom space instead of always
|
|
1940
|
+
* forcing the parent or child tile. This keeps fractional zooms sharp without
|
|
1941
|
+
* holding low-resolution tiles for too long.
|
|
1942
|
+
*
|
|
1943
|
+
* @param {number} value
|
|
1944
|
+
* @param {number} high
|
|
1945
|
+
* @param {number} low
|
|
1946
|
+
* @returns {number}
|
|
1947
|
+
*/
|
|
1948
|
+
function preferNearestZoomLevel(value, high, low) {
|
|
1949
|
+
return value - low * Math.sqrt(high / low);
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
/**
|
|
1953
|
+
* @param {unknown} value
|
|
1954
|
+
* @param {number} min
|
|
1955
|
+
* @param {number} max
|
|
1956
|
+
* @returns {number}
|
|
1957
|
+
*/
|
|
1958
|
+
function clampInteger(value, min, max) {
|
|
1959
|
+
const number = Math.trunc(Number(value));
|
|
1960
|
+
return Number.isFinite(number) ? Math.max(min, Math.min(max, number)) : min;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
/**
|
|
1964
|
+
* @param {unknown} value
|
|
1965
|
+
* @param {number} min
|
|
1966
|
+
* @param {number} max
|
|
1967
|
+
* @returns {number}
|
|
1968
|
+
*/
|
|
1969
|
+
function clampNumber(value, min, max) {
|
|
1970
|
+
const number = Number(value);
|
|
1971
|
+
return Number.isFinite(number) ? Math.max(min, Math.min(max, number)) : min;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1641
1974
|
/**
|
|
1642
1975
|
* @param {number} z
|
|
1643
1976
|
* @param {number} x
|
package/src/3dtiles/b3dm.js
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* Minimal Batched 3D Model (b3dm) container writer.
|
|
3
|
+
*
|
|
4
|
+
* map-zero currently emits one GLB per tile and does not use batch IDs or per
|
|
5
|
+
* feature metadata, so the feature table only declares BATCH_LENGTH and an
|
|
6
|
+
* optional RTC_CENTER for high precision rendering in Cesium.
|
|
7
|
+
*
|
|
2
8
|
* Wrap a GLB buffer in a minimal valid B3DM container.
|
|
3
9
|
*
|
|
4
10
|
* @param {Buffer} glb
|
|
11
|
+
* @param {{ rtcCenter?: [number, number, number] }} [options]
|
|
5
12
|
* @returns {Buffer}
|
|
6
13
|
*/
|
|
7
|
-
export function buildB3dm(glb) {
|
|
8
|
-
const
|
|
14
|
+
export function buildB3dm(glb, options = {}) {
|
|
15
|
+
const featureTable = { BATCH_LENGTH: 0 };
|
|
16
|
+
if (options.rtcCenter) {
|
|
17
|
+
featureTable.RTC_CENTER = options.rtcCenter;
|
|
18
|
+
}
|
|
19
|
+
const featureTableJson = padJsonForSection(featureTable, 28);
|
|
9
20
|
const header = Buffer.alloc(28);
|
|
10
21
|
header.write('b3dm', 0, 4, 'ascii');
|
|
11
22
|
header.writeUInt32LE(1, 4);
|
|
@@ -18,6 +29,9 @@ export function buildB3dm(glb) {
|
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
/**
|
|
32
|
+
* Serialize and pad feature/batch table JSON so the following section starts on
|
|
33
|
+
* an 8-byte boundary, as required by the b3dm container.
|
|
34
|
+
*
|
|
21
35
|
* @param {unknown} value
|
|
22
36
|
* @param {number} sectionOffset
|
|
23
37
|
* @returns {Buffer}
|
|
@@ -29,6 +43,8 @@ function padJsonForSection(value, sectionOffset) {
|
|
|
29
43
|
}
|
|
30
44
|
|
|
31
45
|
/**
|
|
46
|
+
* Round a byte length up to the next multiple of alignment.
|
|
47
|
+
*
|
|
32
48
|
* @param {number} value
|
|
33
49
|
* @param {number} alignment
|
|
34
50
|
* @returns {number}
|