3D Earth — orbital + surface-detail modes
Interactive 3D Earth that bridges two graphics stacks in one app. Three.js powers the orbital view; Cesium takes over for high-detail terrain and satellite imagery on the ground. Switch between them and the camera, the lighting, and the user’s sense of place all carry across cleanly.
Atlas exists for a small, specific moment: when you want to look at the Earth from space, then look at it from ten meters off the ground, without breaking the spell. Most 3D-Earth tools force you to pick a side — either a stylized planet that can’t zoom into anything, or a heavy GIS viewer that doesn’t feel like a planet at all.
The fix here is two graphics stacks in one app. Three.js renders the orbital scene: a textured globe, atmosphere shader, lat/lon graticule, smooth pointer-driven rotation, free-orbit camera. CesiumJS takes over for the surface mode: streamed terrain heightmaps, satellite imagery, accurate ellipsoidal positioning, real lighting based on sun position.
The seam between them is the user’s job to not notice. A coordinate object travels across the boundary — lat/lon, altitude, camera heading — so when you switch modes, the camera lands in the same place looking the same direction, just at a different scale of reality.
Three.js scene with a high-resolution Earth texture, atmosphere shader, and orbit-controls camera. Drag to rotate, scroll to zoom, double-tap to recenter. Background star field rendered as instanced points.
Type a city or paste coordinates and the camera tweens through a great-circle arc to land directly above the location. Markers persist across mode switches; the lat/lon HUD updates continuously while the camera is in motion.
Hit Surface and Cesium initializes with the same lat/lon, heading, and altitude the Three.js camera was holding. Three.js dismounts; Cesium streams in terrain tiles for the visible region.
Cesium surface mode: real terrain heightmaps, satellite imagery, accurate ellipsoidal lighting. Pan, tilt, dolly down to ground level. Switch back to Orbit and the camera is restored to its space-side state.
Atlas is a Vite-built TypeScript app deployed to GitHub Pages. The orbital layer runs on three directly — no react-three-fiber — because the integration point with Cesium needs raw access to the WebGL context and camera math. The surface layer runs on cesium’s viewer with the default OSM terrain provider and a tuned imagery layer.
The handoff is the most interesting bit. Three.js uses a unit sphere with the camera in scene space; Cesium uses WGS84 ellipsoidal coordinates with a real-Earth-radius camera. To switch cleanly, Atlas keeps a single camera state — lat, lon, altitude (km), heading, pitch — and projects it into whichever stack is active. On switch, the inactive stack is fully torn down (canvas, renderer, controls) so memory and GPU resources don’t leak.
Cesium is heavy — about 5 MB minified. It’s lazy-loaded only when the user requests Surface mode, so initial paint stays under 1.5s on the orbital view. The imagery and terrain providers stream tiles on demand; nothing is bundled.
The defining decision was refusing to make the user choose between a stylized planet and a real one. Both stacks live in the same UI shell; the toggle is the only place the seam shows.
Position, heading, altitude, pitch — one object, projected into whichever renderer is active. Switching modes shouldn’t feel like teleporting to a different app.
Cesium takes seconds to spin up. Atlas opens in orbital mode — instant, light, expressive — and only loads the heavyweight surface stack when the user opts in.
The HUD shows actual WGS84 lat/lon, not screen coordinates. Even in orbital mode, the planet is a real ellipsoid; the toy version of the globe and the GIS version of the globe agree on where things are.
Spin Earth from space, then drop down to the ground. One app, two render pipelines, no broken spell.