Январь 2009 г.
Цель
В этом руководстве вы узнаете, как использовать геотегированные фотографии для создания KML PhotoOverlays . Хотя пример кода написан на Python, существует множество аналогичных библиотек на других языках программирования, поэтому перевести этот код на другой язык не составит труда. В этой статье используется библиотека Python с открытым исходным кодом EXIF.py.
Введение
Цифровые камеры — удивительные вещи. Многие пользователи этого не осознают, но они делают больше, чем просто снимают фото и видео. Они также добавляют к своим видео и фотографиям метаданные о камере и ее настройках. За последние несколько лет люди нашли способы добавлять к этой информации географические данные, либо встроенные производителями камер, например, в некоторые камеры Ricoh и Nikon, либо через такие устройства, как GPS-логгеры и EyeFi Explore . Камерофоны, такие как iPhone, и телефоны на базе операционной системы Android, например, G1 от T-Mobile, автоматически встраивают эти данные. Некоторые сайты для загрузки фотографий, такие как Panoramio , Picasa Web Albums и Flickr , автоматически анализируют данные GPS и используют их для геотегирования фотографий. Затем вы можете получить эти данные обратно в ленты новостей. Но где в этом веселье? В этой статье рассматривается, как самостоятельно получить эти данные.
Заголовки Exif
Самый распространённый способ внедрения данных в файл изображения — использование формата Exchangeable Image File Format (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)/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 Получив координаты, можно легко создать простое 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 , которые позволяют создавать глубокие исследования фотографий высокого разрешения. Но он демонстрирует, как разместить фотографию в стиле рекламного щита поверх Google Earth. Вот пример 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 Earth:

Меры предосторожности
Геотеггинг фотографий пока еще находится на начальной стадии развития.
Вот несколько вещей, о которых следует знать:
- GPS-устройства не всегда на 100% точны, особенно те, которые установлены в камерах, поэтому вам следует проверять положение ваших фотографий.
- Многие устройства не отслеживают высоту, устанавливая ее на 0. Если для вас важна высота, вам следует найти другой способ сбора этих данных.
- Положение GPS — это положение камеры, а не объекта съёмки. Именно поэтому в этом примере элемент «Камера» располагается в позиции GPS, а сама фотография — вдали от неё.
- Exif не фиксирует информацию о направлении, в котором направлена камера, поэтому вам может потребоваться настроить
PhotoOverlays. Хорошая новость заключается в том, что некоторые устройства, например, телефоны на базе операционной системы Android, позволяют напрямую регистрировать такие данные, как направление по компасу и наклон, а не в заголовках Exif.
Тем не менее, это всё ещё эффективный способ визуализации фотографий. Надеюсь, в ближайшем будущем мы увидим всё более точную геотегизацию фотографий.
Куда идти дальше
Теперь, когда вы начали использовать заголовки EXIF, изучите спецификацию EXIF . Там хранится множество других данных, и вам может быть интересно их записать и поместить в контекстное описание. Вы также можете рассмотреть возможность создания более насыщенных PhotoOverlays с помощью ImagePyramids . В статье Руководства разработчика, посвящённой PhotoOverlays представлен хороший обзор их использования.