Inside Epic.Projector.Earth

How a dozen NASA photographs a day become a video of the Earth you can actually use.

Epic.Projector.Earth is the continuation of the Blueturn project, but as a web app instead of the legacy Blueturn Unity3D app. Like Blueturn, Projector.Earth’s purpose is to show videos of the Earth based on real imagery. NASA’s EPIC camera, on DSCOVR at Lagrange point L1, does not send a movie. It sends stills: ten to twenty full-disc photographs a day, real color, the whole sunlit planet in one frame. So we download them, store them, project them on a sphere, and interpolate through time so that images one or two hours apart become a continuous video. Performance is good enough to stay fully interactive at maximum image quality and fidelity.

Let's go behind the scenes and explain the architecture and design, from the lowest level (caching) to the highest (UX choices).

Live: app.blueturn.earth · epic.projector.earth · this paper


Contents


Audience

This is a technical paper of the architectural and front-end choices behind the current web app. I speak in terms of methods and approaches, rather than code or even architecture. It is accessible to any user from the About panel in the app's main menu. So the audience is about the same as for Blueturn and the Unity3D years, but with the broader accessibility of a web deployment.


Purpose

The purpose is to show videos of the Whole Earth based on real imagery.

The main engine downloads and stores images from NASA’s DSCOVR / EPIC archive, and interpolates them through time to create a continuous video although the source images are separated by 1 to 2 hours. You can scrub, play, zoom, and share.

In the default view (aka "DSCOVR view"), we do not paint a night side that the camera never saw. We still do it synthetically in some situations, when the user rotates the Earth.

Whole-Earth disc from DSCOVR, EPIC frames interpolated in time

DSCOVR view. Two real EPIC photographs, blended for the current time. The disc is calibrated onto the sphere; the clock is in the corner.


Vision

We are focusing our technology efforts on aesthetics and ergonomics, in order to convey a great emotional experience: the kind that already brought astronauts to another level of environmental consciousness after seeing the Earth from above, the kind that will awaken the poet inside each one of us.

It is meant to be supported on all possible screens and platforms, from a watch to giant screens in cities.


Constraint: 10 to 20 photographs a day

EPIC does not stream video. The satellite has one high-rate antenna on Earth, at Wallops Island, Virginia. It can dump Earth imagery only while that station is in view. Overnight it stores frames on board, and onboard storage is small. So you get about twelve photographs a day in winter, closer to twenty in summer when the Virginia night is shorter. For an eclipse, or a lunar photobomb, NASA sometimes remotely reprograms the satellite's firmware to shoot every 15-20 minutes (usually at the expense of other images of the day).

The result is jumpy, even at that eclipse rate. This is why we need interpolation if we want a smooth video experience. Still, the more key frames, the better the result.

Each frame is 2048×2048. The Earth fills most of the disc. That is roughly twenty kilometers per pixel. This is not a detail-oriented Google Earth thing. This is a big-picture portrait of a planet, with time as the other axis.

The satellite is not perfectly on the Earth-Sun line. It flies a so-called "Lissajous" orbit around L1, so the Sun-Earth-camera angle wanders by about ten degrees. A real EPIC disc is therefore not a uniformly lit circle. You already see the start of the terminator as a slightly heavier shadow on one limb. That is the data.


Concept: Time is rotation

From L1, the camera’s yearly motion around the Sun is slow compared with Earth’s daily spin. Advance the clock twenty-four hours and the planet has turned once. So rotating the Earth and moving through time are the same gesture.

Drag horizontally and you are sliding along the archive, not spinning a texture on a ball. Play, and the day unwinds at a chosen speed: real time, an hour a second, six hours a second. Pause, drag vertically, and you jump a day while keeping the hour. That is how you watch one place through a year.

The globe is the clock. A lot of the rest follows from that.


Graphics technique: No mesh, just a pixel shader

Traditional graphics use a mesh of triangles, and a so-called fragment shader (or pixel shader) that fills those triangles per pixel.

In this app there is no 3D mesh of the Earth, just a screen-facing quad of 2 triangles. The picture is drawn by a single full-screen pixel shader. The GPU gets two triangles that cover the canvas. For every pixel it asks: am I looking at the sphere, and if so, which point on the ground is this? Then it samples the right photographs. In other words, the 3D projection happens at the pixel level.

This is how a lot of 3D content is rendered nowadays, with pixel precision rather than mesh approximation.

All the projection math lives on the GPU, per pixel. The CPU never builds continents. It stays free to fetch, decode, and decide which two frames the current moment sits between.

A static Blue Marble lives underneath, as a fallback. When EPIC frames are missing, too far apart, or still downloading, we lean on that base so the screen is never blank. The living photograph fades in when it is ready.


Interpolation

The interpolation algorithm is based on projecting the images on a sphere and blending them linearly in geodetic space. The blend factor is computed on the CPU from the timestamps; the mix happens on the GPU, per pixel. It is visually good enough provided the images are close enough, i.e. up to about two hours, still honest around four. Past that, clouds have moved by the width of a country and you get a double exposure. We stop. The shader fades toward Blue Marble. If the gap is a void of days, we skip rather than smear ghosts across an ocean.

Geometry interpolates too, not only the pixels: disc size, the point on Earth facing the camera, the orientation that puts that point in the center. The Sun direction is interpolated as a unit vector so the lighting does not pop. None of this invents weather. It only fills the gap between two real photographs.


Calibration

A raw EPIC file is a photograph of a disc, not a globe. Calibration is hanging that disc on the Earth.

NASA’s metadata gives, for each frame, the satellite’s position and the latitude and longitude of the centroid (the ground point looking straight at the camera). From the centroid we build an orientation: a rotation that faces that point toward you, north-up. From the satellite’s distance we size the disc so the photograph’s limb lands on the sphere instead of floating as a stamp. Get the radius wrong and Africa slides off the planet. Get the orientation wrong and the clouds are weather on the wrong country.

The shader then inverts that orientation per pixel: world normal → camera space of that photograph → texture coordinate inside the disc. Only the front of the sphere, inside the unit disc, is allowed to sample. Sampling the back would show a mirrored ghost of the dayside. That would be inventing a night the camera never photographed.

The night side of Earth from L1 is not in the data. When we later added a geostationary camera that can see night, we darkened what the photographs do not cover and laid city lights on a base map. Simulated, and labelled as such. Never offered as EPIC.


Fake part: seeing the invisible

The DSCOVR view is as real as possible. Between source images really coming from space, we do add fake interpolated frames. But the maths is simple and explicit enough that this is as close as we can get to a real image, given the data we have. After all, an image is essentially fake: it is just a set of lit pixels based on the amount of light detected, and based on the necessary color filter to be defined.

But when you leave the DSCOVR viewpoint and perspective, and for example orbit as if from geostationary altitude, the rule changes. Time-bracketing would pick the wrong photographs: the camera has moved, so the disc you want is the one whose centroid is closest to your longitude, not the one whose clock is closest to now. The free camera asks for the nearest west and east discs and crossfades them by longitude. It also exposes the night side, which we render with an artificial darkness terminator, and we also add city lights from a static database. The clock stays honest. The imagery follows the gaze.

As we "intervene" more in the picture, we really exit reality to enter the synthetic world. But again, we do our best with the data we have, trying to invent as little data as possible.

Pipeline

When an image is not available at the current time, the view used to stall. Now it shows Blue Marble until that frame has walked the pipeline. All these steps are what it takes to turn NASA images into a video experience:

Download. Metadata first: which days exist, which frames on that day, timestamps, centroids, satellite positions. Pixels second, as ordinary image files. We can use either NASA’s public archive server, or our own mirror in the cloud, in case of a shutdown on NASA's side. The app does not really care which source it uses. Backends are swappable behind a facade.

Cache. Four tiers, fastest first. 1- The GPU-bound memory holds live textures that are currently in use. 2- RAM holds a downloaded image blob. 3- A persistent store in the browser keeps the compressed files across visits. 4- The network is last, with backoff when the server asks us to slow down. These caches are maintained by classic least-recently-used (LRU) eviction so a long scrub cannot fill the card.

