Архив рубрики: карта

Рельеф — в единых цветах

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

Итак, список высот и соответствующих им цветов такой:

-10   167 223 210
0     172 208 165
100   168 198 143
200   189 204 150
300   209 215 171
400   225 228 181
500   239 235 192
750   232 225 182
1000  222 214 163
1500  211 202 157
2000  202 185 130
2500  195 167 107
3000  185 152  90
3500  170 135  83
4000  172 154 124
5000  186 174 154
6000  202 195 184
7000  224 222 216
8000  245 244 242
9000  245 244 242

Пересчитывать руками для того, чтоб использовать в Маперитиве — не наш метод. Наш метод — автоматизировать:

#!/usr/bin/perl

while (<>) {
    chomp;
    my @data = split;
    printf '%s:#%02x%02x%02x;',
        @data;
}

Ну или можно вообще однострочник написать:

perl -nla -F'\s+' -e 'printf "%s:#%02x%02x%02x;", @F' ramp-colors-above-water.txt

Осталось добавить получившиеся цвета к команде маперитива, рисующей рельеф:

generate-hypsometric ramps=-10:#a7dfd2;0:#acd0a5;100:#a8c68f;200:#bdcc96;300:#d1d7ab;400:#e1e4b5;500:#efebc0;750:#e8e1b6;1000:#ded6a3;1500:#d3ca9d;2000:#cab982;2500:#c3a76b;3000:#b9985a;3500:#aa8753;4000:#ac9a7c;5000:#baae9a;6000:#cac3b8;7000:#e0ded8;8000:#f5f4f2;9000:#f5f4f2

Эти цвета не такие насыщенные как те, что получились сначала. Южный Урал в этой палитре получается таким:

Рельеф Южного Урала

Карта с рельефом

Пару лет назад я пробовал рисовать карты из данных OpenStreetMap при помощи Maperitive — и даже что-то говорил об этом в докладе на UWDC-2013. Что понравилось ещё тогда — возможность «из коробки» отображать рельеф: Маперитив умеет сам ходить в сеть за открытыми данными о рельефе (SRTM) и потом строить из них контуры горизонтали и рисовать отмывку.

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

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

Север Нязепетровского района

Шаблон с картой лучше делать не по инструкции — там образец неправильный. Лучше взять за образец какой-нибудь существующий шаблон и слегка его поменять: надо изменить координаты границ карты, её названия и имена используемых файлов. Если речь идёт об изготовлении карты актуального состояния существующей местности (то есть, мы рисуем не историческую карту и не карту вымышленного мира), то имеет смысл нарисовать несколько карт одной и той же области, хотя бы две: физическую и политическую/административную — википедия позволяет выбрать нужную карту из шаблона как вручную, так и автоматически: например, в географических статьях (о реках, озёрах, горах) автоматически отображается физическая карта, если она есть.

Пример статьи с этой картой — Шемаха (приток Уфы). В статье Шемаха (Челябинская область) используется тот же самый шаблон, но карта там уже другая — административная.

Двадцать шестой троллейбус вернётся на Первое озеро

С понедельника, 13 октября, сообщает Челябинск.ру меняется трасса движения троллейбусного маршрута № 26 — он снова будет ходить из Первоозёрного через центр города, улицы Братьев Кашириных и Чичерина к северо-западной конечной, на углу Комсомольского проспекта и Молдавской.

MapBBCode | Map © OSM (Edit) | Tiles © GIScience Heidelberg

На марсрутах.ру уже опубликовано свежее расписание.

Трамвайная схема — можно и в 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:

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

Результат:

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

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

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

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

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

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

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

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

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

  1. buildings = Polygons(
  2. name = 'buildings',
  3. fields = (
  4. ('area', PseudoArea()),
  5. ('addr:housenumber',String()),
  6. ('building:levels',Integer()),
  7. ('height',Integer()),
  8. ('belongs_to_susu',Bool()),
  9. ),
  10. mapping = {
  11. 'building': (
  12. '__any__',
  13. ),
  14. 'railway': (
  15. 'station',
  16. ),
  17. 'aeroway': (
  18. 'terminal',
  19. ),
  20. }
  21. )

Выставить флаг принадлежности можно выполнением 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/

Пока в городе дзюдо, проще ездить на велосипеде

Аргументы и факты — Челябинск: На время проведения ЧМ по дзюдо в Челябинске вводится ограничение движения:

Улицы северо-запада и центра Челябинска будут перекрывать в связи с проведением в столице Южного Урала Чемпионата мира по дзюдо, сообщили в городском ГИБДД.

Ограничение движения в разное время коснется улиц Труда, Северокрымской, Братьев Кашириных, Салавата Юлаева, Воровского, Доватора, Энгельса, Молодогвардейцев, Лесопарковой, Худякова, Чичерина, 250 лет Челябинска, Комсомольского проспекта, проспекта Ленина, Красной.

Конкретные даты и места не указаны. Если отметить все перечисленные улицы, получится так:

MapBBCode | Map © OSM (Edit) | Tiles © GIScience Heidelberg

Пятница пройдёт в пробках

На полдня в рабочий день (и пятничный вечер захватят) перекроют дороги, сообщает Ura.ru, и даже пешеходам житья не будет:

В пятницу, 6 июня, во время проведения Всероссийского форума «ЖКХ — новое качество», будет временно ограничено движение транспорта и пешеходов на десяти улицах в разных районах Челябинска …

«С 14.00 до 19.00 возможно временное ограничение движения транспорта и пешеходов на следующих улицах города: Бродокалмакский тракт, Героев Танкограда, Лесопарковая, Бажова, 250 лет Челябинску, проспект Комарова, Салавата Юлаева, проспект Ленина, Худякова и Университетская Набережная» …

Напомним, участники форума начнут съезжаться на Южный Урал в четверг, 5 июня. Уже на следующий день им придется подняться в семь, чтобы с 8 до 9 утра собраться в гостинице «Рэдиссон Блу». … Для мероприятий … в Челябинске избраны площадки на базе гостиницы «Рэдиссон Блу», завода «Прибор», ДК «Железнодорожников», техникума городского хозяйства и Ледовой арены «Трактор».

Перечисленные улицы и учреждения отмечены синим. Красным — наиболее вероятный маршрут.

MapBBCode | Map © OSM (Edit) | Tiles © GIScience Heidelberg

Вот зачем нужно это показушное катание по центру города? По такому вот «гостевому маршруту» — зачем? Могли бы по проспекту Победы быстренько довезти, через старые дома да деревню, да многоэтажки панельные.

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

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

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

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

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

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

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

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