borderpoi-rs/README.md
2026-08-12 01:18:34 +02:00

330 lines
6.4 KiB
Markdown

# 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