Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog and this project adheres to Calendar Versioning.

The first number of the version is the year. The second number is incremented with each release, starting at 1 for each year. The third number is for emergencies when we need to start branches for older releases.

Unreleased

Added

  • New operations module that exposes the editing and merging operations behind the gpx edit and gpx merge CLI commands as a public, reusable API:

    • crop(): Crop a GPX to a geographic bounding box

    • trim(): Trim a GPX to a date/time range

    • reverse(): Reverse the routes and/or tracks of a GPX

    • strip_metadata(): Strip metadata (fields) from a GPX

    • reduce_precision(): Reduce the precision of the coordinates and/or elevations of a GPX

    • filter_points(): Filter the points of a GPX with an arbitrary predicate

    • merge(): Merge multiple GPX instances into one

    • split(): Split track segments at time and/or distance gaps

    • simplify(): Simplify the tracks and routes of a GPX with the Ramer-Douglas-Peucker algorithm

    • smooth(): Smooth the coordinates and/or elevations of the tracks and routes of a GPX with a moving average

    • shift_time(): Shift all point timestamps of a GPX by a time delta

    • strip_extensions(): Strip all extensions from a GPX

    • All operations are pure (they return a new GPX instance and never mutate the input) and are importable directly from the top-level package (e.g. from gpx import crop, merge).

  • New gpx edit CLI options exposing the new operations:

    • --split-time-gap SECONDS and --split-distance-gap METERS: Split track segments at gaps

    • --simplify TOLERANCE: Simplify tracks and routes (tolerance in metres)

    • --smooth WINDOW: Smooth track and route coordinates and elevations with a moving average

    • --shift-time SECONDS: Shift all point timestamps (may be negative)

    • --strip-extensions: Strip all extensions

  • New file conversion functions in the io module, exposing the operation behind the gpx convert CLI command as a public, reusable API:

    • convert_file(): Convert a file between the GPX, GeoJSON and KML file formats

    • detect_format(): Detect the file format from a file path’s extension

    • Both are importable directly from the top-level package (e.g. from gpx import convert_file).

  • New validation module that validates GPX data against the GPX 1.1 schema (gpx.xsd) without any extra dependencies:

    • validate(): Validate a file path, a string of GPX content, or a GPX instance and return a ValidationResult.

    • ValidationResult: Holds all issues, with is_valid, errors and warnings properties.

    • ValidationIssue: A single issue with a severity, message, path (e.g. gpx > trk[0] > trkseg[2] > trkpt[14]) and source line (when available).

    • Severity: Enum of ERROR and WARNING.

    • InvalidGPXError: Raised by strict parsing; carries the full ValidationResult.

    • Detects, among others: wrong root element / namespace (with a GPX 1.0 hint), missing required attributes, unknown elements (with “did you mean …?” suggestions), duplicate single-occurrence elements, out-of-order children, <extensions> children that are not in a foreign namespace (e.g. unprefixed elements that inherit the default GPX namespace), and invalid values (latitude/longitude/degrees ranges, fix, dgpsid, sat, copyright year, time).

    • All names are importable directly from the top-level package (e.g. from gpx import validate).

  • New strict keyword argument on read_gpx() and from_string(). When strict=True, the input is validated against the GPX 1.1 schema first and an InvalidGPXError is raised if any errors are found. The default (strict=False) keeps the existing lenient behavior.

  • New gpx validate CLI options:

    • --strict: Treat warnings as failures (non-zero exit code).

    • --json: Output a machine-readable validation report.

  • New --strict option on the gpx info, gpx edit, gpx merge and gpx convert CLI commands. When given, each input is validated against the GPX 1.1 schema first: warnings are printed to stderr and the command aborts (non-zero exit code) if any schema errors are found. For gpx convert, validation only applies to GPX input. The default keeps the existing lenient behavior.

Changed

  • The CLI (gpx edit, gpx merge and gpx convert) now uses the new operations module and io conversion functions internally (behavior is unchanged).

  • The gpx validate CLI command is now a real GPX 1.1 schema validator. It reports all errors and warnings (with source line numbers) instead of only checking whether the file can be parsed, and exits non-zero when errors are found (or, with --strict, when warnings are found).

2026.3.0 - 2026-05-17

