Kartendaten mit Python
Theorie
In Google Maps kann man Dateien einlesen, in denen Orte oder Flächen markiert werden und kann diese verschicken. Dies kann eine Vielzahl an nützlichen Anwendungen haben:
Ferienziele: Mache für dich und deine Freunde eine Weltkarte, auf der deine wichtigsten Ferienziele markiert sind.
Reise-Route: Plane deine nächste Reise
Weltherrschaft: Du willst dein eigenes Land haben? Stecke die Grenzen auf Google Maps ab und teile es mit deinen Freunden.
Google Maps Karte
Typisches Vorgehen:
-
… und logge dich ein.
Klicke auf „Neue Karte erstellen“ und „Importieren“ …
… und ziehe dort ein passendes CSV-File hinein.
Dann muss man die „Spalten zur Positionierung …“ auswählen: typischerweise „WKT“ …
… und dann die „Spalte zur Benennung …“: typischerweise „Name“
Punkte, Linien und co. in WKT
WKT steht für Well-Known Text und ist ein Textformat für räumliche Datenmodelle. Es ermöglicht die Beschreibung geometrischer Objekte wie Punkte, Linien, Polygone und mehr in einem Format, welches sowohl für Menschen wie auch für Computer gut lesbar sind.
Einen Punkt mit Koordinaten $(x,y)$ notiert man darin wie folgt:
"POINT (x y)"
Eine Linie, die von $(x1,y1)$ zu $(x2,y2)$ und von da zu $(x3,y3)$ geht, notiert man so:
"LINESTRING (x1 y1, x2 y2, x3 y3)"
Ein geschlossenes Polygon (Vieleckt) notiert man so:
"POLYGON ((x1 y1, x2 y2, x3 y3, x1 y1))"
In diesem Falle erhält man natürlich ein Dreieck. Beachte hier die Verwendung von doppelten Klammern.
Man kann auch gleichzeitig mehrere Punkte, Linien und Polygone festlegen:
"MULTIPOINT ((x1 y1, x2 y2, x3 y3))"
"MULTILINESTRING ((x1 y1, x2 y2), (x3 y3, x4 y4))""
"MULTIPOLYGON (((x1 y1, x2 y2, x3 y3, x1 y1)),((x4 y4, x5 y5, x6 y6, x4 y4)))"
WKT in Google Maps
Um Punkte, Linien und Polygone in Google Maps einzuzeichnen, kann man diese Formen in einem CSV-File festlegen. Dazu benötigt man die entsprechenden Koordinaten ($x:$ longitude/Längengrad, $y:$ latitude/Breitengrad).
Wir betrachten die folgenden vier Ortschaften mit zugehörigen Längen- und Breitengrad:
Frauenfeld (8.89656423219735, 47.558162001326)
Weinfelden (9.10636576683266, 47.5662241119746)
Romanshorn (9.37937276486743, 47.5655209998311)
Kreuzlingen (9.16907715682578, 47.6525502207935)
- tg.csv
Name,WKT
Weinfelden,"POINT (9.10636576683266 47.5662241119746)"
Line FF to WF,"LINESTRING (8.89656423219735 47.558162001326, 9.10636576683266 47.5662241119746)"
Wann und wo benötigt man Anführungs- und Schlusszeichen?
Für die WKT-Befehle werden diese benötigt. Der Grund ist, dass diese Befehle Kommas beinhalten, welche auch als Trennzeichen (Delimiter) des CSV agieren.
Für die Namen hingegen sind sie optional, ausser die Namen beinhalten Kommas, dann braucht man auch hier Anführungs- und Schlusszeichen.
Beachte, dass das CSV einen passenden Header benötigt: „Name,WKT“. Dank diesem weiss Google Maps, welche Bedeutung die Werte im CSV haben.
Dieses CSV-File kann man herunterladen, in Google Maps importieren und dann sieht man den Punkt und die Linie am richtigen Ort mit der angegebenen Beschriftung.
Aufgaben
Arbeite nicht mit Excel / Google Sheets, sondern mit einem reinen Texteditor wie Visual Studio Code.
Aufgabe 1: Big 4 im TG
Erstelle ein leeres CSV-File mit Header „Name,WKT“ und füge Punkte für die vier Gemeinden Frauenfeld, Weinfelden, Romanshorn, Kreuzlingen hinzu. Verwende die Koordinaten aus der Theorie oben:
Importiere das File dann in Google Maps und stelle sicher, dass alles stimmt.
Aufgabe 2: Top 3 Ferienziele
Welches sind deine Top 3 Orte, die du noch besuchen möchtest?
Finde deren Koordinaten …
… erstelle ein entsprechendes CSV-File …
… und importiere sie auf Google Maps.
Spielregel: Es müssen mind. zwei unterschiedliche Kontinenten vorkommen.
Aufgabe 3: WKT mit Python
Von nun arbeiten wir wieder mit Python und dem folgenden Datensatz, der die Koordinaten aller Bahnhöfe in der Schweiz beinhaltet: sbb_csv_json.zip.
Auftrag:
Generiere mithilfe von Python ein CSV-File für Google Maps, welches die Koordinaten einiger Ortschaften St. Gallen, Bern und Schaffhausen beinhaltet.
Verwende den SBB-Datensatz, um die Koordinaten zu ermitteln.
Schreibe Code so, dass er auch noch funktioniert, wenn man z.B. St. Gallen durch Winterthur ersetzt.
Tipps
Vorgehen:
Lade den SBB-Datensatz herunter, entzippe diesen und lese diesen (JSON-Variante) ein.
Öffne in Python ein leeres CSV-File und schreibe den Header („Name,WKT“) hinein. Du kannst mit csv.reader
arbeiten oder einfach zeilenweise in das File schreiben (empfohlen).
Speichere die Ortschaften in einer Liste und gehe diese in einer Schleife durch.
In dieser generierst du den CSV-Eintrag und schreibst ihn ins File.
Importiere das File auf Google Maps und vergewissere dich, dass es funktioniert.
Aufgabe 4: Gemeinden mit Anfangsbuchstaben
Wähle einen Anfangsbuchstaben, z.B. „W“, und speichere diesen in einer Variable.
Ermittle nun alle Bahnhöfe, die mit diesem Buchstaben beginnen …
und generiere das entsprechende CSV-File.
Importiere es in Google Maps, um diese anzuzeigen.
Aufgabe 5: Städte mit vielen Einwohnern
Identifiziere (möglichst) alle Städte mit $10'000$ Einwohnern oder mehr und zeige sie auf einer Google Map an.
Das Problem ist, dass der SBB-Datensatz die Anzahl Einwohner nicht beinhaltet. Deshalb greifen wir auf den Gemeinden-Datensatz vom Thema Datenverarbeitung mit Python zu. Das Vorgehen ist in etwa so:
Gehe jede Gemeinde im Gemeinden-Datensatz durch und finde heraus, ob sie mehr als $10'000$ Einwohner hat.
Falls ja versuchst du im SBB-Datensatz die Koordinaten dieser Gemeinde zu ermitteln.
Achtung: Das geht nicht immer! Warum? Verwende deshalb try-except
, damit der Code nicht crashed.
Falls du einen passenden Eintrag gefunden hast im SBB-Datensatz, schreibst du einen entsprechenden Eintrag ins CSV.
Optional kann man als „Name“ im CSV-File nicht nur den <Gemeindenamen> sondern z.B. <Gemeindenamen (Einwohner)> anzeigen. Dann hat man eine Karte, bei der man auf die Gemeinden klicken kann und gleich die Anzahl Einwohner sieht.
Aufgabe 5+ (Optional)
Erweitere die letzte Aufgabe: Es fehlen noch einige Ortschaften, auch wichtige wie Zürich und Basel. Warum? Versuche den Code so zu erweitern, dass möglichst viele dieser 'problematischer' Ortschaften auch integriert werden.
Aufgabe 6: Abstimmungsresultate
Zu Abstimmungen kann man jeweils die detaillierten Daten im JSON-Format herunterladen. Hier der Link zu den
Daten der eidgenössischen Abstimmungen
Hier direkt zur
Abstimmung vom 18.06.2023
Lade den Datensatz zur aktuellsten eidgenössischen Abstimmung herunter.
Verschaffe dir einen Überblick: Wie ist der Datensatz aufgebaut? Tipps:
Mit .keys()
kannst du sehen, welches die Schlüssel eines Dicts sind.
Mit type(...)
kannst du ermitteln, um was für einen Typ es sich handelt.
Achtung: Der Datensatz beinhaltet ein Dict, welches Listen enthält, welches wiederum Dicts enthält u.s.w.
Falls du überfordert bist damit, findest du unten die Antwort
Finde in diesem Datensatz die Anzahl Ja-Stimmen zu allen Vorlagen in deinem Wohnort.
Finde die extremsten Gemeinden zu den jeweiligen Vorgaben: Höchste und tiefste Anzahl Ja- resp. Nein-Stimmen
Zusatzaufgabe: Stelle einen Sachverhalt von diesem Datensatz mithilfe von WKT in Google Maps graphisch dar. Beispiel: Ermittle für eine Vorlage von jedem Kanton die beiden Gemeinden mit dem höchsten Ja- resp- Nein Anteil. Erstelle dann eine Karte, die dies darstellt. Falls im Datensatz sbb.json
die jeweilige Gemeinde nicht eingetragen ist, musst du diese halt nachschlagen.
Tipps: Aufbau Datensatz inkl. Beispiel
# lade Datensatz (JSON) in ein dict mit Name data
vorlagen = data['schweiz']['vorlagen'] # vorlagen ist Liste, Länge entspricht Anzahl Vorlagen
vorlage = vorlagen[0] # vorlage beinhaltet alle Infos zur ersten Vorlage (erstes Element der Liste vorlagen
print(vorlage['vorlagenTitel'][0]['text']) # Name der Vorlage (0 für Deutsch)
kantone = vorlage['kantone'] # Liste mit Infos zu allen Kantonen (zur gewählten Vorlage)
kanton = kantone[3] # Infos zu einem Kanton (Zahl 0 - 25, weil 26 Kantone)
print(kanton['geoLevelname']) # Name dieses Kantons
gemeinden = kanton['gemeinden'] # Liste mit Infos zu allen Gemeinden in diesem Kanton (zur gewählten Vorlage)
gemeinde = gemeinden[7] # Alle Infos zu einer Gemeinde
resultat = gemeinde['resultat'] # Resultat
jaStimmenInProzent = resultat['jaStimmenInProzent']
"""
Lösungen
Aufgabe 3
import json
towns = ["St. Gallen","Schaffhausen","Bern"]
with open('sbb.json','r') as json_file:
sbb = json.load(json_file)
with open('aufgabe_3.csv','w') as out_file:
out_file.write('Name,WKT\n') # kann alternativ auch mit csv.writer arbeiten
for town in towns:
out_file.write(town + ',' + '"POINT (' + sbb[town]['longitude'] + ' ' + sbb[town]['latitude'] + ')"\n')
Aufgabe 5
Reguläre Lösung
import json
INHABITANTS_MIN = 10000
with open("sbb.json", "r") as json_file:
sbb = json.load(json_file)
with open("gemeinden.json", "r") as json_file:
gemeinden = json.load(json_file)
with open('gemeinden_min_' + str(INHABITANTS_MIN) + '.csv','w') as out_file:
out_file.write('Name,WKT\n') # kann alternativ auch mit csv.writer arbeiten
for town in gemeinden:
if gemeinden[town]['inhabitants'] >= INHABITANTS_MIN:
try:
out_file.write(town + ',' + '"POINT (' + sbb[town]['longitude'] + ' ' + sbb[town]['latitude'] + ')"\n')
except:
print("Fehler: ",town)
Mit optionalem Part:
import json
INHABITANTS_MIN = 50000
with open("sbb.json", "r") as json_file:
sbb = json.load(json_file)
with open("gemeinden.json", "r") as json_file:
gemeinden = json.load(json_file)
def generate_wkt_point(town):
return '"' + town + '"' + ',' + '"POINT (' + sbb[town]['longitude'] + ' ' + sbb[town]['latitude'] + ')"\n'
with open('gemeinden_min_' + str(INHABITANTS_MIN) + '.csv','w') as out_file:
out_file.write('Name,WKT\n') # kann alternativ auch mit csv.writer arbeiten
for town in gemeinden:
if gemeinden[town]['inhabitants'] >= INHABITANTS_MIN:
success = False
for ext in ['',' HB',' SBB']: # try different typical extensions
try:
out_file.write(generate_wkt_point(town + ext))
success = True
break
except:
continue
if not success:
for t in sbb: # if not found yet, split and try to match first part
if t.split()[0] == town:
out_file.write('"' + t + '"' + ',' + '"POINT (' + sbb[t]['longitude'] + ' ' + sbb[t]['latitude'] + ')"\n')
print(town)
break