Schlagwort-Archive: SWU

Adding shapes to GTFS feeds with pfaedle

Headway frequency mapping in R. Requires shapes.txt

Three years ago, I wrote a little piece about how we cleaned up SWU’s GTFS feed.

I nonchalantly added that adding shapes and Conveyal’s GTFS editor would be a topic for another time, but never came around writing about that. I do not use the GTFS editor anymore, but Patrick Brosi’s pfaedle tool is still invaluable if your GTFS feed does not come equipped with a functional shapes.txt.

I had described the problem and where to find the proper tools back in early 2020 right at the intersection of my activism and public administration work. With the regional transit area spanning two Bundeslaender, there are some pitfalls left, however. Hence, a short primer.


  • One Linux machine, whatever the flavor. Be it a VM or an old Laptop, it hardly matters. It shouldn’t be the slowest available machine, though, and it should come with a decent amount of RAM (the machine I’m using has 8 GiB). And if you go the germany-latest route (see below), about 100 GiB of hard disk space are required.
  • cmake, gcc (>4.9), git, wget, bzip2: sudo apt install cmake gcc git wget bzip2
  1. Get pfaedle, which is pretty much following the steps outlined in the github repo:
git clone --recurse-submodules
mkdir build && cd build
cmake ..
make -j4
# optionally:
make install

2. Navigate to the folder where you store your unzipped(!) GTFS feed you want to add shapes to.

3. Get the proper OSM files. Since we are working with Ulm and Neu-Ulm, we’d either need a download of the metropolitan area of both cities, or download and merge the extracts for Bavaria and Baden-Wuerttemberg… or download and use the extract for the whole of Germany :shrug:

# Whole of Germany
bunzip2 germany-latest.osm.bz2

# Merge, requires osmium-tool: apt install osmium-tool
bunzip2 bayern-latest.osm.bz2 && bunzip2 baden-wuerttemberg-latest.osm.bz2
osmium merge baden-wuerttemberg-latest.osm bayern-latest.osm -o merged.osm

Beware: Unzipping the GTFS feeds takes ages, especially the germany-latest. Expect a file exceeding 70 GiB and quite some decompression time. My laptop takes about 4–5 minutes for each Bundesland to unpack.

All that is left to do now is to let pfaedle do it’s work: pfaedle -D -x merged.osm .
After completion (and again, using it with germany-latest.osm takes quite a lot of time), a new folder gtfs-out is created. Test the results with your usual testing suites, ZIP it up, and off you go.

Cleaning up the SWU GTFS feed with pygtfs and sqlite3

For a Top Secret Project™ we are currently using SWU’s official GTFS feed. Since we had to clean up the feed a bit and ran into some errors, I thought it best to document our workflow to save it for posterity – it might even help others ;

Step 1: Loading into sqlite

For me, the first step is always to load the feed into sqlite3, since it makes it easier to follow through with all the manipulations I have in mind for later.

My current tool of choice is pygtfs, which can be installed through pip. If you don’t have sqlite installed, you might want to do that now, too.

pip3 install pygtfs
apt install sqlite3

pygtfs brings along the handy gtfs2db tool, which allows for importing your feed into a database right from the command line:

gtfs2db append ~/Downloads/ gtfs.db 

This command will, however, fail after importing all the records and trying to write it to the database, because it can’t read a datetime (ValueError: time data “ does not match format ‚%Y%m%d‘). After some digging I found the missing feed_start_date and feed_end_date in feed_info.txt to be the culprit. According to spec, they are merely optional, but the gtfs2db script seems to depend on it. Nothing a quick edit in the text editor of your choice can’t fix. All there is left to do is to retry the import, and open a sqlite3 session with the newly created database. I am in the habit of switching on the headers and into column mode right from the get-go.

sqlite3 -header -column gtfs.db 

Step 2: Exploring the stops table

For our project, we wanted to clean up the stops table a bit. First of all, in violation of the spec, in the SWU feed the stop_id is multiply assigned to every stop point („Haltepunkt“) within a stop („Haltestelle“). We can find this rather easily:

SELECT stop_name, count(stop_code) FROM stops GROUP BY stop_code ORDER BY count(stop_code) DESC limit 15;
stop_name count(stop_code)
---------- ----------------
ZOB Ost 9
Staufenrin 9
Universit 8
Donaustadi 6
Egertweg 6
Römerplat 6
Gewerbesch 6
Stadtwerke 5
Eselsberg 5
Sonnenstra 5
Theodor-He 5
Kuhberg Sc 5
Hauptbahnh 4
Theater 4

Furthermore, some stop points (i.e., the platforms within one stop) are assigned doubly or even thrice with the same coordinates. This happens if one stop point is being served by different operational branches. In the case of the SWU feed, the internal data format distinguishes between bus, tram and night bus service. This means that if a stop point is being served by all three of those branches, it will appear thrice in the data set:

SELECT stop_id, stop_code, stop_name, stop_lat, stop_lon, COUNT(distinct stop_lon) FROM stops GROUP BY stop_code HAVING count(distinct stop_lon) > 1 ORDER BY count(distinct stop_lon) DESC limit 20;
stop_id stop_code stop_name stop_lat stop_lon COUNT(distinct stop_lon)
---------- ---------- ----------------- ---------- ---------- ------------------------
3579 1240 Universität Süd 48.421839 9.956403 6
3559 1383 Gewerbeschulen K� 48.384625 9.959908 6
1869 1700 ZUP 48.39179 10.0032 6
117 1050 Staufenring 48.403521 10.004344 5
3745 1200 Eselsberg Hasenko 48.414326 9.962903 5
3557 1390 Kuhberg Schulzent 48.383775 9.955289 5
1235 1052 Donaustadion 48.405101 10.006927 4
145 1072 Mecklenburgweg 48.43453 10.024493 4
147 1073 Thüringenweg 48.433379 10.020177 4
149 1074 Haslacher Weg 48.429919 10.013786 4
3260 1087 Egertweg 48.42583 10.01243 4
217 1171 Manfred-Börner-S 48.41967 9.942766 4
3581 1241 Botanischer Garte 48.424912 9.956829 4
3584 1245 Kliniken Wissensc 48.424331 9.952453 4
3586 1246 Universität West 48.422234 9.947201 4
3565 1360 Römerplatz 48.39077 9.975428 4
3561 1393 Grimmelfinger Weg 48.38564 9.965312 4
774 1506 Benzstraße 48.365706 9.941817 4
75 1008 Hauptbahnhof 48.39983 9.98404 3
3571 1020 Stadtwerke 48.4031 9.986038 3

