How Sunny Pint Works
Real-time shadow maps for UK pubs, computed client-side from LiDAR elevation data, OpenStreetMap building outlines, and geometric sun projection.
Abstract
Sunny Pint answers a simple question: which pub garden has sun right now? To do this, we combine three open datasets—OpenStreetMap building footprints, Environment Agency LiDAR elevation surveys, and HM Land Registry property boundaries—into a pre-computed dataset of building geometries with measured heights. A browser-based geometric shadow engine then projects real-time shadows from these buildings at any time of day, running entirely client-side at 60 frames per second with no backend server.
Architecture
The system consists of two parts: an offline data pipeline that processes open geospatial data into compact vector tiles, and a client-side renderer that computes and displays shadows in real time. The entire application is served as static files from a CDN—there is no application server, database, or API at runtime.
This architecture means zero ongoing server costs, global edge caching, instant page loads, and full offline support via a service worker. The computational cost of shadow projection is borne by the user's device, which modern phones handle comfortably.
Figure 1. System architecture. The offline pipeline (dashed box) processes open geospatial data into static files served via CDN. All shadow computation runs client-side.
Data Sources
| Source | Provides | Coverage | Licence |
|---|---|---|---|
| OpenStreetMap | Pub locations, building footprints, opening hours | UK-wide, community maintained | ODbL |
| Environment Agency LiDAR | Surface + terrain elevation at 1m resolution | ~75% of England | OGL v3 |
| HM Land Registry INSPIRE | Cadastral parcel boundaries (property plots) | All registered freehold in England & Wales | OGL v3 |
| Open-Meteo | Real-time cloud cover | Global, hourly updates | CC BY 4.0 |
| SunCalc | Solar position (azimuth, altitude) for any time/place | Global | BSD-2 |
OpenStreetMap provides the definitive pub list via the amenity=pub tag. We chose OSM as the sole source over the Food Standards Agency (which lists food businesses, not pubs) after finding that FSA data introduced duplicate entries for restaurants operating inside pubs, incorrect geocodes, and non-pub venues such as bingo halls and barber shops.
Building Heights from LiDAR
Building heights are derived from Environment Agency airborne LiDAR surveys. Two elevation models are used: the Digital Surface Model (DSM), which captures the top of all objects including roofs, and the Digital Terrain Model (DTM), which represents the bare ground surface. Building height is the difference between the two.
Figure 2. Building height derivation. The DSM captures roof tops; the DTM captures bare ground. Their difference yields building height above local ground level.
For each building, we rasterise its OSM footprint onto the LiDAR grid and take the 90th percentile of height values within the footprint. This captures the ridge height of pitched roofs while ignoring chimney and antenna outliers that would inflate a simple maximum.
Height distribution (Norwich, 355,000 buildings)
The distribution reflects Norwich's predominantly two-storey residential character. The tallest structures (up to 57 m) are church spires and multi-storey car parks. Mean height: 7.7 m, median: 8.0 m.
Computing Outdoor Areas
Each pub's outdoor area (beer garden, courtyard, terrace) is derived from HM Land Registry cadastral parcels. The pipeline finds the property parcel containing the pub, then subtracts all building footprints that intersect it—not just the pub's own building, but also sheds, garages, and neighbouring structures.
Figure 3. Outdoor area computation. The cadastral parcel minus all intersecting building footprints yields the usable outdoor space, including interior holes.
The resulting polygon may contain interior holes where buildings are fully enclosed within the plot. These are rendered on the Canvas using the evenodd fill rule, which naturally excludes enclosed regions.
The Sunny Rating
Every pub on Sunny Pint gets a Sunny Rating from 0 to 100. It tells you what percentage of the beer garden is in direct sun on an average day. It's the headline metric of the whole site and the way to compare pubs at a glance.
How we calculate it
Every pub's rating is computed once from real-world data, never guessed:
- Building shapes come from OpenStreetMap.
- Building heights come from Environment Agency LiDAR — laser-scanned elevation, accurate to about 10 cm vertically.
- The pub's outdoor seating area is the HM Land Registry cadastral parcel minus all building footprints inside it (described in the previous section).
- The sun's position is computed for every half-hour of daylight on the spring equinox — when day and night are equal, a fair "average day" of the year.
- At each half-hour, we cast geometric shadows from every nearby building (taller buildings cast longer shadows when the sun is low) and work out what fraction of the outdoor seating is actually in direct sun.
- The Sunny Rating is the average of those fractions, scaled to 0–100.
This is the same shadow-projection code that the live porthole renders in your browser when you select a pub — we just run it offline across a representative time grid and average the results. Single source of truth: there is no separate "rating" model that could disagree with what you see on screen.
Why fraction of garden, not square metres or hours?
We deliberately don't use total square metres of beer garden, or hours of sun per year, as the headline. Both are confounded:
- Square metres of sun-coverage rewards big gardens. A 50 m² walled town courtyard fully in sun for six hours scores worse than a sprawling north-facing field that's shaded most of the day. That's the opposite of what users want.
- Hours of sun per year is confounded by latitude. Cornish pubs would always rank above Scottish pubs simply because Cornwall has longer days, regardless of how good the actual gardens are.
Sun fraction sidesteps both problems. It normalises out garden size and day length. A 70 in Aberdeen and a 70 in Truro both mean “70% of the available sun reaches this garden” — directly comparable, latitude-fair, size-fair.
It's also robust to imperfect outdoor polygons. Cadastral parcel boundaries occasionally include extra land that isn't really garden seating — a country pub's full estate, for example. Square-metre metrics get destroyed by these outliers; sun fraction stays meaningful because it's intrinsically per-area.
What the rating doesn't include
Three things the Sunny Rating deliberately leaves out, in the interest of being honest about what the number represents:
- Weather. The rating is clear-sky theoretical sun. It says “this garden has the geometry to be in sun”; whether it actually is sunny on a given day depends on the clouds. The live cloud-cover badge from Open-Meteo handles that separately, and changes minute to minute.
- Time of year. The rating is computed against the spring equinox, when day length is the same at every UK latitude. A pub will be sunnier in summer (longer days, higher sun) and shadier in winter. The rating is the year-round “average character” of the garden — for the actual sun on a specific date and time, scrub the date picker on the live porthole.
- Direct sun only. “In sun” means “not in any building's shadow”. A garden that's bright from indirect or diffuse light but technically in shadow won't get credit for it. This matches the spirit of “is the seat I'd pick in the sun”.
None of these are reasons to distrust the rating — they're features. They make it defensible. Anyone can audit it by opening the porthole on the equinox at any half-hour and seeing the same shadow geometry the offline computation used.
Shadow Projection
Shadow computation uses geometric projection rather than raster ray-tracing. For each wall segment of every nearby building, the engine projects a shadow quadrilateral onto the ground plane based on the building height and the sun's current position. The roof footprint, offset by the shadow vector, completes the shadow polygon.
Figure 4. Shadow projection geometry. The sun, building top, and shadow tip are collinear. Shadow length is h/tan(α) where α is the sun's altitude above the horizon.
Δlat = −shadow_length × cos(azimuth) / 111,320
Δlng = −shadow_length × sin(azimuth) / (111,320 × cos(latitude))
Shadow length is capped at 200 metres to prevent infinitely long geometry near sunrise and sunset, where the sun altitude approaches zero. At the cap, the shadow is already faint due to the daylight fraction fade.
Overlapping shadow polygons from multiple buildings are composited via an offscreen canvas: all quads are drawn opaquely to a temporary surface, which is then blitted onto the main canvas with a single alpha value. This produces uniform shadow darkness regardless of how many buildings overlap.
Terrain Awareness
Elevation-adjusted building heights
A building on a hill is effectively taller from the perspective of a pub in a valley below. The shadow engine adjusts each building's height by the ground elevation difference between the building and the pub:
Figure 5. Elevation-adjusted building height. A building uphill from the pub has a greater effective height (h + Δelev), casting a proportionally longer shadow.
Terrain horizon occlusion
In hilly areas, the terrain itself can block sunlight even without any building present. For each pub, the pipeline casts 36 rays (every 10° of azimuth) outward to 500 m and records the maximum terrain elevation angle in each direction. At runtime, the browser compares the sun's current altitude against this pre-computed horizon profile.
Figure 6. Terrain horizon occlusion. The dashed amber line is the horizon sightline from the pub, tangent to the hilltop. The blocked sun (red) sits below this line; its sightline intersects the hillside. The visible sun (green) sits above it, clearing the terrain.
If the sun's altitude is below the terrain horizon angle at its current azimuth, the pub is in terrain shade and no building shadows are computed. The horizon profile is stored as a compact base64-encoded byte array (48 characters per pub). Pubs in flat areas have no profile at all, as their maximum horizon angle is below 1°.
The Porthole
The porthole is a circular viewport rendered entirely on an HTML Canvas element. It composites eight layers per frame, from a real cartographic basemap up through shadow geometry to a decorative bezel with compass markings.
Layer stack
evenodd holesThe viewport covers approximately 74 metres radius at zoom 18 map resolution. Buildings up to 274 m away (viewport radius plus the 200 m shadow cap) are loaded from vector tiles, since a distant tall building can cast a shadow into the viewport even if the building itself is not visible.
Optimisations
Height-dependent building filter
A building can only affect the viewport if its shadow can physically reach it. The maximum shadow reach depends on the building's height and the minimum useful sun angle (3°, at which the daylight fraction is approximately 0.5):
| Height | Max shadow at 3° | Reach | Included? |
|---|---|---|---|
| 6 m (shed) | 114 m | 188 m | Only if within 188 m |
| 8 m (house) | 153 m | 227 m | Only if within 227 m |
| 10 m (two-storey) | 191 m | 265 m | Only if within 265 m |
| 15 m+ (tall) | 200 m (cap) | 274 m | Always, if within 274 m |
This filter eliminated 30% of buildings from the Norwich tile set without affecting shadow accuracy.
Vector tile delivery
Building geometry is served as individual z14 vector tiles (.pbf files), each covering approximately 2.4 km². A pub's 274 m loading radius spans at most four tiles, so selecting a pub triggers 1–4 small HTTP requests (typically 10–100 KB each). The service worker caches tiles for 30 days, so revisiting a neighbourhood is instant.
Compact pub data
The full building polygon for each pub (20–50 coordinate pairs) represented 70% of the pub list file. Since the renderer obtains building geometry from the vector tiles, only the polygon centroid is needed for matching. Replacing full polygons with pre-computed centroids reduced the file from 11.3 MB to 6.3 MB (approximately 1 MB after gzip).
Static Web Application
Sunny Pint is a Progressive Web App served entirely as static files. There is no application server, no database, and no API to maintain. The entire runtime is JavaScript executing in the browser.
This design provides several advantages:
- Zero infrastructure cost — hosted on Cloudflare Pages' free tier with global edge distribution
- Offline capability — a service worker caches all application assets and previously viewed pub tiles, allowing the app to function without a network connection
- No cold starts — every request is served from CDN cache or the local service worker; there is no server to wake up
- Privacy — location data stays on the device; no user data is transmitted to any server
- Resilience — no single point of failure; the app works as long as the CDN has the files
The trade-off is that all shadow computation must run on the client. Modern smartphones handle the geometric projection for ~1,000 nearby buildings at 60fps without difficulty. The heaviest operation—the initial tile decode when selecting a pub—typically completes in under 50 ms.