Como converter fotos com geotagging em sobreposições de fotos KML

Mano Marks, equipe de APIs do Google Geo
Janeiro de 2009

Objetivo

Neste tutorial, ensinamos a usar fotos com geotagging para criar KML PhotoOverlays. Embora o código de exemplo seja escrito em Python, existem muitas bibliotecas semelhantes em outras linguagens de programação. Portanto, não deve ser um problema traduzir esse código para outra linguagem. O código neste artigo depende de uma biblioteca Python de código aberto, a EXIF.py (em inglês).

Introdução

As câmeras digitais são incríveis. Muitos usuários não percebem, mas fazem mais do que apenas tirar fotos e gravar vídeos. Eles também marcam esses vídeos e fotos com metadados sobre a câmera e as configurações dela. Nos últimos anos, as pessoas encontraram maneiras de adicionar dados geográficos a essas informações, seja incorporados pelos fabricantes de câmeras, como algumas câmeras Ricoh e Nikon, ou por dispositivos como registradores de GPS e o EyeFi Explore. Smartphones com câmera, como o iPhone e os que usam o sistema operacional Android, como o G1 da T-Mobile, incorporam esses dados automaticamente. Alguns sites de upload de fotos, como Panoramio, Álbuns da web do Picasa e Flickr, analisam automaticamente os dados de GPS e os usam para geotagging de uma foto. Depois, você pode recuperar esses dados nos feeds. Mas isso não tem graça! Neste artigo, explicamos como acessar esses dados.

Cabeçalhos Exif

A maneira mais comum de incorporar dados em um arquivo de imagem é usando o Formato de arquivo de imagem intercambiável, ou EXIF. Os dados são armazenados de forma binária nos cabeçalhos EXIF de maneira padrão. Se você conhece a especificação dos cabeçalhos EXIF, pode analisá-los por conta própria. Felizmente, alguém já fez o trabalho pesado e escreveu um módulo do Python para você. A biblioteca de código aberto EXIF.py é uma ótima ferramenta para ler os cabeçalhos de arquivos JPEG.

O Código

O exemplo de código deste artigo está no arquivo exif2kml.py. Se quiser pular direto para o uso, faça o download desse módulo e de EXIF.py e coloque os dois no mesmo diretório. Execute python exif2kml.py foo.jpg, substituindo foo.jpg pelo caminho de uma foto com geotag. Isso vai gerar um arquivo chamado test.kml.

Analisar os cabeçalhos Exif

O EXIF.py oferece uma interface fácil para extrair os cabeçalhos Exif. Basta executar a função process_file(), que vai retornar os cabeçalhos como um 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

Depois de ter os cabeçalhos Exif, é necessário extrair as coordenadas de GPS. O EXIF.py os trata como objetos Ratio, objetos para armazenar o numerador e o denominador dos valores. Isso estabelece uma proporção precisa em vez de depender de um número de ponto flutuante. No entanto, o KML espera números decimais, não proporções. Então, extraia cada uma das coordenadas e converta o numerador e o denominador em um único número de ponto flutuante para graus decimais:

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

Depois de ter as coordenadas, é fácil criar um PhotoOverlay simples 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)

Como você pode ver, estamos usando apenas métodos W3C DOM padrão, porque são os disponíveis na maioria das linguagens de programação. Para entender como tudo funciona, faça o download do código aqui.

Esta amostra não aproveita todo o poder do PhotoOverlays, que permite criar análises detalhadas de fotos de alta resolução. Mas ele mostra como pendurar uma foto em estilo de outdoor no Google Earth. Confira um exemplo de arquivo KML criado com esse 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>

Veja como ele aparece no Google Earth:


Advertências

A geotagging de fotos ainda está no início.

Confira algumas informações importantes:

  • Os dispositivos GPS nem sempre são 100% precisos, principalmente os que vêm em câmeras. Por isso, verifique as posições das suas fotos.
  • Muitos dispositivos não monitoram a altitude, definindo-a como 0. Se a altitude for importante para você, encontre outra maneira de capturar esses dados.
  • A posição do GPS é a da câmera, não do objeto da foto. Por isso, esta amostra posiciona o elemento de câmera na posição do GPS e a foto real longe dessa posição.
  • O Exif não captura informações sobre a direção em que a câmera está apontando. Por isso, talvez seja necessário ajustar o PhotoOverlays. A boa notícia é que alguns dispositivos, como smartphones criados no sistema operacional Android, permitem capturar dados como direção da bússola e inclinação diretamente, mas não nos cabeçalhos Exif.

Mesmo assim, essa é uma maneira eficiente de visualizar suas fotos. Esperamos ver mais e mais geotags precisas em fotos no futuro próximo.

O que fazer depois disso

Agora que você começou a usar cabeçalhos EXIF, talvez queira conhecer a especificação EXIF (link em inglês). Há muitos outros dados armazenados lá, e talvez você tenha interesse em capturá-los e colocá-los em um balão de descrição. Você também pode criar PhotoOverlays mais avançados usando ImagePyramids. O artigo do guia do desenvolvedor sobre PhotoOverlays tem uma boa visão geral do uso deles.