Wo befindet sich die "längste Gerade" im österreichischen Schienennetz?

Posted on So 28 Februar 2021 in Blog

Im Folder für den Streckenausbau Wien – Bratislava steht folgendes:

Beim vorliegendem Projekt handelt es sich um den Ausbau einer Bestandsstrecke mit der „längsten Geraden“ (rund 32,5 km) im österreichischen Eisenbahnnetz.

Daher dachte ich mir, das wäre ja nett einer Überprüfung zu unterziehen ;-) Mit ein wenig SQL und den Daten der OpenStreetMap ist eine Überprüfung auch möglich.

Inhalt:

Idee

In der OpenStreetMap ist ist das Liniennetz der ÖBB sehr gut erfasst. Daher sollte es doch eine Möglichkeit geben zu überprüfen, wo sich die "längste Gerade" befindet und wie lang diese ist.

Daher wurden die Daten auch in ein Schema at_osm in der Datenbank geladen. Erledigt wurde das mit osm2pgsql (siehe auch Routing von OSM-Daten mit OSRM und Postgres).

Um das Berechnung durchzuführen werden zuerst die entsprechenden Relationen aus den Daten der OpenStreetMap genommen. Danach die einzelnen Punkte extrahiert und der Winkel zueinander berechnet. Im nächsten Schritt wird ermittelt, ob der Winkel zwischen den einzelnen Zeilen gleich ist. Falls dem nicht so ist, ist das der Beginn einer neuen Gerade und für die Berechnung der Länge relevant. Von diesen Punkten ausgehend wird dann rekursiv die Ausdehnung berechnet. Danach wird noch die maximale Ausdehnung herangezogen und mittels des geography Datentyps die Länge ermittelt.

SQL

Um relativ leicht zu zusammenhängenden Streckenabschnitten zu kommen, habe ich mich entschieden Relationen, deren Tag route die Werte train oder railway hat, zu nehmen:

with recursive get_lines as (
    select osm_id, way, row_number() over() as tmp_id from at_osm.planet_osm_line 
    where route in ('train', 'railway')
),

Im nächsten Schritt werden die einzelnen Punkte einer Linie ermitlelt

dump_points as (
    select tmp_id, osm_id, path[1] as path_id, geom from get_lines as a inner join lateral st_dumppoints(way) as b on true 
),

Daraufhin wird die Lage bzw. der Winkel der Punkte zueinander ermittelt

calculate_azimuth as (
    select a.tmp_id, a.osm_id, a.path_id, 
    round(st_azimuth(a.geom, b.geom)::numeric, 1) as azimuth,a.geom
    from dump_points as a inner join dump_points as b on (a.osm_id=b.osm_id and a.path_id+1=b.path_id and a.tmp_id=b.tmp_id)
)

Jetzt wird überprüft, ob der Winkel davor gleich ist, wie der Aktuelle. Falls das nicht der Fall ist, dann haben wir den Beginn eines neuen gerade Streckenabschnitts

calculate_lag as (
    select tmp_id, osm_id, azimuth, geom, path_id,
    case when (azimuth IS DISTINCT FROM lag(azimuth) over (partition by tmp_id, osm_id order by path_id)) then true end as 
    calculate_recursion
    from calculate_azimuth
),

Jetzt wird rekursiv, ausgehend vom Beginn einer jeden "Gerade", die einzelnen Ausdehnungschritte berechnet

calculate_longest_line as (
    select tmp_id, osm_id, path_id as start_path_id, path_id, geom, geom as end_point, azimuth from calculate_lag where calculate_recursion=true
    union all
    select a.tmp_id, a.osm_id, b.start_path_id, a.path_id, b.geom, a.geom, a.azimuth from calculate_lag as a, calculate_longest_line as b
    where a.osm_id=b.osm_id and a.tmp_id=b.tmp_id and a.path_id=b.path_id+1 and a.calculate_recursion is null
),

Nun müssen wir noch ermitteln, was die maximale Ausdehnung ist

calculate_path_range as (
    select tmp_id, osm_id, start_path_id, max(path_id) as end_path_id from calculate_longest_line group by 1,2,3
),

Daraufhin kann die Länge berechnet werden. Hierfür wird einerseits der geography Datentyp herangezogen, um die tatsächliche Länge zu bekommen und um den Unterschied, der durch die Kartenprojektion entsteht, wird auch die Länge in der Projektion der OSM-Daten ermittelt.

calculate_length as (
    select a.tmp_id, a.osm_id, a.start_path_id, end_path_id, azimuth, 
    ST_Distance(ST_Transform(geom, 4326)::geography, ST_Transform(end_point, 4326)::geography) as length_geography, 
    ST_Distance(geom, end_point) as length_geom
    from calculate_longest_line as a,
    calculate_path_range as b where a.osm_id=b.osm_id and a.tmp_id=b.tmp_id and a.start_path_id=b.start_path_id
    and b.end_path_id=a.path_id
),

Jetzt wird noch die maximale Länge pro OSM-Relation berechnet.

get_max_length as (
    select tmp_id, osm_id, max(length_geography) as max_length_geography,
    max(length_geom) as max_length_geom from calculate_length
    group by 1,2
)

Und zum Abschluss kann man sich Resultate sortiert ausgeben

select * from get_max_length order by max_length_geography desc

Zusammenfassung

Auch wenn der SQL-Code am Ende nicht übermäßig lang ist, so war der Weg dorthin durchaus ein wenig länger, da es doch einiges schwieriger war, als am Anfang gedacht. Und es ist auch noch nicht perfekt, da nicht jede Streck einmal vorkommt, sondern jede Linie, d.h. beispielsweise beinhalten die Relationen Graz-Prag und Ljubljana-Wien beide den Streckenabschnitt zwischen Graz Hauptbahnhof und Wien Hauptbahnhof.

Auch kann man natürlich darüber diskutieren, ob es sinnvoll ist auch tracks miteinzubeziehen (Beschreibung im OSM-Wiki). Generell macht, so denke ich, die Abfrage nach Relationen schon Sinn, da man so die gesamte Strecke bekommt. Ansonsten hat man oftmals nur Segmente und müsste diese dann verbinden. Ein weiterer Punkt, der noch genauer betrachtet kann, welche Unterschiede man in der Azimuth-Berechnung erlaubt.

Jedenfalls kommt man mit der durchgeführten Berechnung auf eine Länge von 31,9 km für den Streckenabschnitt zwischen Wien und Bratislava, was meiner Meinung nach schon ein sehr guter Wert ist und sehr nahe an dem, was die ÖBB in ihrem Folder angibt. Beziehungsweise auf 47,8 km für die Berechnung in der Web Mercator projection der OpenStreetMap. Das Explorer Magazin hat auch einen Artikel über die Feinheiten der Web Mercator Projection: "Web Mercator": Ein Blick hinter die Kartendienste im Web

Das gesamte Script hab ich als Github Gist zur Verfügung gestellt: osm-laengste-gerade.sql.