远-方的博客

将地理标记的照片转换为 KML PhotoOverlay

Mano Marks,Google Geo API 小组
2009 年 1 月

目标

本教程将会介绍如何使用地理标记的照片创建 KML PhotoOverlays。尽管本教程中的代码示例都是用 Python 编写的,但其他编程语言中也存在许多类似的库,因此将此代码转化成其他语言并不难。本文章中的代码需要一个开源 Python 库 EXIF.py

简介

数码相机真是一样令人啧啧称奇的东西。许多用户并未意识到这一点,但是他们用数码相机做的事远非拍照和摄像这么简单。他们还使用有关相机及其设置的元数据标记这些视频和照片。近几年,人们已经研究出了向该信息中添加地理数据的多种方式:由相机制造商嵌入(例如某些 Ricoh 和 Nikon 相机),或者通过 GPS 记录器和 EyeFi Explore 等设备。拍照手机(如 iPhone)和使用 Android 操作系统的手机(如 T-Mobile 的 G1)可自动嵌入该信息。某些照片上传网站(例如 PanoramioPicasa 网络相册Flickr)会自动解析出 GPS 数据并使用该数据对照片进行地理标记。然后,您可以在供稿中找回该数据。但是这样目的何在呢?本文章就介绍了您如何自行获取该数据。

Exif 标头

将数据嵌入图片文件的最常用方法是使用可交换图片文件格式 (EXIF)。数据会按照标准方式以二进制形式存储在 EXIF 标头中。如果您了解 EXIF 标头的规范,就可以自行解解析出这些数据。幸运的是,已经有人代您完成了这项艰难的工作并编写了一个 Python 模块。EXIF.py 开源库就是读取 JPEG 文件标头的优良工具。

代码

本文章的示例代码在以下文件中:exif2kml.py。如果您直接使用它,请下载该模块和 EXIF.py,并将它们放在同一目录中。运行 python exif2kml.py foo.jpg 以将“foo.jpg”替换为地理标记的照片的路径。这会生成名为 test.kml 的文件。

解析 Exif 标头

EXIF.py 提供了一个解析 Exif 标头的简单接口。只需运行 process_file() 函数,它就会将标头作为 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

有了 Exif 标头之后,您需要提取 GPS 坐标。EXIF.py 将这些坐标视为 Ratio 对象(存储值的分子和分母)这样可设置精确的比例而不依靠浮点数。但是,KML 需要的是数字而不是比例。因此您需要提取每个坐标,并将分子和分母转换为十进制度数的单个浮点数:

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/degree_den)
  minute
= float(minute_num/minute_den)/60
  second
= float(second_num/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

获得坐标之后,您就可以轻松地为每张照片创建简单的 PhotoOverlay

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)

您可以发现,我们只使用标准的 W3C DOM 方法,因为这些方法在多数编程语言中都可以使用。要了解整个示例是如何关联在一起的,请在此处下载这段代码。

本例并未利用 PhotoOverlays 的全部功能,通过 PhotoOverlay,您还可以对高分辨率的照片进行进一步的研究。但是,本例确实演示了如何在 Google 地球或 Google 地球 API 中以布告板的样式悬挂照片。下面是使用此代码创建的 KML 文件的示例:

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

这是它在 Google 地球中的样子:


警告

对照片进行地理标记尚处在初级阶段。

下面一些注意事项:

  • GPS 设备并不总是完全精确的,尤其对于安装在相机中的设备,因此,请检查照片的位置。
  • 许多设备不跟踪海拔高度,而会把它设置为 0。如果您非常需要了解海拔高度,请寻找其他方法获得该数据。
  • GPS 定位是对相机的定位,而不是对照片本身的定位。这就是本例会在 GPS 定位中定位 Camera 元素的原因,实际的照片距离该位置很远。
  • Exif 不会获取有关您的相机指向的方向的信息,因此您可能需要调整您的 PhotoOverlays。还好,有些设备(例如基于 Android 操作系统构建的手机)可让您直接获得诸如罗盘方向和倾斜度等数据,只是这些数据不在 Exif 标头中。

综上所述,这种方式仍算得上一种强大的照片显示方式。希望在不久的将来,我们能对照片进行越来越精确的地理标记。

跟进工作

开始使用 EXIF 标头后,您可以研究一下 EXIF 规范。这些规范存储了大量的其他数据,您可能很想获得这些数据,并将它们写入说明气泡框。您还可以考虑使用 ImagePyramids 创建功能更多的 PhotoOverlays。有关 PhotoOverlays 的开发人员指南文章详细介绍了如何使用这些功能。

posted on 2009-12-27 22:53  远-方  阅读(2243)  评论(0编辑  收藏  举报

导航