Показаны сообщения с ярлыком osm2pgsql. Показать все сообщения
Показаны сообщения с ярлыком osm2pgsql. Показать все сообщения

воскресенье, 19 августа 2012 г.

Тайловый сервер - что с ним делать?

В прошлых заметках Подготовка карт для генератора тайлов Mapnik, Настройка базы данных для генератора тайлов Mapnik, Установка генератора тайлов Mapnik и Установка renderd и mod_tile - системы отрисовки тайлов по запросу мы настроили обычный тайловый сервер. Однако, от одного лишь просмотра карт толку мало - ведь то же самое без лишних усилий можно увидеть и на официальном сайте OpenStreetMap.

1. Добавление локальной информации

Для того, чтобы на карту можно было вносить локальную информацию, можно настроить локальный веб-сайт OpenStreetMap и подключаясь к нему с помощью редактора JOSM, редактировать имеющуюся информацию. Я пробовал устанавливать Ruby и Rails и мне даже удалось запустить локальный веб-сайт, однако работал он очень медленно, а ускорить его работу мне не удалось - не хватило знаний Ruby on Rails и времени, чтобы в нём разобраться.

Хотя, этот вариант в любом случае кажется мне не самым лучшим. Ведь в таком варианте настройки нельзя будет отделить локальные данные от данных, загруженных из проекта OpenStreetMap. Затрудняется процедура обновления карт OpenStreetMap - ведь нужно оставить локально внесённые данные, обновив при этом всё остальное.

Поэтому я выбрал другой вариант - локальные данные будут храниться в OSM-файле, который можно редактировать с помощью уже знакомого нам редактора JOSM. Локальной информацией может быть, например, информация с расположением торговых точек в случае магазина, с расположением банкоматов в случае банка, платёжных терминалов, телефонов-автоматов, wifi-точек, зон ответственности развозчиков пиццы и т.п.

Этот OSM-файл сразу после редактирования можно импортировать в отдельную базу данных. А для того, чтобы Mapnik отображал информацию из локальной базы данных, нужно добавить в файл стилей /etc/mapnik-osm-data/osm.xml настройки для подключения к новой базе данных и написать стиль отрисовки объектов из неё.

Документацию по написанию файлов стилей можно найти здесь: Mapnik configuration XML.

Вот пример фрагмента файла osm.xml, в котором задаётся стиль отображения некоего зонального деления территорий, берущегося из базы данных zones:
<Style name="zones">
  <Rule>
    &maxscale_zoom0;
    &minscale_zoom10;
  </Rule>

  <Rule>
    &maxscale_zoom9;
    &minscale_zoom11;
    <Filter>not ([name] = '') and [area] = 'yes'</Filter>
    <LineSymbolizer stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="round"/>
  </Rule>

  <Rule>
    &maxscale_zoom12;
    &minscale_zoom13;
    <Filter>not ([name] = '') and [area] = 'yes'</Filter>
    <LineSymbolizer stroke="#000000" stroke-opacity="1" stroke-width="2" stroke-linecap="round"/>
    <TextSymbolizer size="10" allow-overlap="yes" fill="#000000" fontset-name="book-fonts" opacity="1" placement="interior">[name]</TextSymbolizer>
  </Rule>

  <Rule>
    &maxscale_zoom14;
    &minscale_zoom19;
    <Filter>not ([name] = '') and [area] = 'yes'</Filter>
    <LineSymbolizer stroke="#000000" stroke-opacity="1" stroke-width="4" stroke-linecap="round"/>
    <TextSymbolizer size="20" allow-overlap="yes" fill="#000000" fontset-name="book-fonts" opacity="1" placement="interior">[name]</TextSymbolizer>
  </Rule>
</Style>

