Занимаясь разработкой веб-карт, использующих данные OpenStreetMap, часто возникает вопрос о том, как показывать карты с корректными русскими названиями. Этой проблемы не возникает, если ваши карты показывают исключительно Россию. Однако, если вы посмотрите, например, карту Китая, то вам вряд ли понравится такое обилие иероглифов, а тщетные попытки найти Пекин на такой карте, скорее всего, не увенчаются успехом.



Известно, что свободолюбивый проект OpenStreetMap позволяет сохранять названия географических объектов на разных языках. Для этого используются специальные теги, типа name:ru, name:en или name:es, и что самое главное, они заполняются участниками OpenStreetMap. Конечно, наиболее подробные надписи создают пользователи на том языке, на котором они говорят: в России — на русском, в Китае — на китайском, в африканских странах — на местных языках. Шансов, что какая-то улочка в Нигерии будет иметь русский перевод, мало, но все же основные географические объекты (страны, города, реки и т.п.) имеют переводы. Этой небольшой картографической информации бывает вполне достаточно, чтобы русскоязычный пользователь открыл, например, карту Китая и нашел на ней основные названия. Таким образом, ваш ресурс станет чуть более дружелюбным для пользователя.

Если задаться целью найти толковую инструкцию, как использовать эти теги переводов на собственных веб-картах, то во всем пространстве Рунета или даже Интернета найдется лишь разрозненная информация о разных аспектах вопроса локализации веб-карт OpenStreetMap, но «коробочной» инструкции вы вряд ли найдете. С одной стороны, эта проблема представляет собой не самый сложный архитектурный вопрос, т.к. технология разработана в рамках самого проекта OpenStreetMap. Но с другой стороны, в завершенных проектах локализованных веб-карт, например, в известном Ростелекомовском Спутнике, задача локализации была решена, а проблемы, с которыми столкнулись разработчики и то, как их удалось решить, остаются неозвученными IT-общественности.



Существуют даже проекты веб-карт с поддержкой многоязычности, но они не доведены до логического, дружелюбного пользователю конца.



Данная статья ставит перед собой цель собрать воедино эту разрозненную информацию в виде законченной инструкции по локализации карт OpenStreetMap. В статье приводятся минимальные сведения о развертывании тайлового севера на основе данных OpenStreetMap, т.к. данную тему подробно раскрыли другие публикации на Хабре, на Gis-Lab'е и в других спецпроектах. В рамках данной статьи мы развернем хранилище тайлов (плиток-картинок, из которых «сшивается» изображение веб-карты) по известной инструкции, но не вдаваясь и не уточняя подробности. Возникающие вопросы по поводу развертывания тайлового хранилища ищите в первую очередь в упомянутой инструкции.

Итак, нам потребуются пакеты (со всеми необходимыми зависимостями):
  • PostgreSQL >= 8.4
  • PostGIS >= 1.5 < 2
  • Python 2.x
  • Mapnik >= 2
  • Osm2pgsql
  • Subversion

Полноценный тайловый сервер (с Apache httpd, mod_tile и renderd) в целях данный статьи разворачивать не требуется. Но направляющие векторы для развертывания тайлового сервера будут даны ниже, подробную инструкцию вы найдете по вышеперечисленным ссылкам.

1. Получаем векторные данные OpenStreetMap


Эти данные мы будем импортировать (с помощью утилиты osm2pgsql) в базу данных Postgresql, а затем пакет рендеринга Mapnik будет генерировать растровые тайлы.

Итак, скачиваем карту в заданном регионе в формате XML (OSM-файл) или в сжатом бинарном формате PBF с одного из зеркал OpenStreetMap.
Файл планеты можно скачать командами:

wget http://download.bbbike.org/osm/planet/planet-latest.osm.pbf.md5
wget http://download.bbbike.org/osm/planet/planet-latest.osm.pbf
md5sum planet-latest.osm.pbf

Вам следует иметь в виду, что эти данные занимают значительный объем. Например, файл, содержащий векторные данные на всю планету Planet.osm в несжатом формате OSM XML занимает более 1ТБ, в формате XML, сжатом bz2, занимает 41.8ГБ, а в бинарном формате PBF 18.1ГБ. Все эти форматы хранят одни и те же данные. Естественно, если вам необходим только конкретный регион, а не вся планета, то эти цифры будут значительно меньше, но пропорции объемов данных в форматах, OSM XML, OSM XML, сжатом bz2, и в формате PBF, останутся теми же. Вам следует иметь в виду, что перед импортом в Postgresql векторные данные в формате OSM XML, сжатом BZ2, и в бинарном формате PBF будут предварительно обрабатываться (распаковываться, парситься), что увеличит время импорта скачанного файла в базу данных Postgresql. Так, на моей четырехядерной машине Core i5 с 16ГБ ОЗУ, 2ТБ HDD (Ubuntu 14.04 64x) импорт Planet.osm.pbf занял 2 недели. Импорт аналогичного файла Planet.osm.bz2 занял на порядок меньшее время. По моим собственным ощущениям, утилита osm2pgsql требовательна к объему ОЗУ (для оптимального управления памятью почитайте о возможностях ключей --cache и --cache-strategy утилиты osm2pgsql), частоте и количеству ядер процессора (в момент парсинга файла, для управления загрузкой ядер процессора смотрите ключ --number-processes утилиты osm2pgsql), а также к скорости работы жесткого диска (в момент вставки данных и создания индексов в Postgresql, для оптимизации см. ключ --disable-parallel-indexing утилиты osm2pgsql). Если у вас SSD, то импорт данных в базу Postgresql пройдет значительно быстрее.

