LazGTFS eine Freepascal/Lazarus tool-box für die "General Transit Feed Specification"
-
- Beiträge: 69
- Registriert: So 12. Feb 2023, 12:42
- OS, Lazarus, FPC: Windows Lazarus 3.6, FPC 3.2.2
- CPU-Target: 64-Bit
- Wohnort: Hildesheim
LazGTFS eine Freepascal/Lazarus tool-box für die "General Transit Feed Specification"
Hallo Ihr Lieben,
nach einigen Umwegen bin ich jetzt wieder zu meinem ursprünglichen Projekt zurückgekehrt, der Analyse von Fahrplandaten des Öffentlichen Personen (Nah-)Verkehrs.
Hierzu gibt es die von Google entwickelte Darstellung des Fahrplans in Form der "General Transit Feed Specification" (GTFS), welche weite Verbreitung gefunden hat und für große Teile des Bundesgebietes vorliegt (für meine Region Hannover/Hildesheim nur hinter einer Registrierungsschranke, warum auch immer).
Das Format sieht auf den ersten Blick sehr einfach aus: Nur eine Handvoll CSV-Dateien.
Die Tücke liegt aber im Detail.
So sind die CSV-Dateien die Tabellen einer relationalen Datenbank, mit entsprechenden Querverweisen, viele Felder sind "conditionally mandatory", also in ihrer Existenz von einander abhängig und zum Schluss können die Datenmengen gewaltig sein, jedenfalls in RAM gemessen.
Gute Daten-Beispiele findet man auf https://gtfs.de/de/feeds/. Überschaubar ist der Schienenfernverkehr Deutschlands, mit entpackten 2 MB.
Ich habe mich entschieden, dass Projekt schon auf öffentlich zu stellen, auch wenn es noch sehr am Anfang steht. Doch ich hoffe so vielleicht Menschen zu finden, die mir zum einen etwas Fleißarbeit abnehmen und noch die fehlenden Tabellen zum Leben zu erwecken, zum anderen vielleich Beispiele erstellen, wie etwa eine "HeatMap", "Erreichbarkeitskarte" uvm.
Das Projekt findet sich hier, derzeit gibt es nur eine sehr einfache Beispielanwendung, die in der Lage ist, einen Datensatz (=Feed) zu laden und in der Reihenfolge der elementaren Abhängigkeiten (Agency (=Betreiberin)->Routes (=Linien)->Trips (=Fahrten)->StopTimes (=Haltestellenzeiten)->Calendar (=Betriebstage)) anzuzeigen.
Die Quellen finden sich auf gitlab https://gitlab.com/EkkehardDomning/lazgtfs.
Die Screenshots zeigen den geladenen, sehr einfachen, Beispiel-Feed von Google.
nach einigen Umwegen bin ich jetzt wieder zu meinem ursprünglichen Projekt zurückgekehrt, der Analyse von Fahrplandaten des Öffentlichen Personen (Nah-)Verkehrs.
Hierzu gibt es die von Google entwickelte Darstellung des Fahrplans in Form der "General Transit Feed Specification" (GTFS), welche weite Verbreitung gefunden hat und für große Teile des Bundesgebietes vorliegt (für meine Region Hannover/Hildesheim nur hinter einer Registrierungsschranke, warum auch immer).
Das Format sieht auf den ersten Blick sehr einfach aus: Nur eine Handvoll CSV-Dateien.
Die Tücke liegt aber im Detail.
So sind die CSV-Dateien die Tabellen einer relationalen Datenbank, mit entsprechenden Querverweisen, viele Felder sind "conditionally mandatory", also in ihrer Existenz von einander abhängig und zum Schluss können die Datenmengen gewaltig sein, jedenfalls in RAM gemessen.
Gute Daten-Beispiele findet man auf https://gtfs.de/de/feeds/. Überschaubar ist der Schienenfernverkehr Deutschlands, mit entpackten 2 MB.
Ich habe mich entschieden, dass Projekt schon auf öffentlich zu stellen, auch wenn es noch sehr am Anfang steht. Doch ich hoffe so vielleicht Menschen zu finden, die mir zum einen etwas Fleißarbeit abnehmen und noch die fehlenden Tabellen zum Leben zu erwecken, zum anderen vielleich Beispiele erstellen, wie etwa eine "HeatMap", "Erreichbarkeitskarte" uvm.
Das Projekt findet sich hier, derzeit gibt es nur eine sehr einfache Beispielanwendung, die in der Lage ist, einen Datensatz (=Feed) zu laden und in der Reihenfolge der elementaren Abhängigkeiten (Agency (=Betreiberin)->Routes (=Linien)->Trips (=Fahrten)->StopTimes (=Haltestellenzeiten)->Calendar (=Betriebstage)) anzuzeigen.
Die Quellen finden sich auf gitlab https://gitlab.com/EkkehardDomning/lazgtfs.
Die Screenshots zeigen den geladenen, sehr einfachen, Beispiel-Feed von Google.
- Jorg3000
- Lazarusforum e. V.
- Beiträge: 397
- Registriert: So 10. Okt 2021, 10:24
- OS, Lazarus, FPC: Win64
- Wohnort: NRW
Re: LazGTFS eine Freepascal/Lazarus tool-box für die "General Transit Feed Specification"
Guten Morgen Ekkehard!
Ein interessantes Projekt!
Ich selbst habe dafür keine Verwendung, aber ich finde es faszinierend, dass es umfangreiche Fahrplandaten als öffentlichen und tagesaktuellen Download gibt.
Und deine Unit ulazgtfsstatic.pas auf GitLab sieht wirklich sehr ordentlich aus.
In Bezug auf das Thema kann ich nichts beitragen, aber beim Durchscrollen durch deine Unit war ich an dieser Stelle hängengeblieben:
10 ms sind grundsätzlich eine zu kurzer Zeitabstand zum Aktualisieren z.B. einer ProgressBar. Denn die Dateninterpretation und insbesondere die Aktualisierung der Darstellung könnte so lange dauern, dass bei jeder einzelnen Zeile aktualisiert wird - was dann vermutlich länger dauert als das Einlesen der Datei selbst.
Zudem wird nach dem Aktualisieren die Variable t0 nicht auf den aktuellen Zeitstempel gesetzt, so dass die Bedingung t1-t0 > 10 dann jedesmal wieder zutrifft, also Aktualisierung bei jeder Zeile.
Ich würde eine Aktualisierung nach 200 ms nach der letzten Aktualisierung vorschlagen.
Grüße, Jörg
Ein interessantes Projekt!
Ich selbst habe dafür keine Verwendung, aber ich finde es faszinierend, dass es umfangreiche Fahrplandaten als öffentlichen und tagesaktuellen Download gibt.
Und deine Unit ulazgtfsstatic.pas auf GitLab sieht wirklich sehr ordentlich aus.
In Bezug auf das Thema kann ich nichts beitragen, aber beim Durchscrollen durch deine Unit war ich an dieser Stelle hängengeblieben:
Code: Alles auswählen
t1 := GetTickCount64;
if t1-t0 > 10 then // Report every 10ms
begin
t0 := t1;
if Assigned(ALoadProgressEvent) then
ALoadProgressEvent(Self,TableName,j,AStrings.Count);
end;
Zudem wird nach dem Aktualisieren die Variable t0 nicht auf den aktuellen Zeitstempel gesetzt, so dass die Bedingung t1-t0 > 10 dann jedesmal wieder zutrifft, also Aktualisierung bei jeder Zeile.
Ich würde eine Aktualisierung nach 200 ms nach der letzten Aktualisierung vorschlagen.
Grüße, Jörg
-
- Beiträge: 69
- Registriert: So 12. Feb 2023, 12:42
- OS, Lazarus, FPC: Windows Lazarus 3.6, FPC 3.2.2
- CPU-Target: 64-Bit
- Wohnort: Hildesheim
Re: LazGTFS eine Freepascal/Lazarus tool-box für die "General Transit Feed Specification"
Ja, das ist wirklich faszinierend. Es geht tatsächlich noch viel weiter, denn es gibt mit Geopositionen versehene Verbindungen zwischen den einzelnen Haltestellen, inkl. Treppen, Aufzügen etc. so dass man sehr inklusiv (bspw. für Gehbehinderte) die Wege planen kann. Natürlich müssen dann die Rolltreppen und Aufzüge auch funktionierenJorg3000 hat geschrieben: Di 12. Nov 2024, 07:32 Ich selbst habe dafür keine Verwendung, aber ich finde es faszinierend, dass es umfangreiche Fahrplandaten als öffentlichen und tagesaktuellen Download gibt.

