# How the Map Stack Controller Manages Imagery Switching and Emits Events

> Learn how the Map Stack Controller in bilawalsidhu/gods-eye-view seamlessly switches imagery and emits events to keep your UI in sync with asynchronous provider initialization.

- Repository: [Bilawal Sidhu/gods-eye-view](https://github.com/bilawalsidhu/gods-eye-view)
- Tags: internals
- Published: 2026-08-29

---

**The Map Stack Controller orchestrates seamless transitions between photorealistic Google 3D tiles and traditional globe imagery layers while emitting lifecycle events that keep the UI synchronized with asynchronous provider initialization.**

The Map Stack Controller in `bilawalsidhu/gods-eye-view` serves as the central authority for managing Cesium.js visualization modes. Located in [`src/mapStackController.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/mapStackController.js), this class handles the complex coordination required to switch between Google Photorealistic 3D Tiles, Bing aerial imagery, OpenStreetMap, and custom terrain providers while preventing race conditions during rapid user interactions.

## Initialization and Core State

The constructor (lines 52-71) establishes the controller's foundation by storing references to the Cesium viewer, an optional Google 3D tileset, and the Cesium Ion access token. It initializes the `_switchGen` counter as a monotonic generation tracker, creates a cache for imagery providers, and sets up the `_onChange` and `_onError` callback hooks. The controller maintains the active imagery layer reference, a terrain mode flag, and validates the initial stack configuration before marking itself ready.

## The Imagery Switching Workflow

The `setStack(id, {silent})` method (lines 57-73) serves as the public API for requesting imagery changes. When invoked, the controller validates the requested stack ID against available configurations, increments the internal `_switchGen` counter, and immediately transitions the state to **"switching"** unless the `silent` option suppresses events. The method then dispatches to specialized activation handlers based on the stack type.

### Activating Photorealistic 3D Tiles

For Google Photorealistic 3D Tiles, the controller invokes `_activatePhotoreal(gen)` (lines 174-182). This method removes any existing imagery layers from the scene, makes the Google tileset visible, and hides the Cesium globe primitive while preserving the current camera position. The generation parameter `gen` ensures this activation correlates with the specific switch request that initiated it.

### Activating Globe-Based Imagery Stacks

Traditional globe stacks—such as Bing Aerial, Bing Labels, or OSM—flow through `_activateGlobeStack(stack, gen)` (lines 236-245). This handler first retrieves the appropriate imagery provider via `_getImageryProvider(stack)` (lines 251-267), which lazily instantiates and caches `IonWorldImageryStyle` providers for Bing layers or `OpenStreetMapImageryProvider` for OSM. The method then removes existing imagery, constructs a new `Cesium.ImageryLayer`, adds it to the viewer, hides the Google tileset, and reveals the globe entity.

## Terrain Provider Coordination

The `_setWorldTerrainEnabled(enabled, gen)` method (lines 297-312) manages elevation data synchronization. When enabled and a Cesium Ion token is present, it installs Cesium World Terrain; otherwise, it falls back to a keyless Re:Earth terrain provider suitable for OSM stacks. This method checks the generation counter before committing terrain changes, allowing newer switch requests to abort outdated terrain fetches.

## Event Lifecycle and Silent Mode

The controller emits three distinct event states through the `_emitChange` helper (lines 332-334): **"switching"** when activation begins, **"ready"** upon successful completion, and **"error"** when provider initialization fails. These events propagate via the `onChange` callback registered during instantiation, delivering the current state object containing `activeId`, `activeStack`, `status`, and `lastError`.

When `setStack` receives `{silent: true}`, the controller suppresses both the intermediate **"switching"** event and the final **"ready"** or **"error"** notifications. The imagery still changes asynchronously, but UI components remain unaware, enabling batch updates or background preloading scenarios.

## Preventing Race Conditions with Generation Tracking

Every call to `setStack` increments the `_switchGen` counter. Both `_getImageryProvider` and `_setWorldTerrainEnabled` capture this generation value at invocation time and verify it before applying state mutations. If a newer switch has occurred during an asynchronous provider fetch, the stale generation detects the mismatch and aborts the commit. This "M7" pattern guarantees that slower imagery loads cannot overwrite newer user selections.

## Implementation Example

```javascript
import { MapStackController } from './src/mapStackController.js';

// Initialize with callbacks
const mapStack = new MapStackController(viewer, {
  googleTileset: google3dTileset,
  cesiumToken: process.env.CESIUM_ION_TOKEN,
  initialStack: 'photoreal',
  onChange: (state) => console.log('Status:', state.status, 'Active:', state.activeId),
  onError: (msg, stack) => console.warn('Failed to load:', msg),
});

// Standard switch with events
await mapStack.setStack('bing-aerial');

// Silent switch for background updates
await mapStack.setStack('osm', { silent: true });

```

```javascript
// Handling state changes in UI components
function handleMapStackChange(state) {
  if (state.status === 'switching') {
    showLoadingIndicator();
  } else if (state.status === 'ready') {
    updateChipSelection(state.activeId);
    hideLoadingIndicator();
  } else if (state.status === 'error') {
    showErrorToast(state.lastError);
  }
}

mapStack._onChange = handleMapStackChange;

```

## Summary

- The Map Stack Controller in [`src/mapStackController.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/mapStackController.js) manages all imagery transitions between Google 3D Tiles and globe-based providers through the `setStack()` API.
- **Generation tracking** via `_switchGen` prevents race conditions when users switch stacks faster than providers can initialize.
- The controller emits **"switching"**, **"ready"**, and **"error"** events via the `onChange` callback, with an optional **silent mode** to suppress notifications.
- Terrain providers automatically adjust based on stack requirements and token availability through `_setWorldTerrainEnabled()`.
- Failed globe stack activations automatically fall back to the photorealistic Google 3D view while emitting error details.

## Frequently Asked Questions

### What specific events does the Map Stack Controller emit during imagery switching?

The controller emits three primary lifecycle events through the `onChange` callback: **"switching"** immediately when `setStack()` is called and validation passes, **"ready"** after the imagery provider and terrain successfully initialize, and **"error"** when provider creation or terrain fetching fails. Each event includes the complete state object with `activeId`, `activeStack`, and `lastError` properties.

### How does the controller prevent race conditions during rapid stack changes?

Every `setStack()` invocation increments a monotonic `_switchGen` counter. Async methods like `_getImageryProvider()` and `_setWorldTerrainEnabled()` capture this generation value locally and verify it matches the current controller state before applying changes. If a newer switch has occurred, the stale operation detects the generation mismatch and aborts, ensuring only the latest user selection remains active.

### What happens when an imagery provider fails to load?

When `_activateGlobeStack()` encounters a provider error, the controller captures the failure reason in `_unavailableReason`, automatically falls back to the photorealistic Google 3D tileset if available, and emits an **"error"** event via `_emitChange()` (lines 85-92). This ensures the map never enters a broken state while notifying the application layer through the `onError` callback.

### Can I switch imagery without triggering UI updates?

Yes. Pass `{silent: true}` as the second argument to `setStack()`. In silent mode, the controller skips the **"switching"** event emission and suppresses the final **"ready"** or **"error"** notifications, though the underlying imagery and terrain changes still execute asynchronously. This is useful for preloading stacks or batching multiple updates into a single UI refresh.