Тайловый сервер OpenStreetMap

Обновлено 12.11.2022. Добавлены пометки для Ubuntu 22. Основной упор пока на Ubuntu 20. Добавлены источники информации.

OpenStreetMap (OSM) — это создаваемая пользователями, свободно редактируемая карта мира. Можно представить это как альтернативу Google Maps с открытым исходным кодом. Эта инструкция покажет, как создать свой собственный сервер тайлов (tiles) OpenStreetMap в Ubuntu 20.04, чтобы вам не пришлось использовать чужой картографический сервис.

В этой инструкции показано, как настроить сервер растровых плиток. Если вам нужен сервер векторных плиток, вместо этого нужно перейти к инструкции по настройке векторного сервера TileServer GL/OpenMapTiles. Не знаете, какой из них лучше? Просто выберите сервер векторных плиток, который намного быстрее, чем сервер растровых плиток

Требования для развёртывания карты всей планеты довольно высоки:
Минимум 32Гб ОЗУ
1ТБ пространства на SSD

Потребуется много места на диске, если захотите ещё предварительно отрисовывать плитки, чтобы ускорить загрузку карты в браузере. Например, если вы собираетесь предварительно отобразить плитки с уровня масштабирования 0 до уровня масштабирования 15 для карты всей планеты, потребуется дополнительное место на диске объемом 460 ГБ.

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

Исходные данные:

  • ОС: Ubuntu 20.04/22.04
  • Пользователь с sudo правами в системе — administrator
  • Подключаем как можно меньше сторонних репозиториев и пользуемся убунтовскими

Сам алгоритм работы примерно такой:

  • Импортируем файлы карт в специальном формате в БД
  • Ставим модули для apache2, чтобы веб-сервер мог обрабатывать запросы на генерацию карт и пересылать запросы сервисам, которые будут генерировать тайлы
  • Скачиваем утилиты для вывода работы с картами через браузер
  • Создаём index.html, чтобы глядеть на всё это безобразие

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

Устанавливаем базу данных PostgreSQL

Для Ubuntu 20.04

sudo apt install postgresql-12 postgresql-contrib postgis postgresql-12-postgis-3 -y

Для Ubuntu 22.04

sudo apt install postgresql-14 postgresql-contrib postgis postgresql-14-postgis-3 -y

Настраиваем пользователей

Создаём пользователя для работы с базой данных карт, создаём БД с именем osm. Учтите, что некоторые утилиты типа renderd и mapnik настроены на такое имя БД. Дальше создаём расширения postgis и hstore для работы БД с картами.

sudo -u postgres createuser osm
sudo -u postgres createdb -E UTF8 -O osm gis
sudo -u postgres psql -c "CREATE EXTENSION postgis;" -d gis
sudo -u postgres psql -c "CREATE EXTENSION hstore;" -d gis
sudo -u postgres psql -c "ALTER TABLE spatial_ref_sys OWNER TO osm;" -d gis

Добавляем пользователя для работы с базой данных

sudo adduser --system --group osm

Даём необходимые права на директорию с картами текущему пользователю

sudo apt install acl
sudo setfacl -R -m u:administrator:rwx /home/osm/
cd /home/osm/

Копируем репозиторий проекта карт

git clone https://github.com/gravitystorm/openstreetmap-carto.git

Скачиваем файлы карт

Есть сайт https://download.bbbike.org/osm/bbbike/ откуда можно скачать отдельные карты городов

Есть файлы областей планеты — http://download.geofabrik.de/index.html

Скачивать надо файлы с расширением .osm.pbf

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

Скачаем карту России по примеру ниже

cd /home/osm/
wget -c http://download.geofabrik.de/russia-latest.osm.pbf

Настройка сервера PostgreSQL для лучшей производительности.

Пока скачиваются карты, настроим сервер PostgreSQL для работы с картами

Для Ubuntu 20.04

sudo nano /etc/postgresql/12/main/postgresql.conf

Для Ubuntu 22.04

sudo nano /etc/postgresql/14/main/postgresql.conf

Ищем и заменяем параметры, чтобы они были со значениями как указано ниже

shared_buffers = 15GB # (0.25*RAM)
work_mem = 1GB # (1/60 RAM или около гигабайта ОЗУ)
maintenance_work_mem = 8GB # (0,125*RAM)
effective_cache_size = 20GB # (1/3*RAM)