Vielen Dank für die Durchsicht und den Hinweis.Jorg3000 hat geschrieben: Di 12. Nov 2024, 07:32 In Bezug auf das Thema kann ich nichts beitragen, aber beim Durchscrollen durch deine Unit war ich an dieser Stelle hängengeblieben:
10 ms sind grundsätzlich eine zu kurzer Zeitabstand zum Aktualisieren z.B. einer ProgressBar. Denn die Dateninterpretation und insbesondere die Aktualisierung der Darstellung könnte so lange dauern, dass bei jeder einzelnen Zeile aktualisiert wird - was dann vermutlich länger dauert als das Einlesen der Datei selbst.Code: Alles auswählen
t1 := GetTickCount64; if t1-t0 > 10 then // Report every 10ms begin t0 := t1; if Assigned(ALoadProgressEvent) then ALoadProgressEvent(Self,TableName,j,AStrings.Count); end;
Zudem wird nach dem Aktualisieren die Variable t0 nicht auf den aktuellen Zeitstempel gesetzt, so dass die Bedingung t1-t0 > 10 dann jedesmal wieder zutrifft, also Aktualisierung bei jeder Zeile.
Ich würde eine Aktualisierung nach 200 ms nach der letzten Aktualisierung vorschlagen.
Grüße, Jörg
Ich habe der Funktion jetzt mal einen Parameter spendiert, frei nach dem Motto, "wenn man nicht weiß wie es geht, macht man es einstellbar", mit einem Default von 100ms.
Es geht da nur um die Visualisierung der Ladefortschritts, die Tabellen, sind da nicht (vollständig) nutzbar und wenn da innerhalb eines Threads etwas Veränderndes (Sortierung?) aufgerufen wird, kracht es wahrscheinlich furchtbar. Ich wählte 10ms um bei der Verwendung des Ladens im Haupthread der Anwendung diese responsive zu halten. Ist aber vielleicht wirklich zu kurz. Zumal GetTickCount64 ohnehin nur ca alle 50ms springt.
Der Loop sollte aber mMn funktionieren.
Denn sobald die Zeitspanne zwischen der aktuellen Messung (=t1) zum vorherigen Fixpunkt (=t0) größer als die angegebene Zeit (in diesem Fall 10ms, ich habe das auf 100ms defaulted) geworden ist, wird der Fixpunkt (=t0) auf den aktuellen Zeitpunkt (=t1) gesetzt. Damit wird t1-t0 kleiner als die Differenz, die zum Auslösen des Events erforderlich ist. Diese wächst aber an, weil t1 ja in jedem Umlauf auf den aktuellen Zeitpunkt gesetzt wird.
Oder habe ich was übersehen?
Code: Alles auswählen
procedure LoadFromStrings(const AStrings : TStrings;
const ALoadProgressEvent : TGTFSTableLoadProgressEvent = Nil;
const AMSBetweenProgressEvents : Integer = 100);
(...)
t0 := GetTickCount64;
(...)
cnt := AStrings.Count-1;
for j := 1 to cnt do
begin
if CheckTerminated then Exit;
t1 := GetTickCount64;
if t1-t0 > AMSBetweenProgressEvents then // Report every 100ms or UserSettings
begin
(...)
t0 := t1;
end;
end;
- Jorg3000
- Lazarusforum e. V.
- Beiträge: 397
- Registriert: So 10. Okt 2021, 10:24
- OS, Lazarus, FPC: Win64
- Wohnort: NRW
Re: LazGTFS eine Freepascal/Lazarus tool-box für die "General Transit Feed Specification"
Ja, mit dem t0 := t1 ist es gut.
An der Stelle, wo ich den Schnipsel gefunden hatte (ich glaube es ist mehrfach im Code vorhanden), wurde t0 nur einmalig vor dem Beginn der Schleife gesetzt.
Das sind so kleine Ungereimtheiten, die einem selbst evtl. jahrelang nicht aufgefallen wären - weil es ja trotzdem funktioniert.
Und da du alles ordentlich in eigenständige Objekte interpretierst und ja jeder Zeiger schon 8 Byte benötigt (bei 64 Bit) dürfte der RAM-Bedarf vermutlich mehrere GB betragen, oder?
Hast du den RAM-Bedarf des laufenden Programms schon mal nachgeguckt? (bin neugierig)
An der Stelle, wo ich den Schnipsel gefunden hatte (ich glaube es ist mehrfach im Code vorhanden), wurde t0 nur einmalig vor dem Beginn der Schleife gesetzt.
Das sind so kleine Ungereimtheiten, die einem selbst evtl. jahrelang nicht aufgefallen wären - weil es ja trotzdem funktioniert.