Мы будем в целях данной статьи использовать регион Китая, т.к. он по умолчанию не русифицирован и имеет относительно небольшой размер, что удобно на этапе отладки технологии. Файлы векторных данных Китая можно скачать с сайта проекта geofabrik.de командой:

wget http://download.geofabrik.de/asia/china-latest.osm.pbf

2. Актуализируем полученные векторные данные OpenStreetMap


Дело в том, что за время, которое прошло с момента упаковки файла данных до момента разворачивания его на вашей машине, на OpenStreetMap могли быть внесены какие-то изменения. Поэтому перед импортом мы актуализируем файл данных командой osmupdate из пакета osmcutils:

osmupdate china-latest.osm.pbf new-china-latest.osm.pbf

Если ваш OSM-файл не содержит отметку времени (timestamp), то скорее всего утилита osmupdate вернет ошибку. В этом случае (если вы знаете timestamp вашего файла — иногда публикуется на странице скачивания файла) надо запустить команду в следующем формате:

osmupdate china-latest.osm.pbf 2015-05-13T14:48:07Z new-china-latest.osm.pbf

Утилита osmupdate самостоятельно скачивает diff-файл и применяет его к файлу, указанному в первом аргументе команды вызова. Не рекомендуется запускать утилиту osmupdate с большими файлами в формате OSM XML (например, planet-latest.osm.bz2), т.к. необходима предварительная обработка файла утилитой osmconvert из того же пакета osmcutils, а утилита osmupdate с полученным конвертированным файлом будет работать нескольких дней. Для больших файлов рекомендуется использовать PBF-формат. Но следует иметь в виду, что импорт такого файла в Postgresql будет занимать большее время, чем импорт файла формата OSM XML. Так сказать, палка о двух концах. Лично я всегда выбираю формат PBF.

3. Сконфигурируем поддержку нужных языков в настройках утилиты osm2pgsql


По умолчанию в файле стилей /usr/share/osm2pgsql/osm2pgsql/default.style не установлена поддержка любых языков. Для надписей на карте используется тег name, который задается в файле /usr/share/osm2pgsql/osm2pgsql/default.style строчкой:

node,way   name         text         linear

Допишем после нее поддержку, например, русского, английского и испанского языков:

node,way   name:ru      text         linear
node,way   name:en      text         linear
node,way   name:es      text         linear

Эти три строчки скажут утилите osm2pgsql импортировать также из файла PBF еще значения тегов name:ru, name:en, name:se. Прочие локализованные имена буду проигнорированы.

4. Создаем базу данных Postgresql для хранения векторных данных OpenStreetMap


Предварительно необходимо настроить trust-аутентификацию (чтобы не вводить пароль, в рамках данной статьиэ то не требуется) и создать необходимых пользователей БД (см. инструкцию). Мы в рамках данной статьи ограничимся trust-аутентификацией и стандартным пользователем postgres. Итак, создаем БД, например, china и подключаем к ней необходимые расширения:

createdb -U postgres china
psql -U postgres -d china -c 'CREATE EXTENSION hstore; CREATE EXTENSION postgis;'

5. Импортируем векторные данные в базу данных Postgresql


Импорт осуществляется утилитой osm2pgsql. Описания ключей утилиты можно найти в справке по утилите. Некоторые поясняющие сведения приводятся на англоязычном ресурсе.

osm2pgsql -s -m -d china -U postgres --drop new-china-latest.osm.pbf

Ключ --drop позволяет сократить занимаемое базой данных дисковое пространство, жертвуя тем самым возможностью впоследствии обновлять данные в БД из свежих файлов PBF или OSM XML. В случае Китая объем базы данных уменьшился с около 500МБ до 92МБ. Чтобы посмотреть какой размер занимает база данных, введите команду в консоли psql, подключенной к любой существующей БД:

SELECT pg_size_pretty( pg_database_size( 'china' ) );

6. Создаем представления в БД для отображения переведенных географических названий.


Воспользовавшись инструкцией создадим несколько представлений SQL. Для генерации «русских» тайлов (тайлов, содержащих русские подписи) будем использовать префикс china_ru, для генерации «английских» тайлов — china_en, для генерации «испанских» тайлов — china_es. Приведем ниже SQL скрипт создания SQL-представлений для генерации только «русских» тайлов. С SQL-представлениями для генерации «английских» и «испанских» тайлов уважаемый читатель, думаю, разберется сам, взяв за основу «русские» VIEW.

CREATE VIEW china_ru_point AS SELECT 
    data.osm_id, data.access, data."addr:housename", data."addr:housenumber", data."addr:interpolation",  
    data.admin_level, data.aerialway, data.aeroway, data.amenity, data.area, data.barrier, data.bicycle,  
    data.brand, data.bridge, data.boundary,  data.building, data.capital, data.construction, data.covered,  
    data.culvert, data.cutting, data.denomination, data.disused, data.ele, data.embankment, data.foot,  
    data."generator:source", data.harbour, data.highway, data.historic, data.horse, data.intermittent,  
    data.junction, data.landuse, data.layer, data.leisure, data.lock, data.man_made, data.military,  
    data.motorcar,  
    CASE data."name:ru" IS NULL  
        WHEN true THEN ''  
        ELSE data."name:ru" ||  
            CASE data.name IS NULL  
                WHEN true THEN ''  
                ELSE '\n'  
            END  
        END ||  
    CASE data.name IS NULL  
        WHEN true THEN ''  
        ELSE data.name  
        END  
    AS name,  
    data."natural", data.office, data.oneway, data.operator, data.place, data.poi, data.population, data.power,  
    data.power_source, data.public_transport, data.railway, data.ref, data.religion, data.route, data.service,  
    data.shop, data.sport, data.surface, data.toll, data.tourism, data."tower:type", data.tunnel, data.water,  
    data.waterway, data.wetland, data.width, data.wood, data.z_order, data.way FROM planet_osm_point AS data; 
CREATE VIEW china_ru_line AS SELECT 
    data.osm_id, data.access, data."addr:housename", data."addr:housenumber", data."addr:interpolation",  
    data.admin_level, data.aerialway, data.aeroway, data.amenity, data.area, data.barrier, data.bicycle, 
    data.brand, data.bridge, data.boundary, data.building, data.construction, data.covered, data.culvert, 
    data.cutting, data.denomination, data.disused, data.embankment, data.foot, data."generator:source", 
    data.harbour, data.highway, data.historic, data.horse, data.intermittent, data.junction,  
    data.landuse, data.layer, data.leisure, data.lock, data.man_made, data.military, data.motorcar,  
    CASE data."name:ru" IS NULL  
        WHEN true THEN ''  
        ELSE data."name:ru" ||  
            CASE data.name IS NULL  
                WHEN true THEN ''  
                ELSE '\n'  
            END  
        END ||  
    CASE data.name IS NULL  
        WHEN true THEN ''  
        ELSE data.name  
        END  
    AS name,  
    data."natural", data.office, data.oneway, data.operator, data.place, data.population, data.power, 
    data.power_source, data.public_transport, data.railway, data.ref, data.religion, data.route, data.service, 
    data.shop, data.sport, data.surface, data.toll, data.tourism, data."tower:type", data.tracktype, data.tunnel, 
    data.water, data.waterway, data.wetland, data.width, data.wood, data.z_order, data.way 
    FROM planet_osm_line AS data; 
CREATE VIEW china_ru_polygon AS SELECT 
    data.osm_id, data.access, data."addr:housename", data."addr:housenumber", data."addr:interpolation", 
    data.admin_level, data.aerialway, data.aeroway, data.amenity, data.area, data.barrier, data.bicycle, data.brand, 
    data.bridge, data.boundary, data.building, data.construction, data.covered, data.culvert, data.cutting, 
    data.denomination, data.disused, data.embankment, data.foot, data."generator:source", data.harbour, 
    data.highway, data.historic, data.horse, data.intermittent, data.junction, data.landuse, data.layer, 
    data.leisure, data.lock, data.man_made, data.military, data.motorcar,  
    CASE data."name:ru" IS NULL  
        WHEN true THEN ''  
        ELSE data."name:ru" ||  
            CASE data.name IS NULL  
                WHEN true THEN ''  
                ELSE '\n'  
            END  
        END ||  
    CASE data.name IS NULL  
        WHEN true THEN ''  
        ELSE data.name  
        END  
    AS name,  
    data."natural", data.office, data.oneway, data.operator, data.place, data.population, data.power, 
    data.power_source, data.public_transport, data.railway, data.ref, data.religion, data.route, data.service, 
    data.shop, data.sport, data.surface, data.toll, data.tourism, data."tower:type", data.tunnel, data.water, 
    data.waterway, data.wetland, data.width, data.wood, data.z_order, data.way, data.way_area 
    FROM planet_osm_polygon AS data; 
CREATE VIEW china_ru_roads AS SELECT  
    data.osm_id, data.access, data."addr:housename", data."addr:housenumber", data."addr:interpolation", 
    data.admin_level, data.aerialway, data.aeroway, data.amenity, data.area, data.barrier, data.bicycle, data.brand, 
    data.bridge, data.boundary, data.building, data.construction, data.covered, data.culvert, data.cutting, 
    data.denomination, data.disused, data.embankment, data.foot, data."generator:source", data.harbour, 
    data.highway, data.historic, data.horse, data.intermittent, data.junction, data.landuse, data.layer, 
    data.leisure, data.lock, data.man_made, data.military, data.motorcar,  
    CASE data."name:ru" IS NULL  
        WHEN true THEN ''  
        ELSE data."name:ru" ||  
            CASE data.name IS NULL  
                WHEN true THEN ''  
                ELSE '\n'  
            END  
        END ||  
    CASE data.name IS NULL  
        WHEN true THEN ''  
        ELSE data.name  
        END  
    AS name,  
    data."natural", data.office, data.oneway, data.operator, data.place, data.population, data.power, 
    data.power_source, data.public_transport, data.railway, data.ref, data.religion, data.route, data.service, 
    data.shop, data.sport, data.surface, data.toll, data.tourism, data."tower:type", data.tunnel, data.water, 
    data.waterway, data.wetland, data.width, data.wood, data.z_order, data.way 
    FROM planet_osm_roads AS data; 
INSERT INTO geometry_columns VALUES ('', 'public', 'china_ru_point', 'way', 2, 900913, 'POINT'); 
INSERT INTO geometry_columns VALUES ('', 'public', 'china_ru_line', 'way', 2, 900913, 'LINESTRING'); 
INSERT INTO geometry_columns VALUES ('', 'public', 'china_ru_polygon', 'way', 2, 900913, 'POLYGON'); 
INSERT INTO geometry_columns VALUES ('', 'public', 'china_ru_roads', 'way', 2, 900913, 'LINESTRING');

В данном случае мы создадим подписи на карте в формате «name:ru\nname», где \n — как известно, символ перевода каретки на новую строку. Для более изощренного размещения подписей на карте читайте документацию по TextSymbolizer Mapnik'а. В этом случае вам потребуется изменить стили отображения подписей на карте, генерируемые согласно настоящей инструкции в следующем пункте.

7. Устанавливаем скрипты от OpenStreetMap для генерации тайлов


Пусть для хранения всех необходимых скриптов Mapnik'а мы выбрали директорию /home/osm/mapnik. Выкачаем в нее скрипты от OpenStreetMap для генерации тайлов, выкачаем туда же шейпы (shape-файлы) мира, используемые для упрощения генерации тайлов на мелких маштабах, и создадим XML файлы стилей Mapnik'а для присоединения к БД:

svn co http://svn.openstreetmap.org/applications/rendering/mapnik /home/osm/mapnik
/home/osm/mapnik/get-coastlines.sh

Далее нам потребуется создать 3 файла стилей рендеринга тайлов, соответственно, для русских, английских и испанских подписей географических объектов. Для этого установим специальную переменную системного окружения MAPNIK_PREFIX, которая позволяет установить префикс таблиц векторных данных в базе данных Postgres и которую прочитает скрипт /home/osm/mapnik/generate_xml.py и подставит в псевдо-запросы к БД.

export MAPNIK_PREFIX='china_ru'
/home/osm/mapnik/generate_xml.py /home/osm/mapnik/osm.xml china_ru.xml --dbname china --user postgres --accept-none
export MAPNIK_PREFIX='china_en'
/home/osm/mapnik/generate_xml.py /home/osm/mapnik/osm.xml china_en.xml --dbname china --user postgres --accept-none
export MAPNIK_PREFIX='china_es'
/home/osm/mapnik/generate_xml.py /home/osm/mapnik/osm.xml china_es.xml --dbname china --user postgres --accept-none

Эти команды создадут 3 специальных стиля для рендеринга тайлов, позволяющих считывать данные для рендеринга из нужных нам представлений русского, английского или испанского языков.

8. Обозначим в скрипте генерации тайлов область Китая для генерации тайлов


Находим в скрипте /home/osm/mapnik/generate_tiles_multiprocess.py такие строки:

bbox = (-180.0,-90.0, 180.0,90.0) # контекст карты для генерации тайлов на весь земной шар
render_tiles(bbox, mapfile, tile_dir, 0, 5, "World") # команда рендеринга тайлов с 0-го по 5-ый масштаб

После этих строк напишем:

bbox = (85.0,19.7,132.5,40.8)  # контекст карты для генерации тайлов Китая
render_tiles(bbox, mapfile, tile_dir, 6, 15, "China") # команда рендеринга тайлов с 6-го по 15-ый масштаб
exit()

Это позволит сгенерировать тайлы Китая с 6-го по 15-ый масштаб содержащие подписи на необходимом нам языке. Инструкция exit() позволяет отказаться от дальнейшего рендеринга областей, прописанных по умолчанию в скрипте /home/osm/mapnik/generate_tiles_multiprocess.py (Muenchen, Muenchen+, Muenchen++, Nuernberg, Karlsruhe, Karlsruhe+, Augsburg, Augsburg+, Europe+). Существует еще скрипт /home/osm/mapnik/generate_tiles.py, который запускает процесс рендеринга в одном потоке. Но мы его не будем использовать, т.к. при наличии нескольких ядер процессора скрипт /home/osm/mapnik/generate_tiles_multiprocess.py в общем случае отработает быстрее.

9. Сгенерируем тайлы для выбранных локализаций


Нам необходимо создать директории, куда Mapnik будет складывать сгенерированные тайлы:

mkdir /home/osm/mapnik/tiles
mkdir /home/osm/mapnik/tiles/ru
mkdir /home/osm/mapnik/tiles/en
mkdir /home/osm/mapnik/tiles/es

Генерацию тайлов мы должны запустить трижды, меняя значения переменных системного окружения MAPNIK_MAP_FILE и MAPNIK_TILE_DIR. Скрипт прочитает эти переменные и передаст их Mapnik'у для настройки рендеринга тайлов:

export MAPNIK_MAP_FILE=/home/osm/mapnik/china_ru.xml
export MAPNIK_TILE_DIR=/home/osm/mapnik/tiles/ru/
exec python /home/osm/mapnik/generate_tiles_multiprocess.py
export MAPNIK_MAP_FILE=/home/osm/mapnik/china_en.xml
export MAPNIK_TILE_DIR=/home/osm/mapnik/tiles/en/
exec python /home/osm/mapnik/generate_tiles_multiprocess.py
export MAPNIK_MAP_FILE=/home/osm/mapnik/china_es.xml
export MAPNIK_TILE_DIR=/home/osm/mapnik/tiles/es/
exec python /home/osm/mapnik/generate_tiles_multiprocess.py

Тайлы сгенерированы.

10. Отобразим тайлы на веб-карте


Для этого создадим простейшую html-страничку, в которой подключим созданные тайловые хранилища. Файл страницы следует положить рядом с директорией сгенерированных тайлов /home/osm/mapnik/tiles. Для отображения веб-карт будем использовать javascript-фреймворк LeafletJS:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
	<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
	<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
</head>
<body>
    <div id="map" style="height: calc(100vh - 15px)"></div>
    <script>
        var map = L.map('map', {
            center: [34.7, 111.7],
            zoom: 6
        });
        L.control.layers({
            "ru" : L.tileLayer('tiles/ru/{z}/{x}/{y}.png').addTo(map),
            "en" : L.tileLayer('tiles/en/{z}/{x}/{y}.png'),
            "es" : L.tileLayer('tiles/es/{z}/{x}/{y}.png')
        }, null).addTo(map);
    </script>
</body>
</html>

Эта страничка будет выполняться локально. В правом верхнем углу должен быть доступен контрол, позволяющий выбрать источник тайлов. Русская, английская и испанская версии карт будет выглядеть следующим образом:





11. Рендеринг тайлов «на лету»


Вам потребуется настроить несколько модулей mod_tile веб-сервера Apache, каждый из которых будет ответственен за свой маппинг — /ru — за рендеринг русских тайлов, /en — за рендеринг, соответственно, английских тайлов и так далее. Подробности настройки mod_tile, httpd и renderd читайте в инструкции от Gis-Lab'а. В рамках демонстрации примера локализации карт OpenStreetMap не считаю нужным разворачивать полноценный тайловый сервер.

Примечания


  • В первоначальной редакции статьи автор планировал продемонстрировать пример русификации/локализации карт на примере Кипра, а не Китая, т.к. Кипр занимает еще меньшую площадь, чем Китай, а значит, и быстрее будут импортироваться данные в БД, быстрее будут генерироваться тайлы и т.д. Но, как оказалось, «местный» язык содержит символы, похожие на латиницу. Поэтому переводы на английский язык оказались недемонстративными. Кстати говоря, размеры баз данных Кипра и Китая (с и без ключа --drop) оказались приблизительно одинаковыми, плюс-минус пару мегабайт, несмотря на существенное различие в площади, занимаемой этими странами.
  • Автор статьи планировал реализовать в рамках статьи поддержку русского, английского и немецкого языков. Но поскольку английские и немецкие переводы пишутся в большинстве случаев одинаково, в окончательной версии статьи немецкий язык был заменен на испанский для большей демонстративности.
  • Эксперименты автора с прогрузкой Planet.osm не были доведены до конца (как это было сделано в случае карт Кипра и Китая). Дело в том, что значительное время импорта файла в базу данных Postgres (более двух недель) не позволило «поиграться» с теми или иными настройками утилиты osm2pgsql (и ее файлом стилей /usr/share/osm2pgsql/osm2pgsql/default.style) в приемлемое для автора время. Именно поэтому в статье отсутствует конкретная статистика обработки такого большого объема данных.
  • Создание представлений в БД типа china_ru_point, china_ru_roads и т.п., а также принудительное присваивание переменной системного окружения MAPNIK_PREFIX префикса таблиц с нужными нам переводами — это не единственный путь для получения необходимых переводов. В файле стилей Mapnik'а, генерируемого скриптом generate_xml.py, можно настроить формат представления подписей на карте в теге TextSymbolizer. В частности, если заменить сгенерированное имя поля надписи [name] на [name:ru], то Mapnik будет наносить на карту надписи, взятые из поля name:ru таблиц planet_osm_point, planet_osm_line, planet_osm_polygon, planet_osm_roads. Но, как показывает практика, переводов названий географических объектов существенно меньше, чем самих этих названий. Возможно, именно поэтому в проекте Multilingual Map карты «голые», без подписей географических объектов. В связи с этим конкатенация name:ru\nname, которую мы создали в представлениях china_ru_*, более универсальна.
  • Если вы не планируете поддержку нескольких языков и не планируете периодические обновления данных (т.е. будете использовать ключ --drop утилиты osm2pgsql), то громоздкую конструкцию представлений в БД вы можете заменить на один UPDATE-запрос, который заменит имеющееся значение в поле name на конкатенацию name:ru\nname в таблицах planet_osm_point, planet_osm_line, planet_osm_polygon и planet_osm_roads. В этом случае не требуется принудительно устанавливать переменную системного окружения MAPNIK_PREFIX. И, как ожидается, Mapnik сгенерирует тайлы только для выбранного языка.

