Skip to content

Repository files navigation

🧭 ParkScout

An interactive visitor map for any park — in a single HTML file

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.

JavaScript Leaflet OpenStreetMap No build Deploy License: GPL v3

▶ Live demo

Optimized full-park tour with numbered stops   Point-of-interest sheet with photo and distance


Overview

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.

✨ Highlights

🗺️ 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.

🖼️ A tour

Welcome dialog
Welcome — explains what the map does and why it wants location, before the browser ever prompts. Skippable.
Park map with markers
The park — 20 emoji markers over OpenStreetMap tiles, clamped to the park bounds, with the visitor's live position.

🏗️ How it works

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"]
Loading

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.

🌳 Make it your park

  1. Points of interest — edit the pointsOfInterest array 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 in getPoiCategory.
  2. Footpaths — replace park-paths.json with your park's paths. The format is a trimmed Overpass API result: an entrance coordinate plus ways with [id, lat, lng] node triples — the query way["highway"~"path|footway|pedestrian|service"](around:500,LAT,LON) gets you there.
  3. Text & images — swap the title, welcome copy and photos (see images/CREDITS.md for the CC0 demo set).

🚀 Deploy anywhere

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:8000

📁 Project structure

parkscout/
├── 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

🙏 Data & credits

📄 License

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.

About

Interactive park visitor map in a single HTML file — on-path routing, optimized walking tour, live location. Leaflet + OpenStreetMap, no build step.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages