Convert GPX routes into glanceable, map-centric motorcycle navigation PDFs for tank-bag use.
Project description
GPXSheet
Motorcycle sport-touring route awareness generator.
GPXSheet is a Python command-line application and reusable library that converts GPX routes into highly glanceable, map-centric motorcycle navigation PDFs optimized for tank-bag use.
It is not a rally roadbook and not a GPS replacement. The goal is route awareness: a rider should be able to glance at the printed sheet for less than one second and immediately understand what road they're on, what the next navigation decision is, how far away it is, what comes after, and where they are within the overall route.
See PRODUCT.md for the full design specification.
Status
v0.1.0 — Phase 1 complete: analysis engine, schematic strip, tank-bag PDF, and a publish-ready package.
- Route analysis — GPX (track/route/waypoints) → decision points, fuel,
reassurance markers, road segments;
analyzetext output. - Decision detection is two-tier. A geometry baseline (honest, but over-detects on twisty roads — it can't tell a curve from a junction) and an OSM mode that derives decisions from durable road-name changes, so a 22 mi switchback climb collapses to one clean segment ("onto Mount Hamilton Road").
- Schematic map strip — stylized (default) or faithful turns, collision-placed labels with dashed leaders, and the road-name ribbon.
- Tank-bag PDF — route-aware pagination; portrait roadbook (stacked strip lanes, the default) or landscape (one strip/page); page mileage in the header, progress bar.
- Packaged for
pip install gpxsheet(+[osm]extra); PEP 561 typed.
validate (CLI) is still a stub; a web service is future work.
Installation
pip install gpxsheet # core (GPX -> strip / PDF)
pip install "gpxsheet[osm]" # + OpenStreetMap enrichment (heavy geo stack)
Development
git clone <repo-url> gpxsheet && cd gpxsheet
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,osm]" # drop ,osm to skip the OSM stack
python -m build && twine check dist/* # build + check the distribution
# publish (maintainer only): twine upload dist/*
Usage
OpenStreetMap enrichment and the portrait roadbook layout are on by default;
both can be turned off, and OSM degrades gracefully (to geometry-only, with a
warning) when the osm extra is missing, the route is too sparse to follow
roads, or the live Overpass query fails.
gpxsheet generate route.gpx -o route.pdf # portrait roadbook + OSM (defaults)
gpxsheet generate route.gpx --landscape -o route.pdf
gpxsheet generate route.gpx --no-osm -o route.pdf # geometry-only (no network)
# portrait knobs: --lanes N (lanes/page) --lane-decisions M (decisions/lane)
gpxsheet analyze route.gpx # text analysis
gpxsheet strip route.gpx -o route_strip.png # single schematic strip PNG
OSM queries the live Overpass API (seconds for rural routes, up to minutes for
dense urban; cached by osmnx). The end-to-end query is covered by an
integration test gated behind GPXSHEET_LIVE_OSM=1 so CI/offline stay network-free.
Library
from gpxsheet import generate_pdf
# Library defaults are landscape + geometry-only (predictable/offline); pass
# use_osm=True and/or orientation="portrait" to match the CLI product defaults.
generate_pdf("route.gpx", "route.pdf", profile="sport-touring", fuel_range=180)
Web service
A FastAPI service exposes the engine over REST. Renders are slow (matplotlib +
live OSM), so generation runs as a background job (Dramatiq + Redis) with results
in object storage (MinIO); /v1/analyze returns the structured analysis as JSON.
Self-hosted stack (API + worker + Redis + MinIO):
docker compose up --build
# API -> http://localhost:8000/docs
# MinIO -> http://localhost:9001 (minioadmin / minioadmin)
curl -F gpx=@route.gpx "http://localhost:8000/v1/jobs?orientation=portrait" # -> {id, status}
curl http://localhost:8000/v1/jobs/<id> # poll until status=done
curl -L http://localhost:8000/v1/jobs/<id>/result -o route.pdf
Endpoints: POST /v1/jobs (upload GPX + params → 202), GET /v1/jobs/{id},
GET /v1/jobs/{id}/result (streams, or 303 → presigned URL), POST /v1/analyze,
GET /healthz. Single-process dev mode (in-memory, synchronous, no Redis/MinIO):
pip install -e ".[service]"
uvicorn gpxsheet.service.asgi:app # worker not needed in dev mode
Config is env-driven (GPXSHEET_REDIS_URL switches on the prod path; see
gpxsheet/service/settings.py).
Before exposing the service to the public internet, read
docs/security-audit.md. Key hardening knobs:
GPXSHEET_API_KEYS (comma-separated; enables X-API-Key/Bearer auth + per-key
rate limits), GPXSHEET_RATE_LIMIT_PER_MIN, GPXSHEET_MAX_UPLOAD_BYTES,
GPXSHEET_MAX_POINTS, GPXSHEET_ALLOW_OSM (set 0 to block outbound Overpass
calls), GPXSHEET_CORS_ORIGINS, GPXSHEET_ENABLE_HSTS, and the MinIO
credentials (the prod path refuses to boot on the minioadmin defaults). The
service must run behind a TLS-terminating reverse proxy.
License
GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later) — see LICENSE. Copyright © 2026 Paul Traina. Because the AGPL covers use over a network, anyone who runs a modified version of the web service must offer its users the corresponding source.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gpxsheet-0.1.1.tar.gz.
File metadata
- Download URL: gpxsheet-0.1.1.tar.gz
- Upload date:
- Size: 80.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
61a3b1df639cca783c9eaee61c43ae6556acaff29f3641cddc98ac0ff5d7aa9c
|
|
| MD5 |
b2bd8a8272606831fc03888661774aaf
|
|
| BLAKE2b-256 |
53072ecacff26a168eba91769e4ce1c8f93038dc188e9f0d82e663b125208044
|
Provenance
The following attestation bundles were made for gpxsheet-0.1.1.tar.gz:
Publisher:
publish.yml on pleasantone/gpxsheet
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gpxsheet-0.1.1.tar.gz -
Subject digest:
61a3b1df639cca783c9eaee61c43ae6556acaff29f3641cddc98ac0ff5d7aa9c - Sigstore transparency entry: 1761708844
- Sigstore integration time:
-
Permalink:
pleasantone/gpxsheet@56d44dd935e97826c177684c575d4f2c124b9fb3 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/pleasantone
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@56d44dd935e97826c177684c575d4f2c124b9fb3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file gpxsheet-0.1.1-py3-none-any.whl.
File metadata
- Download URL: gpxsheet-0.1.1-py3-none-any.whl
- Upload date:
- Size: 70.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aa817e6007b21119bed6103e8966533296497084223fec3bd0aaf4a862ca40fe
|
|
| MD5 |
df4da42a893a53ad32ce2407f4efaccf
|
|
| BLAKE2b-256 |
dedb9771a7e6e99d433484d9260d5a2738cdc9bf823d1fac708aa28da0c0cfc0
|
Provenance
The following attestation bundles were made for gpxsheet-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on pleasantone/gpxsheet
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gpxsheet-0.1.1-py3-none-any.whl -
Subject digest:
aa817e6007b21119bed6103e8966533296497084223fec3bd0aaf4a862ca40fe - Sigstore transparency entry: 1761708970
- Sigstore integration time:
-
Permalink:
pleasantone/gpxsheet@56d44dd935e97826c177684c575d4f2c124b9fb3 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/pleasantone
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@56d44dd935e97826c177684c575d4f2c124b9fb3 -
Trigger Event:
push
-
Statement type: