Журнал LinuxFormat - перейти на главную

LXF126:Python

Материал из Linuxformat
Перейти к: навигация, поиск
Python Используйте сведения от Flickr для создания карт с геоданными

Содержание

Python: Место на карте

Ник Вейч вновь зачертил, но на сей раз идея хороша: найти самые фотогеничные участки вашей местности при помощи карт с геоданными…

Верите вы или нет, но у меня есть подруга. Ей-Богу. Это реальный человек. Зовут ее Пэм, а живет она в Тампа, Флорида. В прошлом году она собиралась в Барселону и спросила, что там стоит фотографировать. А ведь это зависит от вкуса, верно? Кто-то хочет заснять обычные туристические объекты, как все, а иной предпочитает исследовать менее «затертые» части города. Вы можете прочесать сайты с фотографиями, но на самом деле вам нужна карта.

Множество фотоаппаратов и мобильников с камерами теперь внедряют в свои изображения геотэги – указывая широту и долготу – наряду с добавлением других Exif-данных. Сайты с фотографиями, подобные Flickr, записывают эту информацию и делают ее доступной. Так нельзя ли создать карту типа «где в этом городе люди делают снимки»?

WOE — это я

В 2008 году Yahoo открыла для разработчиков свою службу геолокации. Одной из ключевых концепций была иерархия уникальных ID для мест или областей этой планеты. Например, один ID идентифицирует такую вещь, как Эйфелева башня. Он будет связан с другими ID, таким как квартал, район, город, область, страна и т. д. Эти числовые ID получили название Where On Earth [Где на Земле] ID, или WOEID, и теперь широко используются в других геолокационных службах, включая Flickr. Более подробно о WOEID можно прочитать на http://developer.yahoo.com/geo/geoplanet/guide/concepts.html, но сейчас вам важно знать, что каждый город имеет WOEID, а многие еще и разбиты на районы, также имеющие свои WOEID.

Итак, как узнать WOEID какой-либо местности? Yahoo требует, чтобы вы зарегистрировались и получили ключ API, чтобы воспользоваться ее службами и найти ответ на данный вопрос. Однако, как мы говорили, Flickr также поддерживает WOEID, и, имея доступ к его «песочнице», мы сможем сделать тестовый запрос и получить WOEID интересующего нас города. Перейдите на http://www.flickr.com/services/api/explore/?method=flickr.places.find и введите ‘Barcelona’. Из полученных данных станет ясно, что WOEID для Барселоны – 753692.

API Flickr также включают другой полезный метод с более длинным именем places.getChildrenWithPhotosPublic(). Он возвращает структурированный список данных, представляющий собой потомков той области, что вы указали. В случае города, потомками будут районы и/или, в некоторых случаях, почтовые индексы региона. Каждый из них снабжается долготой и широтой центра области и количеством общедоступных фотографий в ней.

Чтобы использовать для запросов API Flickr, необходимо иметь учетную запись на Flickr и обратиться за ключом API – см. врезку. Кроме того, раздобудьте модуль API Flickr для Python, что выливается просто в загрузку пакета для вашего дистрибутива или посещение http://stuvel.eu/projects/flickrapi.

Заполучив эти компоненты, вы сможете увидеть, как работает ваш сборщик WOEID-изображений.

 >>>import flickrapi
 >>>apikey=’bxxxxxxxxxx2’
 >>>woeid=753692
 >>>flickr = flickrapi.FlickrAPI(apikey)
 >>>request=flickr.places_getChildrenWithPhotosPublic(woe_id=woeid)
 >>>list=request[0].getchildren()
 >>>for item in list:
 ... if item.attrib[‘place_type_id’] == ‘22’:
 ... print item.attrib[‘latitude’], item.attrib[‘longitude’]
 ... print item.text
 ...
 41.380 2.176
 Ciutat Vella, Barcelona, Catalonia, ES, Spain
 41.393 2.161
 L’example, Barcelona, Catalonia, ES, Spain
 41.383 2.183
 Barceloneta, Barcelona, Catalonia, ES, Spain
 41.386 2.177
 Ribera, Barcelona, Catalonia, ES, Spain
 41.406 2.179
 La Dreta De L’eixample, Barcelona, Catalonia, ES, Spain
 41.379 2.167
 ...

Мы вызвали метод getChildrenWithPublicPhotos, который вернул результат. При тестировании его на сайте Flickr мы видели, что он возвращает объект с одним элементом, по имени places, содержащим список мест, соответствующих запросу. Чтобы получить сам список, раскроем объект при помощи метода .getChildren и проверим, пройдя по списку, что в полученных данных place_type означает соседство, тип 22. Причина тут в том, что некоторые области имеют перекрывающиеся объекты – например, области с почтовым кодом или районы, и некоторые снимки могут быть посчитаны дважды, исказив результат.

Метод .attrib() возвращает именованные значения (по ключу) из объектов отдельных мест и текст-описание, хранящийся в свойстве .text. Среди других значений – photo_count и WOEID района. Если у нас имеются значения известных точек, можно отобразить их на диаграмме и показать наиболее фотографируемые области, не так ли? Теперь осталось приискать способ создания привлекательной графики из имеющихся данных.

Строим диаграмму

Если нужно построить контуры, гистограмму или график по точкам, каждый уважающий себя питонист первым делом обращается к модулю Mathplotlib. Как и его проприетарный кузен Matlab, Mathplotlib – это библиотека функций для преобразования данных в нечто приятное глазу. И опять-таки этот модуль Python обычно имеется в вашем дистрибутиве. Можно также установить модуль Numpy: он добавляет в Python несколько удобных возможностей и функций для визуализации данных, включая многомерные массивы.

Matplotlib содержит полезную функцию с именем contourf, которая просто берет список значений x, y и z и строит на их базе контурные цветные области вокруг точек с максимальным значением – по сути, это карта температур. Но прежде чем передать данные, следует устранить проблему: функция contourf работает только с данными в виде равномерной сетки, а наши точки и значения распределены случайным образом. Решение – создать пустую сетку данных, а затем наложить на нее наши значения при помощи какого-либо метода интерполяции. Вот почему модуль Numpy вам очень пригодится.

Метод numpy.linspace() возвращает список, заполненный набором числовых значений из заданного диапазона – а это нам и нужно для создания сетки x–y. Затем мы сможем воспользоваться функцией griddata из Matplotlib для интерполяции наших точек в нормальную сетку. Тогда наш код будет выглядеть так:

 for item in list:
   if item.attrib[‘place_type_id’] == ‘22’:
      y.append(float(item.attrib[‘latitude’]))
      x.append(float(item.attrib[‘longitude’]))
      z.append(int(item.attrib[‘photo_count’]))
 xmin=min(x)
 xmax=max(x)
 ymin=min(y)
 ymax=max(y)
 x += xmin, xmin, xmax, xmax
 y += ymin, ymax, ymin, ymax
 z += 0, 0, 0, 0
 yrange=ymax­ymin
 xrange=xmax­xmin
 maxrange=max(xrange, yrange)
 stepsize=maxrange/500
 # создаем пустую сетку
 xi = numpy.linspace(xmin,xmax,int(xrange/stepsize))
 yi = numpy.linspace(ymin,ymax,int(yrange/stepsize))
 # вносим данные в сетку
 zi = griddata(x,y,z,xi,yi)

Здесь мы позаботились и о других двух моментах. Получение максимальных и минимальных значений для x и y дает нам границы прямоугольной области. Мы можем добавить эти точки (и нулевые значения) в наш массив данных, чтобы убедиться, что построенные контуры доходят до края карты. Кроме того, мы можем использовать эти данные для расчета шага нашей сетки. Не все города квадратные, поэтому мы изменяем размер шага в соответствии с занимаемой ими площадью.


