Water Simulation

How it works

Pick a rectangle anywhere on Earth and watch water run down the real ground under it, in the rain that is actually falling there right now.

Nothing is precomputed and there is no server doing the work. The browser pulls four open datasets for the rectangle you drew, decodes them itself, and hands a plain grid of heights to a small piece of Rust compiled to WebAssembly. No API keys, no accounts, no rate limits.

⛰️ How high is it?

AWS Terrain Tiles, terrarium PNGs at zoom 12 — about 30 m per pixel at mid-latitudes.

🌧️ Is it raining?

RainViewer radar mosaic, a new frame roughly every 10 minutes, two hours of history to scrub through.

🌱 Where does it soak in?

ISRIC SoilGrids sand, silt and clay at 250 m, turned into an infiltration rate.

🔎 Where am I?

Photon resolves a place name to a view. The basemap is CARTO over OpenStreetMap.

1The tiles are data, not pictures

Elevation arrives as PNG images, but they are not images of anything. The terrarium encoding packs one height in metres into each pixel's three colour channels:

meters = R × 256  +  G  +  B ÷ 256  −  32768

Which means one step in the red channel is 256 metres. That single fact decides how the whole geodata stack is built. Draw one of these tiles to a canvas and read it back — the obvious way to get pixels in a browser — and colour management is free to nudge a channel by one on the way through. Nobody would notice on a photograph. Here it moves a hillside a quarter of a kilometre into the sky.

So every tile is decoded with ImageDecoder straight to raw bytes, and never through a canvas. The soil layer needs deflate, which DecompressionStream handles. Both are browser built-ins, which is why the whole geodata stack has no dependencies at all.

2Where the water goes

This is the only part written in Rust, and it is deliberately the only part. It receives a grid of integer heights and knows nothing about maps, projections, tiles or file formats — every attempt to teach it about geodata has been deleted again.

  1. Every cell holds one height. About 30 m across, as an integer number of metres, which is all the precision the source has.
  2. A droplet compares its eight neighbours. It scores each by how far down it is, and takes the largest drop.
  3. If nothing is lower, it stays put and soaks in where it is. That is what fills the hollows.
  4. Repeat for thousands of droplets and the valleys draw themselves. Nothing in the code knows what a river is; the channels are just where the paths keep overlapping.

Each droplet moves one cell per tick and leaves a trail that fades. A route every droplet crosses settles bright; ground crossed once in twenty ticks stays dim. That separation is the picture.

3Only the rain the ground cannot take

Radar gives reflectivity in dBZ, which becomes a rainfall rate through the standard Marshall-Palmer relation, capped at 65 dBZ because above that it is hail rather than rain. That is the easy half.

The interesting half is that rain falling on ground that can absorb it does not run anywhere. So the soil fractions are put through the USDA texture triangle, into the NRCS hydrologic soil groups, into a saturated infiltration capacity:

GroupTextureSoaks up
Asand, loamy sand, sandy loam10.0 mm/h
Bloam, silt loam, silt5.5 mm/h
Csandy clay loam2.5 mm/h
Dthe clays1.0 mm/h

A cell then spawns droplets on rate − capacity, not on rate. Light rain on sand produces nothing at all; the same rain on clay produces streams. And once a droplet exists, the ground underneath keeps taking its rate out of it every tick, so a droplet crossing free-draining soil dies sooner than one on clay. The same number does both jobs.

4What this is, and isn't

5Attribution

The terrain is not one survey but a mosaic of many, merged into Terrain Tiles and hosted on the AWS Registry of Open Data. Its licence asks that every one of them is credited, so:

Rain from RainViewer. Soil from ISRIC SoilGrids, CC BY 4.0. Place search from Photon, over OpenStreetMap. Basemap © OpenStreetMap contributors, © CARTO.

← Open the map