Перезагружаем сервис

sudo systemctl restart postgresql

По умолчанию PostgreSQL будет пытаться использовать HugePages в оперативной памяти. Однако Linux по умолчанию не выделяет HugePages. Получим объём памяти процесса PostgreSQL.

Для Ubuntu 20.04

grep ^VmPeak /proc/$(sudo head -1 /var/lib/postgresql/12/main/postmaster.pid)/status
VmPeak: 16282784 kB

Для Ubuntu 22.04

grep ^VmPeak /proc/$(sudo head -1 /var/lib/postgresql/14/main/postmaster.pid)/status 
VmPeak: 16282784 kB

Это максимальный объем памяти, который будет использоваться PostgreSQL. Теперь проверим размер HugePage в Linux.

cat /proc/meminfo | grep -i huge

Вывод команды:

AnonHugePages:         0 kB
ShmemHugePages:        0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB

Теперь можно подсчитать, сколько нужно HugePages. Разделим значение VmPeak на размер HugePage: 16282784 кБ / 2048 кБ = 7950. И создадим отдельный файл, чтобы значения не перезаписались при обновлении системы.

echo "vm.nr_hugepages = 7950" | sudo tee -a /etc/sysctl.d/60-custom.conf

Применим изменения

sudo sysctl -p /etc/sysctl.d/60-custom.conf
sudo systemctl restart postgresql

Только не забывайте, что отключив HugePages, PostgreSQL не сможет корректно расположить страницы в памяти. Лучше вообще потом ничего не менять 🙂

Следующий шаг — импорт файлов карт в базу данных

Импорт карт в БД

Для импорта карт в базу данных необходима утилита osm2pgsql. И необходимы права для изменения файлов для пользователя БД

sudo apt install osm2pgsql -y
sudo setfacl -R -m u:postgres:rwx /home/osm/

Для импорта нескольких карт их можно объединить программой osmconvert из комплекта утилит osmctools

sudo apt install osmctools

Объединяем скачанные карты

Для примера используем несколько карт скачанных стран

cd /home/osm/
osmconvert <(osmconvert belarus-latest.osm.pbf --out-o5m) \
<(osmconvert finland-latest.osm.pbf --out-o5m) \
<(osmconvert georgia-latest.osm.pbf --out-o5m) \
<(osmconvert kazakhstan-latest.osm.pbf --out-o5m) \
<(osmconvert russia-latest.osm.pbf --out-o5m) \
<(osmconvert ukraine-latest.osm.pbf --out-o5m) -o=merged.pbf

Импортируем карты в базу . Процесс импорта долгий и занимает примерно 5 часов на SSD. Можно предварительно запустить процесс импорта в screen’е. Можно импортировать какой-нибудь город для тестов, это быстрее будет

sudo -u postgres -i
osm2pgsql --slim -d gis --hstore --multi-geometry --number-processes 24 \
--tag-transform-script /home/osm/openstreetmap-carto/openstreetmap-carto.lua \
--style /home/osm/openstreetmap-carto/openstreetmap-carto.style -C 32000 /home/osm/merged.pbf

, где
—slim — указывает osm2pgsql создавать “тонкие” таблицы для хранения данных при импорте, а не пытаться хранить все в памяти. —slim также необходим, если мы хотим обновить данные;
-d gis — выбор базы
—hstore — это хранилище ключей и значений, поддерживающее произвольные ключи и значения;
—multi-geometry — указывает osm2pgsql не разбивать мультиполигоны на отдельные полигоны. Это повышает гибкость и устраняет некоторые артефакты визуализации, но немного медленнее.
—number-processes — количество ядер используемых у процессора;
—style — путь к файлу стиля, который сообщает osm2pgsql, какие столбцы создавать;
-С 32000 — размер кэша в мегабайтах. Должен быть примерно 70% от свободной памяти. Учитывайте, что PostgreSQL также использует память для shared_buffers. Общая формула для вычисления кеша такова: (Всего ОЗУ — PostgreSQL shared_buffers) * 0,7

Если в процессе импорта появились следующие или похожие ошибки

PBF error : invalid Blobheader size (> max_blob_header_size)

То, возможно, файл PBF повреждён. Проверьте контрольную сумму файла и сверьте её с той, откуда скачали файл

md5sum russia-latest.osm.pbf

После импорта даём права osm на таблицы базы

psql -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO osm;" -d gis
exit

Устанавливаем утилиты для рендеринга карт

Это mod_tile и renderd
mod_tile-это модуль Apache, необходимый для обслуживания тайлов (или плиток), а renderd — сервис рендеринга для получения тайлов OpenStreetMap.

Репозиторий Ubuntu не содержит mod_tile и renderd, так что надо ставить их из OSM PPA.

sudo apt install software-properties-common
sudo add-apt-repository ppa:osmadmins/ppa
sudo apt install apache2 libapache2-mod-tile renderd -y

Активируем модуль tile

sudo a2enmod tile

Создадим виртуальный хост apache

sudo nano /etc/apache2/sites-available/tileserver_site.conf

И добавим ниже расположенное содержимое в файл

<VirtualHost *:80>
    ServerName tile.example.com
    LogLevel info
    Include /etc/apache2/conf-available/renderd.conf
</VirtualHost>

Сохраним файл и включим этот виртуальный сайт

sudo a2ensite tileserver_site.conf

Также включим конфигурацию renderd

sudo a2enconf renderd

Перезапустим apache2

sudo systemctl restart apache2

Генерация стилей Mapnik

Устанавливаем нужные пакеты. В том числе подключим репозиторий для установки npm и nodejs

sudo apt install curl unzip gdal-bin mapnik-utils libmapnik-dev python3-pip -y
curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejs

Установим carto через npm и модуль psycopg2 для python

sudo npm install -g carto
sudo -H pip3 install psycopg2

Переключимся на пользователя postgres и сформируем файлы границ

sudo -u postgres -i
cd /home/osm/openstreetmap-carto/
scripts/get-external-data.py
carto project.mml > style.xml
psql -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO osm;" -d gis
exit

Ставим шрифты для корректного отображения не-ASCII символов

Для Ubuntu 20

sudo apt install ttf-dejavu fonts-noto-cjk fonts-noto-cjk-extra fonts-noto-hinted fonts-noto-unhinted ttf-unifont -y

Для Ubuntu 22

sudo apt install fonts-dejavu-core fonts-noto-cjk fonts-noto-cjk-extra fonts-noto-hinted fonts-noto-unhinted unifont -y

Настраиваем renderd

sudo nano /etc/renderd.conf

В секции [renderd] в опции «num_threads» укажите количество потоков, соответствующее количеству потоков в процессоре вашего сервера

По-умолчанию renderd рендерит плитки только для уровня увеличения равного 18. Опцией MAXZOOM можно увеличить или уменьшить данный параметр.

Итоговое содержимое файла:

[renderd]
stats_file=/var/run/renderd/renderd.stats
socketname=/var/run/renderd/renderd.sock
num_threads=24
tile_dir=/var/cache/renderd/tiles

[mapnik]
plugins_dir=/usr/lib/mapnik/3.1/input
font_dir=/usr/share/fonts/truetype           
font_dir_recurse=true 

[default]
URI=/osm/
XML=/home/osm/openstreetmap-carto/style.xml
DESCRIPTION=This is the standard osm mapnik style
;ATTRIBUTION=&copy;<a href=\"http://www.openstreetmap.org/\">OpenStreetMap</a> and <a href=\"http://wiki.openstreetmap.org/w\
iki/Contributors\">contributors</a>, <a href=\"http://creativecommons.org/licenses/by-sa/2.0/\">CC-BY-SA</a>
HOST=tile.example.com
;SERVER_ALIAS=http://a.tile.openstreetmap.org
;SERVER_ALIAS=http://b.tile.openstreetmap.org
;HTCPHOST=proxy.openstreetmap.org

Создадим директорию для настроек сервиса renderd

sudo mkdir /etc/systemd/system/renderd.service.d/

Создадим там конфигурационный файл

sudo nano /etc/systemd/system/renderd.service.d/custom.conf

Сменим имя пользователя

[Service]
User=osm

Сменим владельца у директорий

sudo chown osm /run/renderd/ -R
sudo chown osm /var/cache/renderd/tiles/ -R

И перезапустим сервисы

sudo systemctl daemon-reload
sudo systemctl restart renderd

Проверим в логах всё ли так

sudo journalctl -eu renderd

