Skip to content
kewonitPublic

Aeris

Real-time 3D flight tracking - altitude-aware, visually stunning.

Aeris renders live air traffic over the world's busiest airspaces on a premium dark-mode map. Flights are separated by altitude in true 3D: low altitudes glow cyan, high altitudes shift to gold. Select a city, and the camera glides to that airspace with spring-eased animation.

Live Demo | HN discussion

aeris - 1 Screenshot 2026-02-15 112222 image

Stack

Layer Technology
Framework Next.js 16 (App Router, Turbopack)
Language TypeScript
Styling Tailwind CSS v4
Map MapLibre GL JS
WebGL Deck.gl 9 (ScenegraphLayer, IconLayer, PathLayer, MapboxOverlay)
Animation Motion (Framer Motion)
Data adsb.lol / adsb.fi / airplanes.live / OpenSky (4-tier fallback)
Hosting Vercel

Getting Started

pnpm install
cp .env.example .env.local
pnpm dev

Open http://localhost:3000.

Architecture

src/
├── app/
│   ├── globals.css            Tailwind config, theme vars
│   ├── layout.tsx             Root layout (Inter font)
│   ├── page.tsx               Entry - renders <FlightTracker />
│   └── api/flights/route.ts   readsb provider proxy (validation + rate limiting)
├── components/
│   ├── flight-tracker.tsx     Orchestrator - state, camera, layers, UI
│   ├── map/
│   │   ├── map.tsx            MapLibre GL wrapper with React context
│   │   ├── flight-layers.tsx  Deck.gl overlay - icons, trails, shadows, animation
│   │   ├── aircraft-model-mapping.ts  ADS-B category → 3D model key + bucketing
│   │   └── aircraft-model-layers.ts   Builds per-model ScenegraphLayers
│   └── ui/
│       ├── altitude-legend.tsx
│       ├── control-panel.tsx  Tabbed dialog - search, map style, settings
│       ├── flight-card.tsx    Hover card with flight details
│       ├── scroll-area.tsx    Custom scrollbar
│       ├── slider.tsx         Orbit speed slider (Radix)
│       └── status-bar.tsx     Live status indicator
├── hooks/
│   ├── use-flights.ts         Adaptive polling hook with credit-aware throttling
│   ├── use-settings.tsx       Settings context with localStorage persistence
│   └── use-trail-history.ts   Trail accumulation + Catmull-Rom smoothing
└── lib/
    ├── cities.ts              Curated aviation hub presets
    ├── flight-api.ts          Barrel re-export for the 4-tier flight client
    ├── flight-api-client.ts   adsb.lol → adsb.fi → airplanes.live → OpenSky fallback chain
    ├── flight-api-parsing.ts  readsb JSON → FlightState normalization
    ├── flight-api-types.ts    Shared types for ADS-B providers
    ├── flight-utils.ts        Altitude→color, unit conversions
    ├── map-styles.ts          Map style definitions
    ├── opensky.ts             OpenSky API client + types (Tier 4 fallback)
    └── utils.ts               cn() utility

Design

  • Dark-first: CARTO Dark Matter base map, theme-aware UI
  • 3D depth: 55° pitch, altitude-based z-displacement via Deck.gl

Aircraft Models

Aeris renders 14 distinct aircraft silhouettes based on ADS-B emitter category and ICAO type code:

Model Key Represents Assignment
narrowbody A320, B737 family Category 3 (Small), 4 (Large), 5 (High vortex)
widebody-2eng A330, A350, B777, B787 Category 6 (Heavy)
widebody-4eng A380, B747, A340 -
a380 Airbus A380 Type codes A38x
b737 Boeing 737 family Type codes B73x, B3xM
regional-jet CRJ, E-Jets, Fokker -
light-prop Cessna, Piper, Cirrus Category 2 (Light), 12 (Ultralight)
turboprop ATR, Dash-8, Saab -
helicopter All rotorcraft Category 8 (Rotorcraft)
bizjet Gulfstream, Citation, Learjet -
glider Sailplanes Category 9 (Glider)
fighter Military fast-movers Category 7 (High-perf)
drone UAVs Category 14 (UAV)
generic Fallback for unknown categories Category 0, 1, default

Models are optimised GLB files (no Draco compression - avoids external WASM decoder dependency) served from Cloudinary CDN (local backups in public/models/aircraft/). A second-tier mapping from ICAO type codes (A320, B738, etc.) refines the assignment when type data is available via the readsb feed.

  • Smooth animation: Catmull-Rom spline trails, per-frame interpolation between polls
  • Glassmorphism: backdrop-blur-2xl, bg-black/60, border-white/[0.08]
  • Spring physics: All UI transitions use spring easing
  • Responsive: Desktop sidebar dialog, mobile bottom-sheet with thumb-zone tab bar
  • API efficiency: Adaptive polling (30 s → 5 min) based on remaining credits, Page Visibility pause, grid-snapped cache
  • Persistence: Settings + map style in localStorage, ?city=IATA URL deep links

Environment Variables

All variables are optional - Aeris runs with no secrets. See .env.example for the full template.

Variable Required Description
NEXT_PUBLIC_GA_ID No Google Analytics 4 measurement ID.
OPENSKY_CLIENT_ID No OAuth2 client id for the server-side OpenSky trace fallback in src/lib/trails/source/server-trace-service.ts. Without it, the trace service falls back to public ADS-B aggregators.
OPENSKY_CLIENT_SECRET No OAuth2 secret that pairs with OPENSKY_CLIENT_ID. Set both or neither.
OPENAIP_API_KEY No API key used by the airspace vector-tile proxy src/app/api/airspace-tiles/route.ts. Without it the airspace overlay is disabled cleanly and the client skips OpenAIP tile requests; flight rendering is unaffected.

Live readsb data (adsb.lol, adsb.fi, then airplanes.live) is fetched through the server proxy; OpenSky is the final automatic fallback. No provider credentials are configured. The adsb.fi public API is limited to one request per second per IP for personal, non-commercial use and is credited in the application as required by its terms.

Open Aviation Data

public/data/aviation holds an aircraft registry snapshot built from the Mictronics aircraft database and the FAA releasable aircraft database. Records are split into small chunks by ICAO address, so selecting an aircraft loads one chunk of about 100 KB or less. The generator reads only registration, type, model, manufacturer, and database flags. It never copies FAA owner names or addresses.

pnpm data:refresh   # download both sources and rebuild the snapshot
pnpm data:check     # check the committed files against the manifest
pnpm test:data

A workflow rebuilds the snapshot every Monday and opens a draft PR when it changes. NOTICE.md lists the source licenses.

License

AGPL-3.0