This third release in the year 2026 adds a new GeoGPXModel base class for GPX models that carry geometric data.

Added

  • New GeoGPXModel base class for GPX models that carry geometric data (Waypoint, Route, TrackSegment, Track, Bounds, GPX). Enables isinstance() checks and type-hinted handling of geometric models, and enforces that subclasses implement __geo_interface__.

2026.2.0 - 2026-04-27

This second major release in the year 2026 adds support for GPX extensions, and includes a range of bug fixes and robustness improvements.

Added

  • GPX Extensions support: Added Extensions class for handling GPX extension elements from any XML namespace (e.g., Garmin’s TrackPointExtension). Extensions are now parsed, preserved, and serialized during round-trip processing, enabling lossless handling of vendor-specific data like heart rate, cadence, temperature, etc.

  • New extensions field on all models that support extensions per the GPX 1.1 spec: GPX, Metadata, Waypoint, Track, TrackSegment, and Route.

Changed

  • Track now inherits from PointsMixin and reuses its bounds and elevation aggregations (bounds, _points_with_ele, _eles, avg_elevation, min_elevation, max_elevation, diff_elevation, avg_speed, avg_moving_speed), eliminating duplication between Track and the segment/route mixin. Per-segment semantics are preserved for distance, duration, speed extremes, ascent/descent and the elevation profile by overriding only those properties.

  • int and bool fields in __geo_interface__ properties (e.g. Waypoint.sat, Track.number, Route.number) now keep their JSON-native types.

Removed

  • Removed obsolete remove_encoding_from_string function.

Fixed

  • EWKB format parsing now correctly handles Z coordinates by checking the EWKB flag before the ISO WKB flag.

  • gpx edit no longer drops custom XML namespace prefixes (e.g. gpxtpx, gpxx) from the source file. All edit transformations (crop, trim, reverse, precision, strip metadata) now propagate nsmap so round-trips preserve the original prefixes.

  • gpx edit --min-lat 0 (and the other crop bounds) is no longer silently ignored.

  • Track.elevation_profile no longer skips the first elevation-bearing point of segments after the first, and no longer raises IndexError for tracks with no segments or no points with elevation. PointsMixin.elevation_profile (used by Route and TrackSegment) similarly returns [] for empty inputs instead of crashing.

  • Waypoint.speed_to no longer raises ZeroDivisionError when two waypoints share a timestamp (or either lacks one); it returns 0.0 instead. This also fixes downstream crashes in PointsMixin._speeds, max_speed, min_speed, moving_duration, and speed_profile for tracks containing consecutive points with identical timestamps.

  • Waypoint.slope_to similarly returns Decimal(0) instead of raising ZeroDivisionError when two waypoints share coordinates.

2026.1.0 - 2026-01-07

This first major release in the year 2026 adds two new features to the package: file format conversion (i.e. from GPX to GeoJSON, KML, WKT and/or WKB and vice versa) and a command-line interface (CLI).

Added

  • New cli module with command-line interface (CLI) for common GPX operations:

    • gpx validate: Validate a GPX file

    • gpx info: Show information and statistics about a GPX file

    • gpx edit: Edit a GPX file with various transformations:

      • Crop to a geographic bounding box (--min-lat, --min-lon, --max-lat, --max-lon)

      • Trim to a date/time range (--start, --end)

      • Reverse routes and/or tracks (--reverse, --reverse-routes, --reverse-tracks)

      • Strip metadata fields (--strip-name, --strip-desc, --strip-author, --strip-copyright, --strip-time, --strip-keywords, --strip-links, --strip-all-metadata)

      • Reduce coordinate precision (--precision, --elevation-precision)

    • gpx merge: Merge multiple GPX files into one

    • gpx convert: Convert between GPX, GeoJSON, and KML formats

  • New CLI reference documentation page with auto-generated help output using cog

  • New io module with read_*() functions for reading file formats:

    • read_gpx(): Read GPX files

    • read_geojson(): Read GeoJSON files

    • read_kml(): Read KML files

  • New convert module with from_*() functions for converting from data formats:

    • from_string(): Convert from a GPX string

    • from_geo_interface(): Convert from objects that implement the __geo_interface__ protocol (e.g., Shapely)

    • from_wkb(): Convert from Well-Known Binary bytes

    • from_wkt(): Convert from Well-Known Text strings

  • New write_*() methods on the GPX class for writing to file formats:

    • write_gpx(): Write to GPX file (renamed from to_file())

    • write_geojson(): Write to GeoJSON file

    • write_kml(): Write to KML file (Google Earth format)

  • New to_*() methods on the GPX class for converting to data formats:

    • to_wkt(): Convert to Well-Known Text (OGC standard)

    • to_wkb(): Convert to Well-Known Binary (OGC standard)

