A real-time global intelligence dashboard rendered on a 3D CesiumJS globe.
Track flights, satellites, ships, earthquakes, traffic, and CCTV cameras — all in one tactical interface.
🔗 Live Demo — worldview.kt-o.com
preview.mp4
WorldView is a full-stack tactical intelligence platform that aggregates multiple real-time data sources onto an interactive 3D globe. Inspired by military command-and-control interfaces, it features a dark tactical UI with optional post-processing effects (CRT scanlines, night vision, thermal imaging).
| Layer | Source | Update Rate | Description |
|---|---|---|---|
| FlightRadar24 + adsb.fi | 5–20 s | ~27,000 global aircraft with altitude bands, route arcs, dead-reckoning | |
| 🛰️ Satellites | CelesTrak TLE + SGP4 | 2 s propagation | Real-time orbital position, orbit paths, ground tracks, nadir lines |
| 🌋 Earthquakes | USGS GeoJSON | 60 s | Past 24 hours, magnitude-scaled pulsing markers with colour coding |
| 🚗 Traffic | OpenStreetMap Overpass | On-demand | Road network overlay with animated vehicle particle simulation |
| � Naval / AIS | AISStream.io WebSocket | 30 s (20 s burst + 60 s cache) | Global vessel tracking with ship type categorisation, heading trails, dead-reckoning |
| �📹 CCTV | TfL, Austin TX, Transport NSW | 5 min | Live camera feeds from London, Austin, and New South Wales |
Boot sequence → 3D globe with tactical overlays → CCTV surveillance panel
The interface features:
- Splash screen — Military-style boot sequence with typewriter animation
- Operations panel (left) — Layer toggles, shader modes, altitude filters
- Intel feed (right) — Real-time event stream from all data sources
- Status bar (bottom) — Camera coordinates (DMS), UTC clock, entity counts
- Tracked entity panel — Lock-on detail view (ESC to unlock)
- Crosshair overlay — Centre-screen targeting reticle
- React 19 — Functional components with hooks
- TypeScript 5.9 — Strict mode, bundler module resolution
- CesiumJS 1.138 via Resium — 3D globe rendering
- Tailwind CSS v4 — Utility-first styling with custom tactical colour tokens
- Vite 7 — Dev server with HMR, Cesium plugin, API proxy
- satellite.js — SGP4/SDP4 satellite orbit propagation
- Express 5 — API proxy server
- node-cache — In-memory response caching with TTL
- WebSocket (ws) — Real-time flight data push channel + AISStream.io burst WebSocket for AIS vessel data
- dotenv — Environment variable management
- Imperative Cesium primitives —
BillboardCollection,PointPrimitiveCollection,PolylineCollection,LabelCollectionfor high-performance rendering of 27K+ entities - Dead-reckoning — Aircraft positions extrapolated between API updates at 60 fps
- GLSL post-processing — CRT scanlines, night-vision green phosphor, FLIR thermal palette via
PostProcessStage - CallbackProperty — Smooth entity tracking without React re-renders
- Node.js ≥ 18
- npm ≥ 9
# Clone the repository
git clone <repository-url>
cd worldview
# Install dependencies
npm install
⚠️ This repo does NOT ship any API keys. You must obtain your own.
Copy the example files and fill in your credentials:
cp .env.example .env
cp server/.env.example server/.envThen edit each file with your own API keys (see Obtaining API Keys below).
.env — Client-side (Vite injects VITE_* variables at build time):
| Variable | Required? | Purpose |
|---|---|---|
VITE_GOOGLE_API_KEY |
Optional | Google Maps 3D Photorealistic Tiles (falls back to OpenStreetMap) |
VITE_CESIUM_ION_TOKEN |
Optional | Cesium Ion terrain/imagery services |
WINDY_API_KEY |
Optional | Windy webcam API (reserved, not yet active) |
NSW_TRANSPORT_API_KEY |
Optional | Transport for NSW CCTV cameras |
AISSTREAM_API_KEY |
Optional | AISStream.io global AIS ship tracking |
server/.env — Server-side (loaded by dotenv):
| Variable | Required? | Purpose |
|---|---|---|
GOOGLE_MAPS_API_KEY |
Optional | Server-side Google Maps (currently unused) |
OPENSKY_CLIENT_ID |
Optional | OpenSky Network OAuth2 credentials |
OPENSKY_CLIENT_SECRET |
Optional | OpenSky Network OAuth2 credentials |
AISSTREAM_API_KEY |
Optional | AISStream.io global AIS ship tracking |
All layers degrade gracefully when keys are missing — the globe falls back to OpenStreetMap, CCTV sources without keys are simply skipped, and external APIs that don't require auth (USGS, CelesTrak, adsb.fi) work without any credentials.
- Go to the Google Cloud Console
- Create a new project (or select an existing one)
- Navigate to APIs & Services → Library
- Enable the Map Tiles API
- Go to APIs & Services → Credentials → Create Credentials → API Key
- IMPORTANT — Restrict your key immediately (see Securing Your Google API Key below)
- Copy the key into
VITE_GOOGLE_API_KEYin your.envfile
Google offers a US$200/month free tier which covers approximately 25,000 3D Tiles loads. For personal/demo usage this is typically more than enough — but set a budget alert just in case.
- Sign up for a free account at cesium.com/ion
- Go to Access Tokens → copy your default token
- Paste into
VITE_CESIUM_ION_TOKENin your.env
- Register at opendata.transport.nsw.gov.au
- Go to My Applications → Create Application
- Subscribe to the Traffic & Cameras API
- Copy your API key into
NSW_TRANSPORT_API_KEYin your.env
- Sign up for a free account at aisstream.io
- Log in → navigate to your Dashboard
- Generate an API key
- Paste into
AISSTREAM_API_KEYin both.envandserver/.env
The free tier provides access to the global AIS WebSocket stream. The backend uses a "burst" pattern — connecting for 20 seconds to collect vessel data, then caching results for 60 seconds — to stay well within Vercel's serverless function timeout.
- Register at opensky-network.org
- Log in → go to OAuth → Create Client
- Copy the client ID and secret into
server/.env
# Start the backend proxy server (port 3001)
npm run dev:server
# In a separate terminal, start the Vite dev server (port 5173)
npm run dev
# Or start both at once
npm run dev:allOpen http://localhost:5173 in your browser.
Note: The backend proxy server must be running for data layers to function. Vite's dev server proxies all
/api/*requests tolocalhost:3001.
npm run build
npm run previewThis project is pre-configured for Vercel via vercel.json. The Express backend runs as a Vercel Serverless Function.
- Push your repo to GitHub
- Go to vercel.com/new → Import Git Repository
- Select your repo → Vercel auto-detects the Vite framework from
vercel.json - Click Deploy
In your Vercel project dashboard:
- Go to Settings → Environment Variables
- Add each key from both
.envfiles:
| Variable | Environment |
|---|---|
VITE_GOOGLE_API_KEY |
Production, Preview |
VITE_CESIUM_ION_TOKEN |
Production, Preview |
WINDY_API_KEY |
Production, Preview |
NSW_TRANSPORT_API_KEY |
Production, Preview |
GOOGLE_MAPS_API_KEY |
Production, Preview |
OPENSKY_CLIENT_ID |
Production, Preview |
OPENSKY_CLIENT_SECRET |
Production, Preview |
AISSTREAM_API_KEY |
Production, Preview |
Note:
VITE_*variables are embedded in the client bundle at build time. Server-side variables are available to the serverless function at runtime.
┌──────────────────────────────────────────────────────────┐
│ Browser (Vite Dev) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────────┐ │
│ │ React Hooks │ │ App.tsx │ │ UI Components │ │
│ │ (data fetch) │──│ (state mgr) │──│ OperationsPanel │ │
│ │ │ │ │ │ IntelFeed │ │
│ └──────┬───────┘ └──────┬──────┘ │ StatusBar │ │
│ │ │ │ CCTVPanel │ │
│ │ ┌──────┴──────┐ │ TrackedEntity │ │
│ │ │ GlobeViewer │ └──────────────────┘ │
│ │ │ (Cesium) │ │
│ │ │ ┌─────────┐ │ │
│ │ │ │ Layers │ │ │
│ │ │ │ Flight │ │ │
│ │ │ │ Sats │ │ │
│ │ │ │ Quakes │ │ │
│ │ │ │ Traffic │ │ │
│ │ │ │ CCTV │ │ │
│ │ │ └─────────┘ │ │
│ │ └─────────────┘ │
└─────────┼──────────────────────────────────────────────────┘
│ /api/* proxy
┌─────────▼──────────────────────────────────────────────────┐
│ Express Proxy Server (:3001) │
│ │
│ ┌──────────┐ ┌───────────┐ ┌──────────────────────────┐ │
│ │ node-cache│ │ WebSocket │ │ REST Endpoints │ │
│ │ (TTL) │ │ (ws) │ │ /api/flights │ │
│ └──────────┘ └───────────┘ │ /api/flights/live │ │
│ │ /api/satellites │ │
│ │ /api/earthquakes │ │
│ │ /api/traffic/roads │ │
│ │ /api/ships │ │
│ │ /api/cctv │ │
│ │ /api/cctv/image (proxy) │ │
│ │ /api/health │ │
│ └──────────────────────────┘ │
└─────────────────────────┬──────────────────────────────────┘
│
┌───────────────┼───────────────┬───────────────┐
▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ FlightRadar │ │ USGS │ │ TfL / Austin │ │ AISStream.io │
│ adsb.fi │ │ CelesTrak │ │ NSW Transport│ │ (AIS WSS) │
│ OpenSky │ │ Overpass API│ │ │ │ │
└─────────────┘ └─────────────┘ └──────────────┘ └──────────────┘
- React hooks poll the Express proxy server at layer-specific intervals
- Express caches upstream API responses, manages OAuth2 tokens, and hides credentials
- App.tsx manages global state — layer visibility, camera position, tracked entity, shader mode
- Layer components receive data via props and render imperatively into the Cesium scene
- UI components display controls, feeds, and status information as React DOM overlays
WorldView merges two aircraft data sources for optimal coverage:
| Source | Coverage | Update Rate | Data Richness |
|---|---|---|---|
| FlightRadar24 | Global (7 zones) | 20 s | Origin/destination airports, airline |
| adsb.fi | Regional (250 NM radius) | 5 s | High-frequency position updates |
- When zoomed out: FR24 global data only
- When zoomed in: adsb.fi replaces FR24 for nearby aircraft (deduplicated by ICAO24)
- Route info from FR24 is cross-referenced to enrich adsb.fi data
worldview/
├── server/ # Backend proxy (Node.js ESM)
│ ├── index.js # All routes, WebSocket, AIS burst, caching
│ ├── .env # Server secrets
│ └── data/
│ └── sydneyRoads.js # Static fallback road geometry
├── src/
│ ├── App.tsx # Root component — state, hooks, composition
│ ├── main.tsx # ReactDOM entrypoint
│ ├── index.css # Tailwind v4 + tactical theme + Cesium overrides
│ ├── components/
│ │ ├── globe/
│ │ │ ├── GlobeViewer.tsx # Cesium Viewer (3D tiles, OSM, shader mgmt)
│ │ │ └── EntityClickHandler.tsx # Click-to-track, ESC unlock
│ │ ├── layers/
│ │ │ ├── FlightLayer.tsx # 27K aircraft (imperative, dead-reckoning)
│ │ │ ├── SatelliteLayer.tsx # SGP4 orbit propagation
│ │ │ ├── EarthquakeLayer.tsx # Pulsing seismic markers
│ │ │ ├── TrafficLayer.tsx # Roads + animated vehicles
│ │ │ ├── ShipLayer.tsx # AIS vessels (imperative, dead-reckoning)
│ │ │ └── CCTVLayer.tsx # Camera markers (imperative)
│ │ └── ui/
│ │ ├── OperationsPanel.tsx # Layer/shader/filter controls
│ │ ├── IntelFeed.tsx # Real-time event feed
│ │ ├── CCTVPanel.tsx # Camera grid + preview
│ │ ├── StatusBar.tsx # Coords, clock, data counts
│ │ ├── SplashScreen.tsx # Boot sequence
│ │ ├── TrackedEntityPanel.tsx # Lock-on detail
│ │ └── Crosshair.tsx # SVG targeting reticle
│ ├── data/
│ │ └── airports.ts # IATA → coordinates lookup
│ ├── hooks/
│ │ ├── useFlights.ts # Global FR24 polling
│ │ ├── useFlightsLive.ts # Regional adsb.fi polling
│ │ ├── useSatellites.ts # TLE fetch + SGP4 pipeline
│ │ ├── useEarthquakes.ts # USGS polling
│ │ ├── useTraffic.ts # Road fetch + vehicle simulation
│ │ ├── useShips.ts # AIS vessel polling + burst WebSocket
│ │ └── useCameras.ts # CCTV aggregation
│ ├── shaders/
│ │ └── postprocess.ts # GLSL: CRT, NVG, FLIR
│ └── types/
│ └── camera.ts # CameraFeed, CameraSource types
├── .env # Client-side env vars
├── package.json
├── vite.config.ts # Vite + React + Cesium + Tailwind + proxy
├── tsconfig.json # Project references
├── tsconfig.app.json # Strict TS for src/
└── eslint.config.js
Click any entity on the globe to lock the camera onto it. The view follows the entity in real-time with an appropriate offset:
- Aircraft — 50 km trailing offset with heading alignment
- Satellites — 200 km offset for orbital viewing
- Earthquakes — 100 km overhead view of the epicentre
- Ships — 20 km offset at low angle with heading alignment
- CCTV cameras — 2 km offset at 45° viewing angle
Press ESC to unlock tracking without moving the camera.
| Mode | Effect |
|---|---|
| Standard | No post-processing |
| CRT | Scanlines, chromatic aberration, barrel distortion, vignette |
| NVG | Green phosphor, noise grain, bloom, vignette |
| FLIR | White-hot thermal palette, Sobel edge detection, high contrast |
| Mode | Description |
|---|---|
| Google 3D | Google Photorealistic 3D Tiles (requires API key) |
| OSM | OpenStreetMap 2D imagery (no key required — default fallback) |
- Altitude band filtering: Cruise (>35K ft), High (25–35K), Mid (10–25K), Low (1–10K), Ground (<1K)
- Route arcs: Great-circle paths between origin/destination airports with altitude curves
- Dead-reckoning: Smooth position interpolation at 60 fps between data updates
- Colour coding: Cyan (cruise) → Green (high) → Amber (mid) → Orange (low)
- SGP4 propagation: Real-time position from TLE orbital elements, updated every 2 seconds
- Orbit paths: 90-point polylines showing 90 minutes of predicted trajectory
- Ground tracks: Surface projection of the orbit path
- Nadir lines: Vertical lines from satellite to ground directly beneath
- ISS highlighting: Distinct styling for the International Space Station
- Burst WebSocket pattern: Connects to AISStream.io for 20 seconds on cache miss, collects 2,000–4,000 vessels globally, then caches for 60 seconds
- Moving-only filter:
?moving=1excludes moored, anchored, and aground vessels (navStatus codes) + SOG < 0.5 kt threshold - Ship type categorisation: 9 categories — Cargo (blue), Tanker (orange), Passenger (green), Fishing (amber), Military (red), Tug/Pilot (purple), Pleasure (teal), High-Speed (pink), Other (grey)
- Heading trails: Short wake polylines behind each vessel based on course-over-ground
- Dead-reckoning: Smooth position interpolation at 60 fps using SOG and COG between data updates
- Globe occlusion: Vessels on the far side of the globe are automatically hidden
- Loading indicator: Amber pulsing "LOADING" state while first burst WebSocket completes
- Multi-source aggregation: London (TfL JamCams), Austin TX (Open Data), NSW Australia (Transport API)
- Country filtering: Toggle cameras by country (GB, US, AU)
- Image proxy: Backend proxies camera images to avoid CORS issues
- Thumbnail grid: Paginated camera grid (30 per page) with lazy-loaded previews
- Fly-to: Click any camera to lock the globe view onto its location
| Method | Endpoint | Cache TTL | Description |
|---|---|---|---|
GET |
/api/flights |
30 s | Global aircraft (FR24 → adsb.fi fallback) |
GET |
/api/flights/live?lat=X&lon=Y&dist=Z |
4 s | Regional high-freq aircraft (adsb.fi) |
GET |
/api/satellites?group=stations |
2 hr | TLE text data (3-line format) |
GET |
/api/earthquakes |
60 s | USGS GeoJSON feed (past 24 hours) |
GET |
/api/traffic/roads?south=X&west=Y&north=Z&east=W |
24 hr | Road network from Overpass API |
GET |
/api/ships?moving=1 |
60 s (20 s burst) | Global AIS vessel positions via AISStream.io burst WebSocket |
GET |
/api/cctv?country=XX&source=YY |
5 min | Aggregated CCTV camera feeds |
GET |
/api/cctv/image?url=ENCODED_URL |
60 s | CORS image proxy |
GET |
/api/health |
— | Server health + cache stats |
WS |
/ws |
— | Real-time flight push (subscribe via JSON) |
| Token | Hex | Usage |
|---|---|---|
wv-black |
#0A0A0A |
Background |
wv-dark |
#111111 |
Panel backgrounds |
wv-panel |
#1A1A1A |
Elevated surfaces |
wv-border |
#2A2A2A |
Borders, dividers |
wv-muted |
#666666 |
Disabled/secondary text |
wv-text |
#CCCCCC |
Primary text |
wv-cyan |
#00D4FF |
Primary accent, flights |
wv-green |
#39FF14 |
Satellites, success states |
wv-amber |
#FF9500 |
Warnings, earthquakes |
wv-red |
#FF3B30 |
Errors, CCTV, alerts |
wv-teal |
#00BFA5 |
Secondary accent |
Monospace font stack: JetBrains Mono, Fira Code, SF Mono, monospace
- Panel glass —
backdrop-blur(12px)with 85% black background - Scanline overlay — 8-second animated sweep from top to bottom
- Glow classes —
.glow-cyan,.glow-green,.glow-ambertext-shadow effects
| Command | Description |
|---|---|
npm run dev |
Start Vite dev server (port 5173) |
npm run dev:server |
Start Express proxy (port 3001) |
npm run dev:all |
Start both servers concurrently |
npm run build |
TypeScript compilation + Vite production build |
npm run lint |
ESLint across all .ts/.tsx files |
npm run preview |
Serve production build locally |
- Create a hook in
src/hooks/— fetch data from the backend, return typed state - Create a layer component in
src/components/layers/— render Cesium primitives - Add a proxy endpoint in
server/index.js— cache upstream API, hide credentials - Wire into App.tsx — add layer toggle state, invoke hook, pass data to layer component
- Update OperationsPanel — add toggle control for the new layer
- Update StatusBar — add entity count display
- Use imperative Cesium primitives (
BillboardCollection,PointPrimitiveCollection) for layers with >100 entities - Avoid creating Resium
<Entity>elements in loops for large datasets - Use
useCallbackanduseMemoliberally — the Cesium render loop is sensitive to reference changes - Prefer
CallbackPropertyover React state for Cesium entity positions - Implement dead-reckoning for moving entities to maintain 60 fps between data updates
| Issue | Solution |
|---|---|
| Blank globe / no tiles | Check VITE_GOOGLE_API_KEY is valid with Maps JavaScript API enabled; the app falls back to OSM automatically |
| No flight/satellite/earthquake data | Ensure the backend proxy is running (npm run dev:server) |
| CCTV images not loading | Backend must be running to proxy images through /api/cctv/image |
| "429 Too Many Requests" in console | Upstream API rate limit hit; the cache layer reduces frequency, wait for TTL to expire |
| Overpass API timeout | Traffic layer falls back to static Sydney CBD road data |
| Satellites not appearing | TLE API may be temporarily down; CelesTrak is used as automatic fallback |
| Ships not loading / empty layer | Ensure AISSTREAM_API_KEY is set in server/.env; initial load takes ~20 s while the burst WebSocket collects data |
| Only a few ships visible | Toggle off the moving-only filter by removing ?moving=1; some regions have less AIS coverage |
| Google 3D tiles error | API key may be invalid or quota exceeded; OSM is applied automatically |
- FlightRadar24 — Global flight tracking
- adsb.fi — Open ADS-B aircraft data
- OpenSky Network — Open aircraft surveillance data
- USGS Earthquake Hazards — Real-time earthquake feeds
- CelesTrak — Satellite TLE orbital data
- TLE API — Satellite TLE data service
- OpenStreetMap / Overpass API — Road network data
- Transport for London — London traffic cameras
- City of Austin Open Data — Austin traffic cameras
- Transport for NSW — NSW traffic cameras
- AISStream.io — Global AIS vessel tracking via WebSocket
- CesiumJS + Resium — 3D globe rendering
- satellite.js — SGP4/SDP4 orbit propagation
- Turf.js — Geospatial analysis utilities
No API keys, tokens, or credentials are included in this repository.
All sensitive values are loaded from .env files which are excluded via .gitignore. If you fork or clone this repo, you must supply your own API keys.
If you discover a credential leak or security issue, please open an issue immediately.
-
.envandserver/.envare in.gitignore(they are by default) - No API keys hardcoded in source files
- Google API key has HTTP referrer restrictions applied
- Google API key is restricted to Map Tiles API only
- Budget alerts configured in Google Cloud Console
This project is for educational and demonstration purposes only. External API usage is subject to each provider's terms of service and rate limits. No commercial use is intended.
You are responsible for securing your own API keys and managing your own API usage costs. The authors accept no liability for charges incurred through misconfigured or unrestricted API credentials.