No description
  • Rust 95.5%
  • Nix 4.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-12 02:18:54 +02:00
src gpx: set track name 2026-08-12 02:18:54 +02:00
.gitignore start with chatgpt 2026-08-12 01:18:34 +02:00
Cargo.lock start with chatgpt 2026-08-12 01:18:34 +02:00
Cargo.toml start with chatgpt 2026-08-12 01:18:34 +02:00
flake.lock start with chatgpt 2026-08-12 01:18:34 +02:00
flake.nix start with chatgpt 2026-08-12 01:18:34 +02:00
README.md start with chatgpt 2026-08-12 01:18:34 +02:00

borderpoi-rs

borderpoi-rs finds administrative border crossings along a GPX track.

It is designed for long-distance cycling, bikepacking and ultracycling.

The program reads a GPX track and a GeoJSON file containing administrative boundary polygons. It detects every county/district transition, preserves repeated visits, and adds a GPX waypoint for every border crossing.

Features

  • GPX input
  • GPX output
  • stdin/stdout support
  • GeoJSON boundary input
  • R*-tree spatial index
  • repeated county visits are preserved
  • one GPX POI per border crossing
  • no distance calculation
  • no GDAL dependency
  • no native GIS library dependency

Input boundary data

The boundary GeoJSON must contain Polygon or MultiPolygon features.

Coordinates must be WGS84 / EPSG:4326.

For BKG VG250 data, the relevant layer is normally:

VG250_KRS

The default properties expected by borderpoi-rs are:

GEN
AGS

GEN is used as the displayed county name.

AGS is used as the administrative identifier.

Usage

GPX file to GPX file

borderpoi-rs \
    --track route.gpx \
    --boundaries VG250_KRS.geojson \
    --output route-with-borders.gpx

stdin to stdout

cat route.gpx \
    | borderpoi-rs \
        --boundaries VG250_KRS.geojson \
    > route-with-borders.gpx

Explicit stdin/stdout

- can be used explicitly:

borderpoi-rs \
    --track - \
    --boundaries VG250_KRS.geojson \
    --output -

Unix pipeline

The program writes diagnostic information to stderr and GPX data to stdout.

Therefore this works:

borderpoi-rs \
    --boundaries VG250_KRS.geojson \
    < route.gpx \
    > route-with-borders.gpx

The county report can still be seen in the terminal.

Command line

Usage: borderpoi-rs [OPTIONS] --boundaries <BOUNDARIES>

Options:
  -t, --track <TRACK>
      Input GPX track. Reads from stdin when omitted or set to '-'.

  -b, --boundaries <BOUNDARIES>
      Boundary GeoJSON.

      The GeoJSON must contain Polygon/MultiPolygon geometries
      in WGS84 / EPSG:4326.

      Required.

  --name-field <NAME_FIELD>
      Property containing the county name.

      [default: GEN]

  --id-field <ID_FIELD>
      Property containing the administrative identifier.

      [default: AGS]

  -o, --output <OUTPUT>
      Output GPX. Writes to stdout when omitted or set to '-'.

  -h, --help
      Print help.

  -V, --version
      Print version.

County sequence

The output preserves the order in which counties are visited.

Repeated visits are intentionally preserved.

For example:

Roth
Neumarkt i.d.OPf.
Regensburg
Neumarkt i.d.OPf.
Roth

is reported as exactly that.

It is not reduced to:

Roth
Neumarkt i.d.OPf.
Regensburg

This is important for bikepacking and ultracycling routes that cross administrative boundaries multiple times.

Border POIs

For every transition, a waypoint is added to the output GPX.

Example:

Border 01: Landkreis Roth -> Landkreis Neumarkt i.d.OPf.
Border 02: Landkreis Neumarkt i.d.OPf. -> Landkreis Regensburg
Border 03: Landkreis Regensburg -> Landkreis Neumarkt i.d.OPf.
Border 04: Landkreis Neumarkt i.d.OPf. -> Landkreis Roth

Each waypoint contains:

  • crossing number
  • source county
  • destination county
  • source AGS
  • destination AGS
  • administrative_boundary as GPX waypoint type

Spatial index

All county geometries are inserted into an R*-tree.

For every GPX track segment the algorithm performs:

GPX segment
     |
     v
segment bounding box
     |
     v
R*-tree lookup
     |
     v
candidate county polygons
     |
     v
exact point-in-polygon test
     |
     v
county transition
     |
     v
exact boundary intersection
     |
     v
GPX waypoint

This avoids testing every GPX segment against every county polygon.

This is particularly useful for long GPX tracks.

Coordinate reference system

borderpoi-rs expects the boundary GeoJSON to use:

EPSG:4326 / WGS84

GPX coordinates are also WGS84.

No CRS transformation is performed inside the program.

This is intentional: it removes the GDAL dependency and keeps the application entirely Rust-native.

Why no GDAL?

The previous implementation used GDAL to read the BKG GeoPackage.

That caused the Rust build to depend on the system GDAL version.

For example:

gdal       0.19.0
gdal-sys   0.12.0
system     GDAL 3.13.2

and required generated GDAL bindings.

borderpoi-rs does not actually need GDAL for its runtime operation.

The recommended workflow is therefore:

BKG VG250
     |
     | one-time conversion
     v
WGS84 GeoJSON
     |
     v
borderpoi-rs

The conversion from the original BKG dataset can be performed with GDAL/QGIS once, but the resulting command line tool has no GDAL dependency.

Building with Nix

Enter the development environment:

nix develop

Then:

cargo build --release

Run:

cargo run --release -- \
    --boundaries VG250_KRS.geojson \
    < route.gpx \
    > route-with-borders.gpx

Building entirely with Nix

First generate the lock file:

cargo generate-lockfile

Then:

nix build

The resulting executable is:

./result/bin/borderpoi-rs

Example:

./result/bin/borderpoi-rs \
    --boundaries VG250_KRS.geojson \
    --track route.gpx \
    --output route-with-borders.gpx

Formatting and linting

Format:

cargo fmt

Check:

cargo check

Run Clippy:

cargo clippy --all-targets --all-features -- -D warnings

Input assumptions

The current implementation assumes:

  1. GPX coordinates are WGS84.
  2. Boundary coordinates are WGS84.
  3. Boundary features are Polygon or MultiPolygon.
  4. The GPX track normally lies inside a county polygon.
  5. Administrative boundary geometries are topologically valid.

Tracks that run exactly along a county boundary are inherently ambiguous.

Such a section is deliberately not interpreted as a sequence of crossings.

Output contract

stdout:

output GPX only

stderr:

diagnostic messages
county sequence
border crossing report

This makes the program suitable for Unix pipelines.

Example:

borderpoi-rs \
    --boundaries counties.geojson \
    < route.gpx \
    > result.gpx

while the report remains visible on the terminal.

License

MIT