Changed

  • Renamed GPX.to_file() to GPX.write_gpx() for consistency with other write methods.

  • Replaced built-in tempfile module usage with pytest’s tmp_path and tmp_path_factory fixtures in the test suite for better test isolation and automatic cleanup.

Removed

  • All aliases and proxies.

  • GPX.from_file() method in favor of io.read_gpx().

  • GPX.from_string() method in favor of convert.from_string().

2025.1.0 - 2025-11-29

This is a major release with a lot of breaking changes, primarily due to a complete rewrite of the architecture, and a general modernization of the package. Besides the change in architecture, this release adds comprehensive unit tests, enforces strict linting rules, and drops support folder older versions of Python. Finally, this release implements the __geo_interface__ protocol for all GPX elements that contain geopgraphic information, thus adding support for converting to GeoJSON.

Migration notes for users upgrading from 0.2.x:

  1. Ensure you are using Python 3.11 or higher

  2. Update constructor calls to use keyword arguments for optional parameters

  3. Update field names to use GPX-standard names (e.g., trkseg instead of trksegs)

  4. Remove validate=True from from_file() and from_string() calls

  5. Update error handling to catch standard Python exceptions instead of gpx.errors.ParseError

Example:

# Before (0.2.x)
from gpx import GPX, Waypoint

gpx = GPX.from_file("path/to/track.gpx", validate=True)
waypoint = Waypoint()
waypoint.lat = Decimal("52.0")
waypoint.lon = Decimal("4.0")
waypoint.name = "Amsterdam"
gpx.waypoints.append(waypoint)

# After (2025.1.0)
from gpx import GPX, Waypoint
from decimal import Decimal

gpx = GPX.from_file("path/to/track.gpx")
waypoint = Waypoint(Decimal("52.0"), Decimal("4.0"), name="Amsterdam")
gpx.wpt.append(waypoint)

Added

  • CLAUDE.md for easier interoperability with Claude Code.

  • Comprehensive unit tests.

  • Smoke test.

  • Testing workflow via GitHub Actions.

  • Support for Python 3.12, 3.13 and 3.14.

  • More usage examples in README.md.

  • __geo_interface__ protocol for all GPX elements that contain geographic information.

Changed

  • Upgraded the pre-commit hooks.

  • Enabled ALL rules for the ruff linter by default.

  • Use the uv build backend instead of flit.

  • Updated the installation instructions to make use of uv instead of pip.

  • Switched the versioning scheme from semantic versioning to Calendar Versioning.

  • Refactored the GPX element classes into dataclass-based models.

  • Replaced lxml with the built-in ElementTree module.

  • Optional dependencies to dependency groups.

  • Renamed PyGPX to gpx (purely aesthetically, no changes in installation or usage required).

Removed

  • Support for Python 3.7, 3.8, 3.9 and 3.10.

  • The errors module.

Fixed

  • Erroneous examples in the usage examples in README.md.

0.2.1 (2023-04-09)

Fixed

  • Serialization of route and track numbers. #10

  • Mutable default values. #11

0.2.0 (2023-04-06)

With this release, PyGPX is now fully [1] compatible with the GPX 1.1 specification.

Changes

0.1.1 (2023-03-31)

This release introduces a completely overhauled codebase. For this, I used my cookiecutter-python-package Python package template. As such, this release comes with much higher code quality, documentation and automation.

The API itself has not changed (and as such, there is no minor version bump).

Changes

  • Completely refactored codebase, with:

    • Moved source code from ./gpx to ./src/gpx

    • Fully typed and documented

  • Added documentation via Read the Docs

  • Updated CI/CD via GitHub Actions

  • Added pre-commit hooks w/ CI-integration

0.1.0 (2021-06-08)

Changes

  • Initial release of PyGPX