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.
Documentation: gpxsheet.readthedocs.io — library API, web API guide + interactive reference, and deployment notes.
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; PEP 561 typed. - Web service — a FastAPI app exposing the engine over REST (async jobs); see the Web service section and docs/web-api.md.
Installation
pip install gpxsheet
Development
git clone <repo-url> gpxsheet && cd gpxsheet
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]" # add ,service for the web service stack
python -m build && twine check dist/* # build + check the distribution
# publish (maintainer only): twine upload dist/*
Usage
Decisions and segments come from OpenStreetMap road topology (durable road-name
changes, named roads, fuel). It degrades to the geometry baseline automatically
(with a warning) when the route is too sparse to follow roads or the live
Overpass query fails. The portrait roadbook layout is the default; pass
--landscape for one strip/page.
gpxsheet generate route.gpx -o route.pdf # portrait roadbook (default)
gpxsheet generate route.gpx --landscape -o route.pdf
# layout knobs: --lane-decisions M (decisions per page; default auto-fit); --lanes N (portrait lanes/page)
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 test suite is deterministic and offline: it
replays committed Overpass responses from tests/fixtures/osm_cache (re-record
with GPXSHEET_RECORD_OSM=1; see tests/fixtures/README.md).
Library
The library mirrors the web API — render, analyze, validate — but runs
synchronously:
import gpxsheet
gpxsheet.render("route.gpx", "route.pdf", layout="portrait") # also landscape/preview/strip, format=pdf|png
route = gpxsheet.analyze("route.gpx", fuel_range=180)
report = gpxsheet.validate("route.gpx", fuel_range=180) # report.findings
Web service
A FastAPI service exposes the engine over REST. Every operation is a background
job (Dramatiq + Redis) with results in object storage (MinIO): you POST to a
typed endpoint (GPX + params as multipart form fields), then poll and fetch via
the shared job URLs. /v1/render takes a layout
(portrait/landscape/preview/strip) and format (pdf/png);
/v1/analyze and /v1/validate return JSON reports.
Self-hosted stack (API + worker + Redis + MinIO):
docker compose up --build
# API -> http://localhost:8000/docs
# MinIO -> http://localhost:9001 (minioadmin / minioadmin)
# render a portrait PDF (the defaults); the 202 response's Location header is the job
curl -F gpx=@route.gpx http://localhost:8000/v1/render # -> {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
# other examples (same poll -> fetch flow):
curl -F gpx=@route.gpx -F layout=preview -F format=png http://localhost:8000/v1/render
curl -F gpx=@route.gpx -F layout=landscape -F paper=a4 http://localhost:8000/v1/render
curl -F gpx=@route.gpx http://localhost:8000/v1/analyze # JSON report
Endpoints: POST /v1/render, POST /v1/analyze, POST /v1/validate (each
uploads a GPX + params → 202 job with a Location header, or 200 if an
identical request is already done); GET /v1/jobs/{id} (status, incl.
content_type when done), GET /v1/jobs/{id}/result (streams the artifact with
an immutable ETag, or 303 → presigned URL; 425 until ready, 409 if failed);
GET /healthz (liveness) and GET /readyz (Redis/MinIO reachable). When API
keys are configured, jobs are visible only to the key that created them.
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).
Building a UI on the API? See the front-end integration guide:
docs/web-api.md (submit → poll → fetch flow, every endpoint,
auth, and browser fetch examples). Live OpenAPI docs are served at /docs.
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_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.2.1.tar.gz.
File metadata
- Download URL: gpxsheet-0.2.1.tar.gz
- Upload date:
- Size: 110.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ba7c37f7507c093f1782ca084497a1253aefd114feed6dd45e8651a48a4db95c
|
|
| MD5 |
240cd123220ba6d356f97fd96b00da9a
|
|
| BLAKE2b-256 |
3455d4c06141ff8359cecb755d71dd3a1c02b857dd9344344500eb2b74678031
|
Provenance
The following attestation bundles were made for gpxsheet-0.2.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.2.1.tar.gz -
Subject digest:
ba7c37f7507c093f1782ca084497a1253aefd114feed6dd45e8651a48a4db95c - Sigstore transparency entry: 1784004309
- Sigstore integration time:
-
Permalink:
pleasantone/gpxsheet@c7e60ade70e9436fbc76675e6882ea4fd88488e8 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/pleasantone
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c7e60ade70e9436fbc76675e6882ea4fd88488e8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file gpxsheet-0.2.1-py3-none-any.whl.
File metadata
- Download URL: gpxsheet-0.2.1-py3-none-any.whl
- Upload date:
- Size: 92.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 |
b41ddb5e2dcf6fe141a8211c2fcba305cd10f5c282dfacd6535682632d7e30e2
|
|
| MD5 |
746d5b1ce43736383e1728c60a32feeb
|
|
| BLAKE2b-256 |
4cc830b99fa5fab3afdffa3a95dcd3003db10192b0ea2ced89cb86afa611be28
|
Provenance
The following attestation bundles were made for gpxsheet-0.2.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.2.1-py3-none-any.whl -
Subject digest:
b41ddb5e2dcf6fe141a8211c2fcba305cd10f5c282dfacd6535682632d7e30e2 - Sigstore transparency entry: 1784005246
- Sigstore integration time:
-
Permalink:
pleasantone/gpxsheet@c7e60ade70e9436fbc76675e6882ea4fd88488e8 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/pleasantone
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c7e60ade70e9436fbc76675e6882ea4fd88488e8 -
Trigger Event:
push
-
Statement type: