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


Лесные кварталы


Шаг 1. Изыскания.
Можно конечно угадать пальцем в небе как они могли бы обозначаться, но надёжней отправиться в вики-osm. И там мы можем найти следующее: boundary=forest_compartment


Следовательно лесные кварталы обозначаются полигонами с тегом boundary=forest_compartment. Правда там есть уточнение, что первоначально это обозначалось как boundary=forestry_compartment, но являлось менее грамотным. А так как количество использований со старым обозначением существенно (по данным taginfo около 4 тыс. раз) не будем сбрасывать его со счетов.


Шаг 2. Данные.
Данные возьмём с Geofabrik. Скачиваем файл на всю Россию — russia-latest.osm.pbf. С помощью osmconvert получим данные в формате o5m для последующей фильтрации.


osmconvert russia-latest.osm.pbf -o=russia-latest.o5m

Теперь фильтруем только нужные нам данные с помощью osmfilter


osmfilter russia-latest.o5m --keep="boundary=forest_compartment =forestry_compartment" -o=forest_compartment-local.o5m

Шаг 3. Векторные тайлы.
Немного кратко теории. Старый подход — из большой базы запросить немного данных, получить из них картинку, сохранить её, чтобы в будущем отдать клиенту. В новом — из большой базы запросить немного данных и сохранить их для последующей передачи клиенту. А клиент пусть сам превращает их в картинку. Профит как бы на лицо — нагрузку по рендеру картинки мы перенесли на плечи клиента. Из минусов — на кофеварке возможно не удастся увидеть карту, нужна поддержка WebGL.


И так Mapbox предложил формат векторных тайлов и контейнер для них в виде sqlite базы. Поэтому теперь это не россыпь файлов по папкам, а аккуратный одинокий файл. Векторный тайл содержит в себе логические слои (дома, дороги и т.д.), которые состоят из геометрии и атрибутов.


Вот их мы и будем готовить для наших лесных кварталов. Я буду использовать инструмент TileMaker. На вход он принимает данные OSM в формате pbf, поэтому после фильтрации нам нужно сконвертировать обратно в этот формат.


osmconvert forest_compartment-local.o5m -o=forest_compartment-local.pbf

Теперь нужно объяснить TileMaker какие слои и с какими атрибутами нам нужны, согласно документации.


Шаг 4. Слои?
А какие же нам нужны слои? А это зависит от того, что мы будем показывать. Т.е. прежде всего мы должны уже как-то представлять себе визуальную часть. И как её можно добиться из имеющихся данных. Из данных OSM у нас есть сетка многоугольников и их атрибуты. В атрибутах есть название лесничества и номер квартала.


Сырые данные OSM


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



Поэтому для векторных тайлов готовят отдельный слой с надписями, когда ещё есть вся необходимая информация о геометрии.


Итог: нам нужно два слоя, полигональный для заливки и точечный для подписи. Создаём файл config.json.


{
    "layers": {

    },
    "settings": {
        "minzoom": 11,
        "maxzoom": 11,
        "basezoom": 14,
        "include_ids": false,
        "author": "freeExec",
        "name": "Forest Compartment RUS",
        "license": "ODbL 1.0",
        "version": "0.1",
        "description": "Forest compartment from OpenStreetMap",
        "compress": "gzip",
        "metadata": {
                "attribution": "<a href=\"http://www.openstreetmap.org/copyright/\" target=\"_blank\">&copy; Участники OpenStreetMap</a>",
            "json": { "vector_layers": [

                    ] }
        }
    }
}

В разделе слои указываем что нам нужно


    "layers": {
        "forest_compartment": { "minzoom": 11, "maxzoom": 11 },
        "forest_compartment_label": { "minzoom": 11, "maxzoom": 11 }    
    },

Указаны названия слоёв и на каких масштабах их будем показывать.


    "json": { "vector_layers": [
                { "id": "forest_compartment", "description": "Compartment", "fields": {}},
                { "id": "forest_compartment_label", "description": "Compartment", "fields": {"ref":"String"}}
            ] }

