Sunmaxxing is a map of roughly 2,000 Berlin terraces, rooftops, and beer gardens, each one annotated with whether it's currently in the sun. Everything on the map updates every minute, with no app install and no account required. Here's what's under the hood.
The question
For any given terrace in Berlin, right now, we want to answer: is the sun hitting this place? That question sounds simple, but it has three independent parts:
- Where is the sun? At any instant, at any latitude, the sun has a calculable altitude and azimuth.
- What's in the way? Even if the sun is above the horizon, a neighboring building might cast a shadow on this terrace.
- Are there clouds? Geometric sun is only sun if the sky is also clear enough for the light to reach the ground.
Each of these has a reliable public data source. The interesting engineering is combining them quickly enough that the whole map renders in a couple of seconds on a phone.
1. Where the sun is — SunCalc
The easiest part. For any (latitude, longitude, timestamp), the sun's position in the sky is a closed-form astronomical calculation. We use the open-source suncalc JavaScript library to get the sun's altitude (how high above the horizon) and azimuth (which compass direction) at 1-minute granularity. No network calls, no API keys — it's pure math.
2. What's in the way — OSM buildings + shadow projection
This is where the real work lives. We pull every building footprint in Berlin from OpenStreetMap — about 40,000 polygons with associated height tags (where present; we fall back to a conservative average where not). For each terrace on the map, for each minute of the day, we project shadows from every nearby building and ask: does any shadow cover this terrace's centroid?
The calculation per terrace per minute is roughly:
sunVector = suncalc(lat, lon, time)
if sunVector.altitude <= 0: return "night"
for building in nearbyBuildings(terrace, radius):
shadowPolygon = extrudeShadow(building, sunVector)
if shadowPolygon.contains(terrace.point):
return "in shadow"
return "in sun"
Nearby is roughly 500m — a taller building that far away can still cast a shadow onto a terrace when the sun is low. We build a spatial index (R-tree) on building footprints so the nearby-building lookup is O(log n) instead of O(n).
3. Are there clouds — Open-Meteo
Even with clear geometric sun, 90% cloud cover means you're not getting golden hour. We fetch Berlin's current and hourly cloud cover from Open-Meteo, a free weather API. The map combines the two signals: a terrace with geometric sun and <30% clouds gets the full sunny pin; with heavier cover it gets a muted "partly cloudy" state rather than a false-positive golden dot.
The performance problem
Computing shadows on 2,000 terraces × 1,440 minutes per day × ~100 nearby buildings each = roughly 300 million polygon-inside tests per day, if we were naive. That's obviously too much to do in the browser.
The trick: it's boringly deterministic. Given the buildings, the terraces, and a day's timestamp, tomorrow's shadow map is exactly the same set of computations as today's — nothing stochastic. So we don't compute at runtime. We pre-compute.
Nightly bake
A GitHub Action runs at midnight Berlin time, computes every terrace's sun windows for the next day, and writes a single static JSON bundle:
windows-today.json
{
"klunkerkranich": [
["06:12", "13:45"], // sunny window #1
["14:02", "19:38"] // sunny window #2
],
...
}
The client loads this bundle (under 2 MB gzipped) and does a constant-time lookup: at this minute, is this terrace inside one of its sun windows? No geometry, no math — just a range check. This is what lets the map stay interactive on a mid-range phone.
Weather stays live
The cloud-cover layer is the only thing we can't bake — weather changes hour to hour. So the static bundle carries geometric sun, and a second tiny API call gets fresh cloud data. The client layers them together in the browser.
The stack
- Host: Cloudflare Pages. One static bundle, served from the edge, ~50ms TTFB globally.
- Data: OpenStreetMap (venues and buildings), Open-Meteo (weather), SunCalc (sun position). All free.
- Build: GitHub Actions, nightly, refreshes the baked JSON.
- Client: Vanilla HTML/JS/Leaflet. Zero build step. No framework, no auth, no backend.
What it isn't
A few things Sunmaxxing is deliberately not:
- Not a reservation system. We don't know if a rooftop has a free table; we know if it's in the sun. Those are different questions.
- Not a replacement for the venue's own info. Opening hours, seasonal closures, and private events come and go faster than we can track. When we say "usually open 3pm–midnight, seasonal", we mean check the venue before you go.
- Not global. The shadow data is Berlin-only today. Munich and Amsterdam are on the roadmap — the pipeline is generalizable, but each city needs its own baked building data.
Why it's free
Sunmaxxing is a personal project, not a startup. There's no subscription, no ad, no account, no tracking cookie. It runs on free tiers of Cloudflare Pages, GitHub Actions, and the public APIs listed above. If it gets popular enough to hit a free-tier limit, we'll figure that out then.