Aerial time machine built from scratch. MapboxGL, historical imagery overlays, cinematic flight mode, Montville story research. From initial commit to v2.0 in two days.
Flight mode would jerk the map back to its path after the user dragged it. Multiple competing flyTo calls from different React effects — each one canceling and restarting the others.
Single navigation owner: one effect owns all flyTo calls. Separate mapPos ref from center prop. Cancel stale moveend callbacks before firing new ones. ~6 commits over 45 minutes.
Flight mode stuttered at handoff points between animation steps. Using setInterval with overlapping easeTo calls created visible pauses every 30–60 seconds.
Single long easeTo per step (120s duration). Fire next step on moveend, not interval. Native Mapbox frame interpolation handles smoothness between steps. No more handoff stutters.
Clicking a featured place flew the map correctly but showed no historical imagery — slider was blank. The lat/lng geocoder path worked; the county API path didn't.
Use county API for all location selects (not just user-typed addresses). The lat/lng proxy as a fallback when county endpoint returns no coverage.
Pins existed in the data and the GeoJSON layer was added, but nothing was visible. The raster overlay was inserted on top of the pin symbol layer in Mapbox's layer stack.
Insert raster overlay layers BELOW symbol/pin layers. Mapbox renders layers in insertion order — later = on top.
The historic pins effect ran (logs confirmed correct data) but pins didn't appear on the map. The map object captured in the closure was stale by the time the effect fired.
Use refs for map-dependent values instead of state. Refs don't go stale across re-renders on load.
Sharing a URL with a year hash (e.g. #1970) would briefly show the historical overlay, then snap back to Modern. pendingYear was being cleared before the first hash restore completed.
Clear pendingYear only after the first hash restore fires and the overlay is confirmed visible.
App degraded over time — layers were added multiple times, geolocation listeners piled up. Each navigation triggered a new listener without cleaning up the old one.
Plug all listeners on unmount. Disable antialias/fade, cap tile cache for mobile memory. Return cleanup functions from all effects that add map listeners.
Mapbox's layer system made historical overlays trivial — insert a raster source, set opacity, done. Flight mode animations (easeTo, bearing, pitch) are native capabilities. No custom math required.
Encoding location + year in the URL hash meant sharing was free from the start. No database, no sessions — just a URL. Made testing and collaboration instant during development.
Once flight mode, search, and featured places all tried to call flyTo, the map started fighting itself. Consolidating to one effect that owns all navigation eliminated the entire class of competing-animation bugs in one commit.
The Montville story wasn't UX filler — actual research sourced from county atlases, genealogy records, and land transfer chains. The app told a true story. That made it feel like software that mattered, not just a map toy.
Using existing USGS/county aerial survey APIs meant no infrastructure to build or pay for. Real historical data, not stock photos, available for any location in the US.
One effect owns flyTo. Never call flyTo from multiple places — flight mode, search, and featured places all fight each other if they each control navigation.
State goes stale in closures on initial map load. Store the map object and any map-dependent values in refs so effects always have the current value.
Mapbox renders layers in insertion order. Add raster/imagery layers before adding pin/symbol layers, or use beforeId to insert them below.
Every moveend, click, and geolocation listener added inside an effect must be removed in the cleanup function. Leaks degrade performance and duplicate layers over time.
Encode what matters (location + year) in the URL hash immediately. Retrofitting shareability is expensive. Starting with it is free.
"Some tools solve problems. This one just makes you go 'whoa.'"