How the Dual Map Engine Runtime-Switches Between globe.gl (3D) and deck.gl (Flat) in WorldMonitor
The WorldMonitor application switches between 3D globe.gl and flat deck.gl renderers by destroying the active map instance, preserving state through a snapshot mechanism, and instantiating the alternative engine while rehydrating cached data and callbacks.
The koala73/worldmonitor repository implements a sophisticated dual map engine that allows users to toggle between an immersive 3D globe visualization and a high-performance flat map without losing their current view, layers, or interaction state. This runtime-switch capability relies on a façade pattern centered in MapContainer, which orchestrates the destruction and reconstruction of the underlying rendering engines while maintaining seamless user experience.
Architecture Overview
The dual map engine consists of three primary components that share a common API surface, enabling interchangeable usage:
| Component | Role | Key Source |
|---|---|---|
MapContainer |
Central façade that owns DeckGLMap, MapComponent (SVG fallback), and GlobeMap. It decides which renderer to use, creates it, and forwards all public API calls to the active instance. |
src/components/MapContainer.ts |
GlobeMap |
Wrapper around globe.gl implementing 3-D visualization with methods like setLayers, setView, and render. |
src/components/GlobeMap.ts |
DeckGLMap |
Wrapper around deck.gl (WebGL) for flat map rendering, mirroring the GlobeMap API. |
src/components/DeckGLMap.ts |
The engine instantiates only one concrete map at any moment, preserving callbacks and cached data to ensure switches occur without state loss.
Runtime Switch Mechanism
State Preservation Strategy
Before destroying the active renderer, MapContainer captures the current viewport and data state using getState() and getCenter(). This snapshot includes zoom levels, layer visibility, and geographic coordinates.
// src/components/MapContainer.ts
const snapshot = this.getState();
const center = this.getCenter();
this.resizeObserver?.disconnect();
The Switch Methods
Switching to 3D Globe (switchToGlobe):
When the user selects globe mode, MapContainer destroys the flat map instance, updates internal flags, and constructs a new GlobeMap:
// src/components/MapContainer.ts (switchToGlobe)
if (this.useGlobe) return;
const snapshot = this.getState();
const center = this.getCenter();
this.resizeObserver?.disconnect();
this.destroyFlatMap();
this.useGlobe = true;
this.useDeckGL = false;
this.globeMap = new GlobeMap(this.container, this.initialState);
this.restoreViewport(snapshot, center);
this.rehydrateActiveMap();
Switching to Flat Map (switchToFlat):
The reverse operation destroys the globe instance and recreates either a DeckGLMap or SVG fallback based on WebGL2 support:
// src/components/MapContainer.ts (switchToFlat)
if (!this.useGlobe) return;
const snapshot = this.getState();
const center = this.getCenter();
this.resizeObserver?.disconnect();
this.globeMap?.destroy();
this.globeMap = null;
this.useGlobe = false;
this.useDeckGL = this.shouldUseDeckGL();
this.init();
this.restoreViewport(snapshot, center);
this.rehydrateActiveMap();
Data and Callback Persistence
The rehydrateActiveMap() method ensures that the newly created map instance receives all cached data and event handlers:
// src/components/MapContainer.ts (rehydrateActiveMap)
if (this.cachedOnStateChanged) this.onStateChanged(this.cachedOnStateChanged);
if (this.cachedEarthquakes) this.setEarthquakes(this.cachedEarthquakes);
if (this.cachedWeatherAlerts) this.setWeatherAlerts(this.cachedWeatherAlerts);
This pattern applies to all datasets (flights, hotspots, etc.) and callbacks (onHotspotClicked, onStateChanged), ensuring the user experiences a seamless transition without needing to reload data.
User Interface Integration
The toggle button in the UI triggers the runtime switch through event-handlers.ts:
// src/app/event-handlers.ts → setupMapDimensionToggle()
btn.addEventListener('click', () => {
const mode = btn.dataset.mode;
const isGlobe = mode === 'globe';
const alreadyGlobe = this.ctx.map?.isGlobeMode() ?? false;
if (isGlobe === alreadyGlobe) return;
saveToStorage(STORAGE_KEYS.mapMode, isGlobe ? 'globe' : 'flat');
if (isGlobe) this.ctx.map?.switchToGlobe();
else this.ctx.map?.switchToFlat();
});
The STORAGE_KEYS.mapMode constant is defined in src/config/variants/base.ts, and the saveToStorage utility resides in src/utils/settings-persistence.ts, ensuring the user's preference persists across sessions.
Practical Implementation Example
import { MapContainer } from '@/components/MapContainer';
const mapEl = document.getElementById('mapContainer') as HTMLElement;
const initialState = { latitude: 40.7128, longitude: -74.0060, zoom: 4 };
// Initialize in flat mode
const map = new MapContainer(mapEl, initialState, false);
// Programmatically switch to 3D globe
map.switchToGlobe();
// Later, return to flat map
map.switchToFlat();
Summary
- MapContainer.ts serves as the central façade that manages the dual map engine runtime-switch between globe.gl and deck.gl.
- The switch mechanism destroys the active renderer, captures state via
getState()andgetCenter(), instantiates the alternative engine, and restores the viewport. - Data persistence is handled through
rehydrateActiveMap(), which pushes cached datasets and callbacks into the newly created instance. - The UI toggle in
event-handlers.tspersists user preferences to local storage viasettings-persistence.tsand triggers the appropriate switch method. - Both
GlobeMap.tsandDeckGLMap.tsimplement identical public APIs, enabling seamless interchangeability without consumer code changes.
Frequently Asked Questions
How does WorldMonitor preserve map state when switching between 3D and 2D views?
Before destroying the active map instance, MapContainer captures the current viewport using getState() and getCenter(). These snapshots include zoom levels, geographic coordinates, and layer visibility. After instantiating the new renderer (either GlobeMap or DeckGLMap), the restoreViewport() method reapplies these values, ensuring the user returns to the exact same view.
What happens to data layers and event callbacks during the engine switch?
All datasets and callbacks are cached within MapContainer properties (e.g., cachedEarthquakes, cachedOnHotspotClicked). After the new map engine initializes, the rehydrateActiveMap() method pushes these cached values into the fresh instance via the public API methods like setEarthquakes() and onStateChanged(). This pattern ensures no data loss or broken interactions during the transition.
Can the application switch to 3D globe mode on devices without WebGL2 support?
No. The MapContainer constructor checks for WebGL2 availability using hasWebGLSupport(). If the device lacks support, the useGlobe flag remains false regardless of user preference, and the application falls back to either DeckGLMap (if WebGL1 is available) or the SVG-based MapComponent. The UI toggle typically disables the 3D option on unsupported devices.
Where is the user's 2D/3D preference stored between sessions?
The preference is persisted to local storage using the key defined by STORAGE_KEYS.mapMode in src/config/variants/base.ts. The saveToStorage() utility in src/utils/settings-persistence.ts handles the write operation when the user clicks the toggle button in src/app/event-handlers.ts. On application startup, loadFromStorage() retrieves this value to determine the initial map mode.
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 →