<Layer name="zones" status="on" srs="&srs900913;">
  <StyleName>zones</StyleName>
  <Datasource>
    <Parameter name="table">(select * from planet_osm_polygon) as zones</Parameter>
    <Parameter name="type">postgis</Parameter>
    <Parameter name="password">password</Parameter>
    <!-- <Parameter name="host">localhost</Parameter> -->
    <Parameter name="user">osm</Parameter>
    <Parameter name="dbname">zones</Parameter>
    <Parameter name="estimate_extent">false</Parameter>
    <Parameter name="extent">-20037508,-19929239,20037508,19929239</Parameter>
  </Datasource>
</Layer>
Кстати, этот фрагмент стиля является не самым оптимальным, но он первым пришёл мне в голову, а кроме того, он хорошо иллюстрирует возможности файла стилей.

Его неоптимальность заключается в том, что во-первых, запрос извлекает из таблицы все поля, вне зависимости от того, нужны ли они для отрисовки карты или нет. В моём случае достаточно оставить поля way и name - их вполне достаточно для отрисовки контура участка и его номера.

Второй момент - запрос написан не оптимально, т.к. извлекает из таблицы все строки, а Mapnik будет рисовать только те объекты, которые удовлетворяют настройкам фильтра. Вместо этого можно дополнить запрос условием WHERE name IS NOT NULL AND name <> '' AND area = 'yes', а из описания стиля удалить все фильтры.

Третий момент - это настройка extent, в которой указаны границы всего мира, хотя, наверняка, локальные данные находятся в каких-то предсказуемых границах. Например, локальные данные в моём случае ограничиваются только Республикой Башкортостан, Республикой Татарстан и Оренбургской областью. Можно однажды выполнить следующий запрос:
SELECT ST_Extent(way)
FROM (SELECT ST_ConvexHull(ST_Extent(way)) AS way
FROM planet_osm_polygon
UNION SELECT ST_ConvexHull(ST_Extent(way)) AS way
FROM planet_osm_point
UNION SELECT ST_ConvexHull(ST_Extent(way)) AS way
FROM planet_osm_line
UNION SELECT ST_ConvexHull(ST_Extent(way)) AS way
FROM planet_osm_roads) AS ways;
и прописать в настройки extent возвращённые значения. Это позволит Mapnik'у не выполнять запросы к базе данных, если заведомо известно, что запрос не вернёт результатов для интересующей его области.

Более подробно о рекомендациях по оптимизации файла стилей Mapnik можно прочитать в статье Optimize Rendering with PostGIS.

2. Использование информации из базы данных

Кроме заливки информации в локальную базу данных, я также пользуюсь и основной базой данных Mapnik, непосредственно залезая в её недра с помощью SQL-запросов. Поэтому мне в её таблицах бывают нужны некоторые атрибуты объектов, которые по умолчанию не импортируются в базу данных утилитой osm2pgsql.

Чтобы указать дополнительные поля, нужно отредактировать файл стиля базы данных /usr/share/osm2pgsql/default.style

Например, я добавил в файл стиля базы данных колонки addr:city и addr:street, которые берутся из одноимённых атрибутов объектов из файла OSM:
node,way         addr:city           text
linearnode,way   addr:street         text  linear
node означает, что этот атрибут может быть назначен точке и должен быть импортирован в таблицу planet_osm_point.

way означает, что этот атрибут может быть назначен контуру (линии, дороге или многоугольнику) и должен быть импортирован в таблицу planet_osm_line, planet_osm_roads или planet_osm_polygon.

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

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

1. ST_AsText - возвращает геометрический объект в формате WKT (Well-known Text), описанный в стандартах OpenGIS.

2. ST_Transform - переводит координаты опорных точек геометрического объекта из одной проекции в указанную.

3. ST_GeomFromText - возвращает геометрический объект по его описанию в формате WKT и (опционально) заданной проекции.

4. ST_IsValid - проверяет правильность объекта - замкнутость многоугольника, отсутствие самопересечений и т.п.

5. ST_PointOnSurface - возвращает точку, находящуюся строго на поверхности объекта (многоугольника или мультиполигона, многоугольника с дырами - геометрического объекта, имеющего один внешний контур и произвольное количество внутренних контуров).

