Command-line interface for calculating train paths and projecting GNSS positions onto railway track netelements
# Build release binary
cargo build --release --package tp-lib-cli --no-default-features
# Binary located at: target/release/tp-cli.exe (Windows) or target/release/tp-cli (Unix)# Windows PowerShell
$env:PATH += ";$(pwd)\target\release"
# Unix/Linux/macOS
export PATH="$PATH:$(pwd)/target/release"# Calculate train path and project GNSS coordinates onto track network
tp-cli --gnss positions.csv \
--crs EPSG:4326 \
--network network.geojson \
--output projected.geojson# Calculate the most likely train path without projecting coordinates
tp-cli calculate-path \
--gnss positions.csv \
--crs EPSG:4326 \
--network network.geojson \
--output path.csv# Use an existing train path file to skip path calculation
tp-cli --gnss positions.csv \
--crs EPSG:4326 \
--network network.geojson \
--train-path path.csv \
--output projected.geojson# Enable debug mode to write intermediate GeoJSON files
tp-cli --gnss positions.csv \
--crs EPSG:4326 \
--network network.geojson \
--output projected.geojson \
--debug# Omit --network to download a RINF topology subset on demand
tp-cli --gnss positions.csv \
--crs EPSG:4326 \
--output projected.geojson \
--rinf-endpoint https://graph.data.era.europa.eu/repositories/rinf-plus \
--rinf-buffer-meters 1000Flags --rinf-endpoint and --rinf-buffer-meters are optional and fall back to
the documented defaults. RINF-specific failures map to dedicated exit codes
(4=invalid GNSS, 5=missing coverage, 6=incomplete topology, 7=endpoint failure).
tp-cli has four modes of operation:
Calculates the train path and projects GNSS coordinates onto the path. This is the recommended mode.
tp-cli --gnss <FILE> --network <FILE> --output <FILE> [OPTIONS]Calculates the most likely sequence of netelements (train path) without performing coordinate projection.
tp-cli calculate-path --gnss <FILE> --network <FILE> --output <FILE> [OPTIONS]Legacy nearest-netelement projection (feature 001 behavior). Projects each GNSS position to its nearest netelement independently, without path awareness.
tp-cli simple-projection --gnss <FILE> --network <FILE> --output <FILE> [OPTIONS]Inspection helper: load a GNSS file, query the RINF SPARQL endpoint for the
topology covering those positions, and write the retrieved netelements +
netrelations to a GeoJSON file that round-trips through the standard
--network input.
tp-cli fetch-topology --gnss <FILE> --output <FILE> [OPTIONS]Options:
--crs <CRS>— CRS of the GNSS input (defaults toEPSG:4326).--lat-col,--lon-col,--time-col— CSV column names (defaultslatitude,longitude,timestamp).--rinf-endpoint <URL>— override the SPARQL endpoint.--rinf-buffer-meters <METERS>— override the buffer around the GNSS hull.
The retrieved topology is always written to disk, even when validation
reports issues such as RinfIncompleteTopology (coarse geometries) or
RinfMissingCoverage. The same validation errors are still surfaced through
the process exit code (4–7), so the file can be inspected in QGIS while the
caller can detect that the data is not fit for production use. Network or
endpoint failures (exit 7) abort before any file is written, since there is
no topology to dump in that case.
Path to GNSS input file (CSV or GeoJSON format).
CSV Example:
latitude,longitude,timestamp,altitude,hdop
50.8503,4.3517,2025-12-09T14:30:00+01:00,100.0,2.0
50.8504,4.3518,2025-12-09T14:30:01+01:00,100.5,2.1Requirements:
- CSV must have latitude, longitude, and timestamp columns
- Timestamps may be RFC3339 with timezone (e.g.,
2025-12-09T14:30:00+01:00,2025-12-09T14:30:00Z) or naive ISO 8601 (e.g.,2025-12-09T14:30:00,2025-12-09 14:30:00); naive values are interpreted in the host's local timezone. All emitted timestamps are RFC3339 with an explicit timezone offset. - Additional columns preserved as metadata
Path to railway network GeoJSON file.
Example:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"id": "NE001"
},
"geometry": {
"type": "LineString",
"coordinates": [
[4.35, 50.85],
[4.36, 50.86]
]
}
}
]
}Requirements:
- LineString geometries (track centerlines)
idproperty (unique identifier per netelement)
Output file path. The format is determined by the file extension (.csv or .geojson/.json) or the --format flag.
Coordinate Reference System of GNSS data (e.g., EPSG:4326).
Rules:
- Required for CSV input
- Not allowed for GeoJSON input (CRS is
EPSG:4326by default with GeoJSON)
Common CRS codes:
EPSG:4326- WGS84 (standard GPS coordinates)EPSG:31370- Belgian Lambert 2008EPSG:3857- Web Mercator
Output format: csv, geojson, or auto (detect from file extension). Default: auto.
Pre-calculated train path file. When provided, path calculation is skipped and this path is used directly for projection.
Save the calculated train path to this file in addition to the projected output. Useful for inspecting or reusing the path.
Distance threshold in meters for emitting projection warnings. Default: 50.0.
Warnings are printed to stderr when a GNSS position projects more than this distance from the track.
Latitude column name in CSV input. Default: latitude.
Longitude column name in CSV input. Default: longitude.
Timestamp column name in CSV input. Default: timestamp.
These parameters control the path calculation algorithm. The defaults work well for typical railway scenarios.
| Parameter | Default | Description |
|---|---|---|
--distance-scale |
10.0 |
Distance exponential decay scale (meters) |
--heading-scale |
2.0 |
Heading exponential decay scale (degrees) |
--cutoff-distance |
500.0 |
Maximum distance for candidate selection (meters) |
--heading-cutoff |
10.0 |
Maximum heading difference before rejection (degrees) |
--probability-threshold |
0.02 |
Minimum probability for path segment inclusion |
--max-candidates |
3 |
Maximum candidate netelements per GNSS position |
--resampling-distance |
(none) | Resample GNSS data at this interval (meters) |
Train detections are external observations (track-circuit occupancy, axle counters, balise reads,
operator confirmations) that anchor the calculated path through specific netelements. Punctual
detections force the path through a single netelement at the detection timestamp; linear
detections restrict the path to a netelement throughout an interval [t_from, t_to]. Detections
may be supplied either by netelement_id (topological) or by (lat, lon, crs) coordinates that
are resolved to the nearest netelement via the network R-tree.
| Flag | Default | Description |
|---|---|---|
--punctual-detections <FILE> |
(none) | Punctual detection file (CSV or GeoJSON). Each record carries a timestamp plus either a netelement_id or (lat, lon, crs) location. |
--linear-detections <FILE> |
(none) | Linear detection file (CSV or GeoJSON). Each record carries t_from/t_to plus a netelement_id and optional start_intrinsic/end_intrinsic ∈ [0, 1]. |
--cutoff-distance-detections <DECIMAL> |
2.5 |
Maximum perpendicular distance (meters) for resolving coordinate-only punctual detections to a netelement. Detections beyond this cutoff are discarded with OutOfReach. Alias: --detection-cutoff. |
After running, a one-line summary is emitted to stderr in the form:
detections: <N> applied, <M> discarded (<breakdown>)
where <breakdown> lists discard reasons such as OutOfTimeRange, OutOfReach,
DuplicateOfPriorDetection, etc. The full per-detection provenance (applied, resolved, and
discarded) is included in the path result JSON under detection_provenance.
# Anchor with a punctual CSV file plus a linear GeoJSON file
tp-cli --gnss positions.csv \
--crs EPSG:4326 \
--network network.geojson \
--punctual-detections detections_punctual.csv \
--linear-detections detections_linear.geojson \
--cutoff-distance-detections 5.0 \
--output projected.geojsonEnable debug mode. Writes intermediate GeoJSON files for each phase of the path calculation process. The files are written to the output file's parent directory, or to a custom directory specified with --debug-output-dir.
Debug files produced:
| File | Description |
|---|---|
phase2_candidates.geojson |
GNSS points with candidate projections and per-position probabilities |
phase3_netelements.geojson |
Netelement-level aggregated probabilities |
phase4_netelement_map.geojson |
Final netelement map used for path construction |
candidates.json |
Raw candidate data (JSON) |
positions.json |
Position-level debug data (JSON) |
decisions.json |
Path construction decisions (JSON) |
Directory for debug output files. Only used when --debug is also specified; a warning is emitted if --debug-output-dir is given without --debug.
When --debug is given without --debug-output-dir, files are written to the output file's parent directory.
# Debug files go to the output file's directory
tp-cli --gnss data.csv --crs EPSG:4326 --network network.geojson \
--output results/projected.geojson --debug
# Debug files go to a custom directory
tp-cli --gnss data.csv --crs EPSG:4326 --network network.geojson \
--output results/projected.geojson --debug --debug-output-dir ./debug-out| Flag | Description |
|---|---|
-v, --verbose |
Enable verbose logging output |
--quiet |
Suppress all non-error output |
-h, --help |
Print help |
-V, --version |
Print version |
- 0: Success - All positions processed successfully
- 1: Validation error - Invalid arguments or configuration
- 2: Processing error - Projection or computation failure
- 3: I/O error - File not found, permission denied, or read/write failure
CSV Output Example:
original_lat,original_lon,original_time,projected_lat,projected_lon,netelement_id,measure_meters,projection_distance_meters,crs
50.8503,4.3517,2025-12-09T14:30:00+01:00,50.85074,4.35148,NE001,132.54,51.31,EPSG:4326GeoJSON Output Example:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [4.35148, 50.85074]
},
"properties": {
"netelement_id": "NE001",
"measure_meters": 132.54,
"projection_distance_meters": 51.31,
"original_lat": 50.8503,
"original_lon": 4.3517,
"original_time": "2025-12-09T14:30:00+01:00",
"crs": "EPSG:4326"
}
}
]
}# Calculate path, save it, and project coordinates
tp-cli --gnss train_journey.csv \
--crs EPSG:4326 \
--network infrabel_network.geojson \
--output projected.geojson \
--save-path calculated_path.csv# Calculate the train path without projection
tp-cli calculate-path \
--gnss train_journey.csv \
--crs EPSG:4326 \
--network infrabel_network.geojson \
--output path.geojson# Project using a previously calculated path
tp-cli --gnss train_journey.csv \
--crs EPSG:4326 \
--network infrabel_network.geojson \
--train-path calculated_path.csv \
--output projected.geojson# Write debug GeoJSON files for inspection in QGIS
tp-cli --gnss train_journey.csv \
--crs EPSG:4326 \
--network infrabel_network.geojson \
--output projected.geojson \
--debug --debug-output-dir ./debug# Adjust for a dense urban network with tight track spacing
tp-cli --gnss data.csv \
--crs EPSG:4326 \
--network network.geojson \
--output projected.csv \
--cutoff-distance 100.0 \
--max-candidates 5 \
--distance-scale 5.0tp-cli --gnss gps_data.csv \
--crs EPSG:4326 \
--network tracks.geojson \
--output projected.csv \
--lat-col lat --lon-col lon --time-col tsProblem: Forgot to specify --crs for CSV input.
Solution:
tp-cli --gnss data.csv --crs EPSG:4326 --network network.geojson --output out.csvProblem: Provided --crs with GeoJSON input (CRS read from file).
Solution:
# Remove --crs flag
tp-cli --gnss data.geojson --network network.geojson --output out.csvMeaning: GNSS position is far from nearest track.
Possible causes:
- GPS Inaccuracy: Poor satellite signal or multipath interference
- Parallel Track: Train on adjacent track not in network
- Missing Netelement: Track segment not included in network GeoJSON
- CRS Mismatch: GNSS and network using different coordinate systems
- Outdated Geometry: Track centerline geometry outdated or incorrect
Solutions:
- Increase
--warning-thresholdif distances are expected - Check network completeness (all relevant tracks included)
- Verify CRS consistency between GNSS and network
- Validate GNSS data quality (check HDOP values)
- Update network geometry if tracks have changed
Problem: Timestamps not in RFC3339 nor a recognised naive ISO 8601 form.
Accepted formats:
- ✅
2025-12-09T14:30:00+01:00(RFC3339 with offset) - ✅
2025-12-09T14:30:00Z(RFC3339 UTC) - ✅
2025-12-09T14:30:00(naive, interpreted as local time) - ✅
2025-12-09 14:30:00(naive with space separator, local time) - ❌
09/12/2025 14:30(locale-specific date format) - ❌
14:30:00(time only)
Solution: Reformat timestamps as one of the accepted forms above. Naive timestamps are always interpreted in the machine's local timezone; emit with an explicit offset (e.g., +01:00 / Z) if you need deterministic cross-host behaviour.
Problem: Netelement LineString has < 2 points or invalid coordinates.
Solution: Validate network GeoJSON:
- Each LineString must have at least 2 coordinate pairs
- Coordinates must be valid numbers
- Order:
[longitude, latitude](not lat/lon)
# Process all CSV files in directory
for file in data/*.csv; do
echo "Processing $file..."
tp-cli --gnss "$file" \
--crs EPSG:4326 \
--network network.geojson \
--output "output/$(basename $file .csv)_projected.csv"
done# Convert output GeoJSON to Shapefile with ogr2ogr
ogr2ogr -f "ESRI Shapefile" output.shp projected.geojson -lco ENCODING=UTF-8# View all options
tp-cli --help
# View subcommand options
tp-cli calculate-path --help
# View version
tp-cli --version- TP-Lib README - Library usage and architecture
- GNSS Projection Specification - Projection requirements and design
- Train Path Calculation Specification - Path algorithm specification
For issues or questions:
- File a GitHub issue: Matdata-eu/tp-lib/issues
- Check documentation: ../specs/
- Review test cases: ../tp-core/tests/