Habe gesehen, dass das Gesamtdaten-ZIP ca. 250 MB groß ist. Für gezipptes CSV ist das recht viel.Ekkehard hat geschrieben: So 10. Nov 2024, 13:43 können die Datenmengen gewaltig sein, jedenfalls in RAM gemessen.
Und da du alles ordentlich in eigenständige Objekte interpretierst und ja jeder Zeiger schon 8 Byte benötigt (bei 64 Bit) dürfte der RAM-Bedarf vermutlich mehrere GB betragen, oder?
Hast du den RAM-Bedarf des laufenden Programms schon mal nachgeguckt? (bin neugierig)
-
- Beiträge: 69
- Registriert: So 12. Feb 2023, 12:42
- OS, Lazarus, FPC: Windows Lazarus 3.6, FPC 3.2.2
- CPU-Target: 64-Bit
- Wohnort: Hildesheim
Re: LazGTFS eine Freepascal/Lazarus tool-box für die "General Transit Feed Specification"
Den öffentlichen Nahverkehr (https://gtfs.de/de/feeds/de_nv/) habe ich geladen. ZIP = 200MB, ausgepackt 1,3 GB, das Programm nahm dann 14 GB ein.Jorg3000 hat geschrieben: Di 12. Nov 2024, 10:39 Habe gesehen, dass das Gesamtdaten-ZIP ca. 250 MB groß ist. Für gezipptes CSV ist das recht viel.
Und da du alles ordentlich in eigenständige Objekte interpretierst und ja jeder Zeiger schon 8 Byte benötigt (bei 64 Bit) dürfte der RAM-Bedarf vermutlich mehrere GB betragen, oder?
Hast du den RAM-Bedarf des laufenden Programms schon mal nachgeguckt? (bin neugierig)
Da stellt sich dann die Frage ob das noch sinnvoll ist und ob nicht die Umwandlung in eine SQL-Datenbank der bessere Weg.
Für meine Anwendung (statistische Auswertungen aller Linien und Verbindungen, Erreichbarkeiten im Tages- bzw Wochenverlauf), dürfte das aber sehr langsam werden, weil man ja doch alles zur Hand haben muss.
Ich überlege aber tatsächlich, ob ich eine zweite Schicht baue, die unabhängig von der Datenbasis funktioniert, also abstrakter formuliert wie die Daten bereitzustellen sind.
Dann könnte man sowohl die Daten im RAM verwenden, als auch von einer Datenbank abrufen.
-
- Beiträge: 69
- Registriert: So 12. Feb 2023, 12:42
- OS, Lazarus, FPC: Windows Lazarus 3.6, FPC 3.2.2
- CPU-Target: 64-Bit
- Wohnort: Hildesheim
Re: LazGTFS eine Freepascal/Lazarus tool-box für die "General Transit Feed Specification"
Beim Laden des Datensatzes "Nahverkehr Deutschland" verwendet das Programm bis zu 16,4GB, danach (Ladevorgang abgeschlossen) 12,8GB. Das ist vermutlich dem Einlesen der großen Datei stop_times.txt (=1,3GB) in eine Stringliste geschuldet. Praktisch für die Programmierung, in der Speichernutzung eher unhandlich. Vielleicht bastele ich da mal was Besseres.
- Jorg3000
- Lazarusforum e. V.
- Beiträge: 397
- Registriert: So 10. Okt 2021, 10:24
- OS, Lazarus, FPC: Win64
- Wohnort: NRW
Re: LazGTFS eine Freepascal/Lazarus tool-box für die "General Transit Feed Specification"
Ich glaube du kannst eine ganze Menge Hauptspeicher sparen, z.B. bei ...
stattdessen
Falls du auf trip_id als String nicht verzichten kannst, könntest du alle Wiederholungen mit dem gleichen String-Speicher referenzieren, z.B.
Code: Alles auswählen
trip_id : String;
arrival_time : String;
departure_time : String;
Code: Alles auswählen
// trip_id : String; komplett entfallen lassen, stattdessen nur trip_id_num (und das evtl. nur als Integer statt Int64)
arrival_time : String[9];
departure_time : String[9]; // das direkte Speichern eines kurzen Strings im Record erspart den Zeiger auf externen Speicher
Code: Alles auswählen
if CurrentRec.trip_id = PreviousRec.trip_id then CurrentRec.trip_id := PreviousRec.trip_id; // referenzieren: nutzen nun gleichen Inhalts-Speicher
-
- Beiträge: 69
- Registriert: So 12. Feb 2023, 12:42
- OS, Lazarus, FPC: Windows Lazarus 3.6, FPC 3.2.2
- CPU-Target: 64-Bit
- Wohnort: Hildesheim
Re: LazGTFS eine Freepascal/Lazarus tool-box für die "General Transit Feed Specification"
Tatsächlich kann ich nochmal durch die FieldTypes (https://gtfs.org/documentation/schedule ... ield-types) schauen und sehen ob sich einige Strings in ihrer Länge begrenzen lassen, genau wie Du das im Falle der arrival_time vorschlägst, denn dort wird time als "HH:MM:SS" definiert, also 8 Zeichen lang.
Insbesondere bei den stop_times dürfte sich das positiv auswirken, denn diese Tabelle ist die größte von allen.
Bei den ganzen IDs funktioniert das nicht, oder nur sehr begrenzt. Die IDs sind in den Tabellen alle eindeutig (Jede Haltestelle hat bspw. ihre eigene ID) und die muss nicht zwingend in eine Integer-Zahl umwandelbar sein. Das Basisbeispiel von Google verwendet bspw. alphanumerische IDs.
Der Hinweis ist aber trotzdem gut, denn in den StopTime records wird ja auf die Stops und die Trips verwiesen und diese Strings sind ja gerade so konstruiert, dass sie mit den entsprechenden StopIDs und TripID identisch sind. Somit könnte das gespart werden, wenn der Compiler dabei hilft.
Im Moment mache ich aber beim Einlesen keine Prüfung auf die Richtigkeit der Daten. Um das umzusetzen müsste bspw. StopTimes immer nach den Stops und Trips engelesen werden und man müsste dort suchen ob der String vorhanden ist, also gleich den Index aufbauen. Das hatte ich bisher so gemacht, dass ich erst beim Suchen geprüft habe und im Erfolgsfall den Index gespeichert hatte, die Indizierung wächst also mit dem Gebrauch.
Würde man das zwingend so machen, könnte man die referenzierten IDs allesamt weg lassen und sich im Betrieb über eine entsprechende Methode von der Tabelle direkt holen.
Vom Design aus gesehen stehen also Speichernutzung und Rechenzeit im Konflikt.
"Wenn man nicht weiß wie es geht, macht man es einstellbar", ist das auch hier die Lösung oder macht man es wie Apple ("Setze das um was 90% der Nutzer erwarten, die anderen 10% machen eh keinen Umsatz!")?
Insbesondere bei den stop_times dürfte sich das positiv auswirken, denn diese Tabelle ist die größte von allen.
Bei den ganzen IDs funktioniert das nicht, oder nur sehr begrenzt. Die IDs sind in den Tabellen alle eindeutig (Jede Haltestelle hat bspw. ihre eigene ID) und die muss nicht zwingend in eine Integer-Zahl umwandelbar sein. Das Basisbeispiel von Google verwendet bspw. alphanumerische IDs.
Der Hinweis ist aber trotzdem gut, denn in den StopTime records wird ja auf die Stops und die Trips verwiesen und diese Strings sind ja gerade so konstruiert, dass sie mit den entsprechenden StopIDs und TripID identisch sind. Somit könnte das gespart werden, wenn der Compiler dabei hilft.
Im Moment mache ich aber beim Einlesen keine Prüfung auf die Richtigkeit der Daten. Um das umzusetzen müsste bspw. StopTimes immer nach den Stops und Trips engelesen werden und man müsste dort suchen ob der String vorhanden ist, also gleich den Index aufbauen. Das hatte ich bisher so gemacht, dass ich erst beim Suchen geprüft habe und im Erfolgsfall den Index gespeichert hatte, die Indizierung wächst also mit dem Gebrauch.
Würde man das zwingend so machen, könnte man die referenzierten IDs allesamt weg lassen und sich im Betrieb über eine entsprechende Methode von der Tabelle direkt holen.
Vom Design aus gesehen stehen also Speichernutzung und Rechenzeit im Konflikt.
"Wenn man nicht weiß wie es geht, macht man es einstellbar", ist das auch hier die Lösung oder macht man es wie Apple ("Setze das um was 90% der Nutzer erwarten, die anderen 10% machen eh keinen Umsatz!")?
-
- Beiträge: 69
- Registriert: So 12. Feb 2023, 12:42
- OS, Lazarus, FPC: Windows Lazarus 3.6, FPC 3.2.2
- CPU-Target: 64-Bit
- Wohnort: Hildesheim
Re: LazGTFS eine Freepascal/Lazarus tool-box für die "General Transit Feed Specification"
Ich hab das mal versucht und sowohl diese Time-Strings auf 9 Zeichen gekürzt, als auch die Referenzen der stop_id und trip_id in der StopTimes-Tabelle gesetzt. Und es bringt in der Tat eine Menge. Statt wie vor 12,8 GB sind es jetzt "nur" noch 10,1 GB, also immerhin 20% weniger.Jorg3000 hat geschrieben: Di 12. Nov 2024, 12:24 Ich glaube du kannst eine ganze Menge Hauptspeicher sparen, z.B. bei ...
Das war also ein sehr guter Hinweis, vielen Dank!
-
- Beiträge: 292
- Registriert: Mo 24. Aug 2020, 14:16
- OS, Lazarus, FPC: Ubuntu Xenial 32, Lazarus 2.2.0, FPC 3.2.2
- CPU-Target: i386
Re: LazGTFS eine Freepascal/Lazarus tool-box für die "General Transit Feed Specification"
Sind die Sekunden in den Zeitangaben tatsächlich gesetzt? Sonst könnte man das ja glatt noch weiter einkürzen.
-
- Beiträge: 69
- Registriert: So 12. Feb 2023, 12:42
- OS, Lazarus, FPC: Windows Lazarus 3.6, FPC 3.2.2
- CPU-Target: 64-Bit
- Wohnort: Hildesheim
Re: LazGTFS eine Freepascal/Lazarus tool-box für die "General Transit Feed Specification"
Kurze Antwort: Ja, sie werden tatsächlich genutzt.Sieben hat geschrieben: Di 12. Nov 2024, 14:46 Sind die Sekunden in den Zeitangaben tatsächlich gesetzt? Sonst könnte man das ja glatt noch weiter einkürzen.
Längere Antwort: Manche S/U-Bahnen fahren 8 mal pro Stunde, im Fahrplan steht dann oft "alle 7-8 Minuten", was dann zu Abfahrtszeiten mit halben Minuten = 30 Sekunden führt.
Beispiel: U4 in Frankfurt/Main https://www.rmv.de/c/fileadmin/import/t ... _12_10.pdf