map-zero 0.1.0 → 0.2.0
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 +31 -2
- package/docs/cesium.md +24 -9
- package/package.json +3 -2
- 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 +347 -10
- 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,
|
|
@@ -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
|
|
@@ -306,7 +544,7 @@ function applyLayerZIndex(layer, orderedLayers, styleDocument, zIndexBase) {
|
|
|
306
544
|
* @param {unknown} layer
|
|
307
545
|
* @param {string} instanceId
|
|
308
546
|
* @param {string[]} layerIds
|
|
309
|
-
* @param {'geometry' | 'labels'} role
|
|
547
|
+
* @param {'geometry' | 'labels' | 'raster'} role
|
|
310
548
|
*/
|
|
311
549
|
function tagOpenLayersLayer(layer, instanceId, layerIds, role) {
|
|
312
550
|
const namespacedLayerIds = layerIds.map((layerId) => namespaceLayerId(instanceId, layerId));
|
|
@@ -465,7 +703,6 @@ function createTileUrlFunction(context) {
|
|
|
465
703
|
*/
|
|
466
704
|
function createPmtilesTileUrlFunction(context) {
|
|
467
705
|
const { minZoom, maxZoom } = pmtilesZoomRange(context.manifest);
|
|
468
|
-
const packageBbox = normalizeBbox(context.manifest.bbox);
|
|
469
706
|
|
|
470
707
|
return (tileCoord) => {
|
|
471
708
|
if (!tileCoord) {
|
|
@@ -477,10 +714,6 @@ function createPmtilesTileUrlFunction(context) {
|
|
|
477
714
|
return undefined;
|
|
478
715
|
}
|
|
479
716
|
|
|
480
|
-
if (packageBbox && !bboxIntersects(tileToBbox(z, x, y), packageBbox)) {
|
|
481
|
-
return undefined;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
717
|
if (activeLayerIdsForZoom(context.orderedLayers, context.styleDocument, context.layerVisibility, z).length === 0) {
|
|
485
718
|
return undefined;
|
|
486
719
|
}
|
|
@@ -710,7 +943,9 @@ function createWebGlStyles(context) {
|
|
|
710
943
|
const filter = createLayerFilter(layer.id, rule);
|
|
711
944
|
const styleParts = layer.id === 'roads'
|
|
712
945
|
? createRoadStyleRules(filter, rule, context.layerOpacity)
|
|
713
|
-
: layer.id === 'boundaries'
|
|
946
|
+
: layer.id === 'boundaries'
|
|
947
|
+
? createBoundaryStyleRules(filter, rule, context.layerOpacity)
|
|
948
|
+
: isAipLayer(layer.id)
|
|
714
949
|
? createGeometryAwareStyleRules(filter, rule, layer.id, context.layerOpacity)
|
|
715
950
|
: createLayerStyleRules(filter, rule, layer.type || 'line', layer.id, context.layerOpacity);
|
|
716
951
|
for (const style of styleParts) {
|
|
@@ -1021,6 +1256,45 @@ function createPropertyVisibilityFilter(filter, rule) {
|
|
|
1021
1256
|
return hidden.length > 0 ? ['all', filter, ...hidden] : filter;
|
|
1022
1257
|
}
|
|
1023
1258
|
|
|
1259
|
+
/**
|
|
1260
|
+
* @param {unknown[]} filter
|
|
1261
|
+
* @param {Record<string, unknown>} rule
|
|
1262
|
+
* @param {Map<string, number>} layerOpacity
|
|
1263
|
+
* @returns {Array<{ filter: unknown[], style: Record<string, unknown> }>}
|
|
1264
|
+
*/
|
|
1265
|
+
function createBoundaryStyleRules(filter, rule, layerOpacity) {
|
|
1266
|
+
const polygonFilter = ['all', filter, ['==', ['geometry-type'], 'Polygon']];
|
|
1267
|
+
const lineFilter = ['all', filter, ['==', ['geometry-type'], 'LineString']];
|
|
1268
|
+
const polygonRule = {
|
|
1269
|
+
...rule,
|
|
1270
|
+
stroke: null,
|
|
1271
|
+
strokeOpacity: 0,
|
|
1272
|
+
strokeWidth: 0,
|
|
1273
|
+
glow: {
|
|
1274
|
+
...rule.glow,
|
|
1275
|
+
enabled: false
|
|
1276
|
+
},
|
|
1277
|
+
casing: {
|
|
1278
|
+
...rule.casing,
|
|
1279
|
+
enabled: false
|
|
1280
|
+
},
|
|
1281
|
+
centerLine: {
|
|
1282
|
+
...rule.centerLine,
|
|
1283
|
+
enabled: false
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
const lineRule = {
|
|
1287
|
+
...rule,
|
|
1288
|
+
fill: null,
|
|
1289
|
+
fillOpacity: 0
|
|
1290
|
+
};
|
|
1291
|
+
|
|
1292
|
+
return [
|
|
1293
|
+
...createLayerStyleRules(polygonFilter, polygonRule, 'polygon', 'boundaries', layerOpacity),
|
|
1294
|
+
...createLayerStyleRules(lineFilter, lineRule, 'line', 'boundaries', layerOpacity)
|
|
1295
|
+
];
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1024
1298
|
/**
|
|
1025
1299
|
* @param {unknown[]} filter
|
|
1026
1300
|
* @param {Record<string, unknown>} rule
|
|
@@ -1638,6 +1912,69 @@ function isValidTileCoord(z, x, y) {
|
|
|
1638
1912
|
return Number.isInteger(x) && Number.isInteger(y) && x >= 0 && y >= 0 && x < maxIndex && y < maxIndex;
|
|
1639
1913
|
}
|
|
1640
1914
|
|
|
1915
|
+
/**
|
|
1916
|
+
* @param {Record<string, unknown>} manifest
|
|
1917
|
+
* @returns {{ url?: string, minZoom?: unknown, maxZoom?: unknown }}
|
|
1918
|
+
*/
|
|
1919
|
+
function pmtilesInfo(manifest) {
|
|
1920
|
+
const tiles = manifest.tiles && typeof manifest.tiles === 'object' ? manifest.tiles : {};
|
|
1921
|
+
return /** @type {{ url?: string, minZoom?: unknown, maxZoom?: unknown }} */ (tiles);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
/**
|
|
1925
|
+
* @param {number} width
|
|
1926
|
+
* @param {number} height
|
|
1927
|
+
* @returns {HTMLCanvasElement}
|
|
1928
|
+
*/
|
|
1929
|
+
function emptyCanvas(width, height) {
|
|
1930
|
+
const canvas = document.createElement('canvas');
|
|
1931
|
+
canvas.width = width;
|
|
1932
|
+
canvas.height = height;
|
|
1933
|
+
return canvas;
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
function assertRasterWorkerSupport() {
|
|
1937
|
+
if (typeof Worker !== 'function' || typeof OffscreenCanvas !== 'function' || typeof createImageBitmap !== 'function') {
|
|
1938
|
+
throw new Error('map-zero OpenLayers raster-worker mode requires Worker, OffscreenCanvas, and createImageBitmap');
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
/**
|
|
1943
|
+
* Choose the raster tile zoom at the midpoint in zoom space instead of always
|
|
1944
|
+
* forcing the parent or child tile. This keeps fractional zooms sharp without
|
|
1945
|
+
* holding low-resolution tiles for too long.
|
|
1946
|
+
*
|
|
1947
|
+
* @param {number} value
|
|
1948
|
+
* @param {number} high
|
|
1949
|
+
* @param {number} low
|
|
1950
|
+
* @returns {number}
|
|
1951
|
+
*/
|
|
1952
|
+
function preferNearestZoomLevel(value, high, low) {
|
|
1953
|
+
return value - low * Math.sqrt(high / low);
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
/**
|
|
1957
|
+
* @param {unknown} value
|
|
1958
|
+
* @param {number} min
|
|
1959
|
+
* @param {number} max
|
|
1960
|
+
* @returns {number}
|
|
1961
|
+
*/
|
|
1962
|
+
function clampInteger(value, min, max) {
|
|
1963
|
+
const number = Math.trunc(Number(value));
|
|
1964
|
+
return Number.isFinite(number) ? Math.max(min, Math.min(max, number)) : min;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
/**
|
|
1968
|
+
* @param {unknown} value
|
|
1969
|
+
* @param {number} min
|
|
1970
|
+
* @param {number} max
|
|
1971
|
+
* @returns {number}
|
|
1972
|
+
*/
|
|
1973
|
+
function clampNumber(value, min, max) {
|
|
1974
|
+
const number = Number(value);
|
|
1975
|
+
return Number.isFinite(number) ? Math.max(min, Math.min(max, number)) : min;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1641
1978
|
/**
|
|
1642
1979
|
* @param {number} z
|
|
1643
1980
|
* @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}
|