В метаданных мы подсказываем для будущего визуализатора какие атрибуты у нас доступны. Для слоя с метками у нас будет номер квартала в ref.


Шаг 5. Обработка данных.
Для этой цели служит скрипт на языке lua, который и будет решать какие объекты из данных OSM нам нужны, в какой слой их отправить и с какими атрибутами.


Начнём с шаблона файла process.lua.


-- Nodes will only be processed if one of these keys is present
node_keys = {  }
-- Initialize Lua logic
function init_function()
end
-- Finalize Lua logic()
function exit_function()
end
-- Assign nodes to a layer, and set attributes, based on OSM tags
function node_function(node)
end
-- Similarly for ways
function way_function(way)
end

Что мы здесь имеем:


node_keys — точек в данных OSM очень и очень много, если мы с каждой будем тыкаться в этот скрипт, то обработка продлится очень долго. Это некий фильтр, говорящий нам, точки с какими ключами нам интересны.


function node_function(node) — функция будет вызываться на каждую интересную нам из предыдущего пункта точку. Тут мы должны решить что с ней делать.


function way_function(way) — функция, которую будут вызывать на любую линию и на отношения с типом multipolygon и boundary, т.к. они считаются площадными объектами.


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


node_keys = { "boundary" }

Теперь напишем функцию их обработки:


function node_function(node)
    local boundary = node:Find("boundary")

    if boundary == "forestry_compartment" or boundary == "forest_compartment" then
        local ref = node:Find("ref")
        if ref ~= "" then
            node:Layer("forest_compartment_label", false)
            node:Attribute("ref", ref)
        end
    end
end

Что тут происходит: читаем значение ключа boundary через node:Find("ключ"). Если это forest_compartment, то читаем номер квартала из тега ref. Если он не пустой, то этот объект добавляем на наш слой с метками, через Layer("название_слоя", нет_объект_не_полигон). В атрибут слоя ref сохраняем номер квартала.
Почти так же просто и для площадных кварталов:


function way_function(way)
    local boundary = way:Find("boundary")

    if way:IsClosed() and ( boundary == "forestry_compartment" or boundary == "forest_compartment" ) then
        way:Layer("forest_compartment", true)
        way:AttributeNumeric("nomerge", way:Id())

        local ref = way:Find("ref")
        if ref ~= "" then
            way:LayerAsCentroid("forest_compartment_label", false)
            way:Attribute("ref", ref)
        end
    end
end

Тут дополнительно проверяем, что линия замкнута, т.к. бывает, что теги присутствуют просто на отрезках. Стоит обратить внимание, что слой forest_compartment площадный (поэтому второй аргумент в функции Layer("", true)), а место для подписи берём как центр фигуры LayerAsCentroid.


Так же стоит обратить внимание на атрибут который мы добавляем, хотя и не указывали его в конфиге — nomerge. Он нужен, чтобы победить другую особенность, на этот раз уже конвертера TileMaker (хотя в новой версии появился параметр для её отключения).


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


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


Теперь пришла пора запустить процесс создания векторных тайлов.


tilemaker forest_compartment-local.pbf --output forest_compartment-local.mbtiles

В результате у нас должен появиться файл forest_compartment-local.mbtiles


Шаг 6. Создаём стиль.
Заводим аккаунт на mapbox.com. В Mapbox Studio в разделе Tileset создаём новый tileset, перетаскивая в окошко загрузки наш созданный ранее файл. В течении минуты он должен обработаться и добавиться в список.


Теперь переходим в раздел Styles и создаём новый на основе готового Light, чтобы у нас были видны основные элементы карты, как то дороги, населённые пункты и т.д. Отправляемся в Чебоксары ибо там были замечены лесные кварталы.


Спускаемся на 11 уровень масштаба (мы ведь только для него создали тайлы) и нажимаем кнопку Add layer. В закладке data source находим наш источник данных forest_compartment-local-XXXXX, в нём выбираем полигональный слой. Он должен подсвечиваться справа зелёным.


Добавления слоя


Далее на вкладке style задаём цвет заливки — зелёный, а обводки коричневый.


Настройка цвета


