Skip to main content

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; analyze text 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
git submodule update --init          # populate gpxsamples/
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,service]"      # service required for mypy (pydantic plugin)
python -m build && twine check dist/*    # build + check the distribution
# publish (maintainer only): twine upload dist/*

Dev server modes — see docs/dev-workflow.md for the full breakdown. Short version:

# Simple (no Docker needed — EagerRunner, synchronous):
make dev-api && make dev-ui

# Full stack (real async queue, requires Docker):
make infra && make dev-api-full && make dev-worker && make dev-ui

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 — and ships a built-in browser UI.

Web UI

Drop a GPX file onto the page, see a live strip preview and route stats (distance, turns, fuel stops), adjust options, and download the final PDF or PNG. No account needed; an optional API key field is in the settings popover.

The UI is a React + Vite SPA bundled with the service. Build it once before starting the server:

pip install -e ".[service]"
cd frontend && npm ci && npm run build && cd ..
uvicorn gpxsheet.service.asgi:app
# -> http://localhost:8000/  (UI)
# -> http://localhost:8000/docs  (Swagger)

Or use make build (builds frontend + installs Python) and make dev-api / make dev-ui for a hot-reload dev workflow (Vite on :5173 proxies /v1/ to uvicorn on :8000).

REST API

Every operation is a background job (Dramatiq + Redis) with results in object storage (MinIO): 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/
#   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.

Config is env-driven (GPXSHEET_REDIS_URL switches on the prod path; see gpxsheet/service/settings.py).

Building 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gpxsheet-1.0.0.tar.gz (400.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gpxsheet-1.0.0-py3-none-any.whl (378.2 kB view details)

Uploaded Python 3

File details

Details for the file gpxsheet-1.0.0.tar.gz.

File metadata

  • Download URL: gpxsheet-1.0.0.tar.gz
  • Upload date:
  • Size: 400.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for gpxsheet-1.0.0.tar.gz
Algorithm Hash digest
SHA256 cf40983ca2448716bcb51d0958a93aeda8dce917fcacfaed1202b3c7075bd6ed
MD5 90937c8c6e6435547e7cfe3be3f933a1
BLAKE2b-256 797b60644de4bb01a0d2679c4e625603ef84d7530dc86cad66ec42dce73967e8

See more details on using hashes here.

Provenance

The following attestation bundles were made for gpxsheet-1.0.0.tar.gz:

Publisher: publish.yml on pleasantone/gpxsheet

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file gpxsheet-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: gpxsheet-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 378.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for gpxsheet-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ed4861af22ef57431f1ee9e1eb98d85d24feded784c2636092e60779a1233d8e
MD5 4b13283744fa3bf7975d7897057800ec
BLAKE2b-256 cd59951395f37d6914ac5608795a786c7abefba23ecf5e2365cca0737afb09ac

See more details on using hashes here.

Provenance

The following attestation bundles were made for gpxsheet-1.0.0-py3-none-any.whl:

Publisher: publish.yml on pleasantone/gpxsheet

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page