Осталось только воспользоваться функцией contourf для построения градиента вокруг областей и отобразить их (и сохранить в виде файла):

 CS = plt.contour(xi,yi,zi,15,linewidths=0.5,colors=’k’)
 CS = plt.contourf(xi,yi,zi,15,cmap=plt.cm.jet)
 plt.colorbar() # рисуем цвет ную полоску
 # отображаем точки данных
 plt.scatter(x,y,marker=’o’,c=’b’,s=5)
 plt.xlim(xmin,xmax)
 plt.ylim(ymin,ymax)
 plt.title(‘Barcelona’)
 plt.savefig(‘barcac.svg)
 plt.show()

Здесь мы сперва построили контурную черную (’k’) линию толщиной 0,5, затем применили метод цветной заливки контуров. Параметр cmap – одна из предопределенных цветовых карт Matplotlib, дающая хороший диапазон от голубого через желтый до красного.

Остается наложить созданное изображение на карту. Можно бы зайти на Google Maps, но их карты, естественно, не ваша, а их собственность, поэтому лучше взять картографические данные от OpenStreetMap.

Простого метода изъятия PNG из указанного места нет, но вы можете загрузить его вручную, перейдя на вкладку Export на главной странице карты и введя широту и долготу местности, карту которой желаете загрузить. Выберите отрисовку OSM при наибольшем приближении.

Изображение-карта и созданная карта температур могут быть совмещены в GIMP, Scribus или Inkscape. По-моему, в Inkscape это проще всего, потому что можно загрузить SVG-версию контурной карты, установить прозрачность 50 % и растянуть ее над картой улиц. Соотношение сторон не будет таким же, поскольку в нашей контурной карте предполагается, что шаг по x и y одинаков, а на сферической поверхности Земли это не так, но для малых областей искажения минимальны.

Тем не менее, чтобы получить информацию, соответствующую реальности, необходимо растянуть контур до размера карты.

Подождите, это еще не все

Этот подход работает только для большой местности, имеющей достаточно данных о соседях, чтобы в результате получить значимую информацию. Чтобы получить более точный вид или отрисовать области с малым числом соседей или вообще без них, необходимо действовать по-другому. Лобовой метод – загрузить информацию обо всех снимках, сделанных в требуемом районе. Для большого города это неподъемно, но, например, для городка Бат [курортное место, где находятся Башни LXF] вполне в пределах досягаемости.

Основной метод Flickr photos.search() предоставляет механизм для извлечения данных только из некоторых WOEID. Затем информация об изображениях возвращается в виде постраничного списка, и по запросу в эти данные будут включены сведения о географическом положении.

Поскольку Flickr-сервера ворочаются со скрипом, лучше вытащить информацию для последующей обработки в файл – тогда нам не надо будет загружать данные о городе более одного раза. Их предварительный просмотр также даст нам возможность отсеять снимки с неверными геолокационными тэгами (иногда изображения помечаются как обладающие геоданными, но содержат нулевые широту и долготу) или с невысокой точностью.

 import flickrapi
 apikey=’bxxxxxxxx52’ #your key here
 out=open(“bath.csv”,”w”)
 woeid=12056
 geolist=[]
 flickr = flickrapi.FlickrAPI(apikey)
 #request=flickr.interestingness_getList(extras=’geo’,
 per_page=500)
 request = flickr.photos_search(woe_id=woeid, per_page=250)
 foo=request.getchildren()
 pages=int(foo[0].items()[3][1])
 print pages
 for i in range(1, pages+1):
    try:
      request = flickr.photos_search(woe_id=woeid, extras=’geo’,per_page=250, page=i)
      foo=request.getchildren()
      bar=foo[0].getchildren()
      #bar теперь список фотоэлемен тов
      print ‘attempt ‘+str(i)
      for pic in bar:
         if (pic.attrib[‘accuracy’])>14:
            lat=(float(pic.attrib[‘latitude’]))
            lon=(float(pic.attrib[‘longitude’]))
            if (lat, lon)!=(0.0, 0.0):
               out.writelines(str(lat)+’, ‘+str(lon) +’\n’)
      except:
         print ‘skipped ‘ + str(i)
 out.close

