@map-zero/cesium 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/package.json +6 -3
- package/src/imagery-worker.js +604 -0
- package/src/imagery.js +434 -0
- package/src/index.js +199 -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,10 @@ 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
|
|
83
94
|
* }} options
|
|
84
95
|
* @returns {Promise<{ id: string, manifest: MapZeroManifest, style: Record<string, unknown> | null, tilesets: Record<string, Cesium3DTileset> }>}
|
|
85
96
|
*/
|
|
@@ -95,7 +106,9 @@ export async function createMapZeroCesiumTilesets(options) {
|
|
|
95
106
|
})
|
|
96
107
|
);
|
|
97
108
|
|
|
98
|
-
const tilesetEntries = manifestTilesetEntries(manifest
|
|
109
|
+
const tilesetEntries = manifestTilesetEntries(manifest, {
|
|
110
|
+
buildings3d: options.buildings3d
|
|
111
|
+
});
|
|
99
112
|
if (tilesetEntries.length === 0) {
|
|
100
113
|
return {
|
|
101
114
|
id: instanceId,
|
|
@@ -111,10 +124,12 @@ export async function createMapZeroCesiumTilesets(options) {
|
|
|
111
124
|
const url = resolveRelativeUrl(entry.url, options.manifestUrl);
|
|
112
125
|
const tileset = await Cesium3DTileset.fromUrl(url);
|
|
113
126
|
tagCesiumTileset(tileset, instanceId, entry.layerId);
|
|
127
|
+
configureCesiumTilesetStreaming(tileset, entry.layerId);
|
|
128
|
+
configureCesiumTilesetColor(tileset, entry.layerId);
|
|
114
129
|
tileset.style = createMapZeroCesiumStyle(styleJson, {
|
|
115
130
|
layerId: entry.layerId,
|
|
116
131
|
visibleLayers: new Set([entry.layerId]),
|
|
117
|
-
opacity:
|
|
132
|
+
opacity: tilesetOpacityForLayer(entry.layerId, options)
|
|
118
133
|
});
|
|
119
134
|
tilesets[entry.layerId] = tileset;
|
|
120
135
|
}
|
|
@@ -139,8 +154,17 @@ export async function createMapZeroCesiumTilesets(options) {
|
|
|
139
154
|
* manifestUrl: string,
|
|
140
155
|
* style?: string | Record<string, unknown>,
|
|
141
156
|
* opacity?: number,
|
|
157
|
+
* tilesetOpacity?: number,
|
|
158
|
+
* buildingsOpacity?: number,
|
|
159
|
+
* contextOverlay?: boolean,
|
|
160
|
+
* contextOpacity?: number,
|
|
161
|
+
* contextOverzoomLevels?: number,
|
|
162
|
+
* contextEdgeGuardPixels?: number,
|
|
163
|
+
* contextWorkerUrl?: string | URL,
|
|
164
|
+
* buildings3d?: boolean,
|
|
142
165
|
* zoomTo?: boolean,
|
|
143
166
|
* applyDefaultSceneStyle?: boolean,
|
|
167
|
+
* sceneStyle?: Record<string, unknown>,
|
|
144
168
|
* configureScene?: (viewer: unknown) => void
|
|
145
169
|
* }} options
|
|
146
170
|
* @returns {Promise<{
|
|
@@ -148,6 +172,8 @@ export async function createMapZeroCesiumTilesets(options) {
|
|
|
148
172
|
* manifest: MapZeroManifest,
|
|
149
173
|
* style: Record<string, unknown> | null,
|
|
150
174
|
* tilesets: Record<string, Cesium3DTileset>,
|
|
175
|
+
* imageryProvider?: MapZeroCesiumImageryProvider,
|
|
176
|
+
* imageryLayer?: ImageryLayer,
|
|
151
177
|
* setVisible: (layerId: string, visible: boolean) => void,
|
|
152
178
|
* setOpacity: (layerId: string, opacity: number) => void,
|
|
153
179
|
* destroy: () => void
|
|
@@ -155,7 +181,7 @@ export async function createMapZeroCesiumTilesets(options) {
|
|
|
155
181
|
*/
|
|
156
182
|
export async function addMapZeroToCesium(viewer, options) {
|
|
157
183
|
if (options.applyDefaultSceneStyle) {
|
|
158
|
-
applyMapZeroCesiumSceneStyle(viewer);
|
|
184
|
+
applyMapZeroCesiumSceneStyle(viewer, options.sceneStyle);
|
|
159
185
|
}
|
|
160
186
|
if (typeof options.configureScene === 'function') {
|
|
161
187
|
options.configureScene(viewer);
|
|
@@ -164,8 +190,28 @@ export async function addMapZeroToCesium(viewer, options) {
|
|
|
164
190
|
const result = await createMapZeroCesiumTilesets(options);
|
|
165
191
|
const uniqueTilesets = [...new Set(Object.values(result.tilesets))];
|
|
166
192
|
const visibleLayers = new Set(Object.keys(result.tilesets));
|
|
167
|
-
let opacity = options.opacity ?? 1;
|
|
168
|
-
|
|
193
|
+
let opacity = options.tilesetOpacity ?? options.opacity ?? 1;
|
|
194
|
+
const imageryProvider = shouldCreateContextOverlay(result.manifest, options)
|
|
195
|
+
? new MapZeroCesiumImageryProvider({
|
|
196
|
+
manifest: result.manifest,
|
|
197
|
+
manifestUrl: options.manifestUrl,
|
|
198
|
+
styleDocument: result.style,
|
|
199
|
+
layers: contextOverlayConfig(result.manifest)?.layers,
|
|
200
|
+
overzoomLevels: options.contextOverzoomLevels,
|
|
201
|
+
edgeGuardPixels: options.contextEdgeGuardPixels,
|
|
202
|
+
workerUrl: options.contextWorkerUrl
|
|
203
|
+
})
|
|
204
|
+
: undefined;
|
|
205
|
+
const imageryLayer = imageryProvider
|
|
206
|
+
? new ImageryLayer(imageryProvider, {
|
|
207
|
+
alpha: clamp01(Number(options.contextOpacity ?? options.opacity ?? 1)),
|
|
208
|
+
show: true
|
|
209
|
+
})
|
|
210
|
+
: undefined;
|
|
211
|
+
|
|
212
|
+
if (imageryLayer) {
|
|
213
|
+
viewer.imageryLayers?.add(imageryLayer);
|
|
214
|
+
}
|
|
169
215
|
for (const tileset of uniqueTilesets) {
|
|
170
216
|
viewer.scene.primitives.add(tileset);
|
|
171
217
|
}
|
|
@@ -181,6 +227,8 @@ export async function addMapZeroToCesium(viewer, options) {
|
|
|
181
227
|
id: result.id,
|
|
182
228
|
style: result.style,
|
|
183
229
|
tilesets: result.tilesets,
|
|
230
|
+
imageryProvider,
|
|
231
|
+
imageryLayer,
|
|
184
232
|
setVisible(layerId, visible) {
|
|
185
233
|
const tileset = result.tilesets[layerId];
|
|
186
234
|
if (tileset) {
|
|
@@ -194,6 +242,9 @@ export async function addMapZeroToCesium(viewer, options) {
|
|
|
194
242
|
visibleLayers
|
|
195
243
|
});
|
|
196
244
|
}
|
|
245
|
+
imageryProvider?.setLayerVisible(layerId, visible);
|
|
246
|
+
imageryProvider?.setLayerVisible(layerId === 'aviation' ? 'aip' : layerId, visible);
|
|
247
|
+
viewer.scene?.requestRender?.();
|
|
197
248
|
},
|
|
198
249
|
setOpacity(layerId, nextOpacity) {
|
|
199
250
|
if (!result.tilesets[layerId]) return;
|
|
@@ -204,6 +255,9 @@ export async function addMapZeroToCesium(viewer, options) {
|
|
|
204
255
|
});
|
|
205
256
|
},
|
|
206
257
|
destroy() {
|
|
258
|
+
if (imageryLayer) {
|
|
259
|
+
viewer.imageryLayers?.remove(imageryLayer, true);
|
|
260
|
+
}
|
|
207
261
|
for (const tileset of uniqueTilesets) {
|
|
208
262
|
viewer.scene.primitives.remove(tileset);
|
|
209
263
|
}
|
|
@@ -219,25 +273,29 @@ export async function addMapZeroToCesium(viewer, options) {
|
|
|
219
273
|
* black-background tactical look.
|
|
220
274
|
*
|
|
221
275
|
* @param {any} viewer
|
|
276
|
+
* @param {Record<string, unknown>} [options]
|
|
222
277
|
*/
|
|
223
|
-
export function applyMapZeroCesiumSceneStyle(viewer) {
|
|
278
|
+
export function applyMapZeroCesiumSceneStyle(viewer, options = {}) {
|
|
224
279
|
const Cesium = globalThis.Cesium;
|
|
225
280
|
const scene = viewer?.scene;
|
|
226
281
|
if (!scene || !Cesium) {
|
|
227
282
|
return;
|
|
228
283
|
}
|
|
229
284
|
|
|
230
|
-
|
|
285
|
+
const backgroundColor = colorFromOption(Cesium, options.backgroundColor, Cesium.Color.BLACK);
|
|
286
|
+
const globeBaseColor = colorFromOption(Cesium, options.globeBaseColor, backgroundColor);
|
|
287
|
+
scene.backgroundColor = backgroundColor;
|
|
231
288
|
if (scene.globe) {
|
|
232
|
-
scene.globe.baseColor =
|
|
233
|
-
scene.globe.enableLighting = false;
|
|
234
|
-
scene.globe.depthTestAgainstTerrain = false;
|
|
289
|
+
scene.globe.baseColor = globeBaseColor;
|
|
290
|
+
scene.globe.enableLighting = Boolean(options.enableLighting ?? false);
|
|
291
|
+
scene.globe.depthTestAgainstTerrain = Boolean(options.depthTestAgainstTerrain ?? false);
|
|
235
292
|
}
|
|
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;
|
|
293
|
+
if (scene.fog) scene.fog.enabled = Boolean(options.fog ?? false);
|
|
294
|
+
if (scene.skyBox) scene.skyBox.show = Boolean(options.skyBox ?? false);
|
|
295
|
+
if (scene.sun) scene.sun.show = Boolean(options.sun ?? false);
|
|
296
|
+
if (scene.moon) scene.moon.show = Boolean(options.moon ?? false);
|
|
297
|
+
if (scene.skyAtmosphere) scene.skyAtmosphere.show = Boolean(options.skyAtmosphere ?? false);
|
|
298
|
+
scene.requestRender?.();
|
|
241
299
|
}
|
|
242
300
|
|
|
243
301
|
/**
|
|
@@ -267,8 +325,8 @@ export function createMapZeroCesiumStyle(styleJson, options) {
|
|
|
267
325
|
* Pick a single material color from a map-zero style rule.
|
|
268
326
|
*
|
|
269
327
|
* 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
|
-
*
|
|
328
|
+
* Cesium material cannot show that outline, so building solids use the fill:
|
|
329
|
+
* it keeps the mass quiet while avoiding translucent sorting artifacts.
|
|
272
330
|
*
|
|
273
331
|
* @param {Record<string, any> | null} rule
|
|
274
332
|
* @param {string} layerId
|
|
@@ -277,8 +335,8 @@ export function createMapZeroCesiumStyle(styleJson, options) {
|
|
|
277
335
|
function cesiumLayerMaterial(rule, layerId) {
|
|
278
336
|
if (layerId === 'buildings') {
|
|
279
337
|
return {
|
|
280
|
-
color:
|
|
281
|
-
opacity:
|
|
338
|
+
color: buildingSolidColor(rule),
|
|
339
|
+
opacity: 1
|
|
282
340
|
};
|
|
283
341
|
}
|
|
284
342
|
|
|
@@ -303,6 +361,21 @@ function applyStyleToTilesetMap(tilesets, style, options) {
|
|
|
303
361
|
}
|
|
304
362
|
}
|
|
305
363
|
|
|
364
|
+
/**
|
|
365
|
+
* @param {string} layerId
|
|
366
|
+
* @param {{ opacity?: number, tilesetOpacity?: number, buildingsOpacity?: number }} options
|
|
367
|
+
* @returns {number}
|
|
368
|
+
*/
|
|
369
|
+
function tilesetOpacityForLayer(layerId, options) {
|
|
370
|
+
if (layerId === 'buildings' && Number.isFinite(Number(options.buildingsOpacity))) {
|
|
371
|
+
return Number(options.buildingsOpacity);
|
|
372
|
+
}
|
|
373
|
+
if (Number.isFinite(Number(options.tilesetOpacity))) {
|
|
374
|
+
return Number(options.tilesetOpacity);
|
|
375
|
+
}
|
|
376
|
+
return Number(options.opacity ?? 1);
|
|
377
|
+
}
|
|
378
|
+
|
|
306
379
|
/**
|
|
307
380
|
* @param {Record<string, unknown> | null} styleJson
|
|
308
381
|
* @param {string} layerId
|
|
@@ -315,29 +388,87 @@ function layerStyle(styleJson, layerId) {
|
|
|
315
388
|
|
|
316
389
|
/**
|
|
317
390
|
* @param {MapZeroManifest} manifest
|
|
391
|
+
* @param {{ buildings3d?: boolean }} [options]
|
|
318
392
|
* @returns {Array<{ layerId: string, url: string }>}
|
|
319
393
|
*/
|
|
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
|
-
|
|
394
|
+
function manifestTilesetEntries(manifest, options = {}) {
|
|
328
395
|
if (manifest.tiles3d?.format === '3dtiles' && typeof manifest.tiles3d.url === 'string') {
|
|
329
396
|
const layers = Array.isArray(manifest.tiles3d.layers) && manifest.tiles3d.layers.length > 0
|
|
330
397
|
? manifest.tiles3d.layers.map(String)
|
|
331
398
|
: ['buildings'];
|
|
332
|
-
return layers
|
|
333
|
-
layerId,
|
|
334
|
-
|
|
335
|
-
|
|
399
|
+
return layers
|
|
400
|
+
.filter((layerId) => isAllowedCesiumTilesetLayer(layerId, options))
|
|
401
|
+
.map((layerId) => ({
|
|
402
|
+
layerId,
|
|
403
|
+
url: /** @type {string} */ (manifest.tiles3d?.url)
|
|
404
|
+
}));
|
|
336
405
|
}
|
|
337
406
|
|
|
338
407
|
return [];
|
|
339
408
|
}
|
|
340
409
|
|
|
410
|
+
/**
|
|
411
|
+
* @param {string} layerId
|
|
412
|
+
* @param {{ buildings3d?: boolean }} options
|
|
413
|
+
* @returns {boolean}
|
|
414
|
+
*/
|
|
415
|
+
function isAllowedCesiumTilesetLayer(layerId, options) {
|
|
416
|
+
return layerId !== 'buildings' || options.buildings3d !== false;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* @param {MapZeroManifest} manifest
|
|
421
|
+
* @param {{ contextOverlay?: boolean }} options
|
|
422
|
+
* @returns {boolean}
|
|
423
|
+
*/
|
|
424
|
+
function shouldCreateContextOverlay(manifest, options) {
|
|
425
|
+
if (options.contextOverlay === false) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
return hasMapZeroContextOverlay(manifest);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* @param {Cesium3DTileset} tileset
|
|
433
|
+
* @param {string} layerId
|
|
434
|
+
*/
|
|
435
|
+
function configureCesiumTilesetStreaming(tileset, layerId) {
|
|
436
|
+
if (layerId !== 'buildings') {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
tileset.maximumScreenSpaceError = 24;
|
|
441
|
+
tileset.skipLevelOfDetail = true;
|
|
442
|
+
tileset.baseScreenSpaceError = 1024;
|
|
443
|
+
tileset.skipScreenSpaceErrorFactor = 16;
|
|
444
|
+
tileset.skipLevels = 1;
|
|
445
|
+
tileset.immediatelyLoadDesiredLevelOfDetail = false;
|
|
446
|
+
tileset.loadSiblings = false;
|
|
447
|
+
tileset.cullWithChildrenBounds = true;
|
|
448
|
+
tileset.dynamicScreenSpaceError = true;
|
|
449
|
+
tileset.dynamicScreenSpaceErrorDensity = 0.00278;
|
|
450
|
+
tileset.dynamicScreenSpaceErrorFactor = 4;
|
|
451
|
+
tileset.preloadWhenHidden = false;
|
|
452
|
+
tileset.preloadFlightDestinations = false;
|
|
453
|
+
tileset.cacheBytes = 256 * 1024 * 1024;
|
|
454
|
+
tileset.maximumCacheOverflowBytes = 128 * 1024 * 1024;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* @param {Cesium3DTileset} tileset
|
|
459
|
+
* @param {string} layerId
|
|
460
|
+
*/
|
|
461
|
+
function configureCesiumTilesetColor(tileset, layerId) {
|
|
462
|
+
if (layerId === 'buildings') {
|
|
463
|
+
tileset.backFaceCulling = false;
|
|
464
|
+
tileset.colorBlendMode = Cesium3DTileColorBlendMode.MIX;
|
|
465
|
+
tileset.colorBlendAmount = 0.45;
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
tileset.colorBlendMode = Cesium3DTileColorBlendMode.REPLACE;
|
|
469
|
+
tileset.colorBlendAmount = 1;
|
|
470
|
+
}
|
|
471
|
+
|
|
341
472
|
/**
|
|
342
473
|
* @param {string | undefined} id
|
|
343
474
|
* @param {MapZeroManifest} manifest
|
|
@@ -395,6 +526,39 @@ function safeCssColor(color) {
|
|
|
395
526
|
return /^#[0-9a-f]{6}$/i.test(color) ? color : '#ff00ff';
|
|
396
527
|
}
|
|
397
528
|
|
|
529
|
+
/**
|
|
530
|
+
* @param {Record<string, any> | null} rule
|
|
531
|
+
* @returns {string}
|
|
532
|
+
*/
|
|
533
|
+
function buildingSolidColor(rule) {
|
|
534
|
+
const explicit = rule?.cesium?.color ?? rule?.tiles3d?.color ?? rule?.material?.color;
|
|
535
|
+
if (typeof explicit === 'string' && isHexColor(explicit)) {
|
|
536
|
+
return explicit;
|
|
537
|
+
}
|
|
538
|
+
return '#8a3f82';
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* @param {string} value
|
|
543
|
+
* @returns {boolean}
|
|
544
|
+
*/
|
|
545
|
+
function isHexColor(value) {
|
|
546
|
+
return /^#[0-9a-f]{6}$/i.test(value);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* @param {any} Cesium
|
|
551
|
+
* @param {unknown} value
|
|
552
|
+
* @param {any} fallback
|
|
553
|
+
* @returns {any}
|
|
554
|
+
*/
|
|
555
|
+
function colorFromOption(Cesium, value, fallback) {
|
|
556
|
+
if (typeof value !== 'string') {
|
|
557
|
+
return fallback;
|
|
558
|
+
}
|
|
559
|
+
return Cesium.Color.fromCssColorString(value) ?? fallback;
|
|
560
|
+
}
|
|
561
|
+
|
|
398
562
|
|
|
399
563
|
/**
|
|
400
564
|
* @param {number} value
|