Как стать автором
Обновить

OpenStreetMap часть средняя: визуализация скрытых данных

Время на прочтение6 мин
Количество просмотров3.8K

Все мы привыкли, рассматривая классические базовые подложки в интернете, видеть населённые пункты, дороги и их названия, дома с их номером. Но даже у этих объектов свойств куда больше чем просто имя или номер. У зданий это этажность, у дорог количество полос, а у городов количество жителей. Но это только верхушка айсберга — 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.: Возможно в дополнительной статье я поведаю как я добивался расположения подписи с названием лесничества на группе кварталов в него входящих.

Теги:
Хабы:
Всего голосов 13: ↑13 и ↓0+13
Комментарии11

Публикации