Seit bestimmt 10 Jahren tagge ich praktisch jedes Foto, das ich mache, halbmanuell mit den GPS-Koordinaten des Aufnahmeortes. Mittlerweile geht das auch sehr einfach, da es für jedes Smartphone OS unzählige GPS Tracker gibt, die auf Reisen bequem GPX Tracks mit Zeitstempel und GPS Koordinaten erzeugen. Solche Tracks lassen sich in Software wie Lightroom über den Zeitstempel im Foto recht einfach mit den Fotos verknüpfen. Bilder, die mit dem Smartphone geschossen werden, haben praktisch immer GPS Koordinaten, auch ohne eigenes Zutun.
Gestern hatte ich die Idee, ein kleines Skript zu schreiben, das meinen Server nach Bildern durchsucht und – falls vorhanden – die GPS-Koordinaten extrahiert und sich „merkt“. Da der Scanvorgang je nach Geschwindigkeit des Servers und der Menge der Bilder sehr lange dauern kann, speichere ich die gefundenen Koordinaten in einer Datei. Auf meinem Raspberry Pi 4 dauerte der Scan durch zehntausende Bilder knapp eine Stunde. Auf diese Weise entsteht quasi eine Datenbank, in der jede Position verzeichnet ist, an der ich ein Foto gemacht habe.
Das Script sieht wie folgt aus:
import os, exifread, contextlib, numpy
from progress.bar import Bar
def convert_to_degress(value):
d = float(Fraction(str(value[0])))
m = float(Fraction(str(value[1])))
s = float(Fraction(str(value[2])))
return d + (m / 60.0) + (s / 3600.0)
def get_gps_info(image_path):
with open(image_path, 'rb') as f:
try:
with contextlib.redirect_stderr(None):
tags = exifread.process_file(f)
gps_latitude = tags.get('GPS GPSLatitude')
gps_latitude_ref = tags.get('GPS GPSLatitudeRef')
gps_longitude = tags.get('GPS GPSLongitude')
gps_longitude_ref = tags.get('GPS GPSLongitudeRef')
if gps_latitude and gps_latitude_ref and gps_longitude and gps_longitude_ref:
latitude = convert_to_degress([float(x.num) / float(x.den) for x in gps_latitude.values])
longitude = convert_to_degress([float(x.num) / float(x.den) for x in gps_longitude.values])
if gps_latitude_ref.values[0] == 'S':
latitude = -latitude
if gps_longitude_ref.values[0] == 'W':
longitude = -longitude
return latitude, longitude
else:
return None, None
except Exception as e:
return None, None
def fileNameTester(filename):
extensions = ["jpg", "jpeg", "heic", "arw", "dng"]
filename = filename.lower().split(".")
if filename[-1] in extensions:
return True
else:
return False
def getPins(dir, outputFile):
pins = []
roots = []
for root, dirs, files in os.walk(dir):
for filename in files:
if root not in roots:
roots.append(root)
bar = Bar(f"Scanning {root:<80}", max=len(files))
bar.next()
if fileNameTester(filename):
latitude, longitude = get_gps_info(os.path.join(root, filename))
if longitude and latitude:
if (latitude, longitude) not in pins:
pins.append((latitude, longitude))
bar.finish()
pins_NP = numpy.array(pins)
numpy.save(outputFile, pins_NP)
outputFile = "pins.npy"
inputDir = "/media/disk/Fotos/JPEG/2023"
getPins(inputDir, outputFile)
Ein zweites kleines Script liest diese Datei wieder ein und markiert die Koordinaten auf einer Karte. Das Ergebnis dieses Prozesses ist eine html-Datei, in die man bequem im Browser hineinzoomen kann.
Je nach Anzahl der gefundenen Koordinaten ist es sinnvoll, nicht alle Koordinaten in die Karte einzutragen. Stattdessen empfiehlt es sich, die Koordinaten z.B. nach ein oder zwei Dezimalstellen zu „runden“ bzw. abzuschneiden. Die im Umkreis aufgenommenen Bilder werden so quasi an einem Ort zusammengefasst.
Dieses Script sieht wie folgt aus:
import folium, matplotlib, numpy
def cleanPins(file):
cleanedPins = {}
pins = numpy.load(file)
for pin in pins:
lat, lon = pin
latc = round(lat, 2) #Anzahl der Nachkommastellen
lonc = round(lon, 2) #Anzahl der Nachkommastellen
pinc = (latc, lonc)
if pinc not in cleanedPins:
cleanedPins[pinc] = 1 #Anzahl der Bilder an der "gerundeten" Position
else:
cleanedPins[pinc] += 1
return cleanedPins
def map_values_to_colors(value, vmin, vmax):
norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)
cmap = matplotlib.colormaps['coolwarm']
r, g, b , x = cmap(norm(value))
r = round(r * 255)
g = round(g * 255)
b = round(b * 255)
return f"#{r:x}{g:x}{b:x}"
def makeMap(pins, outputMap):
map = folium.Map(location=[48.17, 11.75], zoom_start=8)
for pin in pins:
lat, lon = pin
n = pins[pin]
max = 200
if n <= max:
ic = map_values_to_colors(n, 1, max)
else:
ic = "red"
marker = folium.CircleMarker(location=[lat, lon], popup=f"{n}", color=ic, fill=True)
marker.add_to(map)
map.save(outputMap)
inputFile ="pins.npy"
outputMap = "map.html"
cleanedPins = cleanPins(inputFile)
makeMap(cleanedPins, outputMap)
Schreibe einen Kommentar