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

Проблема с кодировками в MySQL

Не смотря на то, что в настоящее время почти повсеместно используется кодировка UTF-8, ещё случаются иногда курьёзные случаи с кодировками.

Недавно столкнулся с проблемой такого рода. Сервер базы данных работал в latin1. Кодировка базы данных и всех её таблиц - UTF-8. На веб-сервере работало приложение на PHP, которое само по себе оперировало информацией в базе данных тоже в кодировке UTF-8. Беда в том, что в переменных окружения веб-сервера тоже стояла кодировка latin1. Сервер базы данных общался с клиентом базы данных, думая что оба они работают с информацией в кодировке latin1, хотя по факту информация была в UTF-8. Соответственно, никаких проблем не наблюдалось до тех пор, пока к серверу баз данных не подключился клиент, который захотел общаться в кодировке, отличной от latin1. Клиент получал что угодно, но только не текст в запрошенной кодировке.

Чтобы исправить ситуацию, в веб-приложении пришлось сразу после подключения к базе данных задавать желаемую кодировку запросом "SET CHARACTER SET 'UTF8'". Осталось перекодировать данные.

Сначала снимем дамп с базы данных, запросив данные в кодировке latin1. На самом деле они сольются в той кодировке, которой пользовалось веб-приложение. В данном случае это UTF-8.
$ mysqldump --default-character-set=latin1 -uroot -p base | grep -vE "^\/\*" > base.sql
Из дампа попутно удаляются все строки, начинающиеся с символов "/*" - это директивы, задающие настройки кодировок при импорте-экспорте.

Теперь запустим консольный клиент, и создадим пустую базу данных в кодировке UTF-8:
$ mysql -uroot -p
> create database base2 charset utf8;
Осталось выбрать кодировку, в которой хранится информация в дампе и залить в новую базу данных ранее сохранённый дамп. В нашем случае это опять UTF-8.
> charset utf8;
> use base2;
> source base.sql
Теперь можно выбирать в клиенте ту кодировку, которая стоит в терминале, и видеть текст. В моём случае в терминале была настроена кодировка KOI8-R:
> charset koi8r;
> select * from table_name limit 10;
Если всё сделано правильно, можно повторить восстановление дампа уже в основную базу и удалить тестовую базу данных:
> drop database base;
> create database base charset utf8;
> charset utf8;
> source base.sql
> drop database base2;

воскресенье, 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, но мне она понравилась компактностью и простотой использования.

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

Установка renderd и mod_tile - системы отрисовки тайлов по запросу

Продолжение серии заметок, посвящённых настройке тайлового сервера. Смотрите также предыдущие заметки Подготовка карт для генератора тайлов Mapnik, Настройка базы данных для генератора тайлов Mapnik и Установка генератора тайлов Mapnik. На этот раз мы установим демон renderd и модуль mod_tile для веб-сервера Apache.

Скачиваем дистрибутив модуля mod_tile для apache2 и его бэкенд - демон renderd. mod_tile работает вместе с renderd. Когда браузер пользователя запрашивает тайл у веб-сервера apache2, запрос перенаправляется модулю mod_tile. Если mod_tile находит в кэше нужный тайл, и судя по метаинформации и настройкам кэширования, он не просрочен, mod_tile просто передаёт его apache2. Если же необходимый тайл отсутствует или просрочен, тогда mod_tile обращается по UNIX-сокету к renderd, чтобы тот отрисовал необходимый тайл, разместил его в файловой ситсеме и доложил о готовности тайла.

Скачиваем исходники модуля и демона:
$ svn co http://svn.openstreetmap.org/applications/utils/mod_tile/
Устанавливаем всё необходимое для его сборки:
# apt-get install debhelper apache2-mpm-prefork apache2-threaded-dev libmapnik2-dev autoconf automake m4 libtool
Собираем модуль и демон, и устанавливаем их:
$ cd mod_tile
$ dpkg-buildpackage -B -rfakeroot -us -uc
$ cd ..
# dpkg -i renderd_0.4-10~oneiric1_amd64.deb
# dpkg -i libapache2-mod-tile_0.4-10~oneiric1_amd64.deb
Редактируем файл конфигурации mod_tile /etc/apache2/sites-enabled/tileserver_site: пишем почтовый ящик администратора веб-сервера в опцию ServerAdmin, комментируем опции ServerName, ServerAlias.

Я также поменял порт с 80 на 8080 в опции VirtualHost, поскольку у меня Apache2 будет выступать в роли бэкэнда для более лёгкого сервера Lighttpd. Для этого я поменял порт Apache2 в файле /etc/ports.conf, в опциях NameVirtualHost и Listen.

Теперь нужно настроить renderd. Для этого откроем файл /etc/renderd.conf и поменяем значение опции plugins_dir в секции [mapnik] с /usr/lib/mapnik/0.7/input на /usr/lib/mapnik/2.0/input

Теперь можно перезапустить apache2 и renderd, чтобы их настройки вступили в силу:
# /etc/init.d/apache2 restart
# /etc/init.d/renderd restart
Я также подключил модуль mod_proxy в файле конфигурации Lighttpd /etc/lighttpd/lighttpd.conf:
server.modules += ( "mod_proxy" )
proxy.server = (
  "/osm/" =>
  (
    (
      "host" => "127.0.0.1",
      "port" => 8080
    )
  )
)
И перезапустил веб-сервер:
# /etc/init.d/lighttpd restart
Теперь, если всё сделано правильно, система должна заработать. Для того, чтобы убедиться в правильности настроек, поправим адрес сервера на тестовой странице /var/www/osm/slippymap.html - заменим с localhost на внешнее имя сервера servername.tld. Теперь в браузере переходим на страницу http://servername.tld/osm/slippymap.html и видим карту прямо из недр базы данных:

На этом настройка тайлового сервера закончена.

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

Установка генератора тайлов Mapnik

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

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

Ставим Mapnik, готовим границы мира и береговые линии

Ставим генератор тайлов mapnik2:
# apt-get install python-mapnik2
Скачиваем береговые линии, границы мира и т.п. с помощью скрипта:
$ wget http://svn.openstreetmap.org/applications/rendering/mapnik/get-coastlines.sh
$ chmod +x get-coastlines.sh
$ ./get-coastlines.sh
Всё скачанное помещаем в каталог /usr/share/mapnik/world_boundaries:
# mkdir /usr/share/mapnik
# mv world_boundaries /usr/share/mapnik
Настраиваем стиль отрисовки карты

Скачиваем скрипты и файлы стилей Mapnik (может понадобиться установить subversion):
$ svn co http://svn.openstreetmap.org/applications/rendering/mapnik/
И удаляем лишнее:
$ find . -name .svn -exec rm -Rf \{\} \;
Перемещаем файлы стилей Mapnik в каталог /etc/mapnik-osm-data:
# mkdir /etc/mapnik-osm-data
# mv mapnik/inc mapnik/symbols mapnik/osm.xml /etc/mapnik-osm-data/
Переименовываем файлы шаблонов настроек:
# cd /etc/mapnik-osm-data/inc/
# mv fontset-settings.xml.inc.template fontset-settings.xml.inc
# mv datasource-settings.xml.inc.template datasource-settings.xml.inc
# mv settings.xml.inc.template settings.xml.inc
Исправляем настройки в соответствии с описанием Manually building a tile server.

В файле settings.xml.inc заменим:
1. <!ENTITY symbols "%(symbols)s">
на <ENTITY symbols "symbols">
2. <!ENTITY osm2pgsql_projection "&srs%(epsg)s;">
на <!ENTITY osm2pgsql_projection "&srs900913;">
3. <!ENTITY dwithin_node_way "&dwithin_%(epsg)s;">
на <!ENTITY dwithin_node_way "&dwithin_900913;">
4. <!ENTITY world_boundaries "%(world_boundaries)s">
на <!ENTITY world_boundaries "/usr/share/mapnik/world_boundaries">
5. <!ENTITY prefix "%(prefix)s">
на <!ENTITY prefix "planet_osm">

В файле datasource-settings.xml.inc прописываем настройки подключения к базе данных, заменив:
1. <Parameter name="password">%(password)</Parameter>
на <Parameter name="password">password</Parameter>
2. <Parameter name="host">%(host)s</Parameter>
закомментировав как
<!-- <Parameter name="host">%(host)s</Parameter> -->
3. <Parameter name="port">%(port)s</Parameter>
закомментировав как
<!-- <Parameter name="port">%(port)s</Parameter> -->
4. <Parameter name="user">%(user)s</Parameter>
на <Parameter name="user">osm</Parameter>
5. <Parameter name="dbname">%(dbname)s</Parameter>
на <Parameter name="dbname">osm</Parameter>
6. <Parameter name="estimate_extent">%(estimate_extent)s</Parameter>
на <Parameter name="estimate_extent">false</Parameter>
7. <Parameter name="extent">%(extent)s</Parameter>
на <Parameter name="extent">-20037508,-19929239,20037508,19929239</Parameter>

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

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

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

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

Файл fontset-settings.xml.inc не меняем.

На этом настройка самого генератора тайлов Mapnik закончена. В следующей заметке я расскажу, как настроить верхушку всей инфраструктуры - тайловый сервер.