О POI
2020-03-28
POI — point of interest. Интересное место. Это такие точки на карте, которые обозначают местоположение чего-нибудь интересного. Кинотеатры, отели, магазины, общественные туалеты... Много чего может быть интересно в разные моменты времени.
Именно POI показываются на карте тем самым значком, когда мы что-то на этой карте ищем.
Вот понадобилось на одном проекте искать все интересные места на видимой части карты. Понятно, что для этого нужно использовать какое-нибудь API каких-нибудь карт. Заказчики денег не жалеют и уже используют Google. Посмотрим, что он предлагает...
У Google обнаруживается Places API. Вроде, всё в порядке. Чтобы найти места в определённом радиусе от определённой точки, можно сделать Nearby Search request. Удобнее, конечно, ограничивать площадь поиска по bounding box, но и окружность сойдёт.
$ http GET https://maps.googleapis.com/maps/api/place/nearbysearch/json \
key==API_KEY location==54.9842888,73.3631788 radius==500
HTTP/1.1 200 OK
Content-Encoding: gzip
Content-Length: 6299
Content-Type: application/json; charset=UTF-8
Date: Sat, 28 Mar 2020 06:04:54 GMT
Expires: Sat, 28 Mar 2020 06:09:54 GMT
Server: scaffolding on HTTPServer2
{
"next_page_token": "NEXT_PAGE_TOKEN",
"results": [
{
"geometry": {
"location": {
"lat": 54.9913545,
"lng": 73.3645204
},
"viewport": {
"northeast": {
"lat": 55.141854,
"lng": 73.61482389999999
},
"southwest": {
"lat": 54.8298979,
"lng": 73.097454
}
}
},
"icon": "https://maps.gstatic.com/mapfiles/place_api/icons/geocode-71.png",
"id": "2533ef1675b72d86b7646cf51d20fd8ef74d0bca",
"name": "Omsk",
"photos": [
{
"height": 2861,
"html_attributions": [
"<a href=\"https://maps.google.com/maps/contrib/101400785569019631853\">Fennec Elisabeth</a>"
],
"photo_reference": "CmRaAAAA9y9lzKD-infmtztK1LdE5dimA-NJjpXX1TgHetgncFWclhVQfwWjHi5XjVah_TntrV2hPgYZMhReW_rBE-i2gkBulP_MpF6q4KUQu-NS2-pUh_cyGn16S56J7kzIhQ-7EhAoeK6ek6Ble2W2_YkJOVP8GhS9uDOoxe7lq0u5Oo8itSWT-q4lKA",
"width": 4288
}
],
"place_id": "ChIJCwkB9uL9qkMRGpumYTjD714",
"reference": "ChIJCwkB9uL9qkMRGpumYTjD714",
"scope": "GOOGLE",
"types": [
"locality",
"political"
],
"vicinity": "Omsk"
},
...
Возвращается очень много данных. Координаты, название, типы — это то, чего мне более чем достаточно. Но ещё есть иконка и что-то для получения фоточек. Если порыть глубже, то обнаруживаются какие-то рейтинги и даже флажок, который показывает, открыто ли заведение прямо сейчас.
Все типы мест перечислены в документации. Вполне достойный набор.
Если нужно найти интересные места по некоторому текстовому запросу,
можно воспользоваться Text Search request.
Примерно то же самое,
только ещё нужно передать query
.
Выхлоп тоже точно такой же.
$ http GET https://maps.googleapis.com/maps/api/place/textsearch/json \
key==API_KEY query==магазин location==54.9842888,73.3631788 radius==500
Какие есть ограничения?
Оба запроса возвращают не более 20 результатов на странице.
Следующую страницу можно заполучить, передав next_page_token
из предыдущего ответа
в виде параметра pagetoken
.
$ http GET https://maps.googleapis.com/maps/api/place/textsearch/json \
key==API_KEY pagetoken==NEXT_PAGE_TOKEN
Всего не более трёх страниц. То есть не более 60 интересных точек. В принципе, если нет необходимости найти обязательно все кинотеатры в округе, а лишь показать, что тут есть самого важного поблизости, вполне достаточно. Больше маркеров на карте всё равно будут сливаться.
Максимальный радиус поиска — 50 километров. В такой круг поместится три-четыре Омска. Имхо, более чем достаточно. На бо́льших территориях меньше смысла искать какие-то конкретные места. Там, скорее, нужно искать, где находятся признаки цивилизации вообще. К тому же, интересных точек на таких территориях потенциально сильно много будет. Посмотрите поведение самих Google Maps. Поиск интересных мест, как правило, всё равно приблизит центр карты и будет искать уже там.
Сколько это стоит?
О. Вот это интересно. Есть плата за каждый запрос. Тысяча Nearby или Text Search запросов стоит $32. Но ещё есть плата за возвращаемые данные. Есть Basic Data: название, местоположение, адрес, иконка, только то, что мне надо — это бесплатно. Есть Contact Data: телефоны, сайт, часы работы — $3 за тысячу запросов. Есть Atmosphere Data: уровень цен, рейтинги и отзывы — $5 за тысячу запросов.
В некоторых запросах, например в Find Place, можно указать, какие данные возвращать. И уложиться в Basic Data для экономии. Find Place хорош, он даже умеет искать не в радиусе от точки, а в прямоугольнике. Но вот только он возвращает ровно одно интересное место. А мне надо много.
А вот в Nearby и Text Search нельзя ничего выбрать. И придётся платить и за Contact Data, и за Atmosphere Data, даже если они вам не нужны. То есть $40 за тысячу запросов. Это что, жадность Google, дополнительно $8 содрать?
Уже неприятно, да? А теперь — ягодки.
Читаем правила пользования Google Maps. Пункт 3.2.4. No Scraping, No Caching, No Creating Content From Google Maps Content, No Re-Creating Google Products or Features, No Use With Non-Google Maps... Вы не можете как-либо анализировать полученные данные. Вы не можете закэшировать полученные результаты. Вы не можете показывать эти данные на других картах. Вы ничего не можете, кроме как сделать запрос и выдать результат как он есть.
Ну как минимум закэшировать же хотелось. Чтобы ускорить и сэкономить. Но нет. Низя.
Альтернатива? OpenStreetMap, конечно же.
В OpenStreetMap нет явного понятия POI.
Для моих целей наиболее подходят узлы
(nodes, то есть одиночные точки на карте),
у которых указан тег amenity
(буквально «удобство»).
Также имеет смысл выбирать только узлы с непустым тегом name
.
По этим условиям можно сделать запрос в Overpass API. Эти API в интернетах есть, публичные и бесплатные.
$ http -f POST https://lz4.overpass-api.de/api/interpreter data='
[out:json][timeout:25];
// gather results
(
// node with any amenity and non-empty name in the bounding box
node["amenity"]["name"](54.97537237245619,73.35438251495361,54.99824743822034,73.38129043579102);
);
// print results
out body;
'
HTTP/1.1 200 OK
Content-Encoding: gzip
Content-Length: 10283
Content-Type: application/json
Date: Sat, 28 Mar 2020 07:42:06 GMT
Server: Apache/2.4.18 (Ubuntu)
{
"elements": [
{
"id": 946451869,
"lat": 54.9932212,
"lon": 73.3724576,
"tags": {
"amenity": "cafe",
"name": "Компот"
},
"type": "node"
},
{
"id": 976571444,
"lat": 54.9754806,
"lon": 73.3751452,
"tags": {
"amenity": "restaurant",
"name": "Сенкевич"
},
"type": "node"
},
{
"id": 988613614,
"lat": 54.9981718,
"lon": 73.3556977,
"tags": {
"alt_name": "Kentucky Fried Chicken",
"amenity": "fast_food",
"brand": "KFC",
"brand:wikidata": "Q524757",
"brand:wikipedia": "en:KFC",
"cuisine": "chicken",
"int_name": "KFC",
"name": "KFC",
"name:de": "KFC",
"name:en": "KFC",
"old_name": "Ростик’с-KFC / Ростикс",
"opening_hours": "Mo-Su 08:00-24:00",
"takeaway": "yes",
"website": "http://www.kfc.ru/"
},
"type": "node"
},
...
Рейтингов и фоточек, конечно, нет. Зато есть координаты, имя и тип. Вполне достаточно.
Для Overpass API есть и интерактивные онлайн редакторы. Чтобы потренироваться и отладить запрос.
Можно поискать и определённое amenity
,
где имя содержит определённую подстроку.
$ http -f POST https://lz4.overpass-api.de/api/interpreter data='
[out:json][timeout:25];
(
node["amenity"="cafe"]["name"~"питер",i](54.97537237245619,73.35438251495361,54.99824743822034,73.38129043579102);
);
out body;
'
HTTP/1.1 200 OK
Content-Encoding: gzip
Content-Length: 397
Content-Type: application/json
Date: Sat, 28 Mar 2020 07:58:31 GMT
Server: Apache/2.4.18 (Ubuntu)
{
"elements": [
{
"id": 4349641418,
"lat": 54.9851388,
"lon": 73.3743819,
"tags": {
"amenity": "cafe",
"cocktails": "yes",
"name": "Питер@Pan",
"opening_hours": "Mo-Th, Su 11:00-01:00; Fr, Sa 11:00-03:00",
"outdoor_seating": "terrace"
},
"type": "node"
}
],
"generator": "Overpass API 0.7.56.2 b688b00f",
"osm3s": {
"copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.",
"timestamp_osm_base": "2020-03-28T07:58:02Z"
},
"version": 0.6
}
Прелесть OpenStreetMap в том, что можно поднять свой Overpass API. Или можно просто скачать planet.osm, ну точнее интересующие кусочки земной поверхности, самостоятельно пропарсить и проиндексировать, а потом обновлять индекс по регулярным диффам. И будет локальная база POI, такая быстрая, как надо.
P.S. Команда http
— это HTTPie. Это как curl, но удобнее для ручного тыкания API.
P.P.S. Спасибо Артёму за внимание к пользовательским соглашениям.