# How Map Stack Switching Works in Gods-Eye-View: Events and Implementation

> Learn how Gods-Eye-View's MapStackController handles map stack switching and its custom events. Understand state transitions and data source changes effortlessly.

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

---

**Map stack switching in Gods-Eye-View is orchestrated by the `MapStackController` class in [`src/mapStackController.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/mapStackController.js), which emits three custom events—`mapstackchange`, `mapstackerror`, and `mapstackswitch`—to signal state transitions between map data sources.**

The Gods-Eye-View visualization platform supports multiple map data sources, from photorealistic tiles to OpenStreetMap layers. Understanding **map stack switching** is essential for developers customizing the UI or debugging layer transitions. This process is centralized in the `MapStackController` class, which manages state validation, resource loading, and event broadcasting throughout the application lifecycle.

## Core Switching Architecture

### The MapStackController Class

The **`MapStackController`** class extends `EventTarget` and serves as the single source of truth for map layer state. It exposes the `setStack(stackId, options)` method, which initiates all stack transitions. According to the source code in [`src/mapStackController.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/mapStackController.js), the controller maintains an internal generation counter to track switch attempts and prevent race conditions during rapid successive calls.

The class validates incoming requests through `normalizeStackId`, throwing immediately if the requested ID does not exist in the registry returned by `getStacks()`. This validation occurs at line 86 of [`src/mapStackController.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/mapStackController.js) before any resource loading begins.

### The Switching Lifecycle

The transition follows a strict four-phase lifecycle implemented in [`src/mapStackController.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/mapStackController.js):

1. **Request Validation**: The UI layer invokes `mapStackController.setStack()`, passing a stack ID string. The controller verifies the ID against available stacks and increments the internal generation counter.

2. **State Transition**: The `_activateStack` method (line 158) marks the previous stack as "switching out" and begins asynchronous loading of the new stack's resources, including tilesets, terrain data, and imagery layers.

3. **Event Emission**: Immediately upon initiation, the controller fires the `mapstackswitch` event. Upon completion or failure, it emits either `mapstackchange` or `mapstackerror` through its internal `_emitChange`, `_emitError`, and `_emitSwitching` helpers.

4. **Resolution**: The `setStack` method returns a Promise that resolves at line 112 once the new stack finishes loading, updating `activeId`, `lastError`, and the generation tracker.

## Events Emitted During Stack Switches

`MapStackController` leverages the native `EventTarget` interface to broadcast custom events. UI components in [`src/ui.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/ui.js) register listeners during initialization to update chips, status bars, and share-link metadata.

### mapstackswitch

Fired immediately after initiating a switch but before resources finish loading. This event signals the start of transitions and carries a payload containing `{ fromId, toId, generation }`. The UI uses this to display loading indicators while waiting for tile data. This emission occurs in the `_emitSwitching` helper around line 132 of [`src/mapStackController.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/mapStackController.js).

### mapstackchange

Dispatched after a successful stack activation when the new layer becomes fully visible. The payload includes `{ id, label, generation }`, allowing listeners to update displayed metadata. This event fires from `_emitChange` at line 140 of [`src/mapStackController.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/mapStackController.js).

### mapstackerror

Triggered if resource loading fails due to missing Cesium ion tokens, unavailable terrain endpoints, or network errors. The payload contains `{ id, error, generation }`, enabling error reporting and fallback UI states. This originates in `_emitError` at line 147 of [`src/mapStackController.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/mapStackController.js).

## UI Integration and Component Coordination

The UI layer consumes these events through the `_setMapStack` method defined at line 3505 of [`src/ui.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/ui.js). This facade method wraps the controller's API and ensures the visualization state remains synchronized across subsystems.

When `mapstackchange` fires, the following components react:

- **StyleManager**: Receives the active stack ID via `styleManager.setMapStack()` to apply correct shading rules (e.g., night-day lighting adjustments).
- **Camera/Scene Policy**: Adjusts default altitudes and lighting parameters based on the new data source's characteristics.
- **Share Link Generator**: Encodes the current stack ID into the URL at line 3837 of [`src/ui.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/ui.js), ensuring permalink restoration of the correct map source.

The application bootstrap in [`src/main.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/main.js) instantiates a single `MapStackController` instance and injects it into dependent subsystems during initialization.

## Practical Implementation Example

The following pattern demonstrates programmatic stack switching and event handling:

```javascript
// Access the controller from the UI instance
const controller = uiInstance.mapStackController;

// Switch to photorealistic tiles
await uiInstance._setMapStack('photoreal');

// Listen for successful transitions
controller.addEventListener('mapstackchange', (e) => {
  console.log(`Activated: ${e.detail.label} (gen ${e.detail.generation})`);
});

// Handle loading states
controller.addEventListener('mapstackswitch', (e) => {
  showLoadingSpinner(`Switching from ${e.detail.fromId} to ${e.detail.toId}`);
});

// Capture errors
controller.addEventListener('mapstackerror', (e) => {
  console.error(`Failed to load ${e.detail.id}:`, e.detail.error);
});

```

This implementation leverages the Promise returned by `setStack` for async/await patterns while using event listeners for reactive UI updates.

## Summary

- **MapStackController** in [`src/mapStackController.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/mapStackController.js) centralizes all stack switching logic and state management.
- The controller emits three distinct events: `mapstackswitch` (start), `mapstackchange` (success), and `mapstackerror` (failure).
- Event payloads include generation counters to handle race conditions and metadata like stack IDs and error objects.
- The UI layer in [`src/ui.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/ui.js) consumes these events to synchronize visual components, camera positions, and shareable URLs.
- All switching operations return Promises that resolve upon resource loading completion or rejection upon validation errors.

## Frequently Asked Questions

### How does Gods-Eye-View prevent race conditions during rapid stack switches?

The `MapStackController` maintains an internal **generation counter** that increments with every `setStack()` call. When asynchronous loading completes, the controller compares the current generation against the generation captured at request time. If they do not match, the callback discards the result, ensuring only the most recent switch request affects the UI state.

### What error conditions trigger the `mapstackerror` event?

The event fires when the controller fails to activate a requested stack due to **missing Cesium ion tokens**, unavailable terrain endpoints, network timeouts, or invalid layer configurations. The error payload includes the target stack ID and the specific Error object, allowing the UI to display targeted failure messages or trigger fallback stacks.

### Can external plugins listen for stack changes without modifying [`src/ui.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/ui.js)?

Yes. Since `MapStackController` extends `EventTarget`, any module with access to the controller instance can attach listeners using `addEventListener()`. The controller instance is exposed through the main application context created in [`src/main.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/main.js), allowing third-party extensions to react to `mapstackchange` events for custom visualization overlays or analytics tracking.

### What is the difference between `mapstackswitch` and `mapstackchange` events?

The **`mapstackswitch`** event fires immediately when `setStack()` initiates a transition, providing `{ fromId, toId, generation }` for loading indicators. The **`mapstackchange`** event fires only after the new stack's resources finish loading and the layer becomes active, providing `{ id, label, generation }` for final state updates. This separation allows UIs to distinguish between "loading" and "completed" states.