Архив рубрики: TileMill

Всюду яваскрипт

Что-то стало модно всё подряд писать на яваскрипте. Куда ни глянь — всюду JavaScript, даже за пределами веба. Генератор карт TileMill — на яваскрипте. Текстовые редаторы и IDE теперь тоже на нём же пишут. Потому-то современные редакторы, наверное и тормозят — у них ведь помимо редактора ещё целый браузер внутри! У Komodo Edit и Komodo IDE — Mozilla Firefox, у Атома и Visual Studio Code — Chromium.

ls ~/bin/komodo/lib/mozilla

Красим школы и стоянки

В прошлых сериях экспериментов с ТайлМиллом доводилось раскрашивать находящиеся в университетском городке здания и добавлять номера домов. Теперь попробуем раскрасить какую-нибудь территорию, да и здания можно перекрасить как-нибудь иначе. Заодно и повод подвернулся: авторы сайта mapstr.ru решили поменять самодельную карту Стерлитамака на OpenStreetMap — там город неплохо отрисован (хотя, конечно, есть ещё, что улучшать), им в целом нравится стиль, которым отрисована карта окрестностей нашего университета, но им хочется, чтоб было как в ДубльГИСе — чтоб стоянки, дворы и частный сектор были залиты разными цветами. Ну и школы с детсадами хочется выделить цветом.

Делаем так:

1. Перед импортом данных OSM в PostGIS в файле imposm-mapping.py указываем, что нам нужно предназначение здания, а относится ли оно к университету, нас не интересует:

buildings = Polygons(
    name = 'buildings',
    fields = (
        ('area', PseudoArea()),
        ('addr:housenumber',String()),
        ('building:levels',Integer()),
        ('height',Integer()),
        ('amenity',String()),
    ),
    mapping = {
        'building': (
            '__any__',
        ),
        'railway': (
            'station',
        ),
        'aeroway': (
            'terminal',
        ),
    }
)

там же указываем, что у территорий нас интересует тэг residential — его значение rural можно использовать для обозначения деревенских жилых территорий.

