gpx
gpx is a zero-dependency, pure Python package for reading, manipulating, writing and converting GPX (GPS Exchange Format) files.
Installation
gpx is available on PyPI. Install with uv or your package manager of choice:
uv add gpx
Documentation
Check out the gpx documentation for the User's Guide, API Reference and CLI Reference.
Usage
Reading a GPX file
from gpx import read_gpx
# Read GPX data from file
gpx = read_gpx("path/to/file.gpx")
# Access basic properties
print(f"Creator: {gpx.creator}")
print(f"Number of waypoints: {len(gpx.wpt)}")
print(f"Number of tracks: {len(gpx.trk)}")
print(f"Number of routes: {len(gpx.rte)}")
Working with waypoints
from decimal import Decimal
from gpx import Latitude, Longitude, Waypoint, read_gpx
# Read GPX data from file
gpx = read_gpx("path/to/file.gpx")
# Create a new waypoint
waypoint = Waypoint(
lat=Latitude("52.3676"),
lon=Longitude("4.9041"),
name="Amsterdam",
desc="Capital of the Netherlands",
ele=Decimal("2.0"),
)
# Add waypoint to GPX data
gpx.wpt.append(waypoint)
# Iterate over waypoints
for idx, wpt in enumerate(gpx.wpt):
print(f"{idx}: ({wpt.lat}, {wpt.lon})")
Working with tracks and statistics
from gpx import read_gpx
# Read GPX data from file
gpx = read_gpx("path/to/file.gpx")
# Access track data
for track in gpx.trk:
print(f"Track: {track.name}")
# Get statistics from the track
print(f" Total distance: {track.total_distance:.2f} meters")
print(f" Total duration: {track.total_duration}")
print(f" Average speed: {track.avg_speed:.2f} m/s")
print(f" Max elevation: {track.max_elevation} meters")
print(f" Total ascent: {track.total_ascent} meters")
# Iterate over track segments and points
for track_segment in track.trkseg:
print(f" Segment with {len(track_segment.trkpt)} points")
for track_point in track_segment.trkpt:
print(
f" Point: ({track_point.lat}, {track_point.lon}) at {track_point.time}"
)
Creating a GPX from scratch
import datetime as dt
from decimal import Decimal
from gpx import (
GPX,
Latitude,
Longitude,
Metadata,
Track,
TrackSegment,
Waypoint,
)
# Create track points
track_points = []
for i in range(5):
point = Waypoint(
lat=Latitude(Decimal("52.0") + i * Decimal("0.01")),
lon=Longitude(Decimal("4.0") + i * Decimal("0.01")),
ele=Decimal("10.0") + Decimal(i) * Decimal("2.0"),
time=dt.datetime.now(dt.UTC),
)
track_points.append(point)
# Create a track with segments
track_segment = TrackSegment(trkpt=track_points)
track = Track(name="Morning Run", trkseg=[track_segment])
# Create metadata
metadata = Metadata(
name="My GPS Track",
desc="A sample track",
time=dt.datetime.now(dt.UTC),
)
# Create GPX object (creator defaults to "*gpx*")
gpx = GPX(
creator="My Application",
metadata=metadata,
trk=[track],
)
Writing GPX files
from gpx import from_string
# Write GPX data to file
gpx.write_gpx("output.gpx")
# Convert to string
gpx_string = gpx.to_string()
print(gpx_string)
# Parse from string
gpx = from_string(gpx_string)
Working with routes
from gpx import Latitude, Longitude, Route, Waypoint
# Create route points (waypoints)
route_point_1 = Waypoint(
lat=Latitude("52.3676"),
lon=Longitude("4.9041"),
name="Start: Amsterdam Centraal",
)
route_point_2 = Waypoint(
lat=Latitude("52.3731"),
lon=Longitude("4.8922"),
name="Dam Square",
)
# Create a route
route = Route(name="City Tour", rtept=[route_point_1, route_point_2])
gpx.rte.append(route)
# Access route statistics
print(f"Route distance: {route.total_distance:.2f} meters")
Working with GPX Extensions
gpx supports reading and writing GPX extensions from any namespace, enabling lossless round-trip handling of vendor-specific data like Garmin's TrackPointExtension:
from gpx import read_gpx
# Define extension namespace
GARMIN_TPX = "http://www.garmin.com/xmlschemas/TrackPointExtension/v2"
# Read GPX file with extensions
gpx = read_gpx("activity.gpx")
# Access extension data from track points
for track in gpx.trk:
for track_segment in track.trkseg:
for track_point in track_segment.trkpt:
if track_point.extensions:
if (
hr := track_point.extensions.get_int("hr", namespace=GARMIN_TPX)
) is not None:
print(f"Heart rate: {hr} bpm")
Creating GPX files with extensions:
import xml.etree.ElementTree as ET
from decimal import Decimal
from gpx import (
Extensions,
GPX,
Latitude,
Longitude,
Track,
TrackSegment,
Waypoint,
)
# Register namespace prefix for cleaner XML output
GARMIN_TPX = "http://www.garmin.com/xmlschemas/TrackPointExtension/v2"
ET.register_namespace("gpxtpx", GARMIN_TPX)
# Create extension element
tpx = ET.Element(f"{{{GARMIN_TPX}}}TrackPointExtension")
hr = ET.SubElement(tpx, f"{{{GARMIN_TPX}}}hr")
hr.text = "145"
# Create waypoint with extensions
point = Waypoint(
lat=Latitude("52.0"),
lon=Longitude("4.0"),
extensions=Extensions(elements=[tpx]),
)
# Build GPX with the point
gpx = GPX(trk=[Track(trkseg=[TrackSegment(trkpt=[point])])])
gpx.write_gpx("with_extensions.gpx")
Converting to other formats
gpx supports converting GPX data to various formats:
from gpx import GPX, read_gpx
gpx = read_gpx("path/to/file.gpx")
# Write to file formats
gpx.write_gpx("output.gpx") # GPX file
gpx.write_geojson("output.geojson") # GeoJSON file
gpx.write_kml("output.kml") # KML file (Google Earth)
# Convert to data formats (strings/bytes)
wkt_string = gpx.to_wkt() # Well-Known Text
wkb_bytes = gpx.to_wkb() # Well-Known Binary
# Access GeoJSON-compatible data via the __geo_interface__ property
geojson_dict = gpx.__geo_interface__
Reading from other file formats
gpx can read data from various file formats:
from gpx import read_gpx, read_geojson, read_kml
# Read from files
gpx = read_gpx("path/to/file.gpx")
gpx = read_geojson("path/to/file.geojson")
gpx = read_kml("path/to/file.kml")
Converting between file formats
gpx can convert files between the GPX, GeoJSON and KML file formats directly:
from gpx import convert_file
# Formats are auto-detected from the file extensions...
convert_file("input.gpx", "output.geojson")
convert_file("input.kml", "output.gpx")
# ... or can be given explicitly
convert_file("input.json", "output.gpx", input_format="geojson")
Converting from data formats
gpx can convert from data formats (strings, bytes, objects):
from gpx import from_geo_interface, from_wkt, from_wkb
# Convert from WKT (Well-Known Text)
gpx = from_wkt("POINT (4.9041 52.3676)")
gpx = from_wkt("LINESTRING (4.9 52.3, 4.91 52.31, 4.92 52.32)")
# Convert from WKB (Well-Known Binary)
gpx = from_wkb(wkb_bytes)
# Convert from any object that implements the __geo_interface__ protocol (e.g., Shapely)
from shapely.geometry import Point, LineString
point = Point(4.9041, 52.3676)
gpx = from_geo_interface(point)
line = LineString([(4.9, 52.3), (4.91, 52.31), (4.92, 52.32)])
gpx = from_geo_interface(line)
# Or convert from a GeoJSON dict directly
geojson = {"type": "Point", "coordinates": [4.9041, 52.3676]}
gpx = from_geo_interface(geojson)
Editing and merging GPX data
gpx provides operations for editing and merging GPX data. All operations are pure: they return a new GPX instance and never mutate the input:
import datetime as dt
from gpx import (
crop,
filter_points,
merge,
read_gpx,
reduce_precision,
reverse,
shift_time,
simplify,
smooth,
split,
strip_extensions,
strip_metadata,
trim,
)
gpx = read_gpx("path/to/file.gpx")
# Crop to a geographic bounding box
cropped = crop(gpx, min_lat=52.0, max_lat=53.0, min_lon=4.0, max_lon=5.0)
# Trim to a date/time range
trimmed = trim(gpx, start=dt.datetime(2024, 1, 1, 10, 0, 0, tzinfo=dt.UTC))
# Reverse routes and/or tracks
reversed_gpx = reverse(gpx)
# Strip metadata (fields)
anonymous = strip_metadata(gpx, author=True, copyright=True)
bare = strip_metadata(gpx) # removes all metadata
# Reduce coordinate and/or elevation precision
reduced = reduce_precision(gpx, coordinate_precision=6, elevation_precision=1)
# Filter points with an arbitrary predicate
with_elevation = filter_points(gpx, lambda point: point.ele is not None)
# Split track segments at time and/or distance gaps
split_gpx = split(gpx, time_gap=dt.timedelta(minutes=10))
# Simplify tracks and routes (Ramer-Douglas-Peucker, tolerance in metres)
simplified = simplify(gpx, tolerance=10.0)
# Smooth track and route coordinates and elevations (moving average)
smoothed = smooth(gpx, window=5)
# Shift all point timestamps (e.g., to fix timezone mistakes or clock drift)
shifted = shift_time(gpx, dt.timedelta(hours=-2))
# Strip all extensions (e.g., heart rate, cadence, temperature)
without_extensions = strip_extensions(gpx)
# Merge multiple GPX instances into one
merged = merge([read_gpx("one.gpx"), read_gpx("two.gpx")])
Validating against the GPX 1.1 schema
gpx can validate GPX data against the GPX 1.1 schema, catching problems that lenient parsing silently ignores (unknown/misspelled elements, duplicates, out-of-order children, out-of-range values, and more):
from gpx import read_gpx, validate, validate_text
# Validate a file path, a string of GPX content, or a GPX instance
result = validate("path/to/file.gpx")
# Validate a string that must be treated as GPX content, never as a file path
# (e.g. untrusted input)
result = validate_text(gpx_string)
if not result.is_valid:
for issue in result.errors:
# e.g. "ERROR line 8 gpx > wpt[0]: unknown element <nmae> (did you mean <name>?)"
print(issue)
# Warnings are off-spec but readable (e.g. out-of-order elements, naive timestamps)
for warning in result.warnings:
print(warning)
# Validate a programmatically built GPX before writing it
gpx = read_gpx("path/to/file.gpx")
assert validate(gpx).is_valid
# Opt in to strict parsing: raises InvalidGPXError on any schema error
strict_gpx = read_gpx("path/to/file.gpx", strict=True)
Command-Line Interface
gpx provides a command-line interface (CLI) for common GPX operations:
# Validate a GPX file against the GPX 1.1 schema
gpx validate path/to/file.gpx
gpx validate --strict path/to/file.gpx # also fail on warnings
gpx validate --json path/to/file.gpx # machine-readable report
# Show information and statistics about a GPX file
gpx info path/to/file.gpx
gpx info --json path/to/file.gpx # Output as JSON
# Edit a GPX file
gpx edit input.gpx -o output.gpx --reverse-tracks
gpx edit input.gpx -o output.gpx --min-lat 52.0 --max-lat 53.0
gpx edit input.gpx -o output.gpx --start 2024-01-01T10:00:00 --end 2024-01-01T12:00:00
gpx edit input.gpx -o output.gpx --precision 5 --elevation-precision 1
gpx edit input.gpx -o output.gpx --strip-all-metadata
gpx edit input.gpx -o output.gpx --split-time-gap 600
gpx edit input.gpx -o output.gpx --simplify 10 --smooth 5
gpx edit input.gpx -o output.gpx --shift-time -7200
gpx edit input.gpx -o output.gpx --strip-extensions
# Merge multiple GPX files
gpx merge file1.gpx file2.gpx file3.gpx -o merged.gpx
# Convert between formats
gpx convert input.gpx -o output.geojson
gpx convert input.gpx -o output.kml
gpx convert input.geojson -o output.gpx
# Validate the input first with --strict (info, edit, merge and convert):
# prints warnings and aborts on schema errors before doing any work
gpx info --strict path/to/file.gpx
gpx edit --strict input.gpx -o output.gpx --simplify 10
gpx merge --strict file1.gpx file2.gpx -o merged.gpx
Metadata
Release files for gpx 2026.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| gpx-2026.4.0.tar.gz | 70.5 kB | Without provenance | Details for gpx-2026.4.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| gpx-2026.4.0-py3-none-any.whl | Python 3 | none | any | Details for gpx-2026.4.0-py3-none-any.whl |
Total release size: 148.1 kB
Release files / gpx-2026.4.0.tar.gz
| Download URL | gpx-2026.4.0.tar.gz |
|---|---|
| Size | 70.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ff64a5d64bebf7e32c9d38b7a135cdc40cbaa64d92f537a053b050fcea2d2f4d
|
|
BLAKE2b-256 checksum How to use checksums |
d91e09e708ccdb0605bc77900ec020deab902893fe51e478deb257210002a273
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / gpx-2026.4.0-py3-none-any.whl
| Download URL | gpx-2026.4.0-py3-none-any.whl |
|---|---|
| Size | 77.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8bcbb4964360f20189251bd6bb83bd49014cb9a7222589d227bdecd17bd62cea
|
|
BLAKE2b-256 checksum How to use checksums |
7fac8764e3af2c6a4016c06229e7a3acc8296b2aa25d959567f6e512af7c4745
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for gpx 2026.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.