I hacked together the following python script that will unify these stop points so that each coordinate will appear exactly once. That is, it will output SQL statements to be pasted into sqlite which should do the trick. Hacky and crappy, but there we go. Note that I have uncommented (and did not test) the transfers bit, since the SWU feed does not use transfers. Note that this leaves the multiply assigned stop_ids untreated, as of now.

sched = pygtfs.Schedule("gtfs.db")
sq = sched.stops_query
d = {}
for each in sq.all():
if each.stop_code in d:
for existing in d[each.stop_code]:
if (d[each.stop_code][existing].stop_lat == each.stop_lat) and (d[each.stop_code][existing].stop_lon == each.stop_lon):
print('UPDATE stop_times SET stop_id = ' + d[each.stop_code][existing].stop_id + ' WHERE stop_id = ' + each.stop_id + ';')
#print('UPDATE transfers SET from_stop_id = '+ d[each.stop_code][existing].stop_id + ' WHERE from_stop_id = ' + each.stop_id + ';')
#print('UPDATE transfers SET to_stop_id = '+ d[each.stop_code][existing].stop_id + ' WHERE to_stop_id = ' + each.stop_id + ';')
print('DELETE FROM stops WHERE stop_id = ' + each.stop_id + ';')
d[each.stop_code][each.stop_id] = each;
d[each.stop_code] = {};
d[each.stop_code][each.stop_id] = each

Step 3: Removing Deadheads

One bug in the current SWU feed is that it includes trips that should not be facing towards the customer. Namely, all the trips between the bus depot and the first stop on one vehicle’s Umlauf, and the return trips towards the depot. This might result in strange routing results and will definitely make the transitfeed validator complain. The following SQL statements will delete both the stop_times and the trips for those deadheads:

