How it was built, section by section
An interactive map for discovering hiking trails and huts across New Zealand, built on data from the DOC public API. The frontend runs on React and Mapbox GL JS with 3D terrain, custom styling, and a few experimental layers. The backend is Express with MongoDB and Redis caching.
Express server with MongoDB for user data, plus controllers that pull tracks, huts, and alerts from the DOC API. Added Redis caching with a 6-hour TTL, which brought repeated requests down from around 8 seconds to a few milliseconds.
React 19 with Vite, TypeScript, and Tailwind. Got API calls wired up to fetch data from both MongoDB and the DOC API, then dropped in a map to start testing things visually. All state lives in App.tsx with prop drilling, no state library needed so far.
DOC track coordinates use NZTM (EPSG:2193), not standard lat/lng. Found a StackOverflow post about pyproj, then adapted the approach for JavaScript using Proj4. Each track now converts on the fly from NZTM to WGS84 and renders as a custom SVG marker on the map.
With hundreds of markers on screen at once, clustering was the obvious next step. Google's MarkerClusterer had just added AdvancedMarkerElement support, so custom SVGs worked with native clustering out of the box. Built a custom renderer so the cluster circles matched the rest of the styling.
Added state to track which marker was clicked, then passed that data through to a detail view as props. Also wired in Turf.js to calculate distances to nearby huts from the selected trail.
Each DOC track includes an array of coordinates representing the actual walking path. Some come as flat arrays, some nested with multiple segments. Flattened them where needed, converted from NZTM to WGS84, and drew them as polylines when a trail is selected.
Tested Google's gmp-map-3d web component. It looked great with full 3D terrain and satellite imagery, but it lives outside the React tree and doesn't support clustering or interaction events in the same way. Good for a visual demo, not practical as the main map.
Switched to Mapbox GL JS. Better 3D terrain, full control over layers and styling, and everything runs through the WebGL pipeline so it scales well. Loaded the full DOC dataset as a GeoJSON source with custom icons. This is where the app lives now.
Added a sky layer that shifts colour based on time of day, lighter during daylight, darker at night. Combined with Mapbox's fog API for haze on the horizon. Small touches but they make the 3D terrain feel a lot more real when the map is pitched.
Started by overriding individual Mapbox layer paint properties after load. Eventually moved everything into a custom Mapbox Studio style instead, then layered satellite imagery at low opacity over the terrain-enabled outdoors style. Gives a hybrid look with realistic imagery but topographic detail still visible underneath.
Compared DOM-based markers (quick to set up but each one is a live DOM node, sluggish at scale) versus WebGL symbol layers from a GeoJSON source (handles thousands of points without lag, works with clustering and pitch). Went with symbol layers for the track data.
More experimental. Used Mapbox's raster-particle layer with GFS wind data to render animated particles flowing across the map. Not interactive, but it adds some movement and atmosphere to the background when zoomed out.
Built a density heatmap showing where trails are concentrated across the country. It fades as you zoom in, revealing individual markers and polylines underneath. Clustering keeps things interactive at mid-zoom levels, the heatmap gives the bird's-eye view. They work well together.
Currently working on sidebar search and region filtering, map layer controls, and wiring up the trail detail panel with nearby hut distances. Check out the project page for the video demo.