- Rust 95.5%
- Nix 4.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| src | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
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_boundaryas 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:
- GPX coordinates are WGS84.
- Boundary coordinates are WGS84.
- Boundary features are Polygon or MultiPolygon.
- The GPX track normally lies inside a county polygon.
- 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