How mapStackController.js Handles Switching Between Different Basemaps in Cesium
MapStackController orchestrates basemap transitions by validating stack availability, implementing generation-counter race protection, and delegating to specialized activation routines for photorealistic 3D tiles or traditional globe imagery layers.
The MapStackController class in the bilawalsidhu/gods-eye-view repository serves as the central authority for managing Cesium viewer basemaps. It maintains a registry of available map stacks, handles asynchronous provider loading, and ensures UI state remains synchronized during transitions between satellite imagery, OSM, Esri, and Google Photorealistic 3D tilesets.
Defining Available Basemap Stacks
At initialization, the controller loads a static array of basemap configurations from src/mapStackController.js lines 19-57.
const MAP_STACKS = [
{
id: 'google-photoreal',
label: 'Google Photorealistic 3D Tiles',
kind: 'photoreal',
requiresGoogleTileset: true
},
{
id: 'bing-aerial',
label: 'Bing Maps Aerial',
kind: 'globe',
provider: 'bing'
},
// ... additional stacks
];
Each entry specifies metadata including id, label, kind (either photoreal or globe), and prerequisite flags such as requiresIonToken or requiresGoogleTileset.
State Tracking and Race Condition Protection
The constructor (lines 90-110) initializes critical state variables:
_activeId: Currently active stack identifier_switchGen: Monotonic generation counter for race protection_isSwitching: Boolean flag indicating transition in progress_imageryProviders: Cache for instantiated providers
Generation-based cancellation prevents stale async operations from overwriting newer selections. Every call to setStack() increments _switchGen, creating a unique token for that transition. Subsequent async callbacks verify their generation matches the current _switchGen; if not, they abort immediately (lines 214-218 and 292-294). This M7 pattern ensures that a slow-loading Esri provider cannot replace a more recently selected Bing basemap.
The Core Switching API
The public setStack(id, {silent}) method (lines 203-255) provides the primary interface for switching between different basemaps:
await controller.setStack('bing-aerial');
Execution flow:
- Validation: Checks
isStackAvailable(id)(lines 195-201) to verify prerequisites (Ion tokens, Google tileset access) - Generation increment: Captures current generation number
- State transition: Sets
_isSwitching = trueand emits "switching" event unlesssilentoption is passed - Delegation: Routes to
_activatePhotoreal()for 3D tiles or_activateGlobeStack()for imagery layers - Error handling: Catches failures and triggers fallback to Google Photorealistic 3D or OSM
- Finalization: Updates
_activeId, clears switching state, emits "ready" or "error" event
Activating Photorealistic vs. Globe Stacks
The controller handles two distinct rendering modes:
Photorealistic 3D Tiles (Google)
_activatePhotoreal() (lines 269-285) manages transitions to Google Photorealistic 3D Tiles:
- Hides existing imagery layers via
viewer.imageryLayers.removeAll() - Disables the Cesium globe by setting
viewer.scene.globe.show = false - Displays the Google tileset entity
- Leaves terrain configuration unchanged (photoreal stacks render their own elevation data)
Traditional Globe Imagery (Bing, Esri, OSM)
_activateGlobeStack() (lines 288-305) handles standard imagery providers:
- Retrieves or creates provider via
_getImageryProvider()(lines 336-376) - Removes previous imagery layers
- Instantiates new
ImageryLayerwith the provider - Synchronizes Esri attribution through
_syncEsriAttribution()(lines 319-334) - Enables Cesium globe rendering
- Applies appropriate terrain via
_setWorldTerrainEnabled()(lines 441-458)
Provider Caching and Error Recovery
_getImageryProvider implements intelligent caching to avoid reconstructing expensive tile providers. It supports:
- Cesium Ion imagery with token authentication
- Esri tiled map services with manual attribution injection
- OpenStreetMap as keyless fallback
Esri Failure Handling
Because Cesium ignores the credit option for ArcGIS sources, _syncEsriAttribution manually adds static on-screen credits when Esri layers activate. If tile requests fail twice, _watchEsriProvider (lines 387-410) automatically triggers fallback to OSM, ensuring the map never remains blank.
Terrain Selection
_setWorldTerrainEnabled toggles between:
- Cesium World Terrain (requires valid Ion token)
- Re:Earth terrain or
EllipsoidTerrainProvider(keyless fallback)
This method also respects the generation counter, aborting terrain loads superseded by newer basemap selections.
Querying Controller State
The getState() method (lines 258-267) returns a comprehensive snapshot:
const state = controller.getState();
// Returns: { activeId, activeStack, stacks, status, error }
This enables UI components to build dropdown menus reflecting availability:
const options = controller.getStacks().map(s => ({
id: s.id,
label: s.label,
disabled: !s.available,
title: s.unavailableReason || ''
}));
Practical Implementation Examples
Basic Basemap Switching
import { MapStackController } from './src/mapStackController.js';
const controller = new MapStackController(viewer, {
cesiumToken: import.meta.env.VITE_CESIUM_ION_TOKEN,
onChange: (state) => console.log('Basemap changed:', state.activeStack.label),
onError: (msg) => console.error('Map error:', msg)
});
// Switch to Bing Aerial imagery
await controller.setStack('bing-aerial');
Reacting to State Changes
controller._onChange = (state) => {
document.getElementById('basemap-label').textContent = state.activeStack.label;
document.getElementById('loading-indicator').style.display =
state.status === 'switching' ? 'block' : 'none';
};
Handling Provider Failures
controller._onError = (message, stackId) => {
showToast(`Basemap error: ${message}`);
// Controller automatically falls back to OSM or Google Photoreal
};
Related Architecture Files
src/renderGovernor.js: SuppliesgovernorRequestRenderto trigger Cesium redraws after layer switchessrc/keySetupCore.mjs: Generates human-readable requirement messages when Ion or Google credentials are missing, consumed byisStackAvailable()
Summary
- MAP_STACKS defines all available basemaps with prerequisite metadata in
src/mapStackController.jslines 19-57 setStack()provides the async public API for switching between different basemaps with validation and event emission- _switchGen counter prevents race conditions when users rapidly switch between slow-loading providers
_activatePhotorealdisables the globe and shows Google 3D tiles, while_activateGlobeStackmanages traditional imagery layers with Esri attribution handling- Automatic fallback to OSM or Google Photorealistic 3D occurs when providers fail or tokens are missing
- Terrain synchronization ensures elevation data matches the active basemap requirements
Frequently Asked Questions
How does the controller prevent basemap flickering during rapid switches?
The implementation uses a generation counter (_switchGen) that increments with every setStack() call. Each async operation (provider loading, terrain initialization) captures the generation number at invocation and validates it upon completion. If the generation has changed, the operation aborts, ensuring only the most recent basemap selection renders.
What happens if the Esri basemap fails to load tiles?
mapStackController.js monitors Esri providers through _watchEsriProvider (lines 387-410). After two tile load failures, it automatically triggers a fallback to OpenStreetMap. Additionally, _syncEsriAttribution manually manages on-screen credits when Esri layers activate or deactivate, working around Cesium's limitation with ArcGIS credit options.
Can I switch basemaps without triggering change events?
Yes. Pass { silent: true } as the second argument to setStack():
await controller.setStack('esri-world-imagery', { silent: true });
This prevents the "switching" and "ready" events from firing, useful for programmatic initialization or background restoration of saved state.
How do I check if a specific basemap is available before switching?
Call isStackAvailable(id) (lines 195-201) to verify prerequisites:
const canUsePhotoreal = controller.isStackAvailable('google-photoreal');
// Returns false if Google tileset not configured, with reason in unavailableReason
This method checks for required Ion tokens, Google tileset access, and other dependencies defined in the stack configuration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →