🌆 UmbraCity — The Coolest Route Is the Shadiest One

Inspiration

I was scrolling through the news last week when a headline stopped me cold: over 1,000 people died in France during the June 2026 heatwave. Temperatures topped 40°C across the country, and the Île-de-France region — Paris and its suburbs — was one of the hardest hit, 85% of people were over 65.

And I thought — I've seen this before.

I'm from India. Every summer, our cities become ovens. A single day of extreme heat causes an estimated 3,400 excess deaths across the country. A five-day heatwave? Nearly 30,000. Delhi's Urban Heat Island effect means the city stays hotter than the countryside because of all the concrete and glass. Green cover in Delhi dropped from 25% to 14% in just a decade. Nights don't even cool down anymore — the day-night temperature gap has shrunk by 9%.

And the people who suffer the most? Not the ones with AC. It's the working class — the construction workers, the delivery riders, the street vendors, the people who have to be outside. 82% of India's informal workforce is exposed to daily heat risk. Missing a day of work to escape the heat means their family might not eat.

Google Maps will tell you the fastest route. Uber will tell you the cheapest. But nobody tells you the coolest route — the one that keeps you alive.

So I built UmbraCity.

"Umbra" means shadow in Latin. Because shadows save lives.


What it does

UmbraCity is a real-time, 3D pedestrian navigation system that routes you through the shadiest streets in the city. It uses actual building heights, tree canopies, and sun geometry to calculate where shadows fall — right now, at this exact minute — and colors every road on the map by how comfortable it is to walk on.

Here's what you can do:

🗺️ Live Temperature Map

Every pedestrian road is color-coded in real time:

  • 🟢 Comfortable (18–24°C felt) — shaded by buildings or trees
  • 🟡 Warm (25–30°C) — in a park, partial exposure
  • 🟠 Hot (31–36°C) — exposed, sun below 50°
  • 🔴 Dangerous (37–42°C+) — full sun, high altitude — get off this road

🧭 Coolest Route vs Fastest Route

Pick a start and destination. UmbraCity gives you two routes side-by-side:

  • Fastest Route — shortest physical distance (like Google Maps)
  • 🔵 Coolest Route — a shade-optimized path using a 7× penalty on exposed road segments in our Dijkstra's algorithm

The coolest route might be slightly longer, but it could keep you 70%+ in shade vs 15% on the fastest one. That's the difference between "I'm fine" and heatstroke.

🌳 Urban Shade Planner

Click anywhere to place virtual trees and shade canopies on the map. Watch the shadow simulation update in real time. See how adding a single 10m tree creates ~38m² of shade. UmbraCity even has a "Smart Suggest" button that automatically identifies the 6 hottest, most exposed road segments and recommends exactly where to plant trees.

This isn't just navigation — it's an urban planning sandbox for city officials, architects, and kids like me who want to see the difference one tree can make.

🏛️ Famous Landmarks

Interactive 3D markers for Notre-Dame, Sainte-Chapelle, Pont Neuf, Sorbonne, and more — click any landmark to fly the camera there and see its shade analysis.

🎵 Ambient Audio

A procedurally generated ambient soundscape (Web Audio API synthesis — zero audio files!) with evolving pad chords and randomized pentatonic bell chimes, giving the whole experience a meditative, immersive feel.

🌤️ Live Weather Sync

Real-time weather data from the Open-Meteo API. The 3D scene dynamically adjusts its atmosphere — sunny, cloudy, or rainy — matching what's actually happening outside right now.


How we built it

Step 1: The Data — OpenStreetMap

Everything starts with real-world geodata. I downloaded 6 massive GeoJSON exports from OpenStreetMap for central Paris:

Dataset What it contains Raw size
export_buildings.geojson Every building footprint with heights and levels 35 MB
export_trees.geojson Individual tree locations with species and height 9.2 MB
export_highways.geojson All roads, footpaths, pedestrian streets 30.7 MB
export_parks.geojson Parks and green spaces 424 KB
export_water.geojson Water bodies (ponds, fountains) 422 KB
export_river.geojson The Seine river geometry 894 KB

Step 2: Preprocessing Pipeline

A Python script (preprocess.py) clips and enriches this raw data:

  • Bounding box filter — crops to the Île de la Cité area (48.845° ≤ lat ≤ 48.858°, 2.340° ≤ lng ≤ 2.360°)
  • Building height estimation — uses OSM height tags, or falls back to building:levels × 3.5m, or defaults (apartments → 18m, churches → 25m, typical Paris → 15m)
  • Tree canopy radius — calculated as min(height × 0.35, 6.0m)
  • Road filtering — only keeps pedestrian-relevant highway types: footway, pedestrian, residential, path, steps, living_street, service
  • Output: compressed JSON files served to the browser

Step 3: The Shadow Engine 🌞

This is the brain of UmbraCity. For any given date and time:

  1. Sun position — the SunCalc library computes the sun's altitude and azimuth for Paris coordinates using astronomical formulas

  2. Shadow projection — for every building, we calculate:

shadowLength = buildingHeight ÷ tan(sunAltitude)

The building footprint is translated in the shadow direction by this length (capped at 120m), and the original + translated footprints are merged via a convex hull to form the shadow polygon.

For trees, the shadow is modeled as a projected circle with the canopy radius, offset in the shadow direction (capped at 60m).

  1. Spatial indexing — all shadows are bucketed into a 15×15 spatial grid covering the map area. When checking if a road is shaded, we only look at shadows in nearby grid cells — making it O(1) per query instead of O(n) over all buildings.

  2. Comfort scoring — each road's midpoint is tested against building shadows (point-in-polygon), tree shadows (circle distance), and park boundaries. The result: a comfort score from 0 (Dangerous) to 3 (Comfortable).

Step 4: Shade-Aware Routing 🧭

We built a custom Dijkstra's pathfinding algorithm on a graph constructed from road segment endpoints. The "Coolest Route" uses a modified edge weight:

weight = length × 1.0 (if shaded) weight = length × 7.0 (if exposed)

This steers the pathfinder toward shaded detours, even if they're longer — because a 200m shaded detour is infinitely better than a 50m stretch of 42°C direct sun.

Step 5: 3D Visualization 🏗️

A custom Three.js layer is integrated directly into the MapLibre GL WebGL context:

  • Extruded 3D buildings with real heights from OSM data
  • Instanced tree meshes — thousands of procedurally-generated trees with trunk and canopy, rendered using GPU instancing for performance
  • Dynamic sun lighting — the directional light matches the real sun angle
  • Water plane with semi-transparent blue rendering for the Seine
  • User-placed elements — virtual trees and canopies rendered in 3D with real shadow behavior

The Tech Stack

  • Frontend: Vanilla JavaScript + Vite
  • Map: MapLibre GL JS (open-source)
  • 3D Rendering: Three.js (WebGL, custom MapLibre integration)
  • Geospatial: Turf.js (point-in-polygon, distance, convex hull, translation)
  • Sun Math: SunCalc (astronomical sun position)
  • Weather: Open-Meteo API (live temperature, wind, conditions)
  • Audio: Web Audio API (procedural synthesis, zero dependencies)
  • Data: OpenStreetMap exports, preprocessed with Python

Challenges we ran into

🐌 Performance vs Accuracy

Checking every point on every road against every building shadow polygon is an O(n × m) nightmare. With thousands of buildings and thousands of roads, the browser would freeze. The solution: spatial grid indexing. We split the map into a 15×15 grid and only check shadows in nearby cells. We also only sample the midpoint of each road segment instead of every coordinate. It's an approximation, but it runs at 60fps.

🧊 Convex Hull Shadows

We originally tried using turf.union() to merge building footprints with their projected shadows, but it was incredibly slow and crashed on complex polygons. We switched to turf.convex() — computing the convex hull of the combined point cloud — which is much faster and gives a good enough approximation for shadow shapes.

🎨 Making It Beautiful

The first version was ugly. A map with colored lines. Nobody would use it. We spent a lot of time making it feel premium — the cinematic camera intro with cubic ease-out, the glassmorphic UI panels, the animated route drawing, the procedural ambient audio, the golden hour color temperature transitions. These aren't features on a checklist. They're what make people want to use the tool.

🌍 Projecting 3D shadows onto a 2D map

The sun casts shadows in 3D, but our map is 2D. We had to translate building footprints using geodesic transformations (meters to degrees at Paris latitude), accounting for the fact that 1° of longitude ≠ 1° of latitude in meters. Turf.js transformTranslate saved us here by handling the spherical geometry.


Accomplishments that we're proud of

  • The shadow engine actually works. Drag the time slider from sunrise to sunset and watch the shadows sweep across the city in real time. It's mesmerizing and it's correct — the math tracks with the real sun position.
  • The routing is meaningful. We tested routes where the coolest path had 78% shade coverage vs 12% for the fastest. That's not a gimmick — that's potentially life-saving.
  • Zero external assets. No images downloaded. No audio files loaded. The ambient soundtrack is generated entirely with Web Audio API oscillators. The 3D trees are procedural geometry. Everything runs offline after the initial load.
  • The Smart Suggest feature. It doesn't just show you heat — it tells you exactly where to fix it. Click "Suggest" and it finds the top 6 hottest, most exposed intersections and recommends tree placements. That's actionable urban planning.
  • It's a real tool, not a demo. You can plug in any city's OSM data, run the preprocessor, and have a working shade map. This generalizes beyond Paris.

What we learned

  • The sun is a physics problem. Before this project, I never thought about sun altitude, azimuth, or shadow geometry. Now I can calculate where a shadow falls at 3:47 PM on July 15th from a 22m building. That's cool.
  • Maps are hard. Coordinate systems, Mercator projections, GeoJSON formats, bounding boxes — geospatial work has a steep learning curve but it's incredibly rewarding when you see real-world data come alive.
  • Performance is a design decision. The midpoint sampling, spatial indexing, and convex hull approximation were all tradeoffs between accuracy and speed. Engineering is about finding the right tradeoff.
  • Heatwaves are an inequality problem. The research I did while building this shocked me. 82% of India's informal workers face daily heat risk. Europe lost 16,500 people to heat in 2025 alone. The people who die are almost always the poorest and oldest. Tech can't solve systemic inequality, but it can give people information to protect themselves.
  • Design matters as much as code. Nobody will use a tool that looks bad, no matter how good the algorithm is. The cinematic intro, the ambient audio, the smooth animations — these turned UmbraCity from a project into an experience.

What's next for UmbraCity

  • 🌍 Multi-city support — UmbraCity already auto-detects city names from coordinates. The next step is letting users load any city's OSM data through the UI. Delhi, Mumbai, Madrid, Athens — every heat-vulnerable city deserves this.
  • 📱 Mobile PWA — A Progressive Web App so outdoor workers can check their shade route on their phones before heading out.
  • ⏱️ Time-of-departure planning — "Leave at 4:30 PM instead of 2:00 PM and your route goes from 23% shaded to 71% shaded." Timing matters as much as the path.
  • 🏙️ City dashboard for officials — Aggregate shade data across an entire district. Which neighborhoods have the least tree cover? Where should the city prioritize planting? Turn UmbraCity into a policy tool.
  • 🤖 AI-powered route optimization — Replace Dijkstra's with a learned model that considers not just shade but wind corridors, building material thermal radiation, and personal heat tolerance.
  • 🌳 Community tree planting integration — Let citizens propose tree locations through UmbraCity, vote on them, and submit them to city forestry departments. Make urban shade a community effort.

Heat doesn't discriminate, but survival does. The rich have air conditioning. The poor have the street. The least we can do is make the street a little cooler.

UmbraCity. Follow the shadows, not the sun. 🌿

Built With

Share this project:

Updates