amora

pedalhidrografico

Static PWA: an OpenStreetMap with the Pedal Hidrográfico hydrography overlay, every passeio of the collective drawn on top of it, filterable by date, with the linked Instagram post embedded on click. Includes an in-browser GPX drawing tool and a photo overlay sourced from RDF/Turtle. Architecture and conventions in CLAUDE.md; ontology notes in docs/DESIGN.md; self-hosted backend in backend/README.md.

What it does

Architecture

It’s a fully static site with an optional self-hosted backend for uploads. Two stages:

  1. Build steps (Python, with credentials).
    • web/data/tours.ttl (the Tour catalog) is maintained through the app itself: web/upload_tour.html posts to the backend’s Tour CRUD endpoints. (The old build-tours.py CSV pipeline was removed — the catalog now carries hand-written narratives and announcement images that a spreadsheet rebuild would wipe.)
    • python scripts/build-routes.py reads web/data/tours.ttl, fetches every referenced GPX from RideWithGPS, downsamples each track to ≤400 points, and writes everything into web/routes.json. Unique RWGPS IDs are fetched once even when multiple tours share a route (e.g. anniversary re-rides). This is the only place RWGPS credentials are needed — the browser never sees them.
    • python scripts/build-clips.py (optional) re-encodes any source videos in web/clips/raw/ to 360p + 720p mp4 + .m4a audio + thumbnail jpg, and upserts each as a ph:Video in web/data/uploads.ttl (with ph:capturedDuring set to the closest tour within ±12 h). Requires ffmpeg and exiftool in PATH.
  2. Runtime (browser only). web/index.html loads routes.json and resolves data graphs via web/data/data_graphs.ttl. No backend required at runtime; serve web/ from any static host. For uploads and photo storage, run the backend — see backend/README.md.

Run it

pip install python-dotenv rdflib pyshacl       # build + backend deps
cp .env.example .env
# fill in RWGPS_API_KEY and RWGPS_AUTH_TOKEN — see "Credentials" below

python scripts/build-routes.py                 # writes web/routes.json

Then serve web/ with anything:

cd web && python -m http.server 8000
# open http://localhost:8000/

Python 3.10+ recommended (uses str | None annotations and urllib).

Re-run build-routes.py to refresh routes.json after tour edits (the backend also upserts routes incrementally on every /upload-tour).

Static deploy

After the build steps above, web/ is fully self-contained. Push it to GitHub Pages, Netlify, S3, etc. and you’re done — no server needed. Photos won’t show on a static mirror unless you also publish web/data/ and web/photos/ from a backend run.

Credentials

.env is gitignored. Build steps are the only thing that touches credentials.

Caveats

License

amora is free software under the GNU Affero General Public License v3.0 (AGPL-3.0) — see LICENSE. It was GPL-2.0 until September 2026; the switch lets it bundle GPL-3.0 code (the leaflet-rotate map-rotation plugin) and makes the network clause explicit: if you run a modified amora for other people over a network, you must offer them its source.

Bundled third-party code keeps its own license: Leaflet and flatgeobuf (BSD-2-Clause), leaflet-rotate (GPL-3.0), N3.js, exifr, qrcode.js and leaflet.locatecontrol and geotiff.js (MIT), Tom Select (Apache-2.0), mediabunny (MPL-2.0).