Этот скрипт пробегает все страницы результатов поиска данных и сохраняет широту и долготу в простом формате CSV. Мы можем воспользоваться этим файлом для создания более точной карты. Но нужно все-таки сгенерировать сетку данных; наш случай отличается отсутствием оси z. У нас просто есть точки, а значения в них одинаковы. Поэтому, воспользовавшись тем же подходом, что и раньше, мы получим плоское изображение. Для количественной оценки данных их следует сгруппировать.

Мы можем сделать это, создав, как и ранее, пустую сетку, а затем пройти повсем точкам и определить, к какому «квадрату» новой сетки они относятся. Увеличение каждого отдельного значения массива z в случае принадлежности точки квадрату даст нам заполненность каждого из них, а на базе этого можно строить диаграмму.


 import matplotlib.pyplot as plt
 import numpy as np
 import matplotlib.cm as cm
 ####читаем данные
 lats=[]
 lons=[]
 a = open(‘bath.csv’, ‘r’)
 for line in a.readlines():
   lats.append(float(line.split(‘,’)[0]))
   lons.append(float(line.split(‘,’)[1]))
 print ‘Data points: ‘, len(lats)
 xmin=min(lons)
 xmax=max(lons)
 ymin=min(lats)
 ymax=max(lats)
 yrange=ymax­ymin
 xrange=xmax­xmin
 maxrange=max(xrange, yrange)
 stepsize=maxrange/100
 limit =int(len(lats)/20)
 print ‘area mapped:’
 print xmin, xmax
 print ymin, ymax
 #define a new grid
 xi = np.linspace(xmin,xmax,int(xrange/stepsize))
 yi = np.linspace(ymin,ymax,int(yrange/stepsize))
 zi = np.ones((len(yi), len(xi)))
 print ‘xi dimension ‘ +str(len(xi))
 print ‘yi dimension ‘ +str(len(yi))
 print ‘zi dimension ‘+ str(zi.shape)
 for yindex in range(len(lats)):
   #find which box it goes into
   xx=lons[yindex]­xmin
   xx=int(xx/stepsize)­1
   yy=lats[yindex]­ymin
   yy=int(yy/stepsize)­1
   if zi[yy, xx] < limit:
      zi[yy, xx]+=1
 Plot = plt.contourf(xi,yi,zi,15,cmap=plt.cm.jet)
 plt.savefig(‘bath.svg)
 plt.show()

Окончательная доработка

Есть еще кое-что, требующее доработки. Переменная limit была введена в связи с тем, что, скорее всего, немало изображений будут иметь одинаковые координаты. Даже в логарифмической шкале их количество «задавит» остальные части карты. В нашем скрипте limit вычисляется как процент от общего количества изображений в области – если результат не вдохновляет, поиграйте с этим значением, но 20 % вроде бы хорошо работает для большинства мест.

Еще один варьируемый параметр – количество квадратов нашей сетки. За него отвечает строка:

stepsize=maxrange/100

Фактически мы получаем 100 интервалов вдоль более длинной стороны. Другая сторона вычисляется в соответствии с этим значением. Вы можете счесть, что 100 – слишком мало. Увеличение шага сетки повышает гранулярность результата, но также и «шум» изображения, и в конце концов вы получите на экране практически двоичные данные.

Еще о чтении карт

Посмотрите Basemap (http://matplotlib.sourceforge.net/basemap/doc/html/), облегчающую отрисовку элементов в различных проекциях на реальной карте мира.

Документация Flickr API (http://www.flickr.com/services/api) не только информативна, но и предоставляет удобную функцию пробных вызовов API на web-странице.

О WOEID и других полезных сервисах читайте на Yahoo GeoPlanet (http://developer.yahoo.com/geo/geoplanet/guide/).

Matplotlib, http://matplotlib.sourceforge.net, – это модуль Python для построения различных диаграмм.

И, наконец, посетите http://stuvel.eu/projects/flickrapi для лучшего интерфейса Python–Flickr.

Персональные инструменты
купить
подписаться
Яндекс.Метрика