DELETE FROM trips WHERE trip_id IN (SELECT DISTINCT trip_id FROM stop_times WHERE stop_id IN ("170", "171"));
DELETE FROM stop_times WHERE trip_id IN (SELECT DISTINCT trip_id FROM stop_times WHERE stop_id IN ("170", "171");

Step 4: Re-Exporting the data, casting times past 24:00:00 without using strftime

This was one part which kept me scratching my head for way too long. Simple full-table dumps are not a big issue:

sqlite3 -header -csv gtfs.db 'SELECT * FROM trips'  > trips.txt

After some successfull exports, I hit a bump in the road with the stop_times table because of the way GTFS and sqlite each store times. A vehicle departing one minute before midnight from one stop and arriving two minutes past midnight at the next stop will be modeled with the textual entries of „23:59:00“ and „24:02:00“, respectively. Since the operational day ends somewhen between midnight and the wee hours of the next day, it is not unusual to encounter departure/arrival times that read something like „27:35:00“ – at 03:35 a.m. of the following day, but still within the validity period of the previous/current day (I am getting mixed up with terminology myself, I guess)

However, pygtfs will convert all those textual time entries into DateTime objects, which means that the two times above would be stored as 1970-01-01 23:59:00.000000 and 1970-01-02 00:02:00.000000. And any manipulation with strftime and other functions to manipulate Date and Time didn’t get me anywhere – or, to put it differently, all the usual functions correctly and stubbornly refused to return non-standard times that use funny hours like 24 or anything above.

In the end, the statement to get GTFS compliant times was not that hard, after all. If you ever want to calculate hours, minutes and seconds past a date, this is the way to go:

SELECT trip_id, printf("%02d",((strftime("%s", "arrival_time") - strftime("%s", "1970-01-01 00:00:00.00000")) / (60*60))) || ":" || printf("%02d",(((strftime("%s", "arrival_time") - strftime("%s", "1970-01-01 00:00:00.00000")) % (60*60)) / 60)) || ":" || printf("%02d",(((strftime("%s", "arrival_time") - strftime("%s", "1970-01-01 00:00:00.00000")) % 60) % 60)) AS arrival_time, printf("%02d",((strftime("%s", "departure_time") - strftime("%s", "1970-01-01 00:00:00.00000")) / (60*60))) || ":" || printf("%02d",(((strftime("%s", "departure_time") - strftime("%s", "1970-01-01 00:00:00.00000")) % (60*60)) / 60)) || ":" || printf("%02d",(((strftime("%s", "departure_time") - strftime("%s", "1970-01-01 00:00:00.00000")) % 60) % 60)) AS departure_time, stop_id, stop_sequence, stop_headsign, pickup_type, drop_off_type, shape_dist_traveled, timepoint FROM stop_times

But, as @LucasWerkmeistr pointed out, in this case we are always subtracting 1970-01-01 00:00:00.00000 – which is zero. The simplified version for exporting the tables is as follows:

sqlite3 -header -csv gtfs.db 'SELECT trip_id, printf("%02d",(strftime("%s", "arrival_time") / (60*60))) || ":" || printf("%02d",(strftime("%s", "arrival_time") % (60*60)) / 60) || ":" || printf("%02d",(strftime("%s", "arrival_time") % 60) % 60) AS arrival_time, printf("%02d",(strftime("%s", "departure_time")) / (60*60)) || ":" || printf("%02d",(strftime("%s", "departure_time")  % (60*60)) / 60) || ":" || printf("%02d",(strftime("%s", "departure_time") % 60) % 60) AS departure_time, stop_id, stop_sequence, stop_headsign, pickup_type, drop_off_type, shape_dist_traveled, timepoint FROM stop_times'  > stop_times.txt

Note that the export mentions the shape_dist_traveled and timepoint columns. This is because this iteration was not made on a squeaky clean SWU feed, but after some massaging with Conveyal’s GTFS Editor. But that will be a topic for another time 😉

edited on 2019-03-16 to fix errors in the last two scripts and to clarify stuff

Langsam laeufts an

Seit eineinhalb Wochen gibt es nun offiziell die Soll-Fahrplandaten von den Stadtwerken unter freier Lizenz, und langsam faengt auch die Adaption und Integration in anderer Leute Werkzeuge an. Stefan Wehrmeyer hatte ja noch am Abend des VBB-EntwicklerInnen-Nachtreffens den Datensatz beantragt und Mapnificent entsprechend erweitert; mittlerweile hat auch Knut aus Berlin sein Werkzeug umgebaut, mit dem er die Haltepunkte aus dem GTFS-Feed mit den Haltepunkten in der OpenStreetMap vergleicht. Damit einhergehend kam auch gleich der erste Bug Report zum Feed, den ich heute mittag auch gleich loesen koennte — der bisherige Transformationswerkzeugkasten ist ein wenig codierungsagnostisch, so dass versehentlich UTF-8-codierte Zeichen in ASCII-Textdateien landeten.

Ein weiteres kleines Werkzeug habe ich aus der Ergebnispraesentation des VBB-EntwicklerInnen-Nachtreffens mitgenommen, das auch schnell auf Ulm adaptiert war: Analog zu Mapnificent eine Erreichbarkeitskarte, die Kreise mit waehlbarem Radius um Haltepunkte zeichnet und so anzeigt, wie „erreichbar“ der Nahverkehr in der Stadt ist. 800 Meter (der vorgegebene Wert aus Berlin) umfassen gleich quasi ganz Ulm, interessanter waere da die Abbildung des ganzen DING-Verbunds — die Daten hab ich zwar schon lange, aber leider nicht unter freier Lizenz 😉

Ein weiterer Kritikpunkt sowohl von Mapnificent als auch dieser Erreichbarkeitskarte ist ihre… begrenzte Aussagefaehigkeit (um den Begriff „Unsinnigkeit“ zu vermeiden :D). Konzentrische Kreise bilden quasi nie die Gehdistanz von/zu einem Haltepunkt ab. In einer typischen US-amerikanischen Planstadt waeren Rauten passender, in typischen europaeischen Staedten sieht es meist von Haltepunkt zu Haltepunkt anders aus. Hier mal eine Karte mit tatsaechlichem Fussgang-Routing zu bauen, das waer mal was 😉

Ein ereignisreiches Open-Transit-Wochenende

tl;dr vorneweg: Wir waren am Donnerstag beim DING-Verbund, am Freitag war ich beim VBB in Berlin, und die SWU geben ihre Fahrplaene als GTFS frei. Hurra!


Auf Einladung von Martin Schiller vom DING waren Fox und ich am Donnerstag beim DING als „unserem“ Nahverkehrsverbund zu Besuch und haben uns deren Software zeigen lassen. In Deutschland gibt es nur wenige grosse Player auf dem Markt fuer Fahrplanungs- und Auskunftssysteme, beispielsweise HaCon (HAFAS) und MentzDV (DIVA und EFA), wobei in BaWue hauptsaechlich DIVA fuer die Fahr-, Dienst- und Umlaufplanung und EFA fuer die elektronische Fahrplanauskunft zum Einsatz kommen.

Und wie das in einem kleinen Markt so ist, reissen die dazu gehoerenden Softwareloesungen nicht gerade vom Hocker. DIVA verwendet in Version 3 als Datenbackend nicht etwa einen Standard wie VDV-45X, sondern ein eigenes Textdateiformat, das ich auch nach laengerem Betrachten noch nicht so recht umrissen habe. In DIVA 4 soll wenigstens eine Datenbank im Hintergrund laufen, auf die neue Version seien bislang aber wohl nur wenige Verkehrsverbuende und -betriebe umgestiegen.

Verkehrsbetriebe benutzen solche Planungssoftware ohnehin erst ab einer bestimmten kritischen Groesse ihres Betriebs. Viele der kleineren Dienstleister verwenden entweder ganz andere Umlaufplanungssoftware, oder machen das gar von Hand oder in Excel. Der „einfache“ Transfer von DIVA zu DIVA kommt hier bei uns nur zwischen Stadtwerken und DING zustande, kleinere Anbieter auf dem Land schicken ihre Plaene im besten Fall per XLS, im schlimmsten in sonstigen semistrukturierten Formaten.

Eine weitere Hoffnung fuer den Export der Fahrplaene nach GTFS war, die Daten aus der Datenhaltung der Elektronischen Fahrplanauskunft (EFA) herauszubekommen. Die ist aber nicht minder… spannend. Die Dateien sehen wie Binaerblobs aus, und die EFA selbst ist ein Konglomerat zusammengeflanschter Module, die sehr nach historischem Wachstum aussehen. Die Echtzeitauswertung heisst beispielsweise „rud“ und lehnt sich damit noch ans Projekt RUDY an, das 2004 zu Ende ging. Und zwischendrin poppen auf dem Windows-Server-Desktop, auf dem die EFA laeuft, Adobe-Distiller-Fenster auf, wenn irgendjemand einen PDF-Fahrplan erstellt.

Spaetestens an der Stelle stellte ich mir dann schon die Frage, ob man mit geeigneten freien Software-Werkzeugkaesten nicht viel reissen koennte in diesem Orchideensektor 😀

Nichtsdestoweniger, der Ausflug war interessant, und zeigte auch, dass die CSV-Dateien, die wir von den Stadtwerken bekamen, genauso fuer den gesamten Verbund (und einigem haendischen Aufwand) aus DIVA exportiert werden koennten. Das waere aber tatsaechlich nicht unbedingt die Loesung, sondern vermutlich erst der Anfang weiterer Probleme, angefangen vom Unterschied zwischen Planungs- und Repraesentationsliniendarstellungen bis hin zu eindeutigen Schluesseln fuer Haltepunkte.

Ausflug zum VBB und endlich Ulmer GTFS-Daten 🙂


Tags darauf hatte die Open Knowledge Foundation zusammen mit dem Verkehrsverbund Berlin/Brandenburg (VBB) zur Projektvorstellung und Nachbesprechung des Hackdays im November 2012 eingeladen. Da unsere Arbeitsgruppe nach wie vor kein Reisebudget oder ueberhaupt irgendwelche Finanziers hat, hiess das also, um 0600 Uhr aufzustehen und mit dem Daumen nach Berlin zu reisen :>

Aufgrund meiner etwas unguenstigen Anreise (siehe Trampbericht unten) kam ich leider erst nach der ersten Projektvorstellungsrunde in den VBB-Raeumen am Bahnhof Zoo an, war aber sehr angetan vom grossen Andrang dort. Neben OKFN und VBB sassen dort Leute von der BVG, jemand von HaCon war eigens angereist, und ich konnte neben „alten Bekannten“ auch endlich mal Michael Kreil und anderen persoenlich die Hand schuetteln.

Eine ganz persoenliche Freude war mir, dort spontan eine Botschaft verkuenden zu koennen, auf die ich lange gewartet hatte: Auf der Anreise bekam ich den Link zum Datenauskunftformular der Stadtwerke Ulm zugeschickt, die wir nun ueber mehrere Monate lang begleitet haben, um ihre Soll-Fahrplaene nach GTFS zu exportieren. Leider mit einem Formular zum verpflichtenden Ausfuellen, aber das war ich dann doch durchaus bereit in Kauf zu nehmen, nachdem im Gegenzug die ODbL als Lizenz gewaehlt wurde 🙂



Es werden sich jetzt sicherlich nicht auf einmal™ tausende EntwicklerInnen auf den Ulmer Fahrplan stuerzen. Auch in Berlin passierten seit der Veroeffentlichung des VBB keine Instant-Wunder. Aber das ist meines Erachtens ein bedeutender Schritt und hoffentlich positive Signalwirkung fuer andere Verkehrsbetriebe, ebenfalls die Daten bereitzustellen.

Dementsprechend haben wir nach der Vorstellung das Ganze noch im OKF-Buero (siehe Bild) mit Mate und spaeter Bier begossen und uns noch solange darueber unterhalten, wie man das Thema weiter beackern koennte (wissenschaftliche Aufarbeitung, Hinweis auf das Kundenbindungspotenzial unabhaengiger Apps), bis ich endgueltig koerperlich so fertig war, dass ich mich endlich mit Gastgeber @_HeBu treffen musste, um unfallfrei ins Bett zu kommen.

(Das wurde dann durch einen Spaetibesuch und tags darauf durch einen Doener- und Spaeti-Besuch erfolgreich unterbunden. Trotzdem Danke, HeBu, fuer die neuerliche Gastfreundschaft und den ausgezeichneten Vanillequark von Butter-Lindner :D)



  • Abfahrt Rosengasse mit der Linie 4 um 0706 Uhr(?), Ankunft Eichenplatz 0716 Uhr, wo nix los war.
  • Eichenplatz ab 0742 Uhr (26 Minuten) mit Margarete ehemals aus der Nachbar-WG, die anbot, mich generell unter der Woche immer um die Zeit auf die Lonetal nehmen zu koennen. Cool.
  • Ankunft auf einer total verlassenen Lonetal Ost um 0759 Uhr. Erst an der Ausfahrt gestanden, dann angequatscht, trotzdem erst um 0900 Uhr weiter (61 Minuten). Dafuer im Geschaeftsauto im Tiefflug, 137 km in 67 Minuten.
  • Kammersteiner Land Sued an 1007 Uhr, wenig los, angequatscht, 1047 mit 120 km/h und haeufigen Raucherpausen weiter (40 Minuten)
  • Taktischen Fehler begangen, nicht waehrend der Mittagessenspause meiner Fahrerin in Frankenwald Ost einen neuen Lift zu suchen.
  • Michendorf Süd an 1605 Uhr, machte mal eben 5:18h fuer 408 km. Trotz guter Unterhaltung etwas schade.
  • Weiter um 1620 (15 Minuten) bis zur U Kurfuerstenstrasse um 1710 Uhr, Fussmarsch bis zum Bf Zoo/VBB.


  • Aufbruch bei HeBu mit der S1 ab Wollankstrasse um 1313, S Johannissee an 1400 Uhr. An der Grunewald erst ein wenig rumgeschaut und angequatscht, das lief aber nicht. Also um 1430 mit Schild „Muenchen A9“ ab auf die Rampe, 1440 Lift bekommen 🙂
  • Fraenkische Schweiz/Pegnitz West an 1730 Uhr, d.h. 362 km in 2:50 Minuten und hervorragender Unterhaltung waehrend der Fahrt ueber die Unterschiede zwischen PaedagogInnen und ErzieherInnen 😀
    Sanifair-Gutscheine gegen Burger getauscht, 1750 mit Schild „Ulm“ an die Ausfahrt gestellt, 1804 Lift bis Bahnhof Heidenheim angeboten bekommen. Da sagt man nicht nein 🙂
  • Bf Heidenheim an 1942, 197 km in 1:38h. Das waren rekordverdaechtige 5:12h von Grunewald bis Heidenheim, und selbst mit S-Bahn vorneweg und den 50 Minuten Regionalexpress nach Ulm am Ende gerade mal 45 Minuten langsamer als ein ICE gewesen waere 😀

Erste Schritte in QGIS

Ich schlage mich nun seit einigen Tagen bzw. Wochen damit herum, aus diversen Zwischenprodukten von DIVA einen funktionierenden GTFS-Datensatz zu bauen — beziehungsweise, einen Prozess zu bauen, mittels dessen die Stadtwerke das zukuenftig selber tun koennten, wenn sie das wollten. Die Fahrplaene sind dabei momentan das kleinste Problem, die koennen gemaess der Vorlage per rudimentaerem Hacktool automatisch aus den TSV-Dateien ueberfuehrt werden, die die SWU fuer den Satz ihrer Pocketfahrplaene verwendet.

Mehr Probleme machen derweil die scheinbaren Kleinigkeiten, die es in sich haben. Die Haltestellenorte hat einfach mal jemand irgendwo vom Server des Nahverkehrsverbund geparst. Die koennte man nehmen — dann waer’s aber nicht mehr sauber, weil die Datenquelle einer Nutzung unter freier Lizenz bislang nicht zugestimmt hat. Dasselbe gilt fuer die Fahrwege der Busse — die holt swu2gtfs bislang auch einfach aus der elektronischen Fahrplanauskunft des Verbunds.

Fuer beides habe ich testweise auch die Daten der Stadtwerke zur Verfuegung gestellt bekommen, die aber nur noch mehr Folgefragen aufwerfen. Eine Linie kann im Tages- und Wochenverlauf zig verschiedene Fahrwege haben, je nachdem, wo sie anfaengt, wo sie endet und welche Haltestellenreihung sie nimmt. Die haendisch zuzuordnen ist… aufwaendig. Noch umstaendlicher wird es bei den Haltepunkten: Die sind zentimetergenau vermessen — und zwar pro Haltepunkt, derer es pro Haltestelle gleich mehrere geben kann. Klar: Die meisten werden ja in zwei Richtungen bedient, und kompliziertere Halte wie der am Theater haben auch mal vier Steige, zwischen denen bis zu 50 Meter liegen.

Ich habe die KML-Dateien dann einfach mal in QGIS geladen und war ganz angetan, das auch mal im Ueberblick sehen zu koennen. Wunderbare freie Software, die per OpenLayers-Plugin auch gleich eine passende OpenStreetMaps-Hintergrundkarte einbinden koennen und vieles mehr.

Ich bin mir momentan noch nicht ganz sicher, wie ich hier weitermachen soll. Mehr oder minder ideal waere es, pro Haltestelle die mittlere Koordinate aller Haltepunkte zu berechnen, die dann als „virtuelle“ Oberhaltestelle aller Haltepunkte dient (Beispielsweise OLIF 9001010 fuer das Theater). Das ist eine eher krude Approximation und wird vor allem dann haesslich, wenn (wie aktuell) die Fahrplaene nicht den Steig angeben, von dem sie abfahren (z.B. 90010103 fuer die 6 in Richtung Uni, die am Theater-Steig 3 abfaehrt). Die Alternative ist, einfach immer den ersten Steig zu kopieren und als Oberhaltestelle zu definieren, um dann ggf. mit noch kruderen Abweichungen zu leben — aber hey, wenigstens eine Fahrtrichtung stimmt dann immer exakt. Naja.

tl;dr: QGIS scheint toll zu sein, es laesst einen Karten wie die obige machen. Geoinformationshackerei kann Leute in den Wahnsinn treiben.

Wie schwer es fallen kann, Fahrplaene zu oeffnen

Wer die letzten Tage halbwegs aufmerksam im Netz unterwegs war, hat vermutlich diese Schlagzeile gesehen: OpenPlanB hat mal eben saemtliche deutschen Fahrplandaten gezogen und als Torrent verteilt. Deswegen mal ein kurzer Statusbericht zum Thema aus Ulm.

Klar, in Sueddeutschland dauert alles ein wenig laenger. Seit einigen Jahren versuchen hier einige StreiterInnen, an die Echtzeitdaten der Busse und Strassenbahnen zu kommen, um damit tolle Dinge™ bauen zu koennen. Anfangs hatte Fox einen Parser Pseudo-Anfragen an die Fahrplanauskunft stellen lassen, spaeter gab es dann sogar ein Frontend dafuer, und irgendwann wurde auch klar, dass der Nahverkehrsverbund DING die EFA von mentzdv laufen hatte, zu der es auch eine schoene Schnittstellendokumentation aus Linz gibt.

open Data im ÖPV from c-base on Vimeo.

Darueberhinaus hatten wir aber so etwa das im obigen (leider sehr zerhackstueckelten) Video von Michael Kreil umrissene Problem: Wir kamen nicht an die Referenz-Plandaten heran. Der Verkehrsverbund erzaehlte uns, dass wir keinesfalls einfach so Zugriff darauf bekommen koennten, und generell hielt man uns wohl fuer ahnungslose Irre.

Als Tueroeffner fuer die Kommunikation zumindest mit den Ulmer Stadtwerken bot sich unsere in einem Wochenende zusammengehackte Pseudo-Livemap samt der an einigen Stellen der Uni haengenden Live-Busanzeige an, ueber die wir tatsaechlich innerhalb kuerzester Zeit Kontakt zum Verantwortlichen fuer die Datenhaltung bekamen.

(Anekdoteneinschub: Besonders beeindruckt waren die Verantwortlichen den Erzaehlungen nach davon, dass Fox fuer seine Auskunftsseiten die XML-Schnittstelle benutzt hatte, von deren Existenz offenbar niemand oder kaum jemand beim Verbund ueberhaupt wusste)

Wir dachten nun jedenfalls, dass mit dem direkten Draht zu den Stadtwerken in kuerzester Zeit ein GTFS-Satz fuer die Ulmer Linien gebaut werden koennte, womit Ulm als womoeglich erste deutsche Stadt beispielsweise in Google Transit auftauchen koennte.

So einfach war das aber nicht.

Und das ist auch die groesste Huerde ueberhaupt, wenn man an solcherlei Daten herankommen moechte. Die gesamten Plandaten liegen auf irgendwelchen Betriebsleitrechnern in irgendeiner Planungs- und Betriebsleitsoftware. Da gibt es einige wenige Haeuser, die so etwas herstellen, und es handelt sich soweit wir das sehen koennen um proprietaere Pest. Schnittstellen gibt es, die folgen der VDV-454, und ich weiss auch nach intensiver Lektuere der besagten Schrift noch nicht so recht, wie man daraus irgendetwas stricken sollte, das auch wirklich sinnvoll ist. Michael Kreil bzw OpenPlanB haben wohl in grossem Umfang Hafas-Dumps und reihenweise Fahrplanauskunftdaten gezogen, um sich daraus eine deutschlandweite Datenbank zu stricken. So etwas dachten wir uns anfangs auch, erkannten aber relativ schnell, dass wir selbst fuer den relativ kleinen DING-Verbund zigtausende Abfragen stellen muessten, um hinterher auch ein reales Abbild der Soll-Fahrplaene ueber das Jahr hinweg zu bekommen.

Um ein Gefuehl dafuer zu bekommen, was mit den Daten moeglich waere und somit Tueroeffner zu spielen, taugt dieses Prinzip aber, und die Visualisierungen sind schon wunderbar anzusehen. Uns stand aber der Sinn nach einer Moeglichkeit fuer die Stadtwerke, damit diese zukuenftig selber ein valides GTFS-Set unter Beruecksichtigung aller Sonderfahrplaene ins Netz stellen koennten.

Der Weg, den wir momentan dabei beschreiten, ist ein relativ absurder: Es gibt offenbar irgendeine Schnittstelle, an deren Ende CSV-Tabellen fuer eine Person herauskommen, die diese dann in die gedruckten Fahrplaene giessen darf. Da diese Plaene einem bestimmten Muster folgen, kann man sie mit einem sehr kruden Parser nach GTFS umschreiben und dabei gleich per EFA-Schnittstelle die Fahrwege abfragen und mit einbinden. Leider fehlen hier am Ende immer noch viele Daten, die mit an Sicherheit grenzender Wahrscheinlichkeit irgendwo im DING- bzw. SWU-System abgebildet sind: Gefahrene Distanz an einer Haltestelle, Tarifstrukturen und vieles mehr. Ausserdem muessen auch alle Kalenderbesonderheiten nochmals von Hand nachgetragen werden.

Wenigstens sind wir aber so weit, seit dem Fruehjahr ein grundliegendes GTFS-Set exportieren zu koennen, und nachdem ich mich vor einigen Tagen noch einmal daran gesetzt habe, auch die Fahrwege darin abgebildet zu haben, so dass Ulm nun wohl die zweite deutsche Stadt mit einer Mapnificent-Karte ist und wir hoffentlich demnaechst einmal mit den Stadtwerken besprechen werden, ob und wie wir zum Fahrplanwechsel 2012 tatsaechlich auch ein „offizielles“ Ulmer GTFS-Set veroeffentlichen koennen.

Vielleicht hilft das manchen enthusiastischen OePNV-Fans zu verstehen, warum nicht immer alles so schnell geht, selbst wenn alle beteiligten Stellen eigentlich so etwas wollen. MentzDV bietet anscheinend sogar mittlerweile einen GTFS-Export an — man darf sich aber ausrechnen, dass das hierfuer zuzukaufende Modul nicht kostenlos sein wird.

PS: Wir sind hier in Ulm in der etwas einmaligen und manchmal etwas peinlichen Situation, den umgekehrten Zustand wie in Berlin zu haben. Plakativ geschrieben rennen wir offene Tueren ein und werden mit Daten ueberhaeuft, haben aber zu wenige MitstreiterInnen, um mit all diesen Daten auch etwas anfangen zu koennen. Wir haben hier keine c-base und keine re:publica, aber es gibt Mate und ne Donau und ab und zu ein OpenCityCamp. Falls ihr also schon immer mal die Welt veraendern wolltet: Das geht auch hier. Und man muss sich bei den Buffetts nicht immer mit @mspro um die Schnittchen pruegeln. Kombt alle forbei, es gibt einen noch zu visualisierenden Haushalt, eine zu verbessernde mobile Nahverkehrs-Liveabfrage, Entsorgungsdaten und noch viele andere Kleinigkeiten.¹

(¹ Auf Anregung von @plomlompom soll ich schreiben, dass Berlin eine „HipsterHölle [ist], wo man nix mehr produzieren muss, um Anerkennung zu kriegen. Hier unten dagegen beweisen sich die wahren Hacker!“)

Die Stadtwerke machen Social Media

Ausnahmelagen sind die Momente, in denen das Echtzeitnetz brillieren kann. Heute streikten die BusfahrerInnen der Stadtwerke Ulm bis etwa 1430 Uhr, was auch bereits in den grossen Medien der Region angekuendigt wurde.

Von einer tatsaechlichen medialen Begleitung des Ausstands hatte ich wenig mitbekommen — tatsaechlich war es hauptsaechlich Selbsthilfe der Betroffenen auf Twitter, beispielsweise durch das von @taxilof schnell auf die Lage angepasste Haltestellenscript, mit dessen Hilfe man herausfinden konnte, wann der naechste von der (nicht streikenden) RBA betriebene Bus des Umlaufs 3/5 kommen wuerde, der einen an die Uni bringt. Das wurde dann noch ein wenig untereinander verteilt, und ueber @ulmapi twitterte ich, als auf einmal wieder Ist-Daten der rollenden Busse eintrafen, ansonsten schien es aber ruhig an der Social-Media-Front.

Erst gerade vorhin sah ich durch Zufall, dass die Stadtwerke eine ansehlich gepflegte Facebook-Praesenz haben — auf der sich nicht viel zum Streik fand, aber immerhin alle Rahmendaten und die Information, als es wieder weiterging. Und Videos, die es zwar auch dilettieren, dafuer aber menscheln liessen.

Man kann sich jetzt wieder fragen, ob das so toll ist, wenn diese Informationen auf der Facebook-Seite mit wenigen hundert Fans, aber nicht auf der offiziellen Unternehmensseite zu finden ist. Halt, ich nehme das zurueck: Das ist eigentlich ziemlich beschissen. Dass dort aber etwas geht, und vor allem dass auf jeden einzelnen Kommentar reagiert wird, finde ich respektabel.

Da koennte sich manch andere Instanz eine dicke Scheibe abschneiden.

Bleisatz und Untergrundparty

Letztes Jahr hatte ich die Schnauze von der Ulmer Kulturnacht ziemlich voll. Wlada war zu Besuch und wir hatten es geschafft, fast ausschliesslich „Kunst“ auf niedrigstem Niveau zu besuchen. Teilweise hatte das Kerkelingsche Zuege, wenn eine Autorin grottenschlechte Prosa vortrug, und das Publikum sichtbar pflichtergriffen war, weil das ja Kunst sein musste. Stand ja im Programm. HURZ!

Ich bin immer noch nicht wieder ganz warm mit der Kulturnacht — auch 2011 bekommt man immer noch keine benutzbare Website hin, und die Regeln fuer Veranstalter haben einige Aktionskuenstler in meinem Bekanntenkreis davon abgehalten, selbst teilzunehmen. Wurscht: Dieses Jahr bin ic hnaemlich bei drei wunderbaren Sachen gelandet.

Die Autos aus Langenau hatten eine der alten Ulmer GT4-Strassenbahnen fuer sich, in der sie batterieverstaerkt Livemusik spielten und (leicht unterstuetzt von der huepfenden Mitfahrbesatzung) pro Rundfahrt fuenf Kisten Bier leerten.

Mit bunten Lichtern an der Decke. Und geiler Atmosphaere. Ich will sowas oefter haben! Gerne auch mal einen Poetry Slam, oder ein Wohnzimmerkonzert einer schicken Band, die sonst im Sauschdall spielen wuerde… warum eigentlich nicht?

Zweiter Stopp war in der Pionierkaserne, wo ich endlich einmal die dortige Druckwerkstatt besichtigen konnte. Ich bin verliebt. Die haben dort Heidelberger Tiegel, koennen Lithographieren und haben zig Setzkaesten voller Bleilettern — auch in meiner Lieblingsschrift <3

Man sagte mir, dass man dort auch gerne mal Plakate drucken koennte. Reizt mich schon irgendwie, noch einmal ein Plakat fuer eine Uniparty aufzulegen… im klassischen Bleisatz auf einem Tiegel… rawr 🙂

Zum Abschluss waren wir dann wieder mal in einem Luftschutzraum. Dieses Mal der Haus-LSR eines WG-Hauses, der zum Partykeller umgemodelt wurde und nun eben auch zur Kulturnacht offen hatte. Das ist der sagenumwobene Keller, in dem immer mal wieder Goa-Partys stattfanden, fuer die man eben „Leute kennen musste“, oder eben zum richtigen Zeitpunkt an der Tuer vorbeikommen und eingeladen werden. Dort treiben sich mittlerweile zu meiner Belustigung auch ganz viele Leute aus meinem Heimatdorf herum, von denen die meisten irgendwie nach Berlin migriert waren oder so aehnlich.

Ich fuer meinen Teil bin mit der Kulturnacht wieder ein wenig versoehnt. Das fuehlte sich naemlich ganz vernuenftig nach Berlin an, an dem Abend.

Jetzt sollte das nur mal oefter so sein…

Mein erster Hackathon

Nach der Idee von Benjamin habe ich am vergangenen Wochenende tatsaechlich an meinem ersten Hackathon teilgenommen. Das ist tatsaechlich in etwa so, wie man sich das klischeeweise vorstellt: Man verbringt beinahe 48 Stunden mit seinem Team und stellt in der Zeit etwas auf die Beine.

In unserem Fall eine Livekarte der Stadt Ulm mit den Nahverkehrslinien 1, 3/5 und N1–N8. Auf denen sich die Busse live bewegen. Awesome.

Unser Team bestand aus Benjamin, cmichi, Fox und mir, musste gemaess der Regeln des Nodeknockout-Wettbewerbs node.js verwenden und hatte genau 48 Stunden Zeit, etwas auf die Beine zu stellen. Klar, dass wir vor unserem ulmapi-Hintergrund irgendetwas in der Richtung offene Daten und Nahverkehr machen wollten 😉


„Tatort“ war das Students‘ Lab der Fachschaft Elektrotechnik, wo wir dankenswerterweise von Samstag frueh bis Montag frueh arbeiten und zum Teil auch schlafen durften.

Aber von Anfang an.

Livevisualisierungen sind jetzt keine bahnbrechende, neue und revolutionaere Sache (mehr), das gibt es schon eine ganze Weile. Wir hatten uns aus zwei Gruenden trotzdem dazu entschlossen, so etwas umzusetzen:

  1. Es ging beim Contest um node.js, was insbesondere fuer Benni und Michi eine ziemlich erotisierende Wirkung hat aktuell sowas wie eine Lieblingssprache ist. Zudem baut die Loesung auf freien(!) Frameworks auf, im Gegensatz zum Beispiel zu Google Maps, was man hier ja immer mal wieder sieht
  2. Wir wollten uns einmal selbst „so richtig“ mit der General Transit Feed Specification (GTFS), dem Quasi-Standard fuer maschinenlesbare Nahverkehrsdaten, auseinandersetzen. Ich hatte mich schon vorab immer mal wieder in den Standard eingelesen, jetzt sollte es aber ans Eingemachte gehen.

Womit wir dann schon gleich beim grossen Problem waren.

I can haz GTFS feed, plz?

Unsere Nahverkehrsanbieter (DING fuers Umland, SWU fuer die staedtischen Linien) bieten einfach nichts dergleichen an. Waehrend das Frontend dank der freien Leaflet-Bibliothek und dazu passenden huebschen OpenStreetMap-Karten in kurzer Zeit aufgebaut war, mussten die notwendigen Betriebsdaten erstmal muehselig von Hand zusammengebaut werden.

(An dieser Stelle moechte ich noch einmal erwaehnen, wie toll ich Leaflet und auch die Cloudmade-OSM-Karten finde. Wie neulich hier schon geschrieben: Die freien Dienste muessen gut aussehen, und mit der Leaflet/Cloudmade-Kombination sieht OSM einfach rattenscharf aus und bedient sich erstklassig.)

Ja. Die Fahrplandaten. Das klingt jetzt wirklich archaisch, aber: Alles, was momentan auf der Livekarte zu sehen ist, ist datenseitig mit viel Handarbeit zusammengestueckelt worden. Mittels einiger Skripte konnten wir die Shapefiles der Busrelationen aus der DING-Fahrplanauskunft extrahieren, und die Relationen selbst (also der Plan, welche Fahrgastfahrt der Linie X um welche Zeit von A nach B faehrt und wo sie ueberall haelt) ist aus den PDF-Fahrplaenen der SWU zusammengebaut. Das ist haesslich, das war verdammt zeitaufwaendig, aber es ist zumindest halbwegs vollstaendig.

Wenigstens die Nachtbusse sind tatsaechlich zu 100% und auch korrekt nach GTFS ueberfuehrt. Immerhin. Dafuer fahren sie auf der Karte auch jede Nacht 🙂 (siehe unten)

Da stimmt aber noch irgendetwas nicht…


  • GTFS sieht ziemlich genaue Unterscheidungen vor, welche Services wann fahren. Auf der aktuellen Karte fahren aber auch die Busse, die (korrekterweise) dem Service „university“ angehoeren, also eigentlich nur an Vorlesungstagen fahren. Unser Parser ignoriert das momentan und laesst alles fahren, was er an Daten vorliegen hat: Alles, was Montag bis Freitag an einem Vorlesungstag ausserhalb der Schulferien auf besagten Linien faehrt. Und die Nachtbusse 😉
  • Einige Sonderrelationen sind noch nicht abgebildet. Linien die spaet abends nur freitags fahren, oder nur freitags nicht, dafuer Montag bis Donnerstag. Sorry. Kuerze der Zeit. Die Rohdaten aus den Fahrplaenen sind aber mittlerweile halbwegs so aufbereitet, dass man sie fuer einen kompletten GTFS-Datensatz umparsen koennte. Stattdessen koennte man aber auch eine Datensammlung aus der DING-Onlinefahrplanauskunft machen.

Ich bin mir momentan nicht sicher, was die „beste“ Loesung ist, um endlich einen vollstaendigen GTFS-Datensatz fuer die SWU-Linien inklusive aller Sonderfaelle zu bekommen (im Dezember ist uebrigens wieder Fahrplanwechsel…), aber klar ist: Ideal waere eine vernuenftige Originalquelle direkt vom Verkehrsdienstleister. Vielleicht haben wir mit der Karte ja nun weiteres Interesse bei den Stadtwerken geweckt 🙂

Nachtrag am 31. August

Einige Dinge waren wohl trotz der „About“-Seite nicht so ganz klar, und andere habe ich vergessen zu erwaehnen:

  • Warum fahren da Busse, die da nicht fahren sollten? — Die GTFS-Scrapedaten unterscheiden zwar zwischen Nachtbusterminen und Wochentagen, der Parser diskriminiert hier aber nicht. Das heisst zum Beispiel, dass die Nachtbusse jede Nacht fahren, und auch trotz vorlesungsfreier Zeit die Sonderfahrten zwischen Science Park II und Ehinger Tor auf der Karte unterwegs sind
  • Warum fahren die Busse laenger, als sie sollten? — Der laeuft auf UTC, waehrend Ulm momentan auf UTC+2 liegt. Tatsaechlich sieht man also, was vor zwei Stunden los war. Das ist wegen der zyklischen Plaene tagsueber nicht so schlimm, schoen aber natuerlich auch nicht.
  • Warum macht der Bus beim Umlauf 3/5 keine Klinikpause? — GTFS sieht hier vor, fuer jeden Halt eine zurueckgelegte Strecke auf dem Streckenshape zu hinterlegen. Das auszurechnen war in den 48h nicht moeglich (zum Vergleich: Alleine die Linie 3 hat bei uns momentan fuenf verschiedene Shapes fuer alle moeglichen Start-Ziel-Kombinationen). Die Position wird momentan nur relativ krude ueber Start- und Zielort sowie vergangener Zeit approximiert.
  • Warum verwendet die Karte keine Echtzeitdaten? — Uns war es wichtig, hier auf den offenen Standard GTFS zu setzen. Was in den 48h herausgekommen ist, ist zwar keine wirklich saubere Loesung, kann aber noch so weit „gesaeubert“ werden, dass am Ende ein vollstaendiger GTFS-Datensatz verwendet wird, und eben keine Flickschusterei mit manipulierten Fahrauskunft-Anfragen, die man dann minuetlich fuer alle Relationen aktualisieren muesste.

    Es gibt mittlerweile die Erweiterung GTFS-Realtime, die mit Echtzeitdaten umgehen kann. Mit diesen Daten koennte man sogar soweit herunterbrechen, dass angezeigt wird, welcher Bus da jetzt gleich kommen wird (Kennzeichen, dadurch dann z.B. auch, ob es ein Gelenkbus, Standardbus, Viertuerer, Dreituerer, klimatisiert… ist). All das setzt aber erst einmal voraus, dass wir irgendwann einen GTFS-Volldatensatz von den SWU bekommen. Und darum geht’s jetzt erst einmal.

SWU-Daten: Es geht voran


Die Aktion mit den Gruenen-Mails hat offenbar doch noch einen positiven Nebeneffekt: Ich habe den Verschicker in seiner Eigenschaft als SWU-Beirat gebeten, doch nochmal nachzuhaken, ob es nicht doch irgendwann eine API fuer die RBL-Echtzeitdaten geben wuerde. Die Rueckfrage seitens der SWU, was so eine API denn kosten wuerde, konnte ich natuerlich nich tzufriedenstellend beantworten — ich freue mich aber, dass das Thema langsam Gehoer zu finden scheint.

Bis dahin gibt es die leider etwas ungenauen, aus der Fahrplanauskunft geparsten Daten ueber die Selbststrick-API von Taxilof, und als Beispiel-Mobilanwendung fuer Mobilgeraete die Auskunft von Claus.