How it works.
A weather app tells you when the sun crosses a perfectly flat horizon. In a valley it shows up hours later. Sometimes it doesn't show up at all. This map works out when it actually clears the ridge.
Rise and set times, with the mountains included
Pick a spot and a date. The page builds a 360° outline of the terrain around you: every ridge, shoulder and distant summit that cuts into the sky. Then it follows the sun and the moon across the day and finds the exact moments their discs cross that outline.
Two numbers come out of it for each body: the terrain time (when you actually see it) and the flat-horizon time (what an almanac would say). The difference is what the mountains cost you. In Zermatt on the winter solstice, it is about three hours in the morning and an hour and a half in the evening.
Five steps, all on your device
Map click, search (Nominatim) or pasted coordinates. Time zone via tz-lookup.
~40–90 terrain tiles around you, decoded to heights in metres.
One every 0.5°, out to 150 km. Keep the steepest angle on each ray.
Sun and moon positions every 2 min across the local day.
Where the disc meets the ridge, refined to about one second.
Step 2 needs the network. Everything after that is arithmetic in the browser and takes well under a second. Changing the date only re-runs steps 4 and 5, so it is instant.
What it uses, and where it comes from
No API keys, no accounts, no backend. Every source below is public and free to use with attribution.
| what | source | used for |
|---|---|---|
| Elevation | AWS Terrain Tiles (Mapzen / Tilezen “Terrarium” PNGs), served from s3.amazonaws.com/elevation-tiles-prod | The horizon: every ridge you see in the panorama |
| Base map | OpenTopoMap (default), OpenStreetMap, Esri World Imagery, CARTO Dark Matter | The map you click on. Display only, not used in any calculation |
| Place search | Nominatim (OpenStreetMap's geocoder) | Search box and the place name shown for a clicked point |
| Time zone | tz-lookup (a compact copy of the world's time-zone borders), then the browser's built-in Intl time-zone database | Showing times in the spot's local time, daylight saving included |
| Sun & moon | No data: computed from orbital formulas (section 04) | Positions at any moment, for any date |
| Map library | Leaflet 1.9.4 | Map, markers, rays |
About the elevation tiles
The Terrain Tiles are a global mosaic stitched from many national and international elevation models. Which one you get depends on where you are: SRTM (NASA's Shuttle Radar Topography Mission, ~30 m) covers most of the world between 60° N and 56° S; ArcticDEM and national models such as Norway's, Canada's CDEM or the US NED (down to ~3–10 m) fill in or improve other regions; GMTED2010 and ETOPO1 cover the gaps and the oceans. The full list is in Tilezen's attribution notes.
Each tile is a 256 × 256 PNG in which colour is height. Red, green and blue together encode the elevation in metres:
height = (R × 256 + G + B / 256) − 32768The page draws each tile onto an invisible canvas, reads the pixels back, and keeps the heights in memory. Sea floor is clamped to sea level, because water is flat.
Privacy
Nothing is stored and there is no server of mine involved. The coordinates you pick go only to the tile servers (as tile numbers), to Nominatim (for the place name), and into the page's URL so you can share the view. Your theme choice is kept in your own browser's local storage.
720 rays, and the curvature of the Earth
From your position (ground height plus 2 m of eye height, adjustable) the page walks outward along 720 compass directions, one every half degree. On each ray it samples the terrain height, works out the angle at which that point appears above or below eye level, and keeps the largest angle. That maximum is the horizon in that direction, and its distance is where the ridge sits.
The angle of each sample
For a point at distance d with terrain height h, seen from eye height h₀:
The d² / 2R term is the Earth curving away: 100 km out, the ground has dropped about 785 m below a straight line from your eye. Air bends light slightly back down around the curve, so you see a little further than geometry alone allows. The standard way to model that is to make the Earth look about 15% larger, which is the (1 − k) factor. Without these two corrections, distant ranges would look several hundred metres taller than they are.
Resolution where it matters
Nearby terrain needs fine detail and distant terrain doesn't, so each distance uses a different zoom level of the tiles:
| distance | tile zoom | pixel size at 46° n | why |
|---|---|---|---|
| 0 – 3 km | 13 | ~13 m | The valley wall right above you sets most winter sunrises |
| 3 – 25 km | 11 | ~53 m | The neighbouring ranges |
| 25 – 100 km | 9 | ~210 m | Big massifs on the skyline |
| 100 – 150 km | 7 | ~850 m | Only the highest peaks still reach above the curve |
Heights are interpolated between the four nearest pixels, so the outline doesn't step at pixel edges. A light 3-point smoothing removes speckle from individual pixels without lowering real peaks. Distances along each ray grow with range: 20 m steps near you, then 1% of the distance. That makes about 500 samples per ray and roughly 380,000 per location. It runs in about 0.7 s on a laptop.
Where they are, to a few arcminutes
Positions come from Paul Schlyter's well-known method for computing planetary positions. Each body follows an elliptical orbit whose elements change slowly over time. The Kepler equation is solved for the current position, and the result is turned into right ascension and declination, then into altitude and azimuth for your exact coordinates and moment.
The moon needs more care
The moon's orbit is disturbed by the sun's gravity. Without corrections its position can be off by about 2°, which is roughly 8 minutes of rise time. The model adds the twelve main longitude terms (evection, variation, the annual equation and others), five latitude terms and two distance terms. That brings it to a few arcminutes.
The moon is also close enough that where you stand on Earth matters. Seen from the surface rather than the planet's centre, it sits up to about 1° lower near the horizon (lunar parallax). The model corrects for this, which shifts moonrise by several minutes.
What you see, not where it is
- Refraction. The atmosphere lifts objects near the horizon by about half a degree, roughly the sun's own width. The model uses Sæmundsson's formula, assuming standard conditions (10 °C, 1010 hPa).
- Disc size. Rise and set mean the top edge of the disc, the first flash of light, so the apparent radius (~0.27° for both) is added. Both radii vary slightly with distance and are computed on the fly.
With all of this, the flat-horizon sunrise and sunset match published almanac times to within a minute. For example, Paris on 7 november 2026: 07:47 and 17:22.
Scan, then narrow down
The local day runs from midnight to midnight in the spot's time zone. That isn't always 24 hours: daylight-saving days have 23 or 25. For every 2-minute step the page computes:
margin = apparent altitude + disc radius − horizon angle at that azimuthWhenever the margin changes sign, the body has crossed the ridge. The page then halves that 2-minute window repeatedly until it is about 1 second wide. The reported time is accurate far beyond what the terrain data can justify, but it means rounding never adds error.
Because the scan follows the real outline, it also catches the odd cases:
- The sun setting behind a peak at noon and coming back out. These show up as “hidden behind terrain” rows.
- Days with no direct sun at all: deep north-facing valleys in winter, or polar night.
- Moonrise after moonset on the same day, or a moon that doesn't rise at all that day.
The same scan runs a second time against a flat 0° horizon to produce the comparison times. Civil dawn and dusk (sun centre 6° below the true horizon) are found the same way.
Why it is built the way it is
Architecture
- Static files, no build step. Five small scripts and a stylesheet. Any static host can serve it, and it will still work in ten years.
- Everything in the browser. Terrain analysis is usually done on a server with GDAL and a big elevation file. Here the browser downloads only the tiles around one point and does the maths itself, so there is no server cost, no API key and no queue.
- Own astronomy code. Common libraries such as SunCalc use a moon model that can be several minutes off, and they mix in refraction in ways that are hard to separate from the terrain angle. Writing the ~200 lines myself kept refraction, parallax and the disc radius explicit and testable.
- Shareable URLs. The address contains
#lat,lon,date, so any view can be bookmarked or sent.
Interface
- The panorama is the centre of it. A rise time on its own is just a number. Seeing the sun's arc pass behind a particular summit explains it. The vertical scale is exaggerated (~3–4×) so a 10° ridge reads clearly; “full sky” zooms out to the whole arc.
- Map and panorama are linked. Hover over the panorama and a ray points to that ridge on the map. The rise and set rays end exactly on the ridge that blocks the light, and the dotted line on the map is the skyline itself, projected onto the ground.
- Visual identity. It follows my personal design system: warm near-black canvas, 1px hairlines instead of shadows, sharp corners, Space Grotesk for headings, Space Mono for every label, numbered lowercase sections. Apricot stands for the sun, mist teal for the moon and coral for what the terrain changes. No gradients and no emoji. There is a light theme behind the ◐ button.
Honest error bars
- Grazing angles. When the sun moves almost along a ridge instead of rising steeply over it (common in winter at high latitudes), a few metres of elevation error can move the time by several minutes. Typical cases are ±1–3 min.
- Very close terrain. Within a few hundred metres, the ~13–30 m elevation data can't see a rock band, a building or a tree line. If the blocking ridge is very close, expect larger errors.
- No buildings, no trees, no snowpack. The data is bare-earth terrain (radar surface for SRTM, which partly includes forest canopy).
- Refraction varies. Cold inversions in winter valleys can bend light more than standard conditions assume and shift a sunrise by a minute or two either way.
- Far ranges are coarse. Beyond 100 km the data is ~1 km per pixel. Only the tallest peaks matter that far out anyway.
- Above ~85° latitude the tile grid stops, so polar spots there have no terrain.
Questions people ask
Why is my sunrise hours later than my weather app says?
Weather apps assume a flat horizon at sea level. If you are in a valley, the sun has to climb up to the angle of the surrounding ridges first, which can be 20° or more. In winter, when the sun climbs slowly, that takes hours.
What exactly counts as “sunrise over terrain”?
The moment the top edge of the sun's disc, with atmospheric refraction included, rises above the ridge outline in its direction. It is the first direct light at your eye height, not the moment the whole disc is visible.
Why are there sometimes several rises and sets in one day?
The sun can pass behind a peak and come out the other side. Each crossing is a real event, and the gaps are listed as “hidden behind terrain”. The big rise and set times show the first rise and the last set.
Does it include buildings or forest?
No. The elevation data is terrain only. In a city or at the edge of a forest, the real horizon will be higher than shown. Raising the eye height (settings) helps model a balcony or a summit cairn.
Why change the eye height?
On a summit, a rooftop or a slope, a few metres change what is close by. Eye height is added to the ground elevation before any angle is computed.
Why does the panorama look so dramatic?
The vertical scale is stretched so ridge shapes are readable. 360° fits across the width while the interesting band is only 0–30° tall. The numbers on the left axis are true angles. “Full sky” zooms out so the whole sun arc fits.
Why can the moon rise over terrain but show no moonset that day?
The moon rises about 50 minutes later each day, so on some days it rises in the evening and only sets after the next midnight. The card then says “still up at midnight”.
Is the time zone right near borders?
It comes from a compact copy of the official time-zone borders, which is accurate to a few kilometres. Very close to a border it could pick the neighbour's zone. Daylight-saving rules come from your browser's own up-to-date database.
Can I use it offline?
Only for spots you have already loaded in the current session, because the tiles stay in memory. A new spot needs fresh terrain tiles.
Does it work anywhere in the world?
Yes, between about 85° N and 85° S, which covers every inhabited place. Data quality is best where national elevation models exist (Europe, North America, Australia, New Zealand) and SRTM-level (~30 m) almost everywhere else.
Thanks to the people who made the data free
Elevation: AWS Terrain Tiles by Mapzen / Tilezen, built from SRTM (NASA / USGS), GMTED2010, ETOPO1, ArcticDEM, CDEM, NED, EU-DEM and other national datasets (full list). Maps © OpenStreetMap contributors, OpenTopoMap (CC-BY-SA), Esri, CARTO. Search by Nominatim. Time-zone lookup by tz-lookup. Map library Leaflet. Orbital method after Paul Schlyter.
built by emile · with claude · more post-its than time.