- Предисловие
- Общий план
- Рисование
- Добавление данных высот
- Стили карты
- Компоновка карт в итоговый файл
- Прочие заметки
Предисловие
В районе 2005 года друг научил меня рисовать карты для GPS. Тогда это был Большой-Большой Секрет, потому что никаких OpenStreetMap не существовало, Google Maps был в далёком проекте, а люди на создании карт для Garmin GPS строили целые бизнесы. Типичный набор софта для создания карт выглядел примерно так: MapInfo Pro, cgpsmapper, globalmapper, mapedit и чёрт знает что ещё. В качестве источника данных использовался наложенный слой Генштаба или другой советской карты, спутниковые многодиапазонные снимки LandSat (отличная штука, кстати) и данные высот из SRTM. С тех пор в IT сменилось несколько эпох и рисование карт стало деятельностью, доступной практически каждому, «без регистрации и SMS».
Две недели назад имел сомнительное удовольствие рисовать карту для ММБ 2017/осень. Решил написать эту статью поскольку процесс по-прежнему далёк от дружественности пользователю.
Общий план
Сейчас за основу принято брать готовый вектор из OSM. Способов скачивания куска карты описано в их вики довольно много. Карта получена, теперь надо:
- Нарисовать свои детали
- Добавить данные SRTM1
- Создать стиль отрисовки элементов карты на приборе Garmin (лес будет отображаться ёлочками, поле травинками т.д.)
- Скомпоновать карту
- Залить на прибор
Рисование
Тут всё просто: выбираем любой понравившийся редактор OSM и рисуем. Я остановился на josm. Важно выбрать какую-нибудь хорошую подложку. К сожалению, Yandex.Карты или Google Maps использовать нельзя из-за лицензионных ограничений. Но есть много неплохих альтернатив, доступных прямо в josm.
В OSM все объекты имеют набор атрибутов вида ключ=значение
. Таких
атрибутов у объекта может быть от нуля до бесконечности. По ним
дальнейшие утилиты поймут что вы имели ввиду, нарисовав вон тот
большой многоугольник. По большому счёту, их можно выставлять любыми,
однако рекомендую использовать стандартные. Это спасёт вас от
наступления на давно известные грабли, а также позволит в дальнейшем
экспортировать ваши изменения обратно в OSM.
Добавление данных высот
Проще всего воспользоваться Windows утилитой Srtm2osm. Она возьмёт заданную URL’ом область из OSM, скачает для неё данные SRTM, сама преобразует их в формат OSM.
Выглядит это примерно так:
Srtm2Osm.exe -bounds3 'http://www.openstreetmap.org/export#map=12/43.0626/43.1251' -cat 600 200 -step 40
Здесь:
- URL скопирован из браузера и показывает кусок, для которого хотелось бы получить линии высот
-cat 600 200
шаг в метрах высоты для мажорных и средних линий высот-step 40
шаг в метрах высоты для минорных линий высот.
На выходе у нас получится файл srtm.osm
.
Стили карты
Новая для меня тема и одновременно большое приятное открытие: стили по умолчанию у моего Garmin ужасны! Нормальный стиль радикально улучшает читаемость карты. И я бы сказал, что на последнем ММБ мой стиль дал больше, чем весь вклад непосредственно в картографирование района.
Проблема в том что не может быть стиля, который универсально подойдет для всех целей. Автомобилисту на трассе просеки на фиг не нужны, а у ММБшника в них смысл жизни. Но вы только посмотрите, насколько прекрасно может выглядеть карта, в которой попытались нарисовать универсальный стиль:
Как это работает
С одного конца у нас OSM-карта, где у всех объектов есть атрибуты, я писал о них выше. С другого конца имеем будущую карту для Garmin в формате IMG, где есть свои ID типов объектов. Причем, если идентификаторы типов линий и полигонов на некоторых приборах можно задавать свои, то для точек ID должны быть как завещал Garmin2. Их примерный список.
Наша задача:
- Сопоставить по каким-то правилам атрибуты объектов в OSM с ID типов в Garmin
- Нарисовать для них картинки.
Рисование картинок
Начну во второго пункта программы т.к. без него невозможен первый. Формат хранения XPM (тут все фотошоперы поперхнулись смузи, а программисты на ANSI C ехидно потёрли руки). Я рисовал их в Gimp. После сохранения важно убедиться, что количество цветов и т.д. в получившемся XPM получилось сколько надо, поскольку Gimp, похоже, иногда в этом месте умничает.
Кратко о заголовке XPM. Он имеет следующий вид:
<width> <height> <colors> <chars per color>
Примеры заголовка:
Xpm="32 8 4 1"
битмап шириной 32 пикселя, высотой 8, 4 цвета, каждый цвет представлен одним символом.Xpm="16 16 1 1"
одноцветный битмап 16x16, цвет закодирован одним символом.Xpm="0 0 24 1"
более интересный вариант. Картинка нулевого размера из 24 цветов, каждый закодирован одним символом.Xpm="1 1 255 2"
картинка размером в один пиксель, который закодирован двумя символамиXpm="0 0 1 0"
картинка без картинки нулевого размера, каждый цвет закодирован нулевым количеством символов, есть один цвет
Техническая спецификация расширения над XPM от Garmin захватывает дух даже у бывалого программиста:
Одноцветные полигоны (заливка) задаются XPM, в котором отсутствует битмап. Уточнения:
<chars per color>
всегда равен нулю<colors>
= 1: задает цвет полигона<colors>
= 2: задает дневной и ночной цвет полигона- иное количество цветов запрещено
Полигоны с битмапом задаются с
<chars per color>
равным единице. Уточнения:<chars per color>
всегда равен единице- Ширина и высота битмапов любых полигонов должна быть 32 пикселя
<colors>
= 2: битмап с двумя цветами, один из цветов может быть прозрачным<colors>
= 4: битмап с двумя цветами, варианты цветов для дневного и ночного режимов.- иное количество цветов запрещено
Линии без битмапа задаются
<chars per color>
равным нулю. Дополнительно Garmin ввела константыLineWidth=X
иBorderWidth=Y
, которые задаются в файле спецификации стиля карты перед XPM. Например:LineWidth=3 BorderWidth=1 Xpm="0 0 2 0"
Уточнения по линиям без битмапа:
<chars per color>
всегда равен нулюLineWidth
иBorderWidth
принимают значения от 0 до 32<chars per color>
всегда равен нулю<colors>
= 1: единственный цвет задает цвет линии<colors>
= 2: дневной и ночной цвет линии<colors>
= 4: дневной цвет линии, дневной цвет обводки линии и плюс ночные варианты- иное количество цветов запрещено
Линии с битмапом. Задают картинку линии. Ограничения и формат:
<chars per color>
всегда равен единице<width>
всегда 32<colors>
= 1: единственный цвет — это цвет линии<colors>
= 2: дневной и ночной цвет линии<colors>
= 4: дневной цвет линии, дневной цвет обводки линии и плюс ночные варианты- иное количество цветов запрещено
- один из цветов может быть прозрачным
- по аналогии с
LineWidth=X
, неофициально существуют расширенные булевы параметры за пределами XPM:UseOrientation=Y
иAntialias=Y
. Оба созданы для попытки отобразить рисунок линии под разными углами более качественно. Но, говорят, не всегда это удается.
Из личных наблюдений: линии с битмапами где <height>
меньше 4
смотрятся отвратительно.
Документация на формат XPM для POI (иконок) сюда не поместится, предлагаю ознакомиться самостоятельно.
Сопоставление атрибутов OSM идентификаторам в Garmin
Под Windows вы можете воспользоваться готовым софтом для рисования стилей карт для Garmin3. Но он мне показался мягко говоря убогим — начиная с того, что они предлагают все картинки рисовать в своём встроенном редакторе.
Сначала нам надо сопоставить атрибуты OSM конечным идентификаторам в Garmin. Для этого нужна директория (назовите её как вам удобно) с примерно таким содержимым:
lines
points
polygons
version
(есть и другие файлы, но меня он не коснулись)
Это и есть «стиль», набор правил. Здесь version содержит просто
циферку, а lines
, points
и polygons
описывают правила
преобразования. Они выглядят так:
(landuse=farmland | landuse=farmyard) [0xa resolution 18]
landuse=industrial [0xb resolution 18]
landuse=quarry [0xc resolution 18]
Описываю первую строчку человеческим языком: «если есть атрибут
landuse=farmland
ИЛИ есть атрибут landuse=farmyard
, ТО выставить
для объекта Garmin идентификатор 0xa, а объект начать показывать
начиная с масштаба 18».
На самом деле, выбор стиля для объекта — большая и очень больная тема
в картографии; не стоит рисовать реки поверх дорог, дороги поверх
туннелей, ручьи в мелком масштабе (но на самом деле надо, если у нас
гидрография) и т.д. Но для нашего маленького заводика по утилизации
промышленных отходов производству карт для outdoor все эти детали
перфекциониста можно опустить.
Специально для перфекционистов оставлю боль и ссылку на мануал. Для
остальных достаточно понять, что есть ключ=значение
объектов OSM,
есть булева логика и есть Garmin ID, который совпадающим в заданным
правилом объектам надо присвоить.
Тут важно понять, что на этом этапе вы выбираем, какие элементы исходной карты мы хотим видеть на своей конечной карте. Своеобразный grep+map+fold.
Сопоставление Garmin ID конкретной картинке (TYP файл)
Недавно мы нарисовали кучу офигенных картинок чумовом формате XPM. И
мы не можем дождаться момента, когда эти маленькие Моны и Лизы
размером 32 на 32, сохранённые в виде Цэ исходников, появятся на
экране Garmin. Необходимо сопоставить ID типов Garmin с
картинками. Открываем текстовый редактор и создаем файл
style.txt
:
[_id]
FID=156
CodePage=1251
[end]
[_drawOrder]
Type=0xa,6
Type=0xb,6
Type=0xc,6
Type=0xd,6
Type=0xe,6
Type=0xf,15
... и т.д. ...
end]
[_polygon]
Type=0xa
String1=0x19,some area
Xpm="32 32 2 1"
" c #A72300"
". c #000000"
" . "
" ... "
" . "
" "
" . . "
" ... ... . "
" . . ... "
" . "
" "
" "
" "
" "
" "
" . . "
" ... ... "
" . . . . "
" ... ... "
" . . "
" "
" "
" "
" . "
" ... . "
" . . ... "
" ... . "
" . "
" "
" . "
" ... "
" . . "
" ..."
" . "
[end]
... и т.д. ...
[_line]
Type=0x34
String1=0x19,some line
Xpm="32 4 2 1"
". c #000000"
"+ c #4A4A4A"
"................................"
"++++++++++++++++++++++++++++++++"
"++++++++++++++++++++++++++++++++"
"................................"
[end]
... и т.д. ...
«Здравствуй, ад!».
В секции [_id]
указываем кодировку и идентификатор стиля. Регистр
ключевых слов важен во всём файле!
В секции [_drawOrder]
. Описываем все использованные Garmin ID
полигонов и их приоритет при отрисовке. Чем больше число, тем выше
приоритет.
Далее идет серия секций с именами [_polygon]
, [_line]
и
[_point]
, где мы описываем всё, над чем так долго работали в этих
наших фотошопах:
Type
: Garmin IDString1
: код языка (для русского 0x19) и название типа, например “просека”. Обратите внимание, что у нас CodePage=1251, кодировка файла должна соответствовать!Xpm
: наша картинка без Цэ-заголовка.
Ура, стиль готов! Этот txt файл будет скопилирован в TYP
файл,
который понимает Garmin, который в свою очередь будет вкомпилирован в
конечную карту. В разделе Компоновка карт в итоговый файл вы узнаете,
как эту шаткую конструкцию преобразовать в карту, а в нижеследующем
разделе узнаете, как я попытался от этого ада избавиться с помощью
Emacs и чуть-чуть Perl.
Наводим порядок в стилях с помощью Emacs
В Емаксе есть такой замечательный режим — org-mode. Вероятно первоначально он создавался для организации закладок и записок, но сейчас в нём создают блоги (и я тоже), составляют списки дел, учитывают табельное время, пишут документацию, статьи и книги, применяют в качестве альтернативы MS Excel. Короче говоря, всё что можно структурировать можно запихнуть и в документ org-mode. При этом, org-документ остается простым текстовым файлом, который легко парсить и редактировать чем угодно. Кроме того, Emacs сам по себе умеет показывать картинки прямо в тексте.
Я не смог не воспользоваться такой замечательной возможностью и решил составлять свой первый стиль в формате org. Как это выглядит, можете посмотреть на примере моего стиля для рогейнов (картинки лежат рядом). В Emacs это выглядит довольно прилично:
Для генерации конечных файлов написал Perl-скрипт. К сожалению, до следующего рисования карт нет никакой мотивации выводить его из глубокой бета-версии. Тем не менее, он работает, генерит директорию с polygons, lines, points, mkgmap-config.txt и style.txt, указывает на какой-то небольшой набор ошибок.
Что делать с получившимся выхлопом скрипта читайте в следующем разделе.
Полезные ссылки по главе (больше мне для изучения)
Компоновка карт в итоговый файл
Для этого нам понадобится mkgmap — утилита, умеющая на самом деле довольно много интересных штук, но главное способная преобразовать OSM в Garmin IMG.
Компиляция стиля отрисовки карты (опционально)
Можно скомпилировать style.txt в TYP-файл. Делается это простой командой:
mkgmap style.txt
Этот шаг как правило не имеет смысла, поскольку если подсунуть style.txt в аргументы mkgmap при генерации карты (в wiki говорят, что это должен быть самый последний аргумент!), то TYP-файл будет создан автоматически.
Упаковка mkgmap-фильтров стиля
Стиль необходимо упаковать в zip-архив с такой структурой:
styles/MY_STYLE_NAME/version
styles/MY_STYLE_NAME/lines
styles/MY_STYLE_NAME/points
styles/MY_STYLE_NAME/polygons
Обязателен только version, и он обязательно должен иметь содержимое “1” с переводом строки.
Обновление от [2018-11-29 Чт]: теперь мой скрипт выполняет этот шаг автоматически.
Компоновка карты
Мой конфиг для mkgmap выглядит примерно так (пусть имя файла будет mkgmap-args.txt):
generate-sea: land-tag=natural=background
location-autofill: is_in,nearest
housenumbers
tdbfile
show-profiles: 1
ignore-maxspeeds
add-pois-to-areas
add-pois-to-lines
link-pois-to-ways
make-opposite-cycleways
process-destination
process-exits
preserve-element-order
net
route
index
nsis
gmapsupp
unicode
family-id: 157
product-id: 1
code-page: 1251
style-file: /tmp/mkgmap-style/style.zip
style: generated
input-file: style.txt
Перехожу в каталог с OSM файлами и запускаю:
mkgmap -c mkgmap-args.txt -n 12345678 --description='Bezengi - my map' *.osm
Аргумент -n
задает “имя” карты (на самом деле уникальный
идентификатор). Если используется splitter (см. ниже), то этот номер
будет проставлен автоматически, на основе базового.
В --description
рекомендую дать своё описание карты — оно будет
показано в меню выбора карты на приборе.
На выходе получится несколько IMG файлов и нужный нам gmapsupp.img
(убедитесь, что соответствующая опция включена в конфиге). Этот файл
как есть закидываем в директорию Garmin на устройстве. На современных
приборчиках файл можно как-нибудь по-своему назвать.
splitter
Для старых приборов также имеет смысл поделить картинку на логические квадраты, работать карта будет быстрее. Это можно сделать с помощью утилиты splitter. Использовать её примерно так:
java -jar splitter.jar --num-tiles=25 --mapid=1234000 --keep-complete=false map.osm
Здесь:
--num-tiles=25
разбить карту на 25 кусочков. Чем меньше кусочек, тем проще с ним дружить приборчику, но тем чаще придется подгружать другие кусочки при скроллинге карты.--mapid=12340000
кусочки будут иметь ID 12340000, 12340001 и т.д. Важно! У каждой карты на приборе должен быть свой уникальный идентификатор. Как этого достичь — задача для вашей фантазии.--keep-complete=false
. Если линия или полигон выходит за пределы тайла, то обрезать её. Это может создать проблемы с построением маршрутов и отрисовкой, зато очень большие объекты не добавят тормозов.
Splitter создает файлы с расширением PBF, которые mkgmap тоже умеет принимать в на вход. Пример:
mkgmap -c mkgmap-args.txt --description='Bezengi - my map' *.pbf
Прочие заметки
Скачивание OSM-данных
В JOSM можно легко скачивать большие куски данных. Для этого зайти в меню Файл → Скачать данные. Там переключиться во вкладку “Скачать с Overpass API”. Там можно выделить интересующую область и выполнить запрос:
(
node({{bbox}});
<;
);
out body;
Sparital Radar Topology Mission — многолетняя программа NASA по съёмке относительных высот поверхности земли. На 2017 год есть полное покрытие планеты по сетке с шагом 30 метров ↩︎
Во всяком случае, мне не удалось задать полностью свои ID для моего Garmin; нестандартные ID он просто игнорировал ↩︎
ключевые слова для поиска: TYP file editor, Garmin map style editor ↩︎