mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
refactor(maps): describe map sources to the engine
The map source controller always hands engine.maps an engine-neutral description of the registry source: imagery kind/style/accessToken and credit, photoreal kind and credentials, terrain kind/accessToken. The default registry carries those beside its Cesium factories. Only a Cesium engine also gets the prebuilt provider, tileset and cached terrain (Cesium-mode behaviour, ordering and same-provider guard unchanged); for any other engine the controller builds nothing, falls back by description when a setter rejects, and has no tile-failure watch.
This commit is contained in:
1 parent
ab0acb0e09
commit
6dbd9c983e
4 files changed
+210
-18
No files matched your search
+102
-16
@@ -15,12 +15,18 @@ const AUTOMATIC = Symbol('automatic switch');
|
||||
* choices.
|
||||
*
|
||||
* With `engine` (the page's globe engine) every scene change goes through
|
||||
* `engine.maps`: the controller keeps resolving, caching and watching
|
||||
* providers and tilesets, and hands the engine ready sources. Supplied
|
||||
* tilesets must then be ones the engine knows (its startup tileset) or has
|
||||
* shown. On destroy the engine keeps what it shows; owned tilesets are
|
||||
* released and disposed. Without an engine the controller applies sources
|
||||
* to the viewer itself (standalone use and tests).
|
||||
* `engine.maps` as an engine-neutral description of the registry source
|
||||
* (imagery `kind`/`style`/`accessToken`, terrain `kind`/`accessToken`,
|
||||
* photoreal credentials, credit). The registry's factories build Cesium
|
||||
* objects, so only a Cesium engine also gets them prebuilt (`provider`,
|
||||
* `tileset`, terrain `create`): the controller then keeps resolving, caching
|
||||
* and watching them as before (supplied tilesets must be ones the engine
|
||||
* knows, i.e. its startup tileset, or has shown). Any other engine builds
|
||||
* its own sources from the descriptions; the controller constructs nothing
|
||||
* for it, falls back by description when a setter rejects, and has no tile
|
||||
* failure watch. On destroy the engine keeps what it shows; owned tilesets
|
||||
* are released and disposed. Without an engine the controller applies
|
||||
* sources to the viewer itself (standalone use and tests).
|
||||
*/
|
||||
export class MapSourceController {
|
||||
constructor(
|
||||
@@ -47,6 +53,8 @@ export class MapSourceController {
|
||||
this._onError = onError;
|
||||
this._requestRender = requestRender;
|
||||
this._engine = engine;
|
||||
/** Whether the engine takes the registry's prebuilt Cesium objects. */
|
||||
this._prebuilt = engine?.kind === 'cesium';
|
||||
this._scene = engine
|
||||
? null
|
||||
: createMapScene(viewer, { createImageryLayer });
|
||||
@@ -246,6 +254,11 @@ export class MapSourceController {
|
||||
}
|
||||
|
||||
async _activateTileset(source, gen) {
|
||||
if (this._engine && !this._prebuilt) {
|
||||
this._removeImageryLayer();
|
||||
await this._engine.maps.setPhotorealTiles(this._describeTiles(source));
|
||||
return;
|
||||
}
|
||||
let tileset = source.tileset;
|
||||
if (!tileset) {
|
||||
if (!source.createTileset)
|
||||
@@ -259,10 +272,9 @@ export class MapSourceController {
|
||||
// The engine adds an owned tileset to the scene when it shows it.
|
||||
if (!source.tileset) this._ownedTilesets.add(tileset);
|
||||
this._removeImageryLayer();
|
||||
await this._engine.maps.setPhotorealTiles({
|
||||
tileset,
|
||||
credit: source.credit ?? null,
|
||||
});
|
||||
await this._engine.maps.setPhotorealTiles(
|
||||
this._describeTiles(source, tileset),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!source.tileset && !this._ownedTilesets.has(tileset)) {
|
||||
@@ -285,6 +297,8 @@ export class MapSourceController {
|
||||
}
|
||||
|
||||
async _activateGlobeStack(stack, gen) {
|
||||
if (this._engine && !this._prebuilt)
|
||||
return this._activateDescribedGlobeStack(stack, gen);
|
||||
const resolution = await this._getImageryProvider(stack);
|
||||
if (gen !== this._switchGen) return;
|
||||
if (this._engine) return this._activateEngineGlobeStack(resolution, gen);
|
||||
@@ -325,11 +339,9 @@ export class MapSourceController {
|
||||
const source = this._sources.get(resolution.effectiveStackId);
|
||||
this._removeImageryErrorListener?.();
|
||||
this._removeImageryErrorListener = null;
|
||||
await maps.setBaseImagery({
|
||||
descriptor: source?.descriptor,
|
||||
provider: resolution.provider,
|
||||
credit: source?.credit ?? null,
|
||||
});
|
||||
await maps.setBaseImagery(
|
||||
this._describeImagery(source, resolution.provider),
|
||||
);
|
||||
if (gen !== this._switchGen) return;
|
||||
this._baseProvider = resolution.provider;
|
||||
this._watchProvider(resolution, gen);
|
||||
@@ -344,13 +356,87 @@ export class MapSourceController {
|
||||
() => terrain.create({ signal: this._abort.signal }),
|
||||
);
|
||||
if (gen !== this._switchGen) return;
|
||||
await maps.setTerrain({ id: terrain.id, create: () => result });
|
||||
await maps.setTerrain(this._describeTerrain(terrain, result));
|
||||
if (gen !== this._switchGen) return;
|
||||
this._terrainMode = terrain.id;
|
||||
}
|
||||
return resolution;
|
||||
}
|
||||
|
||||
// A non-Cesium engine builds every source from its description. A source it
|
||||
// cannot build falls back as the construction fallback says.
|
||||
async _activateDescribedGlobeStack(stack, gen, visited = new Set()) {
|
||||
const { maps } = this._engine;
|
||||
const source = this._sources.get(stack.id);
|
||||
try {
|
||||
await maps.setBaseImagery(this._describeImagery(source));
|
||||
} catch (error) {
|
||||
const fallback = source.constructionFallback;
|
||||
if (
|
||||
this._destroyed ||
|
||||
!fallback ||
|
||||
visited.has(stack.id) ||
|
||||
!this.isStackAvailable(fallback.id)
|
||||
)
|
||||
throw error;
|
||||
const resolution = await this._activateDescribedGlobeStack(
|
||||
this.getStack(fallback.id),
|
||||
gen,
|
||||
new Set(visited).add(stack.id),
|
||||
);
|
||||
return resolution && { ...resolution, fallbackMessage: fallback.message };
|
||||
}
|
||||
if (gen !== this._switchGen) return;
|
||||
await maps.setPhotorealTiles(null);
|
||||
if (gen !== this._switchGen) return;
|
||||
this._activeId = stack.id;
|
||||
if (source.terrain && source.terrain.id !== this._terrainMode) {
|
||||
await maps.setTerrain(this._describeTerrain(source.terrain));
|
||||
if (gen !== this._switchGen) return;
|
||||
this._terrainMode = source.terrain.id;
|
||||
}
|
||||
return { effectiveStackId: stack.id, fallbackMessage: null };
|
||||
}
|
||||
|
||||
/** Engine description of an imagery source (+ a prebuilt Cesium provider). */
|
||||
_describeImagery(source, provider) {
|
||||
const { descriptor } = source;
|
||||
return {
|
||||
descriptor,
|
||||
kind: descriptor.kind,
|
||||
...(descriptor.style !== undefined && { style: descriptor.style }),
|
||||
...(source.accessToken !== undefined && {
|
||||
accessToken: source.accessToken,
|
||||
}),
|
||||
credit: source.credit ?? null,
|
||||
...(provider && { provider }),
|
||||
};
|
||||
}
|
||||
|
||||
/** Engine description of a terrain source (+ the cached Cesium result). */
|
||||
_describeTerrain(terrain, result) {
|
||||
return {
|
||||
id: terrain.id,
|
||||
kind: terrain.kind ?? terrain.id,
|
||||
...(terrain.accessToken !== undefined && {
|
||||
accessToken: terrain.accessToken,
|
||||
}),
|
||||
...(result && { create: () => result }),
|
||||
};
|
||||
}
|
||||
|
||||
/** Engine description of a photoreal source (+ a prebuilt Cesium tileset). */
|
||||
_describeTiles(source, tileset) {
|
||||
return {
|
||||
descriptor: source.descriptor,
|
||||
kind: source.descriptor.kind,
|
||||
credit: source.credit ?? null,
|
||||
...(source.googleApiKey && { googleApiKey: source.googleApiKey }),
|
||||
...(source.cesiumToken && { cesiumToken: source.cesiumToken }),
|
||||
...(tileset && { tileset }),
|
||||
};
|
||||
}
|
||||
|
||||
/** The live base imagery provider, or null. */
|
||||
_liveBaseProvider() {
|
||||
return this._engine ? this._baseProvider : this._scene.baseProvider;
|
||||
|
||||
@@ -993,3 +993,75 @@ test('controller and Cesium engine: photoreal to a globe stack restores imagery
|
||||
engine.destroy();
|
||||
assert.deepEqual(credits(), []);
|
||||
});
|
||||
|
||||
// --- On a non-Cesium engine: descriptions only, nothing Cesium is built ------
|
||||
|
||||
function navaraFixture() {
|
||||
return import('../globe/testing/fakeEngine.js').then(
|
||||
({ createFakeEngine }) => {
|
||||
const engine = createFakeEngine({ kind: 'navara' });
|
||||
const registry = createDefaultMapSources({
|
||||
googleTileset: { show: true },
|
||||
cesiumToken: 'ion-test',
|
||||
googleApiKey: 'google-test',
|
||||
});
|
||||
const built = [];
|
||||
for (const source of registry.sources) {
|
||||
if (source.imagery)
|
||||
source.imagery = () => built.push(`imagery:${source.descriptor.id}`);
|
||||
if (source.terrain)
|
||||
source.terrain = {
|
||||
...source.terrain,
|
||||
create: () => built.push(`terrain:${source.terrain.id}`),
|
||||
};
|
||||
}
|
||||
const controller = new MapSourceController(null, { registry, engine });
|
||||
return { engine, controller, built };
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test('on a non-Cesium engine the controller sends descriptions and builds nothing', async () => {
|
||||
const { engine, controller, built } = await navaraFixture();
|
||||
await controller.setStack('esri-imagery');
|
||||
const [imagery] = engine.fake.callsOf('maps.setBaseImagery')[0].args;
|
||||
assert.equal(imagery.kind, 'esri-imagery');
|
||||
assert.equal(imagery.descriptor.id, 'esri-imagery');
|
||||
assert.match(imagery.credit, /Esri/);
|
||||
assert.equal('provider' in imagery, false);
|
||||
assert.equal(typeof imagery.imagery, 'undefined');
|
||||
const [terrain] = engine.fake.callsOf('maps.setTerrain')[0].args;
|
||||
assert.deepEqual(terrain, {
|
||||
id: 'world',
|
||||
kind: 'world',
|
||||
accessToken: 'ion-test',
|
||||
});
|
||||
await controller.setStack('bing-aerial');
|
||||
const [ion] = engine.fake.callsOf('maps.setBaseImagery')[1].args;
|
||||
assert.equal(ion.kind, 'ion');
|
||||
assert.equal(ion.accessToken, 'ion-test');
|
||||
assert.ok(ion.style !== undefined);
|
||||
await controller.setStack('photoreal');
|
||||
const [photoreal] = engine.fake.callsOf('maps.setPhotorealTiles').at(-1).args;
|
||||
assert.equal(photoreal.kind, 'photoreal');
|
||||
assert.equal(photoreal.googleApiKey, 'google-test');
|
||||
assert.equal(photoreal.cesiumToken, 'ion-test');
|
||||
assert.equal('tileset' in photoreal, false);
|
||||
assert.deepEqual(built, [], 'no Cesium provider, terrain or tileset built');
|
||||
assert.equal(controller.getActiveId(), 'photoreal');
|
||||
controller.destroy();
|
||||
});
|
||||
|
||||
test('on a non-Cesium engine a source the engine cannot build falls back by description', async () => {
|
||||
const { engine, controller } = await navaraFixture();
|
||||
const setBaseImagery = engine.maps.setBaseImagery;
|
||||
engine.maps.setBaseImagery = async (source) => {
|
||||
if (source.kind === 'esri-imagery') throw new Error('no Esri here');
|
||||
return setBaseImagery(source);
|
||||
};
|
||||
const state = await controller.setStack('esri-imagery');
|
||||
assert.equal(state.activeId, 'osm');
|
||||
assert.equal(state.lastError, 'Esri Satellite is unavailable; using OSM');
|
||||
assert.equal(engine.fake.maps.imagery.kind, 'osm');
|
||||
controller.destroy();
|
||||
});
|
||||
@@ -12,8 +12,16 @@ export function createDefaultMapSources({
|
||||
} = {}) {
|
||||
const ionToken = String(cesiumToken || '').trim();
|
||||
const hasIon = Boolean(ionToken);
|
||||
const hasGoogle = Boolean(String(googleApiKey || '').trim());
|
||||
const terrain = terrainSourceFor(ionToken);
|
||||
// Engine-neutral descriptions ride beside the Cesium factories: a terrain
|
||||
// `kind` (+ token), ion imagery's token and the photoreal credentials, so
|
||||
// an engine that builds its own sources needs none of the factories.
|
||||
const terrain = {
|
||||
...terrainSourceFor(ionToken),
|
||||
kind: hasIon ? 'world' : 'keyless',
|
||||
...(hasIon && { accessToken: ionToken }),
|
||||
};
|
||||
const googleKey = String(googleApiKey || '').trim();
|
||||
const hasGoogle = Boolean(googleKey);
|
||||
return {
|
||||
defaultId: googleTileset ? 'photoreal' : 'esri-imagery',
|
||||
unknownId: 'photoreal',
|
||||
@@ -33,11 +41,14 @@ export function createDefaultMapSources({
|
||||
available: Boolean(googleTileset),
|
||||
unavailableReason: photorealUnavailableReason(hasIon || hasGoogle),
|
||||
tileset: googleTileset,
|
||||
...(googleKey && { googleApiKey: googleKey }),
|
||||
...(hasIon && { cesiumToken: ionToken }),
|
||||
};
|
||||
const imagery = imageryFactoryFor(descriptor, ionToken);
|
||||
return {
|
||||
...common,
|
||||
imagery,
|
||||
...(descriptor.kind === 'ion' && { accessToken: ionToken }),
|
||||
terrain,
|
||||
...(descriptor.id === 'esri-imagery'
|
||||
? {
|
||||
|
||||
@@ -216,3 +216,26 @@ test('applyTerrain installs a terrain object or a provider', () => {
|
||||
assert.deepEqual(calls, ['terrain']);
|
||||
assert.equal(viewer.terrainProvider, 'provider');
|
||||
});
|
||||
|
||||
test('default sources carry engine-neutral descriptions beside their Cesium factories', () => {
|
||||
const sources = createDefaultMapSources({
|
||||
cesiumToken: 'ion-test',
|
||||
googleApiKey: 'google-test',
|
||||
googleTileset: { show: true },
|
||||
}).sources;
|
||||
const byId = (id) => sources.find(({ descriptor }) => descriptor.id === id);
|
||||
assert.equal(byId('bing-aerial').accessToken, 'ion-test');
|
||||
assert.equal(
|
||||
byId('osm').accessToken,
|
||||
undefined,
|
||||
'only ion imagery needs the token',
|
||||
);
|
||||
assert.equal(byId('osm').terrain.kind, 'world');
|
||||
assert.equal(byId('osm').terrain.accessToken, 'ion-test');
|
||||
assert.equal(byId('photoreal').googleApiKey, 'google-test');
|
||||
assert.equal(byId('photoreal').cesiumToken, 'ion-test');
|
||||
const keyless = createDefaultMapSources().sources;
|
||||
const osm = keyless.find(({ descriptor }) => descriptor.id === 'osm');
|
||||
assert.equal(osm.terrain.kind, 'keyless');
|
||||
assert.equal(osm.terrain.accessToken, undefined);
|
||||
});
|
||||
Reference in new issue
Block a user