Decode. A JPEG is not a texture. The browser has to decode it before the shader can sample it. While you play we stay on JPEG, because we may need dozens of frames in flight. When you pause and settle, we can refine the two bracketing frames to PNG: same photograph, less compression, worth it for a still. That is why pausing can feel a bit sharper.

Texture upload. The decoded image is copied into GPU memory. That is the moment a frame becomes visible, and the moment it starts costing VRAM. The cache keeps the two frames you are looking at, plus a prediction of the next ones, and forgets the rest before the device complains.

The pipeline is abortable at every stage. If you jump to another year while Tuesday is still downloading, Tuesday download is cancelled, and its downloaded content is often evicted. Fast scrubbing must not flood the network with work you will never see.


Predictive buffering

Buffering that only reacts is always late.

The data layer looks at how you are moving and fetches ahead by guessing your next move. During playback it preloads the frames of the next few seconds we will need. During a scrub it preloads neighbors in both directions, because you might reverse. Pause shows an analemma control for the same hour across months; the year fill into the persistent cache (not GPU memory) starts when you tap, so a year of noons can play without hitching, and without downloading a year for someone who only paused.

Every speculative loop carries a timestamp of why it started. If the current time has moved on, the loop exits. We would rather throw away a half-downloaded file than make you wait on a file you are no longer asking for.

The shader is fast. Feeling instant is a cache that guessed right, and a cancel that guessed wrong quickly.


Shader, in one pass

One fragment program does the whole look. Per pixel, roughly:

  1. If you are zoomed, apply a lens around the geographic point you pressed, so that point stays under your finger as the planet turns.
  2. Reconstruct a sphere normal from the pixel (orthographic from DSCOVR’s distance; perspective when the camera has come closer).
  3. Rotate that normal by the current view: DSCOVR lock, geostationary orbit, or the Moon.
  4. Sample Blue Marble, then the two EPIC photographs, each with its own calibration.
  5. Mix the two photographs by the interpolation factor, then mix that result with Blue Marble depending on how confident we are that EPIC is present and close in time.
  6. Mask to the sphere.
  7. Average a few samples per pixel for anti-aliasing. No extra pass, no extra framebuffer.

Anti-aliasing is two samples. Three would be prettier and more expensive. The Earth is already the most expensive thing on the page.


Views

DSCOVR is the native view: whole Earth, Sun behind you, the photograph as taken. This is the so-called EPIC image (taken from the EPIC camera), which we do not fake.

L1 looks like DSCOVR, but it is exactly at L1 (while DSCOVR turns around it), i.e. in the line of the Sun. That will give better analemma. But some limbs before fake.

Geostationary pulls the camera in to about 36,000 km above the equator, like a geostationary satellite. You can drag in longitude and in latitude. Night, stars, and a Sun are simulated here because this viewpoint can see them and EPIC cannot. By default the orbit springs back to the EPIC centroid when you let go. You can pin it if you want to stay.

Earth from geostationary altitude, same EPIC photographs, closer camera

Same EPIC photographs, closer camera. Night, stars, and Sun at this altitude are simulated. The caption on screen says so.

Moon is an Earthrise: Earth centered, a wide lunar arc along the bottom of the frame. Phases are opposite (new Moon is full Earth) because that is the geometry. The path in and out is a spline that always looks at Earth. A teleport would break it.

Earthrise: the same interpolated Earth, camera on the lunar line of sight

Moon view: exact perspective for an Earth-rise effect

Zoom is not a camera dolly. It is a pivot. The place you pointed becomes a geographic anchor. As time advances, that place stays on screen, so you finally see the clouds move instead of watching Earth’s rotation carry them out of frame. That is what makes a sandstorm over Egypt watchable.


Earth interaction

The input layer is a gesture recognizer. It listens to mouse, touch, and wheel, and emits intent: drag, pinch, long-press, swipe. The rest of the app never sees a raw pointer event. That is what lets the same canvas mean different things when you are playing versus paused.

The mapping is meant to be learned with the hands, not a manual:

On a desktop, keys do the important bits: space, arrows for a day or a frame, a key for the Moon. The date in the corner is a picker. Speed is a list of honest ratios, from one second per second to six hours per second.

The chrome fades after a few seconds of stillness. Move, and it comes back. The planet never fades.


Buttons, and the decision to hide them

There is a play control, a speed, a date, a jump-to-latest, a menu. That's a lot of clutter on the screen, even though we invested effort on the layout. But sometimes, nothing better than a plain view of the Earth without any overlay distraction. This is what happens when not touching anything for a few seconds. Here is the screen:

The screen without any UI, just the plain Earth

The screen without any UI, just the plain Earth


Analemma feature

There is also an analemma control that appears when you pause: a triangle opposite the date, with the line this hour, a year. It does not download that year until you tap. Then it fills as those frames land in the persistent cache, and you can play before the year is complete, from the oldest day already in, toward the day you paused on. It is allowed to be a bit mysterious. What it does (Earth’s figure-eight of the Sun, by keeping the same time-of-day and moving the day) is mysterious until you watch it.

Paused: the analemma offers a year of this hour. Tap to load; play can start from the oldest day already in.


Immersion

We are focusing on the feeling, not the HUD.

That is why the renderer is a shader and not a scene graph with gizmos. Why idle chrome vanishes. Why the first paint is already a planet (Blue Marble), not a spinner. Why playback starts on. Why a long-press and a drag are enough to live in the archive without opening a panel.

It is also why we still refuse a lot of “beauty.” Stars around a full Earth would look spectacular and would be wrong: Earth from space is about three times as bright as the Moon, and EPIC cannot expose for both. A crisp night side from L1 would be wrong. Synthetic clouds in a data gap would be wrong. We would rather show a quieter planet than a prettier fiction.

The app installs as a web app if you want it on the home screen. There is no store, no account required to look. The Earth is public domain. Our work on top of it is Creative Commons.


Platforms: from Unity to the browser

The ancestor Blueturn was a Unity application. It ran on Windows, on Android, and in a WebGL build. It taught us the gestures and the interpolation. It also taught us the cost of a game engine when the product is one sphere and a lot of networking: long load, awkward web deployment, a crash surface we did not want, a pipeline that wanted meshes and cameras when we wanted a shader and a cache. One of the open tasks on the original paper was simply “Fix WebGL crash.”

So the new guy is a web application on purpose. Vanilla JavaScript, WebGL2, GLSL. No React, no typescript, and no UI framework. The page is GPU-bound and small; a framework would add weight without buying a better Earth.

Unity, Android, Windows, WebGL: those were the right names for the first decade. For this one: a canvas, a fragment program, a service worker if you install it, and any device that can run a modern browser. It is just about sharing a link, without asking permission from a store.


Other choices that shape the feel

A few decisions were not on the original outline, and they do a lot of the work.

JPEG on the move, PNG when you stop. Playback and prefetch stay on JPEG so the cache can hold a runway of frames. Pause, and the two photographs under your eyes may refine. You feel it as a slight settling of the still.

A year of same-time. Pause, and the analemma control is there: same time of day, up to a year. It does not fetch that year until you tap. Then the frames land in the persistent cache, and play runs them through the GPU in a sliding window, oldest toward the day you paused on. You can start before the year is complete. Cheapest way to feel a year without loading a year of GPU textures at once.

Cancel is a feature. Every request in the data layer knows which time it was for. If that time is no longer current, we abort. The alternative is a spinner busy with last week.

The Earth is embeddable. A page can put the globe in a frame, set a day and a speed in the URL, and talk to it with messages (play, pause, seek) while the globe talks back. Two frames on one page is a solstice. Same control state the share sheet already serializes.

State is a link. Time, view, camera, zoom, source: if it matters, it can live in the query string. The person you send it to should land in the same weather.


Blueturn is a project written by Michaël Boccara, and evangelized by Jean-Pierre Goux. It was built on public-domain NASA DSCOVR / EPIC imagery. The source photographs are NASA’s. The interactive motion between them is ours.