6. ST_ContainsProperly - функция, возвращающая "истину", если второй объект находится строго внутри первого. Достаточно, чтобы хотя-бы одна вершина второго объекта не попала внутрь первого, или попала в дыру первого объекта, чтобы функция вернула "ложь".

7. ST_Extent - агрегатная функция (работает подобно агрегатным функциям COUNT, MIN, MAX, SUM или AVG), возвращает геометрический объект BOX - прямоугольник, охватывающий выбранные геометрические объекты.

Для чего можно использовать эти функции? Приведу несколько примеров, иллюстрирующих, как их использую я.

Например, для того, чтобы удалить из таблицы planet_osm_polygon многоугольники с самопересечениями и просто многоугольники, имеющие какие-то ошибки, можно воспользоваться таким запросом:
DELETE FROM planet_osm_polygon
WHERE NOT ST_IsValid(way);
Или можно вернуть координаты точки на поверхности каждого дома из таблицы planet_osm_polygon в формате WKT в проекции WGS 84:
SELECT ST_AsText(ST_Transform(ST_PointOnSurface(way), 4326))
FROM planet_osm_polygon
WHERE building IS NOT NULL;
Или, например, найти, контур здания по точке внутри него:
SELECT way
FROM planet_osm_polygon
WHERE building IS NOT NULL
  AND ST_ContainsProperly(way, ST_Transform(ST_GeomFromText('POINT(48.2445263783448 55.8405766215408)', 4326), 900913));
Где 48.2445263783448 - долгота, 55.8405766215408 - широта.

Или вычислить прямоугольник, содержащий весь населённый пункт с указанным именем:
SELECT ST_Extent(ST_Transform(way, 4326))
FROM planet_osm_polygon
WHERE place IN ('city', 'town', 'village', 'hamlet')
  AND name = 'Салават';
Естественно, чтобы извлекать значения полей addr:city, addr:street, нужно их сначала добавить в файл стиля базы данных для утилиты osm2pgsql, а затем импортировать данные, что мы уже проделали в предыдущем пункте этой заметки. Правда, не всегда и везде проставляются значения этих полей, потому что для отрисовки карты Mapnik их никак не использует - поверх дома выводится только его номер.

