Что-то стало модно всё подряд писать на яваскрипте. Куда ни глянь — всюду JavaScript, даже за пределами веба. Генератор карт TileMill — на яваскрипте. Текстовые редаторы и IDE теперь тоже на нём же пишут. Потому-то современные редакторы, наверное и тормозят — у них ведь помимо редактора ещё целый браузер внутри! У Komodo Edit и Komodo IDE — Mozilla Firefox, у Атома и Visual Studio Code — Chromium.
Архив рубрики: TileMill
Красим школы и стоянки
В прошлых сериях экспериментов с ТайлМиллом доводилось раскрашивать находящиеся в университетском городке здания и добавлять номера домов. Теперь попробуем раскрасить какую-нибудь территорию, да и здания можно перекрасить как-нибудь иначе. Заодно и повод подвернулся: авторы сайта 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 повыше, чтоб здания располагались над дорогами.
Результат:
В следующий раз мы рассмотрим способ раскраски зданий в разные цвета.
Как добавить номера домов на карту
На карте, отрисовываемой из 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:
Содержимое поля должно быть таким:
( 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
Когда-то для статьи «Челябинский трамвай» в википедии я нарисовал поверх какой-то карты схему маршутов, простенькую, куда проще бирмановской. Нарисовал, выложил нарисованное (без карты, только схему) в векторном виде, да и забыл — пить-есть не просит.
Прошло шесть с половиной лет, OpenStreetMap развился до состояния, когда на карты некоторых районов уже можно стало смотреть без слёз. «Почему б не попробовать схему с картой из OSM?» — подумал я. И попробовал.
Сначала был TileMill (я год назад о нём рассказывал на UWDC) — можно, например, сделать свой картостиль, основываясь на OSM Bright. Карта получается достаточно симпатичной и её внешний легко настраивается — в тайлмилле используется CSS-подобный язык разметки. Однако добавить на карту трамвайные маршруты не получилось — в OSM Bright трамвайные пути и железные дороги отображаются совершенно одинаково. Это, наверное, можно изменить, поковырявшись в настройке, но скрипты преобразования геоданных написаны на питоне, в котором я почти ничего не понимаю. Поэтому пришлось добавлять трамвайные линии вручную. Результат —
Попробовал альтернативный способ — alaCarte. В alaCarte оказалось возможным сразу выделить трамвайные пути, и, кроме того, alaCarte создаёт набор тайлов, пригодный для использования с библиотеками OpenLayers и Leaflet.
Получается, например, так:
в виде интерактивной карты — http://tile.susu.ac.ru/tram.html, используемые стили скоро будут на гитхабе. Stay tuned!