Теперь осталось добавить подписи. Добавляем новый слой, только на этот раз выбираем в данных forest_compartment_label, а тип выбираем symbol, справа должны появиться номера.


Добавляем слой меток


В закладке стиля укажем что нужно выводить наш атрибут ref.


Атрибут для подписи


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


P.S.: Возможно в дополнительной статье я поведаю как я добивался расположения подписи с названием лесничества на группе кварталов в него входящих.

Комментарии (11)


  1. brzsmg
    22.02.2019 13:05

    Когда смотрел что у OSM внутри, тоже удивился сколько там полезной информации.
    Тип дороги, направление.
    Тип ограждения.
    Для дома есть простонародное название. POI.
    Готовая БД для навигатора.

    Я так понимаю, там можно свои атрибуты добавлять?
    Или это какой то строгий список?


    1. staticlab
      22.02.2019 13:37

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


      Бд-то хорошая, но из-за "базарного" метода внесения изменений в данных может быть мало консистентности. Для создателей навигаторов это большая головная боль.


    1. JediPhilosopher
      22.02.2019 15:56

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

      OSM конечно восхитительная вещь, не имеющая аналогов для разработчиков и исследователей, но к данным там надо очень осторожно подходить.


      1. resetme
        23.02.2019 17:36
        +1

        Мрак это когда данных вообще нет. Для того и был создан OSM чтобы постоянно уточнять, перепроверять и улучшать геоданные. Для человека со стороны кажется там анархия и бардак, но если внимательно вникнуть в суть происходящего, то не все так плохо как кажется. Есть DWG которая следит за данными, есть активные контрибуторы в каждой области. Вандализм исправят и откатят быстро. Если неадекваты залезут на карту, то их забанят, несмотря на демократичность OSM, как вот этотого персонажа www.openstreetmap.org/user/Serg%20Podarevsky/blocks

        Чем больше людей на OSM интересующихся геоданными, тем она будет лучше.


  1. amarao
    22.02.2019 13:13

    При редактировании OSM у меня было острое ощущение одиночества, отсутствия комьюнити. Страниц обсуждения (как в обычных вики) нет, где кого о чём-то спрашивать не понятно.


    1. freeExec Автор
      22.02.2019 13:45

      На форуме, а нынче можно в телеграме.


    1. AAT666
      22.02.2019 13:53

      А это -> forum.openstreetmap.org/viewforum.php?id=21

      Я, правда, там не спрашивал ничего но, вроде, отвечают. Хотя, похоже, пыл ОСМщиков идет на убыль… Раньше, как видно, обсуждаемых тем было больше и они реально обсуждались.


      1. resetme
        23.02.2019 17:25

        А вы не обсуждайте, а делайте правки на карте. Самые интересные и полезные обсуждения не на форуме, а в комментариях к правкам. Так обычно на OSM заинтересованные пользователи знакомятся.


        1. AAT666
          23.02.2019 17:53

          А я и делаю — у меня ник такой же как и здесь. Правда, только по воде — ибо, рыбак. Города и асфальтовые дороги мне неинтересны. А обсуждать надо — так как руководства для начинающего ОСМщика нет. От слова — вообще. И либо, изучай вики — в которой черт ногу сломит, либо — задавай вопросы в форуме.

          Комментарии к правкам надо искать — что тоже далеко не очевидно как делать и куда смотреть. Хотя, может, я и ползаю там где нет комментариев…

          Да и вообще, я так понял, основная масса правщиков — это заинтересованные (скорее всего материально) разработчики каких-либо мобильных и веб сервисов. Таких как я — мизер. Хотя у них и есть информация по труднодоступным и малопосещаемым местам.

          ЗЫ: Большое мерси mavl'у — если он тут есть. Реально тыкал носом в мои ошибки! )))


  1. sshikov
    22.02.2019 15:01

    Хм. Долго же вы писали продолжение… )


  1. ebt
    22.02.2019 23:33

    В продолжение начатой темы: также есть небезынтересный проект OpenRailwayMap по извлечению из OSM данных мировой железнодорожной инфраструктуры (кое-какие технические детали).