Выводы


Проблема локализации карт OpenStreetMap не представляет большой трудности. Существует несколько подходов к решению этой задачи. В данной статье предложен вполне конкретный рабочий вариант. Уверен, у уважаемого читателя найдется немало вопросов по содержанию данной статьи, появятся собственные идеи для ветвления предложенного алгоритма. Автор статьи не претендует на единственно верную методу решения проблемы русификации/локализации карт OpenStreetMap и с удовольствием прочитает о ваших «хитростях» в решении данного вопроса.

Комментарии (33)


  1. dns78
    29.05.2015 20:01

    А не может быть такого, что в «Спутнике» делался просто автотранслит en -> ru с какими-то минимальными хотфиксами? Оно ведь должно давать приемлемые результаты.


    1. voidSoul Автор
      01.06.2015 12:52

      Возможно и такое. Там ведь свой GoPnik вместо Mapnik'а. На этот вопрос лучше ответят разработчики «Спутника».


      1. voidSoul Автор
        01.06.2015 14:30
        +1

        Я, кстати, связывался с разработчиками Спутника по данному поводу. Они мне тогда отвечали, что для карт, курпнее 9-го масштаба, для данных OSM используют теги name:ru. Если в базе нет русских названий — добавляют вручную. Но если посмотреть на их карты, то там очень много добавлено переводов по сравнению с тем, что я получил для того же Китая, взяв уже имеющиеся переводы.


  1. entze
    30.05.2015 13:57

    Импорт в Postgres занимает длительное время, а существуют ли дампы или бинарники базы, в которые уже импортированы данные? С возможностью апдейта.


    1. voidSoul Автор
      01.06.2015 12:55
      +1

      Я искал, но к сожалению не нашел. Возможно у Вас получится лучше. Но я думаю не просто же так OpenStreetMap распространяет данные карт в форматах PBF и OSM XML, а не pg_dump-овские файлы. Наверное, есть тому причины. Предположу, что есть проблемы с версионностью Postgres'а и PostGis'а. Чтобы дамп без ошибок перелился, надо версии иметь совпадающие. При несоответствии версий кто поручится за целостность восстановления базы из дампа?


    1. voidSoul Автор
      01.06.2015 13:55

      Кстати, я искал не только дампы баз, но и сгенерированные тайлы в архивах. Но тоже не нашел. К слову, размер тайлов на весь Земной шар до 19-го масштаба составляет около 5,5ТБ. Это то из PBF файла размером всего то 18,5ГБ.


    1. Komzpa
      06.06.2015 17:31

      На i7 / 32GB RAM / SSD импорт 400-гиговой базы занимает всего часов 12.
      Если у вас нет похожей машины, вы всё равно потом не сможете нормально работать с полной базой.


      1. voidSoul Автор
        06.06.2015 21:16

        В рамках моих профессиональных интересов меня интересует не сама возможность работать с полной базой, а возможность сгенерировать тайлы вплоть до 17-го масштаба. Это однократная задача. Я лишь периодически перегенерирую тайлы, например, Москвы, когда, например, разобрался как рисовать станции метро с «правильными» картинками.
        Другое дело, что меня интересует возможность строить маршруты с помощью PgRouting. Вот тут не обойтись без хорошего железа. Но пока мои поиски не привели хотя бы к одному успешному проекту, где импортировался бы весь Planet.osm. А по частям/регионам догружать стандартный osm2pgrouting не умеет. Может Вы чего посоветуете?


        1. Komzpa
          06.06.2015 21:22

          Для роутинга положительный опыт водится только с двумя движками — gosmore и osrm. На нынешнем размере планеты для gosmore дамп без вариантов нужно резать на куски, но поднимать его проще. osrm способен заглотить всю планету, но очень паметежорок.


      1. voidSoul Автор
        08.06.2015 10:29

        Подскажите какие ключи Вы используете для osm2pgsql, что импорт у Вас занимает всего 12 часов. Я лично почувствовал эффективность --drop и --unlogged. Остальные настройки иногда даже ухудшали время импорта.


        1. Komzpa
          11.06.2015 16:10

          Как-то так: github.com/Komzpa/furry-sansa/blob/master/config/h64-12.conf#L3
          -С до 20000, .style-файл без лишнего, и flat nodes.


  1. DjOnline
    30.05.2015 19:12
    -2

    Хоть я и вносил многое на OSM, но как же недружелюбен, а поэтому и далёк от людей этот гиковский OSM, по сравнению даже с тем же Wikimapia. Там задать название на разных языках можно за один клик, поменять язык интерфейса и всех названий — за один клик. Но на openstreetmaps (что .ru, что .org) нихрена подобного нет.
    А всё потому, что гики и основатели OSM гики (или птицы) гордые, рекламу повесить и на заработанные деньги нанять нормальных разработчиков религия не позволяет, поэтому всё сделано через жопу, и удобство по прежнему в хвосте что по сравнению с редактором яндекс карт, что с wikimapia.
    Извините, наболело. И при всё при этом на смартфоне я пользуюсь картами maps.me на основе OSM, так как других таких в оффлайне нет. Видимо потому что пока не заносило меня в Китай.


    1. Self_Perfection
      30.05.2015 22:17
      +2

      Да ладно вам, в один клик добавляется перевод. Скриншот: img-fotki.yandex.ru/get/6707/15441060.0/0_b56ef_fca2ac85_orig.png


      1. DjOnline
        30.05.2015 23:51
        -1

        Слишком это неочевидно — спрятано под плюсом, ни разу туда даже мышку не наводил.
        Да и потом неправильно это — иметь name без привязки к языку. Сравни с wikimapia, где изначально идет привязка к языку интерфейса, и отображение на карте сразу в языке интерфейса или если его нет, то на другом занесённом. Такая мелочь, но там продуманно, а здесь — нихрена.


        1. Self_Perfection
          31.05.2015 00:34
          +2

          Простановка названии продумана очень хорошо: wiki.openstreetmap.org/wiki/RU:%D0%9D%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F К сожаление, в общем случае название это очень нетривиальная и многогранная сущность.

          OSM позволяет размечать местность с разными уровнями детализациями и позволяет каждому заниматься тем, что ему нравится. Запретить указывать название без указания языка? Может ещё запретить обозначать здания без указания количества этажей? Оба предложения кажутся мне примерно одинаково странными: этажность может быть и неизвестна если обрисовывать спутниковые снимки, да и язык названия, в общем, может быть неизвестен. В любом случае требование к пользователю сделать что-то, чего он не хочет — зло.

          Я не вижу проблемы в отсутствии указания языка в просто теге name. Как только кто-то захочет добавить перевод на другой язык, он отметит, на каком языке было основное название.

          И вообще есть предложения не маяться дурью и не повторять уже сделанную работу, а просто ставить для географических объектов ссылки на wikidata, чтобы переводы названия брались уже оттуда ( lists.openstreetmap.org/pipermail/talk/2015-May/072982.html ).

          Тут я почувствовал, что меня тянет написать ещё пару-тройку абзацев, но оборву себя.


          1. DjOnline
            31.05.2015 11:09

            Ссылка не прояснила, что мешает любое многогранное имя сразу привязывать к выбранному языку.


            1. Self_Perfection
              31.05.2015 12:04

              А мне не ясно, почему по-вашему «неправильно это — иметь name без привязки к языку».

              Зато я довольно неплохо представляю себе ситуацию, как иностранец приезжает вна Украину, или вот давайте даже в Крым, обнаруживает себя на улице, название которого в OSM не обозначено. Он видит адресную табличку с каким-то названием и хочет его указать в OSM. Перепечатать он может, но на каком это языке ему не известно. И в таком случае он воспользуется просто тегом name.


              1. DjOnline
                31.05.2015 14:02

                Он дурак что ли, не понимает в какой он стране и на каком языке надписи в этой стране?
                Лучше пусть название переведёт на свой родной английский язык и укажет его по умолчанию для него в en:name, ведь у него же наверняка не установлена ни украинская, ни русская расскладка. По твоей логике он бы написал английское название в name, вот местные бы жители «обрадовались».

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

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


                1. Self_Perfection
                  31.05.2015 14:42

                  Вполне можно не понимать на каком языке название даже зная в какой ты стране. Гипотетический путешественник не может написать название на английском, он же не знает языка и не может перевести. И не знает, на каком языке это написано на табличке: логика ему говорит, что 3 года назад таблички, видимо, вешали с названиями на украинском, сейчас, видимо, с названиями на русском, но когда конкретно эта табличка повешена — фиг знает.

                  Не бывает названия без языка. Не бывает здания без количества этажей. У дороги можно указать количество полос, гладкость покрытия, тип покрытия (это разные характеристики. асфальт может быть гладким и раздолбанным), максимальную разрешённую правилами скорость и т.д. Не бывает дорог без этих характеристик. Нужно ли заставлять пользователя всё это заполнять? Пожалуй, нет. Так почему нужно пользователя заставлять указывать для названия язык? Чем принципиально отличается свойство «язык названия» от свойства «покрытие дороги»?


                  1. DjOnline
                    01.06.2015 16:40

                    В этом случае путешественик должен задать название на своём языке. Потому что на местном он ввести не сможет — нет раскладки клавиатуры.


                    1. Self_Perfection
                      01.06.2015 17:38

                      Он дурак что ли, не может запустить «таблицу символов»?


                      1. DjOnline
                        01.06.2015 17:56

                        Что за таблица символов? Мы про мегагиков каких-то говорим или обычных людей?


                        1. Self_Perfection
                          01.06.2015 23:41

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

                          Вообще сформулируйте *чётко* свою позицию. Пока она выглядит примерно как: «Давайте запретим в интернете использовать тег <img> без атрибутов alt и title!». Оно может и содержит здравую мысль и желание сделать мир лучше, но практически нереализуемо, совершенно непонятно, кому это нужно настолько, чтобы впрягаться в такой сизифов труд, тем более что в некоторых аспектах от такого изменения станет хуже.


  1. vaily
    31.05.2015 02:20
    -2

    ИМХО, бессмысленная и бесполезная затея.
    На примере того же Китая — карта с русскими названиями центральных объектов( рек, крупных городов, озер) есть на любых картах — даже на яндексе.
    А мелких( небольших городов, рек, деревень, адресов) нет. И русская транскрипция не спасет ни разу. Найти по русскому названию город Циньюань или деревню Дайоу невозможно — все равно не догадаешься, что за оригинальное название было.
    Вот вам пример — город Лонцзинь. Это примерно в 200 км от границы с Россией. Найдите его на карте.


    1. entze
      31.05.2015 06:16
      +2

      Не Китаем единым, рассматривается же технический вопрос. И пример с Китаем показан как наиболее яркий.
      То, что нужен простой механизм отображения локализованных тайлов, причем локализованных для любого языка, сомнений не вызывает?


      1. vaily
        31.05.2015 08:53

        Для английского — сомнений никаких. Для любого — абсолютно бесполезно.
        Все названия на любом языке мира локализованы на английский, и на примере выше я легко найду город Qingyuan, деревню Dayou или город Longjing.
        А если будет звуковая транскрипция на иврит, хинди и малагасийский — это никому не поможет.
        ИМХО, достаточно иметь два варианта для каждого тайла — локальный и английский.


        1. DjOnline
          31.05.2015 11:28

          Да, именно так и сделано в wikimapia, но адепты OSM этого не понимают.


        1. entze
          31.05.2015 11:54
          +4

          Я хочу видеть на карте Кёльн, а не только Cologne или Koln.


        1. vlivyur
          01.06.2015 11:17

          Удачи в поисках!


          1. Borz
            01.06.2015 12:12

            а это на какой из трёх улиц в городе N?


        1. wholeman
          01.06.2015 14:13

          Транслировать Дайоу в Dayou для человека, мало знакомого с транслитерацией китайских топонимов, нетрудно. А вот Циньюань в Qingyuan или Лонцзинь в Longjing намного сложнее. Поэтому тот, кому нужна русская версия должен иметь возможность её добавить.


    1. voidSoul Автор
      01.06.2015 13:04
      +1

      Ваши аргументы уходят за границы данной статьи. Но Вам скажу, что использование Яндекс-карт как и Гугл-карт ограничено лицензионным соглашением, которое имеет свои «особенности» для юр.лиц., в т.ч. по количеству запросов к серверам (тайловым, геокодера и т.п.). В этом смысле OpenStreetMap не ограничивает разработчиков, особенно если Ваш ресурс высоконагруженный (более 1 млн.запросов в сутки). Но выбор реализации веб-карт — это выбор разработчика. Решать Вам.


      1. voidSoul Автор
        01.06.2015 13:15
        +2

        Есть еще аргументы в пользу OpenStreetMap, например, возможность реализации оффлайного тайлового сервера, в закрытых сетях, без доступа к данным серверов (Яндекса, Гугла и даже OpenStreetMap'а), или в случае обрыва связи с упомянутыми серверами (хотя бы из-за санкций :) ). Без собственного тайлового сервера Вы не сможете это реализовать. Вот тут-то список возможных реализаций веб-карт сильно проредеет.