Skip to content

Routes across borders

Track access charges are billed per infrastructure manager (IM), and a cross-border train has to handle every electrification and train protection system on its way (for an estimate of the charges per IM, see track access charges). POST /v1/route lists them for each route in travel order, with km along the route, when you ask for them with include:

includeFields of each routeWhat
countriescountries{country, from_km, to_km, distance_km, source}: stretches by country (ISO 3166-1 alpha-2)
border_crossings{from_country, to_country, km, lat, lon, name?, source}
infrastructure_managersinfrastructure_managers{im, im_code?, source, from_km, to_km, distance_km}: stretches by IM
distance_by_im{im, im_code?, distance_km, share}: km per IM, longest first; the shares sum to 1
changeschanges{km, kind, from, to, lat, lon}, kind electrification or train_protection
Terminal window
curl -H "Authorization: Bearer $RAIL_API_KEY" -H "Content-Type: application/json" \
https://api.routing.gleisklar.de/v1/route -d '{
"from": { "station": "RF" }, "to": { "station": "8500010" },
"profile": "freight-electric-multisystem",
"include": ["countries", "infrastructure_managers", "changes"]
}'

Freiburg (Breisgau) Hbf by its DS100 code, Basel SBB by its UIC code (find codes by name with GET /v1/places?q=). Each route then carries, besides distance_km, duration_min, reversals and legs:

"border_crossings": [{ "from_country": "DE", "to_country": "CH", "km": 62.4, "lat": 47.5638, "lon": 7.6359,
"name": "Basel Bad Bf CH/BW", "source": "rinf" }],
"distance_by_im": [{ "im": "DB InfraGO", "im_code": "0080", "distance_km": 62.4, "share": 0.93 },
{ "im": "SBB Infrastruktur", "im_code": "0085", "distance_km": 4.7, "share": 0.07 }],
"changes": [{ "km": 62.4, "kind": "train_protection", "from": "PZB", "to": "Integra-Signum", "lat": 47.5638, "lon": 7.6359 }]

(Illustrative values.) With the SDK:

const { routes: [trip] } = await api.route({
from: { station: "RF" },
to: { station: "8500010" },
include: ["countries", "infrastructure_managers", "changes"],
});
for (const b of trip.border_crossings ?? []) console.log(`${b.from_country} → ${b.to_country} at km ${b.km} (${b.source})`);
for (const m of trip.distance_by_im ?? []) console.log(`${m.im}: ${m.distance_km} km`);
for (const c of trip.changes ?? []) console.log(`km ${c.km} ${c.kind}: ${c.from} → ${c.to}`);

Each piece of track gets a value; the response groups equal values into stretches.

  • Country: the country of the track’s RINF section of line (the member state that reports it to ERA). Track without RINF data: the country of the IM its OpenStreetMap operator tag names (DB InfraGO AG, ÖBB-Infrastruktur AG, SBB …), else the country of the nearest station whose code says (RINF UOPID prefix or UIC country digits, within 30 km). source says which of rinf, osm or stations decided most of the stretch.
  • Infrastructure manager: the organisation code RINF gives for the section (0080 DB InfraGO, 0081 ÖBB-Infrastruktur, 0085 SBB, 0084 ProRail …, named from a table of each RINF country’s main IM; others appear as IM 3023), else the OSM operator tag (the known IM it means, or the operator as tagged).
  • Electrification system: RINF energy supply (AC 15kV-16.7Hz), else OSM voltage and frequency, written as 15 kV 16.7 Hz, 25 kV 50 Hz, 3 kV DC, or none for track without electrification. Electrified track of unknown voltage or frequency counts as unknown.
  • Train protection: RINF legacy systems and ETCS level, plus OSM railway:pzb/lzb/etcs, sorted and joined: PZB, LZB+PZB, ETCS+PZB, Integra-Signum; none where RINF reports none.
  • Border crossing: where the country changes. It is placed at an OSM railway=border node on the route within 3 km of the change (source osm, named after the node), else where the RINF sections of the two countries meet (rinf, named after the operational point they share), else between the two stretches (estimate).

Stretches under 200 m (a switch, a crossover, one oddly tagged way) are ignored. Unknown stretches under 1 km are bridged: merged when both sides agree, else split halfway. Longer unknown stretches are reported as unknown (a country, an IM, or the from/to of a change), not attributed to a neighbour: a 3 km stretch without an IM shows up as unknown in distance_by_im.

  • With RINF on both sides, countries, IMs and borders are accurate to the OpenStreetMap way the section was matched to (typically a few hundred metres).
  • RINF reports border stations by the IM that runs them: Basel Bad Bf, on Swiss soil, is in the German data up to the operational point Basel Bad Bf CH/BW. The route’s countries follow RINF there, which is what matters for track access.
  • Track that RINF doesn’t cover (sidings, unmatched ways, countries whose RINF can’t be placed yet, e.g. Switzerland) relies on the OSM operator tag, then on the nearest station: such a border can be off by up to half the distance between the last stations on either side (estimate), unless OSM maps a railway=border node there.