Но некоторые поля можно проставить довольно просто. Например, чтобы проставить поле "addr:city" у всех домов, попадающих в административную границу какого-либо населённого пункта, я пользуюсь скриптом на Perl, часть которого приведена ниже:
# Перебираем населённые пункты, прописываем домам населённый пункт в поле addr:city
sub osm_fill_city()
{
  my $total = 0;
  my $sth_polygon = $dbh_o->prepare("UPDATE planet_osm_polygon
                                     SET \"addr:city\" = ?
                                     WHERE building IS NOT NULL
                                       AND (\"addr:city\" IS NULL OR \"addr:city\" = '')
                                       AND ST_ContainsProperly(ST_GeomFromText(?, 900913), way)");
  my $sth_point = $dbh_o->prepare("UPDATE planet_osm_point
                                   SET \"addr:city\" = ?
                                   WHERE building IS NOT NULL
                                     AND (\"addr:city\" IS NULL OR \"addr:city\" = '')
                                     AND ST_ContainsProperly(ST_GeomFromText(?, 900913), way)");
  my $sth_city = $dbh_o->prepare("SELECT name,
                                         ST_AsText(way)
                                  FROM planet_osm_polygon
                                  WHERE place IN ('city', 'town', 'village', 'hamlet')
                                     AND name IS NOT NULL
                                     AND name <> ''");
  $sth_city->execute();
  while (my ($name, $wkt) = $sth_city->fetchrow_array())
  {
    $sth_polygon->execute($name, $wkt);
    $sth_point->execute($name, $wkt);
    $total++;
    print "Простановка населённых пунктов на зданиях, всего обработано населённых пунктов: $total\r";
  }
  $sth_city->finish();
  $sth_point->finish();
  $sth_polygon->finish();
  print "Простановка населённых пунктов на зданиях, всего обработано населённых пунктов: $total\n";
}
Можно, конечно, не заниматься этим, а взять координаты или контур интересующего нас объекта и с помощью функции ST_ContainsProperly узнать, в административные границы какого населённого пункта этот объект попадает.

3. Геокодинг - поиск географических объектов

Эту информацию без дополнительной обработки можно использовать для обратного геокодинга, то есть для получения адреса здания по географическим координатам точки, попавшей в контур здания:
SELECT "addr:city", "addr:street", "addr:housenumber"
FROM planet_osm_polygon
WHERE building IS NOT NULL
  AND building <> ''
  AND "addr:city" IS NOT NULL
  AND "addr:city" <> ''
  AND "addr:street" IS NOT NULL
  AND "addr:street" <> ''
  AND "addr:housenumber" IS NOT NULL
  AND "addr:housenumber" <> ''
  AND ST_ContainsProperly(way, ST_Transform(ST_GeomFromText('POINT(55.98886 54.74241)', 4326), 900913));
Прямой же геокодинг - нахождение координат дома по адресу - не является столь же тривиальной задачей, как обратный геокодинг. Это так, потому что людей довольно трудно заставить писать адрес всегда одним и тем же образом. Люди используют сокращения слов, переставляют слова местами, пропускают слова, кажущиеся им незначимыми, а подобные знания в "голову" компьютера не заложишь.

Для себя я нашёл подходящее решение, которое в целом меня устраивает, но не является универсальным, т.к. опирается на некоторые допущения, которые верны для интересующих меня населённых пунктов.

Для поиска адреса создаётся индекс адресов, в который помещаются "нормализованные" строки, содержащие название населённого пункта, улицы и дома. Перед поиском адреса по индексу, искомый адрес тоже переводится в нормализованную форму, а дальнейший поиск выполняется простым SQL-запросом.

Процедура нормализации у меня делится на три части, из которых самой сложной является нормализация названия улицы.

Нормализация названия населённого пункта:
  1. Буквы переводятся в нижний регистр,
  2. Удаляются незначащие пробельные символы - символы в начале и конце строки, а подряд идущие пробельные символы заменяются на один пробел,
  3. Буква "ё" заменяется на "е",
  4. Удаляются сокращения "г.", "п.", "с.", "д.", слова "город", "поселок", "село", "деревня".
Полученная строка используется для сравнения.

Из неучтённых особенностей тут могут быть одноимённые населённые пункты разного класса. Например, посёлок Октябрьский и город Октябрьский. Или одноимённые населённые пункты из разных районов - посёлок Фёдоровка рядом с Уфой и посёлок Фёдоровка в Фёдоровском районе. Но поскольку мне нужен поиск адресов только в 9 городах, то эти особенности я учитывать не стал.

Нормализация номера дома:
  1. Буквы переводятся в нижний регистр,
  2. Удаляются незначащие пробельные символы - символы в начале и конце строки, а подряд идущие пробельные символы заменяются на один пробел,
  3. Буква "ё" заменяется на "е".
Из неучтённых особенностей тут могут быть попытки вставить в поле номера дома слово "дом" или сокращение "д.", могут быть присутствовать слова "корпус", "корп.", "строение", "стр.", попытки вместо знака дроби написать слово "дробь" и т.п.

Нормализация названия улицы:
  1. Буквы переводятся в нижний регистр,
  2. Удаляются незначащие пробельные символы - символы в начале и конце строки, а подряд идущие пробельные символы заменяются на один пробел,
  3. Буква "ё" заменяется на "е",
  4. Получившаяся строка разбивается на последовательность слов, а границами слов считаются пробелы и точки. Это сделано для того, чтобы различные сокращения и инициалы отделились от слов, с которыми они написаны слитно,
  5. Удаляются одиночные буквы,

  6. Раскрываются сокращения "ул" -> "улица", "пер" -> "переулок", "пр" -> "проспект", "пер" -> "переулок", "бул" -> "бульвар", "пл" -> "площадь", "шос" -> "шоссе", "наб" -> "набережная", "им" -> "имени",

  7. От чисел отрезаются окончания, так что строки типа "60-летия", "2-й", "1-я" превращаются просто в числа,

  8. Удаляются незначащие слова типа "лет", "летия", "реки", "имени". Названия многих улиц приурочены к юбилеям каких-либо памятных событий ("50-летия Октября" или "60 лет СССР"). Набережные, естественно, часто имеют в своём названии названия рек, вдоль которых они расположены, поэтому между названием типа "набережная реки Уфы" или "набережная Уфы" нет никакой разницы. И, наконец, улицы часто называются в честь каких-то людей, поэтому нет разницы между названиями типа "проспект имени Ленина" или "проспект Ленина",

  9. Удаляются слова-классификаторы адреса типа "улица", "проспект", "площадь", "тракт", из которых запоминается только первое.

  10. Из оставшихся слов собирается нормализованный адрес, перед которым ставится слово-классификатор адреса.

  11. В получившейся строке ищутся идущие подряд пары слов типа "имя фамилия" или "титул фамилия", из которых остаётся только фамилия. Тут я делаю предположение, что в городе не бывает улиц одного класса, названных именами однофамильцев. То есть, в городе не может быть улицы Льва Толстого и улицы Алексея Толстого, но может быть улица Льва Толстого и проспект Алексея Толстого - в этом случае однофамильцы будут различаться классом улицы. И сюда же относятся различия в титулах - алгоритм нормализации не учитывает, что могут быть улицы академика Морозова и Павлика Морозова. Это преобразование помогает находить названия улиц, в случае если имя или титул человека, в честь которого названа искомая улица, не были указаны. Тут мне пришлось приложить усилия и составить список людей, именами которых названы улицы. У меня это единый список, но вообще, хорошо бы иметь отдельный список для каждого населённого пункта - так и точность и скорость нормализации будут выше. В России для этого можно использовать адресный справочник КЛАДР или пришедший ему на смену ФИАС - читайте, например КЛАДР умер, да здравствует ФИАС?

Также при поиске дома по адресу следует учитывать, что существуют угловые дома, которым часто назначаются сразу два адреса. В проекте OpenStreetMap нет единого соглашения по тому, каким образом в базе данных указывать такие адреса. Есть несколько разных подходов, которые описаны на этой странице: Key:addr. Угловые дома Для отображения информации на карте я использую JavaScript-библиотеку LeafLet, написанную киевским программистом Владимиром Агафонкиным. Эта библиотека отстаёт по возможностям от библиотеки OpenLayers, которая используется самим проектом OpenStreetMap, но мне она понравилась компактностью и простотой использования.

воскресенье, 29 июля 2012 г.

Настройка базы данных для генератора тайлов Mapnik

Продолжение серии заметок, посвящённых настройке тайлового сервера. Смотрите также предыдущую заметку Подготовка карт для генератора тайлов Mapnik.

Введение

Эта заметка посвящена настройке базы данных для генератора тайлов Mapnik. Генератор тайлов - это программа, которая на основе векторного описания карты и файла описания стилей формирует квадратные растровые картинки с изображением карты.

Генератор тайлов можно использовать в сочетании с различными JavaScript-библиотеками, позволяющими прямо в веб-браузере просматривать карту. Проект OpenStreetMap, в котором используется генератор тайлов Mapnik, пользуется JavaScript-бибилиотекой OpenLayers. Забегая вперёд хочу сказать, что мой выбор остановился на JavaScript-библиотеке Киевского программиста Владимира Агафонкина LeafLet, которая хотя и уступает библиотеке OpenLayers по возможностям, зато проще в использовании и имеет более качественную документацию.

Вообще, о начинке проекта OpenStreetMap могу сказать следующее. Видно что проект динамично развивается и не имеет какой-либо генеральной линии развития. Основные компоненты проекта написаны на самых разных языках: C++ и Python (Mapnik), Java (JOSM и Osmosis), JavaScript (OpenLayers, но тут уж без альтернатив), Ruby On Rails (веб-сайт OpenStreetMap). Одно время сайт OpenStreetMap хранил свои данные в MySQL, в то время как вся географическая информация хранилась в PostgreSQL. Однако, разработчики сайта всё-таки перешли полностью на PostgreSQL, отказавшись от MySQL.

Из-за такого буйства технологий и энергичности развития бывает довольно трудно подобрать согласованные версии программ, которые бы хорошо работали друг с другом, а документация часто оказывается либо не полной, либо просто устаревшей. Есть много побочных и альтернативных проектов, которые тоже частично устарели, а частично потеряли совместимость с современными версиями программ.

Для облегчения настройки я советую взять Debian 7.0 Wheezy, хотя и на Debian 6.0 Squeeze настройка тоже возможна, но она сопряжена с бОльшими усилиями (подключение сторонних репозиториев, пересборка пакетов и т.п.). Всё дальнейшее описание делается применительно именно к Debian 7.0 Wheezy.

Установка СУБД

Установим пакеты, необходимые нам на этом этапе:
# apt-get install postgresql postgresql-contrib postgresql-9.1-postgis osm2pgsql
Настраиваем авторизацию по паролю в файле /etc/postgresql/9.1/main/pg_hba.conf, заменив первую строчку на вторую:
local all all peer
local all all password
Теперь выполним тюнинг ядра Linux для увеличения скорости работы PostgreSQL:
# sysctl kernel.shmmax=268435456
И пропишем такую же настройку в файле /etc/sysctl.conf:
kernel.shmmax=268435456
После тюнинга ядра можно произвести тюнинг самого сервера PostgreSQL. Для этого в файле /etc/postgresql/9.1/main/postgresql.conf изменим следующие строчки:
shared_buffers = 128MB
checkpoint_segments = 20
maintenance_work_mem = 256MB
autovacuum = off
Перезапустим сервер PostgreSQL, чтобы новые настройки вступили в силу:
# /etc/init.d/postgresql restart
Создание базы данных для Mapnik.

Теперь создадим пользователя, базу данных, установим в неё расширения, необходимые для работы Mapnik. Войдём под администратором PostgreSQL:
# su - postgres
Создадим пользователя:
$ createuser -PRDS osm
Создадим базу данных, принадлежащую только что созданному пользователю:
$ createdb -E UTF8 -O osm osm
Установим в базу данных расширение для индексирования геометрических объектов, находящееся в пакете postgresql-contrib:
$ psql -d osm -c "CREATE EXTENSION btree_gist;"
Установим в базу данных расширение PostGIS, необходимое для осуществления различных операций над геометрическими объектами. Расширение находится в пакете postgresql-9.1-postgis. Подробнее почитать о функциях, добавляемых PostGIS в PostgreSQL, можно на официальном сайте проекта: PostGIS 1.5.4 Manual.
$ psql -d osm -f /usr/share/postgresql/9.1/contrib/postgis-1.5/postgis.sql
Делаем пользователя osm владельцем таблиц geometry_columns и spatial_ref_sys, чтобы osm2pgsql мог импортировать карты от имени пользователя osm:
$ psql -d osm -c "ALTER TABLE geometry_columns OWNER TO osm; ALTER TABLE spatial_ref_sys OWNER TO osm;"
Теперь нужно установить описания проекций. Самому Mapnik'у нужна лишь одна проекция - 900913, проекция Меркатора. Установить её можно с помощью следующей команды:
$ psql -d osm -f /usr/share/doc/osm2pgsql/examples/900913.sql
Я же установил все доступные проекции, так как в дальнейшем я с помощью SQL-запросов извлекал геометрическую информацию в проекции 4326 WGS84 (обычные широта и долгота), преобразовывая проекцию объектов с помощью функции PostGIS ST_Transform.
$ psql -d osm -f /usr/share/postgresql/9.1/contrib/postgis-1.5/spatial_ref_sys.sql
На этом настройка базы данных завершается, можно выйти из сеанса пользователя postgres, открытого командой su.

Заливка данных в базу данных Mapnik

Осталось залить карты в базу данных. Для этого можно воспользоваться утилитой из одноимённого пакета osm2pgsql:
$ osm2pgsql -U osm -d osm -W /home/stupin/Downloads/RU-BA_TA_ORE.pbf

воскресенье, 22 июля 2012 г.

Подготовка карт для генератора тайлов Mapnik

Это - первая заметка, посвящённая настройке и использованию генератора тайлов Mapnik. Генератор тайлов - это программа, создающая квадратные растровые изображения фрагментов карты, по её векторному описанию. Прежде чем настраивать сам генератор тайлов, нужно подготовить для него данные, чем мы и займёмся в этой заметке.

Заливка карт в базу данных Mapnik на первый взгляд не должна представлять каких-то сложностей - достаточно взять карту в формате с расширением osm, вооружиться утилитой osm2pgsql и вперёд. Но на самом деле даже в этом, на первый взгляд простом, деле, не имея никакого опыта, можно довольно долго ходить по плотно разложенным граблям.

Мне нужно было скачать и залить в базу данных Mapnik карты Башкирии, Татарстана и Оренбургской области. Я нашёл их на сайте gis-lab.ru, по ссылке http://gis-lab.info/projects/osm_dump/. Попробовал залить их в базу данных и был несколько разочарован - карты, выложенные там, устарели.

Дело в том, что сайт OpenStreetMap с 1 апреля 2012 года приостановил публикацию файлов карты планеты из-за смены лицензии: http://gis-lab.info/blog/2012-03/osm-license-change/. Была уже середина июня, а данные на сайте gis-lab.ru по-прежнему толком не обновлялись. И я решил вырезать из последнего дампа планеты интересующие меня области самостоятельно.

Для различных операций по обработке карт osm нам понадобится утилита osmosis. Лучше всего установить osmosis версии не ниже 0.40. Я поставил эту утилиту из репозитория Debian Wheezy (7.0), который на момент написания заметки был тестовой веткой Debian.

Скачиваем и готовим карту планеты

На скачивание архива размером 23 гигабайта со страницы http://planet.openstreetmap.org/ у меня ушло примерно 6 часов. Ещё 12 часов ушло на разжатие архива, а разжатый файл занял примерно 330 гигабайт.

В интернете я наткнулся на заметку *.osm or *.pbf ?, в которой сравнивались размеры файлов в этих форматах. Чтобы освободить место на диске и ускорить обработку данных, я решил преобразовать файл планеты в двоичный формат:
$ osmosis --read-xml file="planet-latest.osm" --write-pbf file="planet-latest.pbf" omitmetadata="yes"
Преобразование заняло примерно 25 часов, а двоичный файл занял всего-лишь 14 гигабайт.

Я очень советую проделать эту операцию, особенно если вы собираетесь работать с этим файлом в дальнейшем - накладывать файлы обновлений, вырезать из него различные области и т.п. Разница в нагрузке на диск по чтению уменьшается существенно и заметно даже на глаз. Ну и свободное место на диске лишним не бывает ;-)

Готовим полигон для обрезки карты

Теперь нам понадобятся файлы полигонов для обрезки интересующих нас областей. Я взял интересующие меня файлы с сайта gis-lab.ru, всё на той же странице http://gis-lab.info/projects/osm_dump/ по ссылке http://data.gis-lab.info/osm_dump/poly/.

Ещё нам понадобятся утилиты для преобразования файлов полигонов в файлы osm и обратно, которые можно взять здесь: http://svn.openstreetmap.org/applications/utils/osm-extract/polygons/ и редактор JOSM, который можно установить из дистрибутива.

Скачаем и подготовим к использованию скрипты:
$ wget http://svn.openstreetmap.org/applications/utils/osm-extract/polygons/poly2osm.pl
$ wget http://svn.openstreetmap.org/applications/utils/osm-extract/polygons/osm2poly.pl
$ chown +x poly2osm.pl osm2poly.pl
Установим редактор JOSM и его плагины:
# apt-get install josm josm-plugins
Программа постоянно обновляется, а свежую версию JOSM можно скачать с сайта josm.ru. Я рекомендую скачивать последнюю протестированную версию. Запустить её можно так:
$ java -jar josm-tested.jar
Преобразуем файлы полигонов в формат osm:
$ ./poly2osm.pl RU-BA.poly > RU-BA.osm
$ ./poly2osm.pl RU-TA.osm > RU-TA.osm
$ ./poly2osm.pl RU-ORE.osm > RU-ORE.osm
Теперь запустим редактор JOSM, и откроем в нём все три получившихся файла в отдельных слоях. Объединим слои, а затем воспользуемся инструментом "Объединить накладывающиеся полигоны". Выделим два любых полигона и нажмём Shift-J. Повторим операцию для всех оставшихся полигонов. В итоге получим один полигон, который сохраним в файл с расширением osm. Я сохранил под имененм RU-BA_TA_ORE.osm.


Осталось преобразовать файл из формата osm в формат полигона:
$ ./osm2poly.pl RU-BA_TA_ORE.osm > RU-BA_TA_ORE.poly
Вырезаем из карты планеты интересующую область

Теперь нужно обрезать карту планеты по полигону. Как выяснилось, в процессе обрезки карты планеты по полигону, osmosis создаёт временные файлы в каталоге /tmp. У меня этот каталог находится на корневом разделе, на котором было свободно всего 6 гигабайт. Программа работала довольно долго, а обнаружил я её уже завершившейся с сообщением о нехватке места без какой бы то ни было конкретики. При втором запуске я стал следить за местом на дисках, надеясь что программе не хватило именно его, а не места в оперативной памяти. Спустя несколько часов я увидел, что программа начала занимать место на корневом диске и нашёл её временные файлы в каталоге /tmp. Я поискал ответа в интернете и нашёл совет воспользоваться переменной окружения JAVACMD_OPTIONS. Как я узнал потом, временные файлы занимали примерно 24 гигабайта.

Кроме того, на карте могут существовать объекты, частично выходящие за границы полигона. Для того, чтобы эти объекты целиком попали в результирующую карту, нужно указать опции completeWays=true - дороги полностью и completeRelations=true - все объекты, относящиеся к вырезаемым (смысл этой опции мне не совсем понятен).

Для ускорения процесса вырезки карты рекомендуют воспользоваться опцией idTrackerType=BitSet, которая позволяет эффективно отслеживать идентификаторы объектов при вырезании из карты больших фрагментов. По умолчанию используется idTrackerType=idList, которая обладает какими-то ошибками в реализации, приводящими к высоким затратам времени на обрезку карты. Важно, что эту опцию необходимо указывать для каждой опции-фильтра (взята эта рекомендация отсюда: http://fprog.livejournal.com/5508.html?thread=166276). На моём компьютере osmosis при использовании этой опции завершался с ошибкой нехватки оперативной памяти, поэтому я не смог оценить прирост скорости обрезки карты.

Итоговая команда для обрезки карты у меня получилась такой:
$ env JAVACMD_OPTIONS="-Djava.io.tmpdir=/home/stupin/Downloads" \
osmosis --read-pbf file=planet-latest.pbf \
--bounding-polygon file=RU-BA_TA_ORE.poly completeWays=true \
--write-pbf file=RU-BA_TA_ORE.pbf omitmetadata=true