---
title: "Map Engine"
description: "3D globe and flat map rendering, textures, shaders, clustering, and mobile touch gestures in World Monitor."
---

### Dual Map Engine — 3D Globe + Flat Map

Two rendering engines are available, switchable at runtime via Settings or the `VITE_MAP_INTERACTION_MODE` environment variable (`globe` or `flat`). The preference is persisted in localStorage.

**3D Globe (globe.gl + Three.js)** — a photorealistic 3D Earth with full pitch and rotation:

- **Earth textures** — topographic-bathymetric day surface (`earth-topo-bathy.jpg`), specular water map for ocean reflections, and a starfield night-sky background
- **Atmosphere shader** — a Fresnel limb-glow effect simulates atmospheric scattering at the globe's edge
- **Auto-rotation** — the globe slowly rotates when idle, pausing on any user interaction and resuming after 60 seconds of inactivity
- **HTML marker layer** — all 28+ data categories (conflicts, intel hotspots, AIS vessels, flights, protests, fires, etc.) render as HTML elements pinned to geographic coordinates on the globe surface
- **Geopolitical polygon overlays** — the Korean DMZ and other boundary polygons render directly on the globe under the conflicts layer
- **Debounced marker flush** — rapid data updates are coalesced via `debounceFlushMarkers()` to prevent Three.js scene graph crashes during high-frequency data refresh
- **Configurable render quality** — a Settings dropdown offers five pixel-ratio presets: Auto (matches device DPR, capped at 2×), Eco (1×), Sharp (1.5×), 4K (2×), and Insane (3×). The setting updates the Three.js renderer live without page reload. Desktop (Tauri) builds cap the default at 1.25× to avoid software-rendering fallback on machines without discrete GPUs
- **Desktop-optimized defaults** — Tauri desktop builds request the high-performance GPU (`powerPreference: 'high-performance'`), disable the logarithmic depth buffer (saves shader overhead), and turn off auto-rotation and camera damping to eliminate continuous render loop wakeups when idle — addressing reports of 1 fps performance on some machines
- **Background pause** — when the desktop app window loses focus or the globe panel is hidden, the WebGL render loop pauses entirely, stopping the Three.js animation loop and canceling auto-rotate. Data updates received while paused are queued and flushed in a single batch when the globe returns to view, eliminating background GPU load on laptops
- **Beta indicator** — a pulsing cyan "BETA" badge appears when the globe is active, signaling the feature is newer than the flat map

### Map Tile Providers

The flat map supports multiple tile providers, selectable at runtime via **Settings → Map Tile Provider**. The selection persists in localStorage.

