@map-zero/cesium 0.1.0 → 0.2.2
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/package.json +6 -3
- package/src/imagery-worker.js +604 -0
- package/src/imagery.js +434 -0
- package/src/index.js +216 -35
package/src/index.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Cesium3DTileColorBlendMode,
|
|
2
3
|
Cesium3DTileStyle,
|
|
3
|
-
Cesium3DTileset
|
|
4
|
+
Cesium3DTileset,
|
|
5
|
+
ImageryLayer
|
|
4
6
|
} from 'cesium';
|
|
7
|
+
import {
|
|
8
|
+
contextOverlayConfig,
|
|
9
|
+
hasMapZeroContextOverlay,
|
|
10
|
+
MapZeroCesiumImageryProvider
|
|
11
|
+
} from './imagery.js';
|
|
12
|
+
|
|
13
|
+
export { MapZeroCesiumImageryProvider } from './imagery.js';
|
|
5
14
|
|
|
6
15
|
let autoInstanceCounter = 0;
|
|
7
16
|
|
|
@@ -14,8 +23,7 @@ let autoInstanceCounter = 0;
|
|
|
14
23
|
* bbox?: [number, number, number, number],
|
|
15
24
|
* styles?: Record<string, string>,
|
|
16
25
|
* tiles3d?: { format?: string, url?: string, layers?: string[] },
|
|
17
|
-
*
|
|
18
|
-
* layers?: Array<{ id: string, table?: string, style?: string }>
|
|
26
|
+
* layers?: string[]
|
|
19
27
|
* }} MapZeroManifest
|
|
20
28
|
*/
|
|
21
29
|
|
|
@@ -79,7 +87,13 @@ export async function loadMapZeroStyle(input, options = {}) {
|
|
|
79
87
|
* manifest?: MapZeroManifest,
|
|
80
88
|
* style?: string | Record<string, unknown>,
|
|
81
89
|
* styleJson?: Record<string, unknown> | null,
|
|
82
|
-
* opacity?: number
|
|
90
|
+
* opacity?: number,
|
|
91
|
+
* tilesetOpacity?: number,
|
|
92
|
+
* buildingsOpacity?: number,
|
|
93
|
+
* buildings3d?: boolean,
|
|
94
|
+
* tilesetMaximumScreenSpaceError?: number,
|
|
95
|
+
* tilesetCacheBytes?: number,
|
|
96
|
+
* tilesetMaximumCacheOverflowBytes?: number
|
|
83
97
|
* }} options
|
|
84
98
|
* @returns {Promise<{ id: string, manifest: MapZeroManifest, style: Record<string, unknown> | null, tilesets: Record<string, Cesium3DTileset> }>}
|
|
85
99
|
*/
|
|
@@ -95,7 +109,9 @@ export async function createMapZeroCesiumTilesets(options) {
|
|
|
95
109
|
})
|
|
96
110
|
);
|
|
97
111
|
|
|
98
|
-
const tilesetEntries = manifestTilesetEntries(manifest
|
|
112
|
+
const tilesetEntries = manifestTilesetEntries(manifest, {
|
|
113
|
+
buildings3d: options.buildings3d
|
|
114
|
+
});
|
|
99
115
|
if (tilesetEntries.length === 0) {
|
|
100
116
|
return {
|
|
101
117
|
id: instanceId,
|
|
@@ -111,10 +127,12 @@ export async function createMapZeroCesiumTilesets(options) {
|
|
|
111
127
|
const url = resolveRelativeUrl(entry.url, options.manifestUrl);
|
|
112
128
|
const tileset = await Cesium3DTileset.fromUrl(url);
|
|
113
129
|
tagCesiumTileset(tileset, instanceId, entry.layerId);
|
|
130
|
+
configureCesiumTilesetStreaming(tileset, entry.layerId, options);
|
|
131
|
+
configureCesiumTilesetColor(tileset, entry.layerId);
|
|
114
132
|
tileset.style = createMapZeroCesiumStyle(styleJson, {
|
|
115
133
|
layerId: entry.layerId,
|
|
116
134
|
visibleLayers: new Set([entry.layerId]),
|
|
117
|
-
opacity:
|
|
135
|
+
opacity: tilesetOpacityForLayer(entry.layerId, options)
|
|
118
136
|
});
|
|
119
137
|
tilesets[entry.layerId] = tileset;
|
|
120
138
|
}
|
|
@@ -139,8 +157,20 @@ export async function createMapZeroCesiumTilesets(options) {
|
|
|
139
157
|
* manifestUrl: string,
|
|
140
158
|
* style?: string | Record<string, unknown>,
|
|
141
159
|
* opacity?: number,
|
|
160
|
+
* tilesetOpacity?: number,
|
|
161
|
+
* buildingsOpacity?: number,
|
|
162
|
+
* contextOverlay?: boolean,
|
|
163
|
+
* contextOpacity?: number,
|
|
164
|
+
* contextOverzoomLevels?: number,
|
|
165
|
+
* contextEdgeGuardPixels?: number,
|
|
166
|
+
* contextWorkerUrl?: string | URL,
|
|
167
|
+
* buildings3d?: boolean,
|
|
168
|
+
* tilesetMaximumScreenSpaceError?: number,
|
|
169
|
+
* tilesetCacheBytes?: number,
|
|
170
|
+
* tilesetMaximumCacheOverflowBytes?: number,
|
|
142
171
|
* zoomTo?: boolean,
|
|
143
172
|
* applyDefaultSceneStyle?: boolean,
|
|
173
|
+
* sceneStyle?: Record<string, unknown>,
|
|
144
174
|
* configureScene?: (viewer: unknown) => void
|
|
145
175
|
* }} options
|
|
146
176
|
* @returns {Promise<{
|
|
@@ -148,6 +178,8 @@ export async function createMapZeroCesiumTilesets(options) {
|
|
|
148
178
|
* manifest: MapZeroManifest,
|
|
149
179
|
* style: Record<string, unknown> | null,
|
|
150
180
|
* tilesets: Record<string, Cesium3DTileset>,
|
|
181
|
+
* imageryProvider?: MapZeroCesiumImageryProvider,
|
|
182
|
+
* imageryLayer?: ImageryLayer,
|
|
151
183
|
* setVisible: (layerId: string, visible: boolean) => void,
|
|
152
184
|
* setOpacity: (layerId: string, opacity: number) => void,
|
|
153
185
|
* destroy: () => void
|
|
@@ -155,7 +187,7 @@ export async function createMapZeroCesiumTilesets(options) {
|
|
|
155
187
|
*/
|
|
156
188
|
export async function addMapZeroToCesium(viewer, options) {
|
|
157
189
|
if (options.applyDefaultSceneStyle) {
|
|
158
|
-
applyMapZeroCesiumSceneStyle(viewer);
|
|
190
|
+
applyMapZeroCesiumSceneStyle(viewer, options.sceneStyle);
|
|
159
191
|
}
|
|
160
192
|
if (typeof options.configureScene === 'function') {
|
|
161
193
|
options.configureScene(viewer);
|
|
@@ -164,8 +196,28 @@ export async function addMapZeroToCesium(viewer, options) {
|
|
|
164
196
|
const result = await createMapZeroCesiumTilesets(options);
|
|
165
197
|
const uniqueTilesets = [...new Set(Object.values(result.tilesets))];
|
|
166
198
|
const visibleLayers = new Set(Object.keys(result.tilesets));
|
|
167
|
-
let opacity = options.opacity ?? 1;
|
|
168
|
-
|
|
199
|
+
let opacity = options.tilesetOpacity ?? options.opacity ?? 1;
|
|
200
|
+
const imageryProvider = shouldCreateContextOverlay(result.manifest, options)
|
|
201
|
+
? new MapZeroCesiumImageryProvider({
|
|
202
|
+
manifest: result.manifest,
|
|
203
|
+
manifestUrl: options.manifestUrl,
|
|
204
|
+
styleDocument: result.style,
|
|
205
|
+
layers: contextOverlayConfig(result.manifest)?.layers,
|
|
206
|
+
overzoomLevels: options.contextOverzoomLevels,
|
|
207
|
+
edgeGuardPixels: options.contextEdgeGuardPixels,
|
|
208
|
+
workerUrl: options.contextWorkerUrl
|
|
209
|
+
})
|
|
210
|
+
: undefined;
|
|
211
|
+
const imageryLayer = imageryProvider
|
|
212
|
+
? new ImageryLayer(imageryProvider, {
|
|
213
|
+
alpha: clamp01(Number(options.contextOpacity ?? options.opacity ?? 1)),
|
|
214
|
+
show: true
|
|
215
|
+
})
|
|
216
|
+
: undefined;
|
|
217
|
+
|
|
218
|
+
if (imageryLayer) {
|
|
219
|
+
viewer.imageryLayers?.add(imageryLayer);
|
|
220
|
+
}
|
|
169
221
|
for (const tileset of uniqueTilesets) {
|
|
170
222
|
viewer.scene.primitives.add(tileset);
|
|
171
223
|
}
|
|
@@ -181,6 +233,8 @@ export async function addMapZeroToCesium(viewer, options) {
|
|
|
181
233
|
id: result.id,
|
|
182
234
|
style: result.style,
|
|
183
235
|
tilesets: result.tilesets,
|
|
236
|
+
imageryProvider,
|
|
237
|
+
imageryLayer,
|
|
184
238
|
setVisible(layerId, visible) {
|
|
185
239
|
const tileset = result.tilesets[layerId];
|
|
186
240
|
if (tileset) {
|
|
@@ -194,6 +248,9 @@ export async function addMapZeroToCesium(viewer, options) {
|
|
|
194
248
|
visibleLayers
|
|
195
249
|
});
|
|
196
250
|
}
|
|
251
|
+
imageryProvider?.setLayerVisible(layerId, visible);
|
|
252
|
+
imageryProvider?.setLayerVisible(layerId === 'aviation' ? 'aip' : layerId, visible);
|
|
253
|
+
viewer.scene?.requestRender?.();
|
|
197
254
|
},
|
|
198
255
|
setOpacity(layerId, nextOpacity) {
|
|
199
256
|
if (!result.tilesets[layerId]) return;
|
|
@@ -204,6 +261,9 @@ export async function addMapZeroToCesium(viewer, options) {
|
|
|
204
261
|
});
|
|
205
262
|
},
|
|
206
263
|
destroy() {
|
|
264
|
+
if (imageryLayer) {
|
|
265
|
+
viewer.imageryLayers?.remove(imageryLayer, true);
|
|
266
|
+
}
|
|
207
267
|
for (const tileset of uniqueTilesets) {
|
|
208
268
|
viewer.scene.primitives.remove(tileset);
|
|
209
269
|
}
|
|
@@ -219,25 +279,29 @@ export async function addMapZeroToCesium(viewer, options) {
|
|
|
219
279
|
* black-background tactical look.
|
|
220
280
|
*
|
|
221
281
|
* @param {any} viewer
|
|
282
|
+
* @param {Record<string, unknown>} [options]
|
|
222
283
|
*/
|
|
223
|
-
export function applyMapZeroCesiumSceneStyle(viewer) {
|
|
284
|
+
export function applyMapZeroCesiumSceneStyle(viewer, options = {}) {
|
|
224
285
|
const Cesium = globalThis.Cesium;
|
|
225
286
|
const scene = viewer?.scene;
|
|
226
287
|
if (!scene || !Cesium) {
|
|
227
288
|
return;
|
|
228
289
|
}
|
|
229
290
|
|
|
230
|
-
|
|
291
|
+
const backgroundColor = colorFromOption(Cesium, options.backgroundColor, Cesium.Color.BLACK);
|
|
292
|
+
const globeBaseColor = colorFromOption(Cesium, options.globeBaseColor, backgroundColor);
|
|
293
|
+
scene.backgroundColor = backgroundColor;
|
|
231
294
|
if (scene.globe) {
|
|
232
|
-
scene.globe.baseColor =
|
|
233
|
-
scene.globe.enableLighting = false;
|
|
234
|
-
scene.globe.depthTestAgainstTerrain = false;
|
|
295
|
+
scene.globe.baseColor = globeBaseColor;
|
|
296
|
+
scene.globe.enableLighting = Boolean(options.enableLighting ?? false);
|
|
297
|
+
scene.globe.depthTestAgainstTerrain = Boolean(options.depthTestAgainstTerrain ?? false);
|
|
235
298
|
}
|
|
236
|
-
if (scene.fog) scene.fog.enabled = false;
|
|
237
|
-
if (scene.skyBox) scene.skyBox.show = false;
|
|
238
|
-
if (scene.sun) scene.sun.show = false;
|
|
239
|
-
if (scene.moon) scene.moon.show = false;
|
|
240
|
-
if (scene.skyAtmosphere) scene.skyAtmosphere.show = false;
|
|
299
|
+
if (scene.fog) scene.fog.enabled = Boolean(options.fog ?? false);
|
|
300
|
+
if (scene.skyBox) scene.skyBox.show = Boolean(options.skyBox ?? false);
|
|
301
|
+
if (scene.sun) scene.sun.show = Boolean(options.sun ?? false);
|
|
302
|
+
if (scene.moon) scene.moon.show = Boolean(options.moon ?? false);
|
|
303
|
+
if (scene.skyAtmosphere) scene.skyAtmosphere.show = Boolean(options.skyAtmosphere ?? false);
|
|
304
|
+
scene.requestRender?.();
|
|
241
305
|
}
|
|
242
306
|
|
|
243
307
|
/**
|
|
@@ -267,8 +331,8 @@ export function createMapZeroCesiumStyle(styleJson, options) {
|
|
|
267
331
|
* Pick a single material color from a map-zero style rule.
|
|
268
332
|
*
|
|
269
333
|
* In 2D, buildings commonly use a dark fill plus a bright stroke. A single
|
|
270
|
-
* Cesium material cannot show that outline, so building solids use the
|
|
271
|
-
*
|
|
334
|
+
* Cesium material cannot show that outline, so building solids use the fill:
|
|
335
|
+
* it keeps the mass quiet while avoiding translucent sorting artifacts.
|
|
272
336
|
*
|
|
273
337
|
* @param {Record<string, any> | null} rule
|
|
274
338
|
* @param {string} layerId
|
|
@@ -277,8 +341,8 @@ export function createMapZeroCesiumStyle(styleJson, options) {
|
|
|
277
341
|
function cesiumLayerMaterial(rule, layerId) {
|
|
278
342
|
if (layerId === 'buildings') {
|
|
279
343
|
return {
|
|
280
|
-
color:
|
|
281
|
-
opacity:
|
|
344
|
+
color: buildingSolidColor(rule),
|
|
345
|
+
opacity: 1
|
|
282
346
|
};
|
|
283
347
|
}
|
|
284
348
|
|
|
@@ -303,6 +367,21 @@ function applyStyleToTilesetMap(tilesets, style, options) {
|
|
|
303
367
|
}
|
|
304
368
|
}
|
|
305
369
|
|
|
370
|
+
/**
|
|
371
|
+
* @param {string} layerId
|
|
372
|
+
* @param {{ opacity?: number, tilesetOpacity?: number, buildingsOpacity?: number }} options
|
|
373
|
+
* @returns {number}
|
|
374
|
+
*/
|
|
375
|
+
function tilesetOpacityForLayer(layerId, options) {
|
|
376
|
+
if (layerId === 'buildings' && Number.isFinite(Number(options.buildingsOpacity))) {
|
|
377
|
+
return Number(options.buildingsOpacity);
|
|
378
|
+
}
|
|
379
|
+
if (Number.isFinite(Number(options.tilesetOpacity))) {
|
|
380
|
+
return Number(options.tilesetOpacity);
|
|
381
|
+
}
|
|
382
|
+
return Number(options.opacity ?? 1);
|
|
383
|
+
}
|
|
384
|
+
|
|
306
385
|
/**
|
|
307
386
|
* @param {Record<string, unknown> | null} styleJson
|
|
308
387
|
* @param {string} layerId
|
|
@@ -315,29 +394,88 @@ function layerStyle(styleJson, layerId) {
|
|
|
315
394
|
|
|
316
395
|
/**
|
|
317
396
|
* @param {MapZeroManifest} manifest
|
|
397
|
+
* @param {{ buildings3d?: boolean }} [options]
|
|
318
398
|
* @returns {Array<{ layerId: string, url: string }>}
|
|
319
399
|
*/
|
|
320
|
-
function manifestTilesetEntries(manifest) {
|
|
321
|
-
const cesiumTilesets = manifest.cesium?.tilesets;
|
|
322
|
-
if (cesiumTilesets && typeof cesiumTilesets === 'object') {
|
|
323
|
-
return Object.entries(cesiumTilesets)
|
|
324
|
-
.filter(([, url]) => typeof url === 'string' && url.length > 0)
|
|
325
|
-
.map(([layerId, url]) => ({ layerId, url }));
|
|
326
|
-
}
|
|
327
|
-
|
|
400
|
+
function manifestTilesetEntries(manifest, options = {}) {
|
|
328
401
|
if (manifest.tiles3d?.format === '3dtiles' && typeof manifest.tiles3d.url === 'string') {
|
|
329
402
|
const layers = Array.isArray(manifest.tiles3d.layers) && manifest.tiles3d.layers.length > 0
|
|
330
403
|
? manifest.tiles3d.layers.map(String)
|
|
331
404
|
: ['buildings'];
|
|
332
|
-
return layers
|
|
333
|
-
layerId,
|
|
334
|
-
|
|
335
|
-
|
|
405
|
+
return layers
|
|
406
|
+
.filter((layerId) => isAllowedCesiumTilesetLayer(layerId, options))
|
|
407
|
+
.map((layerId) => ({
|
|
408
|
+
layerId,
|
|
409
|
+
url: /** @type {string} */ (manifest.tiles3d?.url)
|
|
410
|
+
}));
|
|
336
411
|
}
|
|
337
412
|
|
|
338
413
|
return [];
|
|
339
414
|
}
|
|
340
415
|
|
|
416
|
+
/**
|
|
417
|
+
* @param {string} layerId
|
|
418
|
+
* @param {{ buildings3d?: boolean }} options
|
|
419
|
+
* @returns {boolean}
|
|
420
|
+
*/
|
|
421
|
+
function isAllowedCesiumTilesetLayer(layerId, options) {
|
|
422
|
+
return layerId !== 'buildings' || options.buildings3d !== false;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* @param {MapZeroManifest} manifest
|
|
427
|
+
* @param {{ contextOverlay?: boolean }} options
|
|
428
|
+
* @returns {boolean}
|
|
429
|
+
*/
|
|
430
|
+
function shouldCreateContextOverlay(manifest, options) {
|
|
431
|
+
if (options.contextOverlay === false) {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
return hasMapZeroContextOverlay(manifest);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* @param {Cesium3DTileset} tileset
|
|
439
|
+
* @param {string} layerId
|
|
440
|
+
* @param {{ tilesetMaximumScreenSpaceError?: number, tilesetCacheBytes?: number, tilesetMaximumCacheOverflowBytes?: number }} options
|
|
441
|
+
*/
|
|
442
|
+
function configureCesiumTilesetStreaming(tileset, layerId, options = {}) {
|
|
443
|
+
if (layerId !== 'buildings') {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
tileset.maximumScreenSpaceError = finiteNumber(options.tilesetMaximumScreenSpaceError, 24);
|
|
448
|
+
tileset.skipLevelOfDetail = true;
|
|
449
|
+
tileset.baseScreenSpaceError = 1024;
|
|
450
|
+
tileset.skipScreenSpaceErrorFactor = 16;
|
|
451
|
+
tileset.skipLevels = 1;
|
|
452
|
+
tileset.immediatelyLoadDesiredLevelOfDetail = false;
|
|
453
|
+
tileset.loadSiblings = false;
|
|
454
|
+
tileset.cullWithChildrenBounds = true;
|
|
455
|
+
tileset.dynamicScreenSpaceError = true;
|
|
456
|
+
tileset.dynamicScreenSpaceErrorDensity = 0.00278;
|
|
457
|
+
tileset.dynamicScreenSpaceErrorFactor = 4;
|
|
458
|
+
tileset.preloadWhenHidden = false;
|
|
459
|
+
tileset.preloadFlightDestinations = false;
|
|
460
|
+
tileset.cacheBytes = finiteNumber(options.tilesetCacheBytes, 768 * 1024 * 1024);
|
|
461
|
+
tileset.maximumCacheOverflowBytes = finiteNumber(options.tilesetMaximumCacheOverflowBytes, 512 * 1024 * 1024);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* @param {Cesium3DTileset} tileset
|
|
466
|
+
* @param {string} layerId
|
|
467
|
+
*/
|
|
468
|
+
function configureCesiumTilesetColor(tileset, layerId) {
|
|
469
|
+
if (layerId === 'buildings') {
|
|
470
|
+
tileset.backFaceCulling = false;
|
|
471
|
+
tileset.colorBlendMode = Cesium3DTileColorBlendMode.MIX;
|
|
472
|
+
tileset.colorBlendAmount = 0.45;
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
tileset.colorBlendMode = Cesium3DTileColorBlendMode.REPLACE;
|
|
476
|
+
tileset.colorBlendAmount = 1;
|
|
477
|
+
}
|
|
478
|
+
|
|
341
479
|
/**
|
|
342
480
|
* @param {string | undefined} id
|
|
343
481
|
* @param {MapZeroManifest} manifest
|
|
@@ -395,6 +533,39 @@ function safeCssColor(color) {
|
|
|
395
533
|
return /^#[0-9a-f]{6}$/i.test(color) ? color : '#ff00ff';
|
|
396
534
|
}
|
|
397
535
|
|
|
536
|
+
/**
|
|
537
|
+
* @param {Record<string, any> | null} rule
|
|
538
|
+
* @returns {string}
|
|
539
|
+
*/
|
|
540
|
+
function buildingSolidColor(rule) {
|
|
541
|
+
const explicit = rule?.cesium?.color ?? rule?.tiles3d?.color ?? rule?.material?.color;
|
|
542
|
+
if (typeof explicit === 'string' && isHexColor(explicit)) {
|
|
543
|
+
return explicit;
|
|
544
|
+
}
|
|
545
|
+
return '#8a3f82';
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* @param {string} value
|
|
550
|
+
* @returns {boolean}
|
|
551
|
+
*/
|
|
552
|
+
function isHexColor(value) {
|
|
553
|
+
return /^#[0-9a-f]{6}$/i.test(value);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* @param {any} Cesium
|
|
558
|
+
* @param {unknown} value
|
|
559
|
+
* @param {any} fallback
|
|
560
|
+
* @returns {any}
|
|
561
|
+
*/
|
|
562
|
+
function colorFromOption(Cesium, value, fallback) {
|
|
563
|
+
if (typeof value !== 'string') {
|
|
564
|
+
return fallback;
|
|
565
|
+
}
|
|
566
|
+
return Cesium.Color.fromCssColorString(value) ?? fallback;
|
|
567
|
+
}
|
|
568
|
+
|
|
398
569
|
|
|
399
570
|
/**
|
|
400
571
|
* @param {number} value
|
|
@@ -403,3 +574,13 @@ function safeCssColor(color) {
|
|
|
403
574
|
function clamp01(value) {
|
|
404
575
|
return Math.max(0, Math.min(1, Number.isFinite(value) ? value : 1));
|
|
405
576
|
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* @param {unknown} value
|
|
580
|
+
* @param {number} fallback
|
|
581
|
+
* @returns {number}
|
|
582
|
+
*/
|
|
583
|
+
function finiteNumber(value, fallback) {
|
|
584
|
+
const number = Number(value);
|
|
585
|
+
return Number.isFinite(number) ? number : fallback;
|
|
586
|
+
}
|