январь 2009 г.
Задача
В этом учебном пособии вы узнаете, как использовать фотографии с геотегами для создания KML PhotoOverlays
. Хотя пример кода написан на Python, многие аналогичные библиотеки существуют и на других языках программирования, так что не должно возникнуть проблем с переводом этого кода на другой язык. Код в этой статье основан на библиотеке Python с открытым исходным кодом EXIF.py.
Вступление
Цифровые камеры — довольно удивительная вещь. Многие пользователи этого не осознают, но они делают больше, чем просто фотографируют и снимают видео. Они также помечают эти видео и фотографии метаданными о камере и ее настройках. За последние несколько лет люди нашли способы добавлять к этой информации географические данные, либо встроенные производителями камер, например, некоторые камеры Ricoh и Nikon, либо с помощью таких устройств, как регистраторы GPS и EyeFi Explore . Телефоны с камерой, такие как iPhone, и телефоны с операционной системой Android, такие как G1 от T-Mobile, встраивают эти данные автоматически. Некоторые сайты для загрузки фотографий, такие как Panoramio , Веб-альбомы Picasa и 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 Планета Земля. Вот пример файла 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
есть хороший обзор их использования.