Cómo convertir fotos geoetiquetadas a superposiciones de fotos KML

Mano Marks, equipo de API de Google Geo
Enero de 2009

Objetivo

En este instructivo, aprenderás a usar fotos con etiquetas geográficas para crear PhotoOverlays en KML. Si bien el código de muestra está escrito en Python, muchas bibliotecas similares existen en otros lenguajes de programación, por lo que no debería ser un problema traducir este código a otro lenguaje. El código de este artículo se basa en una biblioteca de código abierto de Python, EXIF.py.

Introducción

Las cámaras digitales son cosas increíbles. Muchos usuarios no se dan cuenta, pero hacen más que tomar fotografías y grabar videos. También etiquetan esos videos y fotos con metadatos sobre la cámara y su configuración. En los últimos años, las personas han encontrado formas de agregar datos geográficos a esa información, ya sea mediante los fabricantes de cámaras incorporados, como algunas cámaras Ricoh y Nikon, o mediante dispositivos como los registradores de GPS y la exploración EyeFi. Los teléfonos con cámara (como los de iPhone) y el sistema operativo Android (como el G1 de T-Mobile) incorporan esos datos automáticamente Algunos sitios para cargar fotos, como Panoramio, Álbumes web de Picasa y Flickr, analizarán automáticamente los datos GPS y los utilizarán para agregar una etiqueta geográfica a una foto. Luego, puede recuperar esos datos en los feeds. ¿Dónde es divertido? En este artículo, se explica cómo acceder a esos datos por tu cuenta.

Encabezados de Exif

La forma más común de incorporar datos en un archivo de imagen es usar el formato de archivo de imagen intercambiable o EXIF. Los datos se almacenan de forma binaria en los encabezados EXIF de manera estándar. Si conoces la especificación de los encabezados EXIF, puedes analizarlos tú mismo. Afortunadamente, alguien ya está trabajando duro y escribió un módulo de Python para ti. La biblioteca de código abierto EXIF.py es una excelente herramienta para leer los encabezados de un archivo JPEG.

El código

El código de muestra para este artículo está en este archivo: exif2kml.py. Si desea ir directamente a usarla, descargue el módulo, así como EXIF.py, y colóquelo en el mismo directorio. Ejecuta python exif2kml.py foo.jpg y reemplaza foo.jpg con la ruta de acceso a una foto etiquetada geográficamente. Produce un archivo llamado test.kml.

Cómo analizar los encabezados EXIF

EXIF.py proporciona una interfaz sencilla para extraer los encabezados EXIF. Solo ejecuta la función process_file(), y se mostrarán los encabezados como un objeto dict.

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

Una vez que tengas los encabezados Exif, deberás extraer las coordenadas de GPS. EXIF.py los trata como objetos Ratio, objetos para almacenar el numerador y el denominador de los valores. Esto establece una proporción precisa en lugar de depender de un número de punto flotante. Pero KML espera números decimales, no proporciones. Por lo tanto, debes extraer cada una de las coordenadas y convertir el numerador y el denominador en un único número de punto flotante para grados decimales:

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

Una vez que tengas las coordenadas, es fácil crear un PhotoOverlay simple para cada foto:

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)

Puede ver que solo estamos usando métodos W3C DOM estándares, ya que están disponibles en la mayoría de los lenguajes de programación. Para ver cómo funciona todo en conjunto, descarga el código aquí.

Esta muestra no aprovecha la potencia total de PhotoOverlays, lo que te permite crear exploraciones profundas de fotos de alta resolución. Sin embargo, sí demuestra cómo colgar una foto con un estilo de cartel en Google Earth. Aquí te mostramos un ejemplo de un archivo KML que se creó con este código:

<?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>

Así se ve en Google Earth:


Advertencias

El geoetiquetado de fotos aún está en etapa de desarrollo.

A continuación, le indicamos algunos aspectos que debe tener en cuenta:

  • Los dispositivos GPS no siempre son 100% precisos, en especial, aquellos que vienen en cámaras, por lo que debes comprobar las posiciones de tus fotos.
  • Muchos dispositivos no registran la altitud; en cambio, la estableces en 0. Si la altitud es importante para ti, debes buscar otra manera de captar esos datos.
  • La posición de GPS es la posición de la cámara, no del sujeto de la foto. Es por eso que este ejemplo coloca el elemento de la cámara en la posición del GPS, y la foto real se aleja de esa posición.
  • Exif no captura información sobre la dirección a la que apunta tu cámara, por lo que es posible que debas ajustar tu PhotoOverlays debido a eso. La buena noticia es que algunos dispositivos, como los teléfonos integrados en el sistema operativo Android, te permiten capturar datos como la inclinación y la dirección de la brújula directamente, solo que no en los encabezados Exif.

Dicho esto, sigue siendo una forma eficaz de visualizar tus fotos. Esperamos que pronto tengamos una ubicación geográfica cada vez más precisa de las fotos.

Dónde ir desde aquí

Ahora que comenzaste a usar encabezados EXIF, puedes explorar las especificaciones EXIF. Hay muchos otros datos que se almacenan allí y te puede interesar capturarlos, colocarlos en un globo descriptivo. También puedes crear PhotoOverlays más completos con ImagePyramids. En la guía para desarrolladores sobre PhotoOverlays se ofrece una buena descripción general de cómo se usan.