Digitalfotos geokodieren mit Google und Python (Software)

Martin Vogel ⌂ @, Dortmund / Bochum, Sonntag, 19.05.2019, 20:10 (vor 32 Tagen)

Smartphones sind erstklassige Datensammler. Sie ermöglichen Google und Apple, bemerkenswert genaue Protokolle über die Bewegungen ihrer Kundinnen und Kunden zu führen. Das kann man bedenklich finden und in gewissem Maße auch beschränken, man kann die Daten aber auch für seine eigenen Zwecke nutzen. Google erlaubt es sogar, sämtliche seit Beginn der Nutzung gesammelten Bewegungsdaten herunterzuladen.

https://takeout.google.com/settings/takeout

Das Zusammenstellen dauert ein paar Minuten. Wer die Zeit nicht durch Warten verbringen möchte, kann sich den Link auf die fertige Google-Takeout-Datei auch per Mail schicken lassen. Packt man sie aus, findet man eine Datei mit dem Namen „Standortverlauf.json“, die durchaus mehrere hundert Megabyte groß sein kann. In ihr ist jeder einzelne Datenpunkt als Geokoordinate enthalten, einschließlich einem Wert, der besagt, wie genau die Messung war, manchmal auch um eine Höhenangabe und weitere Daten ergänzt.

Was kann man man nun sinnvolles mit den Daten anfangen? Als jemand, der nicht nur mit dem Smartphone, sondern gelegentlich auch mit einer „richtigen“ Digitalkamera fotografiert, finde ich es immer schade, dass ausgerechnet die besseren Fotos von meiner Bildverwaltung DigiKam nicht mit ihrem korrekten Standort auf einer Karte angezeigt werden. Sofern die Uhr in der Kamera richtig gestellt ist, lässt sich aber über den Standortverlauf für jedes Bild herausfinden, wo denn das Smartphone zu dem Zeitpunkt war. Idealerweise hat man das beim Fotografieren dabei und weiß daher, welche Koordinaten ins Bild einzutragen sind.

Von Hand ist das natürlich etwas mühselig. Allein das Öffnen der riesigen JSON-Datei dauert auf einem mittleren PC eine gefühlte Ewigkeit. Sinnvoller ist es, diese Aufgabe zu automatisieren. DigiKam verfügt dazu über die Funktion „Geo-Korrelator“, die einer Gruppe von Fotos die Geokoordinaten einer Wegaufzeichnung im GPX-Format zuweist. Hier besteht sogar noch die Möglichkeit, Abweichungen durch eine nicht ganz korrekte Uhrzeit der Kamera zu korrigieren.

Jetzt müssen wir nur die JSON-Datei aus dem Google-Takeout ins GPX-Format übertragen und schon können wir tausende von Digitalfotos auf dem Globus verorten (vorausgesetzt, das Handy war dabei, siehe oben). Das unten aufgelistete Python-Programm erledigt genau diese Übersetzung. Anstelle einer Riesen-GPX-Datei werden jedoch handlichere Dateien für einzelne Jahre angelegt. Bei mir waren die GPX-Jahresprotokolle jeweils etwa um die 30 MB groß und recht flott von DigiKams Geokorrelator geladen.

Aus einem Foto-Rundgang durch die mittelalterliche Stadt Lucca wurde so beispielsweise dieser Lageplan:
[image]
Karte: OpenStreetMap, Lizenz: CC-BY-SA 2.0

Die Zahlen geben an, wie viele Fotos an dem jeweiligen Kamerastandort vom Geokorrelator verortet wurden.

Das hier vorgestellte Pythonprogramm übernimmt jedoch nicht alle Koordinaten, sondern nur die mit ausreichender Qualität. GPS-Messungen, deren angegebene Genauigkeit mit einer möglichen Abweichung von mehr als 100 Metern angegeben wird, werden ausgefiltert. Ebenso werden plötzliche Sprünge der Koordinaten um mehrere Kilometer verworfen, die aus ungeklärten Gründen immer wieder in Googles Standortverlauf verzeichnet sind. Um die Sprünge herauszufiltern, betreibe ich allerdings keinen großen Aufwand, sondern verwerfe einfach alle Punkte, wenn seit der letzten gültigen Messung eine Geschwindigkeit von mehr als etwa 150 km/h erforderlich gewesen wäre, um den neuen Punkt zu erreichen. Wer regelmäßig bei höheren Geschwindigkeiten fotografiert, mag das Programm an der Stelle anpassen.

  1. #!/usr/bin/env python3
  2. import datetime
  3. from math import radians, sin, cos, acos
  4.  
  5. print("Konvertiere Standortverlauf.json aus dem Google Takeout zu gpx …")
  6. Startjahr = 2012
  7. Endjahr = 2020
  8.  
  9. def Geschwindigkeit(tim0,lat0,lon0,tim,lat,lon):
  10. """ Schätzung der Geschwindigkeit zwischen zwei Streckenpunkten """
  11. # Zeitabstand zwischen den beiden Messungen?
  12. s = tim - tim0
  13. # Kein Zeitunterschied?
  14. if not s:
  15. return 0
  16. # Etwas sphärische Geometrie …
  17. # Herleitung der Formel unten:
  18. # https://www2.informatik.hu-berlin.de/~sanftleb/Kugelabstand.pdf
  19. # Winkel in Radiant statt Grad
  20. lat0, lon0, lat, lon = map(radians, (lat0, lon0, lat, lon))
  21. # Mittlerer Erdradius: 6371000 Meter
  22. r = 6371000
  23. # Das acos-Argument kann durch Rundungsfehler größer als 1 werden und
  24. # wird daher auf Werte <= 1 begrenzt.
  25. m = r*acos(min(1,cos(lat0)*cos(lat)*cos(lon0-lon)+sin(lat0)*sin(lat)))
  26. # Geschwindigkeit in m/s
  27. return m / s
  28.  
  29. # Eingabedatei aus Google Takeout lesen
  30. with open("Standortverlauf.json") as json:
  31. tim0 = 0 # Zeitstempel des letzten Koordinatenpunktes
  32. K = [] # Liste der Koordinatenpunkte
  33. nk = 0 # Anzahl gefunder Koordinaten
  34. nzu = 0 # Anzahl verworfener, zu ungenauer Punkte
  35. nzs = 0 # Anzahl verworfener Punkte (Ausreisser)
  36. for zeile in json:
  37. zs = zeile.split(":")
  38. if "acc" in zs[0]:
  39. nk += 1
  40. # acc ist immer der letzte Eintrag zu einem Trackpunkt.
  41. # lat, lon tms sind da bekannt.
  42. acc = int(zs[1].split(",")[0])
  43. # Nur Punkte mit weniger als 100 Metern angegebener Ungenauigkeit
  44. # verwenden
  45. if acc < 100:
  46. if not tim0: # erster Durchlauf
  47. tim0, lat0, lon0 = tim, lat, lon
  48. # nur Punkte verwenden, wenn sie nicht offensichtlich Glitches
  49. # sind.
  50. # Kriterium: v < 150 km/h bzw. 42 m/s
  51. if Geschwindigkeit(tim0,lat0,lon0,tim,lat,lon) < 42:
  52. K.append([tim,lat,lon])
  53. tim0, lat0, lon0 = tim, lat, lon
  54. else:
  55. nzs += 1
  56. else:
  57. nzu += 1
  58.  
  59. elif "lat" in zs[0]:
  60. lat = float(zs[1].split(",")[0])/1e7
  61. elif "lon" in zs[0]:
  62. lon = float((zs[1].split(",")[0]))/1e7
  63. elif "time" in zs[0]:
  64. tim = float(zs[1].split('"')[1])/1e3
  65.  
  66. print(f"{nk:9} Koordinaten gefunden.")
  67. print(f"{nzu:9} Koordinaten zu ungenau (>100 m).")
  68. print(f"{nzs:9} Koordinaten zu schnell (>42 m/s).")
  69. print(f"{len(K):9} Koordinaten werden umgewandelt.")
  70.  
  71. # Um die Ausgabedatei nicht übermäßig groß werden zu lassen, wird sie
  72. # jahreweise unterteilt.
  73. for jahr in range(Startjahr,Endjahr+1):
  74. print(jahr)
  75. js = f"{jahr}-"
  76. with open(f"Standortverlauf {jahr}.gpx","w") as gpx:
  77. tim0 = 0
  78. gpx.write('<?xml version="1.0" encoding="UTF-8" ?>\n')
  79. gpx.write('<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" '
  80. 'creator="Standortverlauf.py" '
  81. 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
  82. 'xsi:schemaLocation="http://www.topografix.com/GPX/1/1 '
  83. 'http://www.topografix.com/GPX/1/1/gpx.xsd">\n')
  84. gpx.write('<trk>\n')
  85. for tim, lat, lon in K:
  86. # ISO-formatierte Zeit
  87. tif = datetime.datetime.fromtimestamp(tim).isoformat()
  88. if js in tif:
  89. # 20 Minuten Pause? Neuer Track.
  90. if tim > tim0 + 1200:
  91. if tim0: # jedoch nicht beim ersten Eintrag
  92. gpx.write('</trkseg>')
  93. gpx.write('<trkseg>\n')
  94. gpx.write(f'<trkpt lat="{lat}" lon="{lon}">')
  95. gpx.write(f'<time>{tif}</time></trkpt>\n')
  96. tim0 = tim
  97. gpx.write('</trkseg>\n</trk>\n</gpx>\n')

--
Dipl.-Ing. Martin Vogel
Leiter des Bauforums

Heute schon programmiert? Einführung in Python 3 (PDF)

Tags:
Python, Google, Fotografie, GPS

RSS-Feed dieser Diskussion
powered by my little forum