Mit Geotags versehene Fotos in KML-PhotoOverlays konvertieren

Mano Marks, Google Geo APIs Team
Januar 2009

Ziel

In dieser Anleitung erfahren Sie, wie Sie KML-Dateien PhotoOverlays mit geotaggten Fotos erstellen. Der Beispielcode ist zwar in Python geschrieben, aber es gibt viele ähnliche Bibliotheken in anderen Programmiersprachen. Daher sollte es kein Problem sein, diesen Code in eine andere Sprache zu übersetzen. Der Code in diesem Artikel basiert auf der Open-Source-Python-Bibliothek EXIF.py.

Einführung

Digitalkameras sind wirklich erstaunliche Geräte. Viele Nutzer wissen es nicht, aber sie machen mehr als nur Fotos und Videos. Außerdem werden diese Videos und Fotos mit Metadaten zur Kamera und ihren Einstellungen getaggt. In den letzten Jahren haben Nutzer Möglichkeiten gefunden, diesen Informationen geografische Daten hinzuzufügen. Diese werden entweder von den Kameraherstellern eingebettet, wie bei einigen Kameras von Ricoh und Nikon, oder über Geräte wie GPS-Logger und die EyeFi Explore. Kamerasmartphones wie das iPhone und Smartphones mit dem Android-Betriebssystem wie das G1 von T-Mobile betten diese Daten automatisch ein. Auf einigen Websites zum Hochladen von Fotos, z. B. Panoramio, Picasa-Webalben und Flickr, werden GPS-Daten automatisch analysiert und zum Geotaggen von Fotos verwendet. Anschließend können Sie diese Daten wieder in Feeds abrufen. Aber wo bleibt da der Spaß? In diesem Artikel erfahren Sie, wie Sie selbst auf diese Daten zugreifen können.

EXIF-Header

Die gängigste Methode zum Einbetten von Daten in eine Bilddatei ist das Exchangeable Image File Format (EXIF). Die Daten werden standardmäßig in binärer Form in den EXIF-Headern gespeichert. Wenn Sie die Spezifikation für EXIF-Header kennen, können Sie sie selbst parsen. Glücklicherweise hat sich bereits jemand die Mühe gemacht und ein Python-Modul für Sie geschrieben. Die Open-Source-Bibliothek EXIF.py ist ein hervorragendes Tool zum Lesen der Header von JPEG-Dateien.

Der Code

Der Beispielcode für diesen Artikel befindet sich in der Datei exif2kml.py. Wenn Sie das Modul direkt verwenden möchten, laden Sie es und EXIF.py herunter und legen Sie beide in dasselbe Verzeichnis. Führen Sie python exif2kml.py foo.jpg aus und ersetzen Sie foo.jpg durch den Pfad zu einem geotaggten Foto. Dadurch wird eine Datei mit dem Namen test.kml erstellt.

EXIF-Header parsen

EXIF.py bietet eine einfache Schnittstelle zum Extrahieren der EXIF-Header. Führen Sie einfach die Funktion process_file() aus. Die Header werden dann als dict-Objekt zurückgegeben.

def GetHeaders(the_file):
  """Handles getting the Exif headers and returns them as a dict.

  Args:
    the_file: A file object

  Returns:
    a dict mapping keys corresponding to the Exif headers of a file.
  """

  data = EXIF.process_file(the_file, 'UNDEF', False, False, False)
  return data

Sobald Sie die Exif-Header haben, müssen Sie die GPS-Koordinaten extrahieren. EXIF.py behandelt diese als Ratio-Objekte, also Objekte zum Speichern des Zählers und Nenners der Werte. So wird ein genaues Verhältnis festgelegt, anstatt sich auf eine Gleitkommazahl zu verlassen. In KML werden jedoch Dezimalzahlen und keine Verhältnisse erwartet. Sie extrahieren also die einzelnen Koordinaten und wandeln Zähler und Nenner in eine einzelne Gleitkommazahl für Dezimalgrad um:

def DmsToDecimal(degree_num, degree_den, minute_num, minute_den,
                 second_num, second_den):
  """Converts the Degree/Minute/Second formatted GPS data to decimal degrees.

  Args:
    degree_num: The numerator of the degree object.
    degree_den: The denominator of the degree object.
    minute_num: The numerator of the minute object.
    minute_den: The denominator of the minute object.
    second_num: The numerator of the second object.
    second_den: The denominator of the second object.

  Returns:
    A deciminal degree.
  """

  degree = float(degree_num)/float(degree_den)
  minute = float(minute_num)/float(minute_den)/60
  second = float(second_num)/float(second_den)/3600
  return degree + minute + second


def GetGps(data):
  """Parses out the GPS coordinates from the file.

  Args:
    data: A dict object representing the Exif headers of the photo.

  Returns:
    A tuple representing the latitude, longitude, and altitude of the photo.
  """

  lat_dms = data['GPS GPSLatitude'].values
  long_dms = data['GPS GPSLongitude'].values
  latitude = DmsToDecimal(lat_dms[0].num, lat_dms[0].den,
                          lat_dms[1].num, lat_dms[1].den,
                          lat_dms[2].num, lat_dms[2].den)
  longitude = DmsToDecimal(long_dms[0].num, long_dms[0].den,
                           long_dms[1].num, long_dms[1].den,
                           long_dms[2].num, long_dms[2].den)
  if data['GPS GPSLatitudeRef'].printable == 'S': latitude *= -1
  if data['GPS GPSLongitudeRef'].printable == 'W': longitude *= -1
  altitude = None

  try:
    alt = data['GPS GPSAltitude'].values[0]
    altitude = alt.num/alt.den
    if data['GPS GPSAltitudeRef'] == 1: altitude *= -1

  except KeyError:
    altitude = 0

  return latitude, longitude, altitude

Sobald Sie die Koordinaten haben, können Sie ganz einfach ein einfaches PhotoOverlay für jedes Foto erstellen:

def CreatePhotoOverlay(kml_doc, file_name, the_file, file_iterator):
  """Creates a PhotoOverlay element in the kml_doc element.

  Args:
    kml_doc: An XML document object.
    file_name: The name of the file.
    the_file: The file object.
    file_iterator: The file iterator, used to create the id.

  Returns:
    An XML element representing the PhotoOverlay.
  """

  photo_id = 'photo%s' % file_iterator
  data = GetHeaders(the_file)
  coords = GetGps(data)

  po = kml_doc.createElement('PhotoOverlay')
  po.setAttribute('id', photo_id)
  name = kml_doc.createElement('name')
  name.appendChild(kml_doc.createTextNode(file_name))
  description = kml_doc.createElement('description')
  description.appendChild(kml_doc.createCDATASection('<a href="#%s">'
                                                     'Click here to fly into '
                                                     'photo</a>' % photo_id))
  po.appendChild(name)
  po.appendChild(description)

  icon = kml_doc.createElement('icon')
  href = kml_doc.createElement('href')
  href.appendChild(kml_doc.createTextNode(file_name))

  camera = kml_doc.createElement('Camera')
  longitude = kml_doc.createElement('longitude')
  latitude = kml_doc.createElement('latitude')
  altitude = kml_doc.createElement('altitude')
  tilt = kml_doc.createElement('tilt')

  # Determines the proportions of the image and uses them to set FOV.
  width = float(data['EXIF ExifImageWidth'].printable)
  length = float(data['EXIF ExifImageLength'].printable)
  lf = str(width/length * -20.0)
  rf = str(width/length * 20.0)

  longitude.appendChild(kml_doc.createTextNode(str(coords[1])))
  latitude.appendChild(kml_doc.createTextNode(str(coords[0])))
  altitude.appendChild(kml_doc.createTextNode('10'))
  tilt.appendChild(kml_doc.createTextNode('90'))
  camera.appendChild(longitude)
  camera.appendChild(latitude)
  camera.appendChild(altitude)
  camera.appendChild(tilt)

  icon.appendChild(href)

  viewvolume = kml_doc.createElement('ViewVolume')
  leftfov = kml_doc.createElement('leftFov')
  rightfov = kml_doc.createElement('rightFov')
  bottomfov = kml_doc.createElement('bottomFov')
  topfov = kml_doc.createElement('topFov')
  near = kml_doc.createElement('near')
  leftfov.appendChild(kml_doc.createTextNode(lf))
  rightfov.appendChild(kml_doc.createTextNode(rf))
  bottomfov.appendChild(kml_doc.createTextNode('-20'))
  topfov.appendChild(kml_doc.createTextNode('20'))
  near.appendChild(kml_doc.createTextNode('10'))
  viewvolume.appendChild(leftfov)
  viewvolume.appendChild(rightfov)
  viewvolume.appendChild(bottomfov)
  viewvolume.appendChild(topfov)
  viewvolume.appendChild(near)

  po.appendChild(camera)
  po.appendChild(icon)
  po.appendChild(viewvolume)
  point = kml_doc.createElement('point')
  coordinates = kml_doc.createElement('coordinates')
  coordinates.appendChild(kml_doc.createTextNode('%s,%s,%s' %(coords[1],
                                                              coords[0],
                                                              coords[2])))
  point.appendChild(coordinates)

  po.appendChild(point)

  document = kml_doc.getElementsByTagName('Document')[0]
  document.appendChild(po)

Wie Sie sehen, verwenden wir nur Standardmethoden des W3C-DOM, da diese in den meisten Programmiersprachen verfügbar sind. Wenn Sie sich ansehen möchten, wie alles zusammenpasst, können Sie den Code hier herunterladen.

In diesem Beispiel wird nicht die volle Leistungsfähigkeit von PhotoOverlays genutzt, mit der Sie hochauflösende Fotos detailliert untersuchen können. Es wird jedoch gezeigt, wie Sie ein Foto im Stil einer Werbetafel über Google Earth aufhängen. Hier sehen Sie ein Beispiel für eine KML-Datei, die mit diesem Code erstellt wurde:

<?xml version="1.0" encoding="utf-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
  <Document>
    <PhotoOverlay id="photo0">
      <name>
        1228258523134.jpg
      </name>
      <description>
<![CDATA[<a href="#photo0">Click here to fly into photo</a>]]>      </description>
      <Camera>
      	<longitude>
          -122.3902159196034
        </longitude>
        <latitude>
           37.78961266330473
        </latitude>
        <altitude>
          10
        </altitude>
        <tilt>
          90
        </tilt>
      </Camera>
      <Icon>
        <href>
          1228258523134.jpg
        </href>
      </Icon>
      <ViewVolume>
        <leftFov>
          -26.6666666667
        </leftFov>
        <rightFov>
          26.6666666667
        </rightFov>
        <bottomFov>
          -20
        </bottomFov>
        <topFov>
          20
        </topFov>
        <near>
          10
        </near>
      </ViewVolume>
      <Point>
        <coordinates>
          -122.3902159196034,37.78961266330473,0
        </coordinates>
      </Point>
    </PhotoOverlay>
  </Document>
</kml>

So sieht das in Google Earth aus:


Warnhinweise

Das Geotagging von Fotos steckt noch in den Kinderschuhen.

Beachten Sie Folgendes:

  • GPS-Geräte sind nicht immer hundertprozentig genau, insbesondere die in Kameras. Sie sollten daher die Positionen Ihrer Fotos überprüfen.
  • Viele Geräte erfassen die Höhe nicht und setzen sie stattdessen auf 0. Wenn die Höhe für Sie wichtig ist, sollten Sie eine andere Methode zum Erfassen dieser Daten verwenden.
  • Die GPS-Position ist die Position der Kamera, nicht die des Motivs des Fotos. Daher wird in diesem Beispiel das Kameraelement an der GPS-Position und das eigentliche Foto abseits dieser Position platziert.
  • In EXIF-Daten werden keine Informationen zur Ausrichtung der Kamera erfasst. Möglicherweise müssen Sie PhotoOverlays daher anpassen. Die gute Nachricht ist, dass Sie mit einigen Geräten, z. B. Smartphones mit dem Android-Betriebssystem, Daten wie Kompassrichtung und Neigung direkt erfassen können, nur nicht in den Exif-Headern.

Trotzdem ist es eine gute Möglichkeit, Ihre Fotos zu visualisieren. Wir hoffen, dass Fotos in naher Zukunft immer genauer geografisch getaggt werden.

So geht es weiter

Nachdem Sie mit der Verwendung von EXIF-Headern begonnen haben, können Sie sich die EXIF-Spezifikation ansehen. Dort sind viele weitere Daten gespeichert, die Sie möglicherweise erfassen und in einer Beschreibung einfügen möchten. Sie können auch umfangreichere PhotoOverlays mit ImagePyramids erstellen. Der Artikel im Entwicklerleitfaden zu PhotoOverlays bietet eine gute Übersicht über die Verwendung.