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:

  1. Validation: Checks isStackAvailable(id) (lines 195-201) to verify prerequisites (Ion tokens, Google tileset access)
  2. Generation increment: Captures current generation number
  3. State transition: Sets _isSwitching = true and emits "switching" event unless silent option is passed
  4. Delegation: Routes to _activatePhotoreal() for 3D tiles or _activateGlobeStack() for imagery layers
  5. Error handling: Catches failures and triggers fallback to Google Photorealistic 3D or OSM
  6. 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:

  1. Retrieves or creates provider via _getImageryProvider() (lines 336-376)
  2. Removes previous imagery layers
  3. Instantiates new ImageryLayer with the provider
  4. Synchronizes Esri attribution through _syncEsriAttribution() (lines 319-334)
  5. Enables Cesium globe rendering
  6. 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
};
  • src/renderGovernor.js: Supplies governorRequestRender to trigger Cesium redraws after layer switches
  • src/keySetupCore.mjs: Generates human-readable requirement messages when Ion or Google credentials are missing, consumed by isStackAvailable()

Summary

  • MAP_STACKS defines all available basemaps with prerequisite metadata in src/mapStackController.js lines 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
  • _activatePhotoreal disables the globe and shows Google 3D tiles, while _activateGlobeStack manages 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →