LazGTFS eine Freepascal/Lazarus tool-box für die "General Transit Feed Specification"

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Ekkehard
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"

Beitrag von Ekkehard »

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.
verysimple_01.jpg
verysimple_01.jpg (65.06 KiB) 3653 mal betrachtet
verysimple_02.jpg
verysimple_02.jpg (61.09 KiB) 3653 mal betrachtet

Benutzeravatar
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"

Beitrag von Jorg3000 »

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:

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;
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

Ekkehard
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"

Beitrag von Ekkehard »

Jorg3000 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.
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 funktionieren ;-)

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:

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;
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
Vielen Dank für die Durchsicht und den Hinweis.
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;  

Benutzeravatar
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"

Beitrag von Jorg3000 »

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. :)
Ekkehard hat geschrieben: So 10. Nov 2024, 13:43 können die Datenmengen gewaltig sein, jedenfalls in RAM gemessen.
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)

Ekkehard
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"

Beitrag von Ekkehard »

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)
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.
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.

Ekkehard
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"

Beitrag von Ekkehard »

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.
verysimple_memusage.jpg
verysimple_memusage.jpg (37.91 KiB) 3540 mal betrachtet

Benutzeravatar
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"

Beitrag von Jorg3000 »

Ich glaube du kannst eine ganze Menge Hauptspeicher sparen, z.B. bei ...

Code: Alles auswählen

    trip_id : String;
    arrival_time : String;
    departure_time : String;
stattdessen

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
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

    if CurrentRec.trip_id = PreviousRec.trip_id then CurrentRec.trip_id := PreviousRec.trip_id;  // referenzieren: nutzen nun gleichen Inhalts-Speicher

Ekkehard
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"

Beitrag von Ekkehard »

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!")?

Ekkehard
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"

Beitrag von Ekkehard »

Jorg3000 hat geschrieben: Di 12. Nov 2024, 12:24 Ich glaube du kannst eine ganze Menge Hauptspeicher sparen, z.B. bei ...
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.

Das war also ein sehr guter Hinweis, vielen Dank!
verysimple_memusage_02_small.jpg
verysimple_memusage_02_small.jpg (37.59 KiB) 3510 mal betrachtet

Sieben
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"

Beitrag von Sieben »

Sind die Sekunden in den Zeitangaben tatsächlich gesetzt? Sonst könnte man das ja glatt noch weiter einkürzen.

Ekkehard
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"

Beitrag von Ekkehard »

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.
Kurze Antwort: Ja, sie werden tatsächlich genutzt.
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

Antworten