Настраиваем apache2

sudo nano /etc/apache2/sites-available/tileserver_site.conf

и указываем

ServerName tile.example.com

Перезапускаем apache2

sudo systemctl restart apache2

Настраиваем отображение карт в браузере

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

Есть две бесплатные библиотеки карт JavaScript с открытым исходным кодом, которые вы можете использовать для своего сервера плиток: OpenLayer и Leaflet. Преимущество Leaflet в том, что она проста в использовании, и карта будет удобна в использовании на мобильных устройствах

Но если скачать Leaflet не получается, то используйте OpenLayer или cкачайте Leaflet по ссылке.

Используем OpenLayer

cd /var/www/html/
sudo wget https://github.com/openlayers/openlayers/releases/download/v5.3.0/v5.3.0.zip
sudo unzip v5.3.0.zip

Создадим index.html

sudo nano /var/www/html/index.html

Вставим текст

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Accessible Map</title>
<link rel="stylesheet" href="http://tile.example.com/v5.3.0/css/ol.css" type="text/css">
<script src="http://tile.example.com/v5.3.0/build/ol.js"></script>
<style>
  a.skiplink {
    position: absolute;
    clip: rect(1px, 1px, 1px, 1px);
    padding: 0;
    border: 0;
    height: 1px;
    width: 1px;
    overflow: hidden;
  }
  a.skiplink:focus {
    clip: auto;
    height: auto;
    width: auto;
    background-color: #fff;
    padding: 0.3em;
  }
  #map:focus {
    outline: #4A74A8 solid 0.15em;
  }
</style>
</head>
<body>
  <a class="skiplink" href="#map">Go to map</a>
  <div id="map" class="map" tabindex="0"></div>
  <button id="zoom-out">Zoom out</button>
  <button id="zoom-in">Zoom in</button>
  <script>
    var map = new ol.Map({
      layers: [
        new ol.layer.Tile({
          source: new ol.source.OSM({
             url: 'http://tile.example.com/osm/{z}/{x}/{y}.png'
          })
       })
     ],
     target: 'map',
     controls: ol.control.defaults({
        attributionOptions: /** @type {olx.control.AttributionOptions} */ ({
          collapsible: false
        })
     }),
    view: new ol.View({
       center: [244780.24508882355, 7386452.183179816],
       zoom:5
    })
 });

  document.getElementById('zoom-out').onclick = function() {
    var view = map.getView();
    var zoom = view.getZoom();
    view.setZoom(zoom - 1);
  };

  document.getElementById('zoom-in').onclick = function() {
     var view = map.getView();
     var zoom = view.getZoom();
     view.setZoom(zoom + 1);
  };
</script>
</body>
</html>

Карты теперь доступны по адресу http://tile.example.com

Или

Используем Leaflet

cd /var/www/html
sudo wget http://cdn.leafletjs.com/leaflet/v1.7.1/leaflet.zip
или
sudo wget https://kurazhov.ru/programs/leaflet.zip
sudo unzip leaflet.zip

Создаём файл показа карты

sudo nano /var/www/html/index.html
<html>
<head>
<meta charset="UTF-8">
<title>OSM Tile</title>
<link rel="stylesheet" type="text/css" href="leaflet.css"/>
<script type="text/javascript" src="leaflet.js"></script>
<style>
   #map{width:100%;height:100%}
</style>
</head>

<body>
  <div id="map"></div>
  <script>
    var map = L.map('map').setView([60,31.0],9);
    L.tileLayer(
    'http://tile.example.com/osm/{z}/{x}/{y}.png',{maxZoom:18},
    ).addTo(map);
</script>
</body>
</html>

Карты доступны по адресу http://tile.example.com

Дополнение: (2024.02.12)

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

render_list --all --max-zoom=9 --min-zoom=1 --num-threads=24

Для того, чтобы не было серых тайлов из-за того, что renderd ещё не успел их подготовить, в модуле /etc/apache2/conf-enabled/renderd.conf можно привести следующие параметры к таким значениям:

ModTileRequestTimeout 30
ModTileMissingRequestTimeout 100

Дополнение:

Для ускорения рендеринга можно воспользоваться скриптом в консоли postgres

-- These are indexes for rendering performance with OpenStreetMap Carto.
-- This file is generated with scripts/indexes.py
CREATE INDEX planet_osm_line_ferry ON planet_osm_line USING GIST (way) WHERE route = 'ferry' AND osm_id > 0;
CREATE INDEX planet_osm_line_label ON planet_osm_line USING GIST (way) WHERE name IS NOT NULL OR ref IS NOT NULL;
CREATE INDEX planet_osm_line_river ON planet_osm_line USING GIST (way) WHERE waterway = 'river';
CREATE INDEX planet_osm_line_waterway ON planet_osm_line USING GIST (way) WHERE waterway IN ('river', 'canal', 'stream', 'drain', 'ditch');
CREATE INDEX planet_osm_point_place ON planet_osm_point USING GIST (way) WHERE place IS NOT NULL AND name IS NOT NULL;
CREATE INDEX planet_osm_polygon_admin ON planet_osm_polygon USING GIST (ST_PointOnSurface(way)) WHERE name IS NOT NULL AND boundary = 'administrative' AND admin_level IN ('0', '1', '2', '3', '4');
CREATE INDEX planet_osm_polygon_military ON planet_osm_polygon USING GIST (way) WHERE (landuse = 'military' OR military = 'danger_area') AND building IS NULL;
CREATE INDEX planet_osm_polygon_name ON planet_osm_polygon USING GIST (ST_PointOnSurface(way)) WHERE name IS NOT NULL;
CREATE INDEX planet_osm_polygon_name_z6 ON planet_osm_polygon USING GIST (ST_PointOnSurface(way)) WHERE name IS NOT NULL AND way_area > 5980000;
CREATE INDEX planet_osm_polygon_nobuilding ON planet_osm_polygon USING GIST (way) WHERE building IS NULL;
CREATE INDEX planet_osm_polygon_water ON planet_osm_polygon USING GIST (way) WHERE waterway IN ('dock', 'riverbank', 'canal') OR landuse IN ('reservoir', 'basin') OR "natural" IN ('water', 'glacier');
CREATE INDEX planet_osm_polygon_way_area_z10 ON planet_osm_polygon USING GIST (way) WHERE way_area > 23300;
CREATE INDEX planet_osm_polygon_way_area_z6 ON planet_osm_polygon USING GIST (way) WHERE way_area > 5980000;
CREATE INDEX planet_osm_roads_admin ON planet_osm_roads USING GIST (way) WHERE boundary = 'administrative';
CREATE INDEX planet_osm_roads_admin_low ON planet_osm_roads USING GIST (way) WHERE boundary = 'administrative' AND admin_level IN ('0', '1', '2', '3', '4');
CREATE INDEX planet_osm_roads_roads_ref ON planet_osm_roads USING GIST (way) WHERE highway IS NOT NULL AND ref IS NOT NULL;

Источники информации:
https://www.linuxbabe.com/ubuntu/openstreetmap-tile-server-ubuntu-20-04-osm
https://raw.githubusercontent.com/gravitystorm/openstreetmap-carto/master/indexes.sql
https://wiki.gis-lab.info/w/Создание_тайлового_сервера_на_основе_данных_OpenStreetMap_и_mod_tile

Подписаться
Уведомить о
guest

5 комментариев
Новые
Старые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Виктор
Виктор
6 месяцев назад

Спасибо за статью!
Хочу добавить один момент.
В последней версии ubuntu папка для сокетов /run/renderd создается по умолчанию от имени пользователя _renderd. Сервис renderd, который запускается от имени osm писать в нее не может.
Чтобы это победить, отредактировал файл /var/lib/tmpfiles.d/renderd.conf поменяв _renderd на osm.
Для системы init.d в сценарии запуска есть создание папки с сокетами и задание владельца. Надо только RUNASUSER задать.

robbinBobbin
robbinBobbin
1 год назад

not found

Алекс
Алекс
1 год назад

Пытался поставить по инструкции OSM на Ubuntu 22.04
Сайт через Leflet в результате рисует пустую страничку.
Renderd валит кучу варнингов вида:
 Mapnik LOG> 2023-03-03 13:10:31: warning: unable to find face-name ‘Noto Emoji Regular’ in FontSet ‘fontset-1’
на все шрифты, видимо
Куда копать?

Михаил
Михаил
1 год назад
Ответить на  Алекс

Статья по установке шрифтов для OSM — тут