feat(maps): map sources for the Navara engine

createDefaultMapSources takes the engine kind. On Navara, Google 3D is
available from the browser key alone (Navara builds the tiles itself and
has no ion route to them), so it is the default and the recovery stack
when a key is set; without one the default is Esri imagery. Bing (Cesium
ion imagery) has no Navara path and is unavailable with a reason. World
terrain still needs an ion token and falls back to the keyless terrain.
MapStackController passes the kind of the engine it is given; Cesium
sources are unchanged.
This commit is contained in:
German Khokhlov committed 2026-10-15 20:55:59 +04:00
1 parent d112ecf296
commit 72fec292d4
4 files changed
+109 -10

No files matched your search

+7 -1
View File
@@ -9,7 +9,13 @@ export class MapStackController extends MapSourceController {
constructor(viewer, options = {}) {
const googleApiKey =
typeof window !== 'undefined' ? window.__GOOGLE_MAPS_API_KEY__ : '';
const registry = createDefaultMapSources({ ...options, googleApiKey });
// Sources for the page's engine: Navara builds its own from the
// descriptions (src/maps/defaultSources.js).
const registry = createDefaultMapSources({
...options,
googleApiKey,
engineKind: options.engine?.kind === 'navara' ? 'navara' : 'cesium',
});
super(viewer, {
registry,
initialStack: options.googleTileset
+25
View File
@@ -22,3 +22,28 @@ test('the map stack controller applies its stacks through a given globe engine',
assert.equal(engine.fake.callsOf('maps.setPhotorealTiles')[1].args[0], null);
controller.destroy();
});
test('on a Navara engine the map stack controller opens on Google 3D from the browser key alone', async (t) => {
const previous = globalThis.window;
globalThis.window = { __GOOGLE_MAPS_API_KEY__: 'google-test' };
t.after(() => {
if (previous === undefined) delete globalThis.window;
else globalThis.window = previous;
});
const engine = createFakeEngine({ kind: 'navara' });
const controller = new MapStackController(null, { engine });
assert.equal(controller.getActiveId(), 'photoreal');
assert.equal(controller.isStackAvailable('bing-aerial'), false);
await controller.setStack('photoreal');
const [photoreal] = engine.fake.callsOf('maps.setPhotorealTiles')[0].args;
assert.equal(photoreal.kind, 'photoreal');
assert.equal('tileset' in photoreal, false);
controller.destroy();
window.__GOOGLE_MAPS_API_KEY__ = '';
const keyless = new MapStackController(null, {
engine: createFakeEngine({ kind: 'navara' }),
});
assert.equal(keyless.getActiveId(), 'esri-imagery');
assert.equal(keyless.isStackAvailable('photoreal'), false);
keyless.destroy();
});
+29 -9
View File
@@ -4,12 +4,26 @@ import { keySetupRequirement } from '../keySetupCore.mjs';
import { imageryFactoryFor, ESRI_ATTRIBUTION_HTML } from './imagery.js';
import { terrainSourceFor } from './terrain.js';
/** Select sources and setup guidance without putting provider branches in the controller. */
const NAVARA_ION_IMAGERY_REASON =
'Bing imagery is not available on the experimental Navara engine';
/**
* Select sources and setup guidance without putting provider branches in the
* controller. `engineKind` is the globe engine kind the sources are for: Cesium
* builds them from the factories (photoreal needs its loaded tileset); Navara
* builds them from the descriptions, so photoreal needs only the browser key
* (Navara has no ion route to Google tiles) and Cesium ion imagery (Bing) is
* unavailable.
*/
export function createDefaultMapSources({
googleTileset = null,
cesiumToken = '',
googleApiKey = '',
engineKind = 'cesium',
} = {}) {
if (engineKind !== 'cesium' && engineKind !== 'navara')
throw new TypeError(`Unknown globe engine: ${engineKind}`);
const navara = engineKind === 'navara';
const ionToken = String(cesiumToken || '').trim();
const hasIon = Boolean(ionToken);
// Engine-neutral descriptions ride beside the Cesium factories: a terrain
@@ -22,24 +36,30 @@ export function createDefaultMapSources({
};
const googleKey = String(googleApiKey || '').trim();
const hasGoogle = Boolean(googleKey);
const photorealAvailable = navara ? hasGoogle : Boolean(googleTileset);
return {
defaultId: googleTileset ? 'photoreal' : 'esri-imagery',
defaultId: photorealAvailable ? 'photoreal' : 'esri-imagery',
unknownId: 'photoreal',
recoveryId: googleTileset ? 'photoreal' : null,
recoveryId: photorealAvailable ? 'photoreal' : null,
state: { hasCesiumIonToken: hasIon },
sources: MAP_STACKS.map((descriptor) => {
const ionImageryOnNavara = navara && descriptor.kind === 'ion';
const common = {
descriptor,
available: !descriptor.requiresIon || hasIon,
unavailableReason: descriptor.requiresIon
? keySetupRequirement('cesium-ion')
: null,
available: !ionImageryOnNavara && (!descriptor.requiresIon || hasIon),
unavailableReason: ionImageryOnNavara
? NAVARA_ION_IMAGERY_REASON
: descriptor.requiresIon
? keySetupRequirement('cesium-ion')
: null,
};
if (descriptor.kind === 'photoreal')
return {
...common,
available: Boolean(googleTileset),
unavailableReason: photorealUnavailableReason(hasIon || hasGoogle),
available: photorealAvailable,
unavailableReason: photorealUnavailableReason(
navara ? hasGoogle : hasIon || hasGoogle,
),
tileset: googleTileset,
...(googleKey && { googleApiKey: googleKey }),
...(hasIon && { cesiumToken: ionToken }),
+48
View File
@@ -239,3 +239,51 @@ test('default sources carry engine-neutral descriptions beside their Cesium fact
assert.equal(osm.terrain.kind, 'keyless');
assert.equal(osm.terrain.accessToken, undefined);
});
test('on the Navara engine photoreal keys on the browser key and ion imagery is unavailable', () => {
const byId = (registry, id) =>
registry.sources.find(({ descriptor }) => descriptor.id === id);
// Navara builds Google tiles from the browser key alone; no Cesium tileset.
const keyed = createDefaultMapSources({
engineKind: 'navara',
googleApiKey: 'google-test',
});
assert.equal(keyed.defaultId, 'photoreal');
assert.equal(keyed.recoveryId, 'photoreal');
assert.equal(byId(keyed, 'photoreal').available, true);
assert.equal(byId(keyed, 'photoreal').googleApiKey, 'google-test');
assert.equal(byId(keyed, 'photoreal').tileset, null);
// Bing (Cesium ion imagery) has no Navara path, with or without a token.
for (const registry of [
keyed,
createDefaultMapSources({ engineKind: 'navara', cesiumToken: 'ion-test' }),
]) {
for (const id of ['bing-aerial', 'bing-labels']) {
assert.equal(byId(registry, id).available, false);
assert.match(byId(registry, id).unavailableReason, /Navara/);
}
assert.equal(byId(registry, 'esri-imagery').available, true);
assert.equal(byId(registry, 'osm').available, true);
}
// The ion route to Google tiles is Cesium's; a token alone is not enough.
const ionOnly = createDefaultMapSources({
engineKind: 'navara',
cesiumToken: 'ion-test',
});
assert.equal(byId(ionOnly, 'photoreal').available, false);
assert.equal(ionOnly.defaultId, 'esri-imagery');
assert.equal(byId(ionOnly, 'osm').terrain.kind, 'world');
// No key: base imagery over keyless terrain.
const keyless = createDefaultMapSources({ engineKind: 'navara' });
assert.equal(keyless.defaultId, 'esri-imagery');
assert.equal(keyless.recoveryId, null);
assert.equal(byId(keyless, 'photoreal').available, false);
assert.equal(byId(keyless, 'esri-imagery').terrain.kind, 'keyless');
});
test('default sources reject an unknown engine', () => {
assert.throws(
() => createDefaultMapSources({ engineKind: 'unreal' }),
/Unknown globe engine/,
);
});