landusages = Polygons(
    name = 'landusages',
    fields = (
        ('area', PseudoArea()),
        ('residential', String()),
        ('z_order', ZOrder([
            'pedestrian',
             ...

2. Импортируем данные.

3. Запускаем TileMill, выбираем нужный проект, идём в свойства слоёв, путём редактирования фрагмента SQL-запроса добавляем поля:

  • amenity — к слою #buildings
  • residential — к слоям #landuse, #landuse_gen0, #landuse_gen1

4. Исправляем стилевые правила:

В базовом файле base.mss указываем необходимость особенной заливки частного сектора

#landuse[zoom>12] {
  [type='allotments']    { polygon-fill: @agriculture; }
  // ...
  [type='residential']   {
    polygon-fill: @residential;

    [residential='rural']   {
      polygon-fill: @rural;
    }
  }
}

а также зданий образовательных учреждений: детских садов, школ, ПТУ/техникумов/колледжей и институтов/академий/университетов:

// At the highest zoom levels, render buildings in fancy pseudo-3D.
// Ordering polygons by their Y-position is necessary for this effect
// so we use a separate layer that does this for us.
    [type != 'hedge'] {
     building-fill: @building;
     building-height: 2 + [building:levels] * 2;
     [height>0] {
       building-height: 2 + [height] / 1.5;
     }

      // Our buildings
      [amenity = 'kindergarten'] {
        building-fill: @kindergarten_building;
        line-color:darken(@kindergarten_building,10%);
      }

      [amenity = 'school'] {
        building-fill: @school_building;
        line-color:darken(@school_building,10%);
      }

      [amenity = 'college'] {
        building-fill: @college_building;
        line-color:darken(@college_building,10%);
      }
      [amenity = 'university'] {
        building-fill: @university_building;
        line-color:darken(@university_building,10%);
      }
    }

    [type = 'hedge'] {
      building-fill:@wooded;
      building-height:1.25;
    }
  }
}

добавляем цвета в палитру palette.mss

@residential:       #e9e1cd;
@rural:             #ece2b5;
@commercial:        #e7e0f4;
@industrial:        #eeeeee;
@parking:           #e5eaf0;

/* Buildings */
@building:          #bbaa99;
@educational_mix:   #0099ff;

@kindergarten_building: mix(@building, @educational_mix, 90%);
@school_building:       mix(@building, @educational_mix, 85%);
@college_building:      mix(@building, @educational_mix, 80%);
@university_building:   mix(@building, @educational_mix, 75%);

Результат — на http://mapstr.ru/map/

Фрагмент карты Стерлитамака

Karta ne po-russki

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

Для того, чтоб иметь контроль над внешним видом подложки, не зависеть от размещающих тайлы (квадратные растровые фрагменты карт) сторонних сервисов, и не платить им денег в конце концов, тайлы генерируются из геоданных OpenStreetMap самостоятельно. OpenStreetMap для любого объекта может содержать множество имён — это и то, что хранится с ключом name — имя вообще, и int_name — международное имя, и куча имён с ключами вида name:ru, name:en, name:что_попало. Если делать тайлы с помощью TileMill, а свой стиль создавать на основе OSM Bright, то доступно только одно имя — name, однако в настройках сопоставления для imposm можно выбрать нужный язык — по умолчанию эта строка в файле imposm-mapping.py закомментирована:

set_default_name_type(LocalizedName(['name:en', 'int_name', 'name']))

Запускаем импорт, английские имена попадают в базу… Однако английских имён мало, сильно меньше, чем объектов с именами, записанными кириллицей.

Выхода из этой ситуации два — правильный и быстрый.
Правильный заключается в аккуратном переводе имён в OSM — слишком долго, да и неохота руками ковыряться.
Быстрый способ — не трогать OSM, а имена транслитерировать локально, в своём экземпляре базы данных. Так и поступим: создадим функцию транслитерации (прообраз подсмотрел на sql.ru) и выполним кучу UPDATE, вызывающих эту функцию. Мне, как перловому программисту, больше был симпатичен вариант с написанием перловой функции внутри PostgreSQL, но сразу такой вариант у меня не заработал, а разбираться было лень.

Итак, скармливаем постгресу такой код:

CREATE OR REPLACE FUNCTION ru_translit(p_string character varying)
  RETURNS character varying AS
$BODY$
-- Transliteration of Cyrillic letters
select
replace(
  replace(
    replace(
      replace(
        replace(
          replace(
            replace(
              replace(
                replace(
                  replace(
                    replace(
                      replace(
                        replace(
                          replace(
                            replace(
                              replace(
                                replace(
                                  replace(
                                    replace(
                                      replace(
                                        replace(
                                          replace(
                                            replace(
                                              replace(
                                                translate(
                                                  $1, 
                                                  'АБВГДЕЗИЙКЛМНОПРСТУФЫЭабвгдезийклмнопрстуфыэ',
                                                  'ABVGDEZIYKLMNOPRSTUFYEabvgdeziyklmnoprstufye'
                                                ),
                                              'ё', 'yo'),
                                            'ж', 'zh'),
                                          'х', 'kh'),
                                        'ц', 'ts'),
                                      'ч', 'ch'),
                                    'ш', 'sh'),
                                  'щ', 'shch'),
                                'ъ', ''),
                              'ь', ''),
                            'э', 'e'),
                          'ю', 'yu'),
                        'я', 'ya'),
                      'Ё', 'Yo'),
                    'Ж', 'Zh'),
                  'Х', 'Kh'),
                'Ц', 'Ts'),
              'Ч', 'Ch'),
            'Ш', 'Sh'),
          'Щ', 'Shch'),
        'Ъ', ''),
      'Ь', ''),
    'Э', 'E'),
  'Ю', 'Yu'),
'Я', 'Ya');
$BODY$
  LANGUAGE sql IMMUTABLE
  COST 100;

UPDATE osm_admin            SET name=ru_translit(name);
UPDATE osm_aeroways         SET name=ru_translit(name);
UPDATE osm_amenities        SET name=ru_translit(name);
UPDATE osm_barrierpoints    SET name=ru_translit(name);
UPDATE osm_barrierways      SET name=ru_translit(name);
UPDATE osm_buildings        SET name=ru_translit(name);
UPDATE osm_landusages       SET name=ru_translit(name);
UPDATE osm_landusages_gen0  SET name=ru_translit(name);
UPDATE osm_landusages_gen1  SET name=ru_translit(name);
UPDATE osm_mainroads        SET name=ru_translit(name);
UPDATE osm_mainroads_gen0   SET name=ru_translit(name);
UPDATE osm_mainroads_gen1   SET name=ru_translit(name);
UPDATE osm_minorroads       SET name=ru_translit(name);
UPDATE osm_motorways        SET name=ru_translit(name);
UPDATE osm_motorways_gen0   SET name=ru_translit(name);
UPDATE osm_motorways_gen1   SET name=ru_translit(name);
UPDATE osm_places           SET name=ru_translit(name);
UPDATE osm_railways         SET name=ru_translit(name);
UPDATE osm_railways_gen0    SET name=ru_translit(name);
UPDATE osm_railways_gen1    SET name=ru_translit(name);
UPDATE osm_transport_points SET name=ru_translit(name);
UPDATE osm_waterareas       SET name=ru_translit(name);
UPDATE osm_waterareas_gen0  SET name=ru_translit(name);
UPDATE osm_waterareas_gen1  SET name=ru_translit(name);
UPDATE osm_waterways        SET name=ru_translit(name);

Если мы добавляли номера домов на карту — транслитерируем и их заодно:

UPDATE osm_buildings SET "addr:housenumber"=ru_translit("addr:housenumber");

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

Карта без кириллицы

Больше букв

Функция selectall_arrayref перлового модуля DBI хороша для тех, кому лень писать:

This utility method combines «prepare», «execute» and «fetchall_arrayref» into a single call. It returns a reference to an array containing a reference to an array (or hash, see below) for each row of data fetched.

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

Реальный пример: скрипт, извлекающий тайлы из пакета, созданного Тайлмиллом, пытался читать данные как раз функцией selectall_arrayref. Зная, что применяется запрос

SELECT * FROM tiles

и что представление tiles содержит, помимо прочего, содержимое тайлов, занимающее места больше всего остального, нетрудно догадаться, что попытка выполнения запроса потребует выделения памяти в объёме, сопоставимом с размером файла, в котором сидит база (пакет с тайлами — это база SQLite).

Набор тайлов для территории размером 600×400 км в средних широтах — например, с Челябинском по центру, Ашой на западе, Карталами на юге и Тюменью на северо-востоке — займёт больше гигабайта для набора масштабов не больше шестнадцатого. На практике так и получилось: скрипт отжирал больше гигабайта памяти и всё никак не мог приступить к полезной части, пытаясь отожрать ещё. Если же увеличивать масштаб, затраты вырастут ещё сильнее: добавим семнадцатый зум масштаб — понадобятся ещё три-четыре гигабайта, Добавим восемнадцатый, которого хватит даже для любопытных исследователей карт — ещё на десять-двадцать объём вырастет. Если будем сохранять тайлы с глубиной цвета 24 бита, а не восемь — ещё больше места израсходуем. Получается, что средних размеров российская область может занять своими тайлами десятки гигабайт. И скрипт бы безуспешно пытался эти десятки получить.

Переписал:

-my $tiles = $dbh->selectall_arrayref(
-    'SELECT * FROM tiles',
-    { Slice => {} }
-);
-
-foreach my $tile ( @$tiles ) {

+my $sth = $dbh->prepare('SELECT * FROM tiles');
+   $sth->execute;
+
+while ( my $tile = $sth->fetchrow_hashref ) {

и всё наладилось: скрипт перестал жрать память (ему хватило десяти мегабайт) и ждать её выделения — сразу работает.

Вывод: не всегда надо экономить рабочее время программиста — иногда надо и о машинном времени задумываться.

Интерактивная карта Ильменки

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

Карта

Карта — OpenStreetMap, стиль отображения — свой собственный на основе OSM Bright, отрисованный с помощью TileMill, иконки — The Map Icons Collection. Библиотека для отображения — Leaflet c плагинами Leaflet-hash и Leaflet.fullscreen.

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

Трамвайная схема — можно и в TileMill

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

На самом деле в том, чтоб TileMill нарисовал трамвайные пути, нет особо хитрой магии. Более того, даже не надо, пугаясь питона, ковырять imposm-mapping.py — трамвайные пути можно выделить при помощи CartoCSS, потому что для железных дорог сохраняется их тип:

SELECT DISTINCT type FROM osm_railways;
type
--------------
preserved
narrow_gauge
light_rail
subway
tram
rail

Стиль трамвайных путей можно задать в файле roads.mss. В OSM Bright все доро́ги: и железные, и обычные — слиты в один слой. Правила рисования дорог весьма витиеваты, поэтому, чтоб не тратить на эксперименты лишние усилия, мы не будем пытаться переопределить стиль трамвайных путей — вместо этого добавим новое правило, рисующее широкие красные линии поверх трамвайных путей, не трогая более ничего. Правило это надо поместить после правил, описывающих слой #roads_high:

#roads_high::tram_highlight [type='tram'] {
  line-width: 5;
  line-color: @tram_line;
  line-comp-op: darken;
}

Результат:

Схема трамвайных путей

Для того, чтоб убедиться в работоспособности метода, этого вполне достаточно.

Бросается в глаза отсутствие подсветки на мостах — связано это с тем, что слой #bridge находится выше, чем #roads_high. Возможный способ решения — завести свой слой железных дорог (а для этого надо уже́ лезть в imposm-mapping.py, но теперь это не страшно), поместить его выше слоя с мостами и написать для железнодорожного слоя свои стилевые правила.

Раскрасим домики

Итак, после предыдущих упражнений здания на нашей карте обзавелись номерами и высотой. Что дальше? Дальше можно, например, раскрасить здания в разные цвета. Можно раскрасить дома в зависимости от основного их предназначения (то есть, в зависимости от содержимого тэгов amenity и building). Можно — в зависимости от принадлежности здания.

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

Достичь подобного результат с картами OpenStreetMap, рисуемыми через библиотеку Leaflet, можно разными путями: можно, взяв какую-либо карту, не трогать её растровый слой, оставить его без изменений, добавив свой векторный слой, куда нанести контуры зданий. Пример — usjeans.ru/map. Другой подход — сделать собственный растровый слой, на котором сразу и отметить необходимое. Так как предыдущие эксперименты касались как раз собственного растрового слоя — продолжим опыты в том же направлении.

Отметить принадлежность зданий в OpenStreetMap можно разными путями: первый, очевидный — задать у каждого нужного нам здания какой-нибудь новый тэг. Тэг может быть практически любым — даже при существующих ограничениях на имена можно придумать что-нибудь. То множество тэгов, что применяется в OSM — результат не жёсткого диктата разработчиков, а договорённости сообщества участников и множество это легко расширяемо. Другой путь, который мне представляется более верным — создать отношение. Для группировки объектов, представляющих собой что-то общее, предназначено отношение с типом site. Как выяснилось, нужное мне отношение уже́ существовало — надо было лишь проверить его корректность и внести необходимые изменения (например, добавить здания, в отношение не попавшие).

На следующем этапе, импорте данных, выяснилось, что в imposm не предусмотрен способ сохранить в базе данных информацию о принадлежности к отношению. Во всяком случае, в документации он не описан. Не беда — эту информацию можно внести и после импорта, напрямую обратившись к базе данных. Для хранения информации о принадлежности нужно создать дополнительное поле. Так как нам нужен только признак принадлежности здания конкретному отношению, достаточно будет типа Bool. После всех изменений описание сопоставления данных для зданий в файле imposm-mapping.py стало выглядеть так:

buildings = Polygons(
    name = 'buildings',
    fields = (
        ('area', PseudoArea()),
        ('addr:housenumber',String()),
        ('building:levels',Integer()),
        ('height',Integer()),
        ('belongs_to_susu',Bool()),
    ),
    mapping = {
        'building': (
            '__any__',
        ),
        'railway': (
            'station',
        ),
        'aeroway': (
            'terminal',
        ),
    }
)

Выставить флаг принадлежности можно выполнением SQL-запроса

UPDATE osm_buildings
SET belongs_to_susu=1
WHERE osm_id IN (список_id, через запятую);

Чтоб не перечислять объекты вручную, был написан перловый скрипт. Попутно выяснилось, что, несмотря на то, что отношение может в себе содержать другие отношения, нет смысла устраивать рекурсивный обход: в поле osm_buildings.osm_id хранится первый попавшийся номер: он может быть как номером линии (для большинства зданий), так и номером отношения.

Задача определения роли здания после всех предыдущих манипуляций кажется не такой уж и сложной: во-первых, тип здания (точнее, его предназначение) может хранится в тэге building — его содержимое при импорте попадает в поле osm_buildings.type. Среди допустимых типов есть и dormitory — общежития (кстати, не надо путать с tourism:hostel — это совсем разные тэги). Тип «учебные корпуса» (например, academic_building) среди часто применяемых не встречается — есть лишь university, который не совсем подходит: университет — это не только учебные корпуса. Можно, конечно, прямо у самих зданий указывать building=academic, но мне более правильным показался вариант указания роли здания внутри отношения — там и поле для роли есть, и оно не используется совсем. Больше шансов, что данные спокойно сохранятся именно тогда, когда они лежат внутри отношения. А вот в базу данных роль вносится в поле osm_buildings.type:

UPDATE osm_buildings
SET type='academic'
WHERE osm_id IN (список_id, через, запятую);

Как и прежде, для упрощения назначения ролей, написан ещё один перловый скрипт.

Стилевые правила для зданий (в OSM Bright он лежат в файле base.mss) начинаются так:

#buildings[zoom>=12] {
  polygon-fill:@building;
  // Our buildings
  [belongs_to_susu=1] {
    polygon-fill: @susu_building;
    [type='dormitory'] {
     polygon-fill: @susu_dormitory;
    }
    [type='academic'] {
     polygon-fill: @susu_academic;
    }
  }

Определение цветов @susu_building, @susu_dormitory, @susu_academic добавлено в файл palette.mss — там для них самое подходящее место.

@susu_building:     saturate(darken(@building, 15%), 10);
@susu_dormitory:    mix(@susu_building, #f60, 95%);
@susu_academic:     mix(@susu_building, #06f, 93%);

Результат:
Карта с разноцветными домами

Больше этажей!

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

С одной стороны в CartoCSS есть свойство building-height, описывающее «высоту» зданий — это видно на крупных масштабах (от 17). В OSM Bright величина building-height постоянна, что даёт на карте здания одинаковой высоты.

Карта

С другой стороны, в OpenStreetMap можно хранить данные о высоте различных зданий с сооружений: для этого предназначены тэги height (высота, по умолчанию в метрах) и building:levels (количество этажей). И эти, взятые из OSM, сведения о высоте либо числе этажей вполне можно использовать для building-height — там можно указывать не только числа, но и выражения.

Задача по добавлению поля с высотами в PostGIS аналогична предыдущей — добавлению номеров домов. Надо добавить в imposm-mapping.py пару полей — building:levels и height типа Integer. Я не нашёл способ, как сразу, на этапе импорта данных, выбирать нужное поле, то у которого есть хоть какое-то значение — это не страшно, можно выбрать позже.

Аналогично предыдущей выполняется и задача добавления данных в TileMill: надо добавить новые поля в SQL-запрос в настройках слоя #buildings.

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

building-height: 1 + [building:levels];

Как выбрать одно из двух полей? В выражении нельзя использовать операцию || — можно лишь or, но она в качестве результата выдаёт true либо false. Можно сделать так: сначала указать высоту, основываясь на приблизительном источнике (на числе этажей), а затем, при наличии более точного (собственно, высоты), взять данные с него. В CartoCSS, как и в обычном CSS, при одинаковом приоритете правил срабатывает то, что было объявлено последним.

Правила для указания высоты зданий на карте будут выглядеть так:

building-height: 1 + [building:levels];
[height>0] {
    building-height: 1 + [height] / 3;
}

Кроме того, лучше передвинуть слой зданий #buildings повыше, чтоб здания располагались над дорогами.

Результат:

Карта OSM со зданиями разной высоты

В следующий раз мы рассмотрим способ раскраски зданий в разные цвета.

Как добавить номера домов на карту

На карте, отрисовываемой из OpenStreetMap стилем, основанным на OSM Bright, нет номеров домов. Попытки на скорю руку сочинить какой-нибудь стиль, заглядывая в нагугленное, не увенчались успехом — пришлось разобраться подробнее. Выяснилось следующее: одним редактированием стилевого файла не обойтись, надо ещё и внести данные в базу.

Итак, есть PostgreSQL, PostGIS, imposm, TileMill и OSM Bright. Как запустить этот комбайн, написано, например, на mapbox.com/tilemill/docs/guides/osm-bright-ubuntu-quickstart/. Пробуем, запускаем — номеров домов нет. Если посмотреть, какие данные, относящиеся к зданиям, хранятся в базе данных, то увидим, что там нет адресов:

$ psql -U юзер -d база -c '\d osm_buildings'

                                  Table "public.osm_buildings"
  Column  |          Type          |                         Modifiers                          
----------+------------------------+------------------------------------------------------------
 id       | integer                | not null default nextval('osm_buildings_id_seq'::regclass)
 osm_id   | bigint                 | 
 name     | character varying(255) | 
 type     | character varying(255) | 
 area     | real                   | 
 geometry | geometry               | 
Indexes:
    "osm_buildings_pkey" PRIMARY KEY, btree (id)
    "osm_buildings_geom" gist (geometry) CLUSTER
Check constraints:
    "enforce_dims_geometry" CHECK (st_ndims(geometry) = 2)
    "enforce_srid_geometry" CHECK (st_srid(geometry) = 900913)

Чтоб сохранять адрес (а точнее, номер дома — нам этого достаточно), надо добавить строковое поле (точнее, это будет VARCHAR(255)) с именем 'addr:housenumber' к таблице с зданиями и заполнить это поле номерами. Сделать это можно, указав его в файле, где хранятся правила соответствия (в OSM Bright такой файл называется imposm-mapping.py):

--- a/imposm-mapping.py
+++ b/imposm-mapping.py
@@ -143,6 +143,7 @@ buildings = Polygons(
     name = 'buildings',
     fields = (
         ('area', PseudoArea()),
+        ('addr:housenumber',String()),
     ),
     mapping = {
         'building': (

После этого можно запускать импорт, в получившейся таблице появится поле addr:housenumber, содержащее номера домов.

Следующий этап — указать новое поле в настройках слоя TileMill в поле Table or subquery:

Настройка слоя buildings

Содержимое поля должно быть таким:

( SELECT geometry, type, name, area,"addr:housenumber"
    FROM osm_buildings
  ORDER BY ST_YMin(ST_Envelope(geometry)) DESC
) AS data

После чего останется добавить стиль номеров в файл labels.mss:

#buildings[zoom>16] {
  text-name:'[addr:housenumber]';
  text-face-name:@sans;
  text-size:9;
  text-fill: lighten(@poi_text, 20%);
}

Результат — susu.ac.ru/ru/about/campus

Картографические данные © Участники OpenStreetMap http://osm.org/

Трамвайная схема из OpenStreetMap

Когда-то для статьи «Челябинский трамвай» в википедии я нарисовал поверх какой-то карты схему маршутов, простенькую, куда проще бирмановской. Нарисовал, выложил нарисованное (без карты, только схему) в векторном виде, да и забыл — пить-есть не просит.

Прошло шесть с половиной лет, OpenStreetMap развился до состояния, когда на карты некоторых районов уже можно стало смотреть без слёз. «Почему б не попробовать схему с картой из OSM?» — подумал я. И попробовал.

Сначала был TileMill (я год назад о нём рассказывал на UWDC) — можно, например, сделать свой картостиль, основываясь на OSM Bright. Карта получается достаточно симпатичной и её внешний легко настраивается — в тайлмилле используется CSS-подобный язык разметки. Однако добавить на карту трамвайные маршруты не получилось — в OSM Bright трамвайные пути и железные дороги отображаются совершенно одинаково. Это, наверное, можно изменить, поковырявшись в настройке, но скрипты преобразования геоданных написаны на питоне, в котором я почти ничего не понимаю. Поэтому пришлось добавлять трамвайные линии вручную. Результат —

Попробовал альтернативный способ — alaCarte. В alaCarte оказалось возможным сразу выделить трамвайные пути, и, кроме того, alaCarte создаёт набор тайлов, пригодный для использования с библиотеками OpenLayers и Leaflet.

Получается, например, так:

Схема трамвайных маршрутов

в виде интерактивной карты — http://tile.susu.ac.ru/tram.html, используемые стили скоро будут на гитхабе. Stay tuned!