330 lines
6.4 KiB
Markdown
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
|