| Provider | Description | Cost | Default |
|----------|-------------|------|---------|
| **OpenFreeMap** | Free community-hosted OpenStreetMap tiles. Dark and Positron styles. No API key required. | Free | Yes (when `VITE_PMTILES_URL` is unset) |
| **CARTO** | CARTO's Dark Matter and Voyager GL styles. No API key required. | Free tier | No |
| **PMTiles (self-hosted)** | Self-hosted vector tiles via the [PMTiles](https://protomaps.com/docs/pmtiles) format. Tiles are served as a single `.pmtiles` archive file over HTTP Range requests — the browser only downloads tiles for the current viewport (~50-200KB per pan/zoom), not the entire file. Requires `VITE_PMTILES_URL` environment variable. | Self-hosted | Yes (when `VITE_PMTILES_URL` is set) |
| **Auto** | Tries PMTiles first, falls back to OpenFreeMap on error (2+ tile load failures or 10s timeout). Only available when `VITE_PMTILES_URL` is set. | Self-hosted | No |

**OSS-friendly defaults**: When `VITE_PMTILES_URL` is not set (the default), only OpenFreeMap and CARTO appear in Settings. PMTiles and Auto options are hidden. This ensures community installations work out of the box with zero configuration and no external tile hosting costs.

**Self-hosting PMTiles**: To use your own tiles:

1. Download a PMTiles planet file from [Protomaps builds](https://maps.protomaps.com/builds/) or generate a regional extract with [`go-pmtiles extract`](https://github.com/protomaps/go-pmtiles)
2. Upload to any HTTP server that supports Range requests (Cloudflare R2, S3, or a static file server)
3. Set `VITE_PMTILES_URL=https://your-server.example/planet.pmtiles` in `.env.local`
4. The PMTiles and Auto options will appear in Settings

### Map Themes

Each tile provider offers different visual themes, selectable via **Settings → Map Theme**. The theme selection is **per-provider** — switching providers remembers each provider's last-used theme. Map theme is fully independent of the app theme (Auto/Dark/Light); the app theme only affects UI chrome, while the map theme controls basemap appearance.

| Provider | Available Themes | Default |
|----------|-----------------|---------|
| **PMTiles** | Black (deepest dark), Dark, Grayscale, Light, White | Black |
| **OpenFreeMap** | Dark, Positron (light) | Dark |
| **CARTO** | Dark Matter, Voyager (light), Positron (light) | Dark Matter |

**Sprite mapping**: PMTiles themes `black`, `dark`, and `grayscale` use the `dark` Protomaps sprite sheet; `light` and `white` use the `light` sprite sheet.

**Overlay paint adaptation**: Country highlight/hover paint colors automatically adapt to the selected map theme (not the app theme), using lower opacity on light themes for visibility.

**Fallback behavior**: When using PMTiles or Auto mode, if tile loading fails (CORS errors, server downtime, 403s), the map automatically falls back to OpenFreeMap after detecting 2+ errors within 10 seconds. The fallback respects the current map theme's light/dark nature — a light PMTiles theme falls back to OpenFreeMap Positron, not Dark. A console warning is logged when fallback activates.

**Flat Map (deck.gl + MapLibre GL JS)** — a WebGL-accelerated 2D map with smooth 60fps rendering and thousands of concurrent markers:

- **Layer types** — `GeoJsonLayer`, `ScatterplotLayer`, `PathLayer`, `IconLayer`, `TextLayer`, `PolygonLayer`, `ArcLayer`, `HeatmapLayer` composited in a single render pass
- **Smart clustering** — Supercluster groups markers at low zoom, expands on zoom in. Cluster thresholds adapt to zoom level
- **Progressive disclosure** — detail layers (bases, nuclear, datacenters) appear only when zoomed in; zoom-adaptive opacity fades markers from 0.2 at world view to 1.0 at street level
- **Label deconfliction** — overlapping labels (e.g., multiple BREAKING badges) are automatically suppressed by priority, highest-severity first
- **Day/night overlay** — a terminator line divides the map into sunlit and dark hemispheres based on the current UTC time

**Shared across both engines:**

- **45 data layers** — conflicts, military bases, nuclear facilities, undersea cables, pipelines, satellite fire detection, protests, natural disasters, datacenters, displacement flows, climate anomalies, cyber threat IOCs, GPS/GNSS jamming zones, Iran attacks, CII country risk heatmap, day/night terminator, geopolitical boundaries (Korean DMZ), stock exchanges, financial centers, central banks, commodity hubs, Gulf investments, trade routes, airport delays, sanctions regimes, and more. All layer definitions are maintained in a single shared catalog (`map-layer-definitions.ts`) consumed by both renderers — adding a new layer is a single-file operation. Layers are variant-specific: full (29 geopolitical + military + infrastructure), tech (12 startup/cloud/cyber), finance (15 exchange/banking/trade), and happy (5 positive-events/conservation)
- **8 regional presets** — Global, Americas, Europe, MENA, Asia, Africa, Oceania, Latin America
- **Time filtering** — 1h, 6h, 24h, 48h, 7d event windows
- **URL state sharing** — map center, zoom, active layers, and time range are encoded in the URL for shareable views (`?view=mena&zoom=4&layers=conflicts,bases`)
- **Mobile touch gestures** — single-finger pan with inertial velocity animation (0.92 decay factor, computed from 4-entry circular touch history), two-finger pinch-to-zoom with center-point preservation, and bottom-sheet popups with drag-to-dismiss. An 8px movement threshold prevents accidental interaction during taps
- **Timezone-based region detection** — on first load, the map centers on the user's approximate region derived from `Intl.DateTimeFormat().resolvedOptions().timeZone` — no network dependency, no geolocation prompt. On mobile, the browser's Geolocation API is queried (5-second timeout) and the map auto-centers on the user's precise GPS coordinates at zoom level 6. If the URL already contains shared coordinates, the shared view takes precedence and geolocation is skipped
- **Cmd+K map navigation** — the command palette supports `Map:` prefixed commands to fly to any country or region on either engine

### Static Map Assets & Geocoding

Country boundaries, boundary overrides, and the geocoding service are documented in [Maps & Geocoding](/maps-and-geocoding). All large static files are served from R2 CDN via `maps.worldmonitor.app`.
