Points of interest, on-path walking routes and an optimized full-park tour, running entirely in the browser. No build step, no backend, no framework.
ParkScout turns a park, garden, campus or estate into a guided-visit experience visitors open in their phone browser — no app install:
- Tap a marker → a bottom sheet with photos, a story, category and live distance.
- "Show route" → the shortest path to that spot along the park's actual footpaths, not a straight line — computed client-side with Dijkstra over real OpenStreetMap path data.
- The tour button → a full visiting loop past all 20 places, ordered by a travelling-salesman heuristic (nearest-neighbour construction + 2-opt refinement), with numbered stops, total distance and walking time.
- Live location → a pulsing blue dot, per-spot distances, and routes that start from wherever the visitor is standing.
The whole thing is one index.html (~68 KB), one JSON file of path data, and a handful of images.
The demo content is a real allotment-park layout in Amsterdam; swapping in your own park is
three small steps.
| 🗺️ One file | All markup, styles and logic in a single index.html. View-source friendly; nothing to install or compile. |
| 🧭 Real on-path routing | Footpath network from OpenStreetMap → graph → Dijkstra shortest paths. Disconnected path segments are auto-bridged so every spot stays reachable. |
| 🚶 Optimized full tour | All-pairs leg matrix + nearest-neighbour + 2-opt produce a sensible walking loop past every POI, from the entrance or from your live position. |
| 📍 Live location, politely | The map recenters only when you ask it to — GPS updates never hijack panning, never slam the info sheet shut, and routes refresh only after real movement. |
| 🎫 Polished POI sheets | Photo hero, story card, category chips, live distance, and a route button — with staggered motion that respects prefers-reduced-motion. |
| 🛡️ Fails gracefully | Version-pinned, SRI-verified Leaflet; a data loader that retries after a failed fetch; transient toasts that can't get stuck; denied-permission handling that cleans up after itself. |
| 📱 Mobile-first | Bottom sheets, safe-area padding, 44 px touch targets, and a welcome dialog that explains location use before asking for it. |
flowchart LR
OSM["park-paths.json<br/>OpenStreetMap ways"] --> GRAPH["buildRouteGraph<br/>nodes + edges"]
GRAPH --> CONNECT["connectRouteComponents<br/>bridge disconnected segments"]
CONNECT --> DIJKSTRA["shortestRoute<br/>Dijkstra"]
CONNECT --> MATRIX["all-pairs leg matrix"]
MATRIX --> TSP["nearest-neighbour + 2-opt<br/>tour order"]
POIS["pointsOfInterest[]<br/>name · story · photo · icon"] --> MARKERS["Leaflet markers<br/>+ bottom sheets"]
DIJKSTRA --> ROUTE["route polyline + banner"]
TSP --> TOURV["tour polyline + numbered stops"]
GPS["geolocation watch"] --> DIST["live distances<br/>route refresh"]
Everything runs client-side. The only network dependencies are OpenStreetMap tiles and the pinned Leaflet CDN files; the path graph and all content ship with the site.
- Points of interest — edit the
pointsOfInterestarray at the top of the script in index.html: name, coordinates, an emoji icon, a story (<p>paragraphs) and an optional photo (<img>first). Categories map from the emoji ingetPoiCategory. - Footpaths — replace park-paths.json with your park's paths. The format is
a trimmed Overpass API result: an
entrancecoordinate pluswayswith[id, lat, lng]node triples — the queryway["highway"~"path|footway|pedestrian|service"](around:500,LAT,LON)gets you there. - Text & images — swap the title, welcome copy and photos (see images/CREDITS.md for the CC0 demo set).
It's a static site — any web host works. Serving over HTTP(S) is required (the path data is
fetched, and geolocation needs a secure context).
GitHub Pages — included. Enable Settings → Pages → Source: GitHub Actions and the bundled
workflow deploys on every push to main.
Cloudflare Workers — included. With wrangler installed:
npx wrangler deploy # uses wrangler.jsonc — static assets, no code…or point Cloudflare Pages / Netlify / Vercel at the repo with no build command and / as the
output directory.
Locally:
python -m http.server 8000 # → http://localhost:8000parkscout/
├── index.html # the entire app — styles, markup, logic
├── park-paths.json # footpath graph (OpenStreetMap extract) + entrance
├── images/ # POI photos (CC0) + CREDITS.md
├── wrangler.jsonc # Cloudflare Workers static-asset deploy
└── .github/workflows/ # GitHub Pages deploy
- Map tiles and path data © OpenStreetMap contributors.
- Maps rendered with Leaflet.
- Demo photos are CC0 — see images/CREDITS.md.
GPL-3.0 © xfznprojects
Free to use, modify, and share — but if you distribute ParkScout or anything built from it, your version must stay open source under the same license, with credit to the original.



