Как я делал свою первую карту на Leaflet.js.

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

Итак задание было следующее: есть черно-белый планшет (маленький кусок карты города) размером 5913x7863 пикселей в формате .bmp + .shp слои.
(изначально карты были отрисованы в формате .dwg (формат автокада), но это закрытый формат и с ним ничего не сделаешь, поэтому ребятам пришлось сохранить каждый слой отдельно в .shp + атрибутивные данные в .dbf)

Из этого всего нужно сделать онлайн карту, основной функционал которой — это вывод атрибутов при нажатии на слой и включение/отключение этих слоёв.

Выбор пал на leaflet.js, так как это оболочка с открытым кодом, на ней сделаны OSM и мой любимый 2GIS. К тому же он хорошо работает на мобильных устройствах.

Начинаем с того, что нужно порезать карту на тайлы (квадратики 256х256 px) чтобы карта быстро грузилась. Так как я мало чего в этом понимал, я не стал копаться с gdal2tiles.py, а его графическая оболочка maptiler стоит денег, я просто скачал простенькую программку LeafletPano. Она просто режет любую картинку на тайлы, достаточно задать минимальный и максимальный уровень приближения (переменную z).

Скриншот LeafletPano


Когда мы все порезали и загрузили на хостинг, можно и подключать:

Код скрипта
map = L.map('map', {                                                       // подключаем карту
     crs: L.CRS.Wall,                                                    // выбираем систему координат (об этом ниже)
        maxZoom: 5,                                                     // максимальный zoom (приближение)
         minZoom: 0                                                      // минимальный zoom (приближение)
        }).setView([1700,170], 0);                                      // точка просмотра при заходе на сайт с картой
 
        var osn = L.tileLayer('./images/map/{z}-{x}-{y}.jpg', {         // подключаем тайлы
            attribution: 'супер-карты',                               // комментарий (отображается справа внизу)
            continuousWorld: true,                                   // В документах написано, что если мы используем не настоящие координаты, то должно быть true (что-то связанное с долготой и широтой)
            noWrap: true                                                  // Не загружает до бесконечности лишние тайлы вне рамок карты
        }).addTo(map); 


После нарезки нужно привязать тайлы (квадратики 256х256 px) к координатам. И тут начинается самое интересное: дело в том, что система координат этих карт — условная местная план схема, то есть это плоская местная городская система координат, которая по сути не имеет никакого отношения к широте и долготе.

Итак, что предстояло сделать: карта размером 5913x7863 пикселей должна была находится вот в таких координатах:



В чем трудность? В том, что, как я уже говорил, тайлы — это квадратики 256х256px, а число пикселей по высоте 7863 на 256 без остатка не делится, не хватает 73 пикселя. Соответственно программа LeafletPano (как и любой другой нарезчик на тайлы) дополняет последние квадратики белым цветом, чтобы их размер был 256х256, а не 256х183.
Выглядит это так:



Аналогично с шириной — 5913 также не делится на 256 и не хватает 231px.



И так как речь идёт о плоской системе координат, то заходя в документацию мы видим готовую, встроенную в Leaflet систему L.CRS.Simple (она предназначена для плоских изображений, без привязки к координатам). Как она работает? L.CRS.Simple выставляет ширину и высоту на 256 и -256 соответственно.



На основе L.CRS.Simple мы и будем создавать нашу систему координат при помощи transformation = new L.Transformation(a, b, c, d). Формула в документации приведена очень простая: (x,y) трансформируются в (a*x + b, c*y + d), всего то 4 числа. Дальше немного математики:

Вычисляем высоту в нужных нам координатах:
1968.2715 - 1468.9700 = 499,3015
(Это высота нашей карты в координатах).

Делим ее на высоту в пикселях:
499,3015 / 7863 = 0,0635001271779219

Умножаем на 73 недостающих пикселя:
0,0635001271779219 * 73 = 4,6355092839883

Прибавляем высоту карты в координатах к высоте недостающих пикселов в координатах (надеюсь понятно написал):
499,3015 + 4,6355092839883 = 503,9370092839883
(Получили высоту карты + белый остаток тайла (те самые 73 пиксела в координатах)).

Теперь делим высоту в предыдущей системе координат (L.CRS.Simple) на нужную нам высоту:
256 / 503,9370092839883 = 0,5079999985786595

И вычитаем из единицы этот коэффициент:
1 - 0,5079999985786595 = 0,4920000014213405

Ура! Мы получили a и -c из формулы (a*x + b, c*y + d), осталось получить b и d

координату по Х (почему-то без минуса) умножаем на коэффициент:
11.7050 * 0,4920000014213405 = 5,758860016636791

и координату по Y умножаем на коэффициент
1968.2715 * 0,4920000014213405 = 968,389580797584


Создаем новую систему координат:

L.CRS.Wall = L.extend({}, L.CRS.Simple, {
  transformation: new L.Transformation(0.4920000014213405, 5.758860016636791, -0.4920000014213405, 968.389580797584),
});

Теперь подключаем .shp. Основная суть этих интерактивных карт в выводе атрибутивной информации при нажатии на карту. При подключении .shp она есть, но русские надписи выводятся кракозябрами. Атрибутивная информация хранится в файле .dbf и дело явно в его кодировке, но что бы вы не делали с ним — это не поможет (я испробовал огромное количество способов изменить кодировку .dbf), поэтому единственный выход — это перевести .shp в более родной для Leaflet формат .geojson с указанием кодировки utf-8. Сделать это можно при помощи программы QGIS



Далее мы подключаем плагин для Leaflet, который облегчает форму записи подключение слоев .geojson под названием leaflet-ajax

Подключаем .geojson слои:

Код вставки .geojson
var dorogi = new L.GeoJSON.AJAX("geoj/dorogi.geojson", {onEachFeature: function (feature, layer) {     
      if (feature.properties) {
        var info = function(k){
            var str = k + ": " + feature.properties[k];
            return str;
        }
        layer.bindPopup(Object.keys(feature.properties).map(info).join("<br />"),{maxHeight:200});
        layer.setStyle({ color: '#555', clickable: true, weight: 4, opacity: 0.8});
      }
    }});


Ну и в конце делаем отображение слоев включаемым/отключаемым, для этого вставляем L.control.layers:

Код L.control.layers
var baseMaps = {};
    var overlayMaps = {
      "Планшет": osn,
       "Дороги": dorogi,
       "Газопровод": gazoprovod,
      "Водопровод": vodoprovod,
      "Здания": zdaniz_new
	};
    L.control.layers(baseMaps, overlayMaps).addTo(map);    


Итог:



P.S. Что в дальнейшем: хочется, чтобы карты адекватно выглядели, как на OSM, а не просто здания обозначеные линиями. Также я не уверен, что делаю самым адекватным способом, наверное есть способы с GeoServer или что-то вроде этого, где не надо так много делать вручную. Скорее всего нужно копать в сторону устройства карт на OSM, потому что сейчас мои карты выглядят ужасно.

Любые подсказки и замечания приветствуются.

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


  1. aramby
    12.10.2015 15:09

    Вообще нормалды)


  1. forgotten
    12.10.2015 16:24

    А можете исходники (картинку + shp) дать? Хочу сделать то же самое на API Яндекс Карт.


    1. fynjy_8
      12.10.2015 16:49

      К сожалению нет. Карты секретные, из земельного кадастра


  1. Moskus
    12.10.2015 20:07

    Для того, чтобы карта выглядела «красиво, а не так, как сейчас», вам нужно, чтобы карта была в векторе, а не этот чудовищный bmp, который нарисован вручную и отсканирован. Очевидно, что использование GeoServer вам тут не поможет.
    Если у вас будут векторные данные, то наличие сервера также не является обязательным, стилизовать вектор можно и без него, хотя с ним стили применять, конечно, проще.
    Еще — не режьте схематические изображения в JPEG, для таких изображений существует PNG.
    Система координат «план схема» и «местная система координат» — разные вещи. Первое — это вообще не система координат, потому что она абсолютно условна, там только масштаб сохраняется. Второе — это таки должна быть утвержденная система координат со всеми полагающимися атрибутами вроде проекции, используемого эллипсоида и так далее.


    1. Moskus
      12.10.2015 20:10

      «Копать в сторону OSM» вам также совершенно не нужно. Достаточно овладеть любым сравнительно простым инструментом из арсенала GIS, где вы сможете оперировать набором векторных слоев, их атрибутами, стилизацией и так далее. Инструментарий OSM имеет достаточно узкое применение (для задач самого проекта) и для чего-то более простого является слишком громоздким.


    1. fynjy_8
      12.10.2015 20:16

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

      Вопрос где что отрисовывать в векторе? Например чтобы дороги были дорогами как на картах, а не черной линией


      1. Moskus
        12.10.2015 20:27
        +1

        Сначала — векторизовать и разнести на слои.
        Затем — стилизовать.
        Потом — нарезать на тайлы.
        Для первого существует EasyTrace, у него есть бесплатная версия, вам ее хватит.
        Для второго можете воспользоваться практически чем угодно — тем же QGIS. Оттуда же и экспорт в растр и нарезку сделать сможете.
        Повторю еще раз: вы не хотите связываться с инфраструктурой, которая предназначена для OSM — ваш проект на порядок проще, это будет стрельба из пушки по воробьям, при том разбираться с «пушкой» вам придется очень долго, судя по тому, что вас уже gdal2tiles так напугал, что вы его трогать не стали.


        1. fynjy_8
          12.10.2015 21:10

          Moskus спасибо вам еще раз) помогли мне на тостере и теперь подсказываете тут) Gdal2tiles не напугал, просто раз без привязки к координатам, то зачем делать сложно)

          Еще раз, шейпы сделаны и разнесены по слоям, значит переходим к стилизации в QGIS? А после стилизации мы режем векторы на тайлы? Это странно, видимо тут я недопонял) мне казалось все карты типа 2GIS, Яндекса и т.д. сделаны в векторе, а не в растре порезанном на тайлы


          1. Moskus
            12.10.2015 21:29

            Использовать растровые или векторные тайлы — это личный выбор каждого разработчика (в соответствии с задачей, конечно).

            Не понимаю, почему вы оперируете такими понятиями, как «казалось» — в любой современный browser встроен отладчик, вы можете посмотреть внутреннее устройство любой страницы (включая картографические сервисы) и почти везде сможете увидеть растровые тайлы в .png


            1. fynjy_8
              12.10.2015 21:42

              Да, это я видел) просто как я себе опять же «представлял» у них есть сервер с данными, которые обрабатываются и выводятся в виде растра, в ОСМ как я мельком глянул, есть стили для сельской дороги, для городской дороги и т.д. Готовый набор условных знаков.

              Вы же предлагаете практически вручную рисовать карты, выводить в растр и резать? Других способов обработки шейпов нет? Переводить в базу данных точек и залить в мапсервер, который будет их уже обрабатывать например. Или так не работает никто?

              Картинка такая планируется не одна и не 10, а желательно чтобы они были на едином пространстве. Тот планшет что сейчас, это просто экспериментируем на маленьком кусочке карты)


              1. Moskus
                12.10.2015 22:04

                Ну да, есть база с векторными данными, которые потом, в соответствии с выбранными стилями, растеризуются в тайлы, которые, в свою очередь, раскладываются по content distribution network и отдаются пользователям.

                Вы очень сумбурно изложили все остальное, мне сложно это комментировать, потому что я просто не понимаю, о чем вы говорите.

                Если у вас есть векторная геометрия тех данных, которые вы хотите отобразить (линии, точки), то что в MapServer (точнее — для MapServer, потому что встроенного редактора стилей у него нет, он есть, по-моему, в uDig), что в QGIS, вам нужно создать набор правил, стилей, условных знаков, которые будут применяться к этой геометрии на основании логических атрибутов, имеющихся у геометрии.

                Правила, условно, выглядят так: если у замкнутого многоугольника есть атрибут class, имеющий значение building, то закрасить этот многоугольник черным, написав по центру шрифтом такого-то размера белыми буквами содержимое атрибута address.

                Правила придется создать вручную. И атрибуты при векторизации планшетов тоже придется задавать (не обязательно для каждого объекта, если вы будете, скажем, последовательно векторизовать только здания, потом только дороги и так далее).

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


              1. Moskus
                12.10.2015 22:08

                Чтобы получить представление, на что похож проект карты в QGIS со стилями, можете с gis-lab.info скачать одну из готовых сборок OSM под QGIS и посмотреть, как оно там все сделано. При этом совершенно не важно, что это данные OSM, в данном случае, это просто геометрия со стилями.


  1. fynjy_8
    12.10.2015 20:11

    Ребят, любые подсказки куда дальше двигаться, в сторону mapserver или смотреть как устроены osm или вообще что делать с условными знаками) будет очень кстати если вы дадите комментарии


    1. KoGor
      12.10.2015 23:59
      +1

      Пожалуй, нужно просто пойти на gis-lab и изучить его содержимое подробнее. Тогда не будет высказываний следующего вида:

      так как многой информации на русском языке в интернете по-моему нету, а до какой-то я дошел случайно

      Да и вопросы, по-моему, на профильном форуме задавать куда логичнее.


      1. Moskus
        13.10.2015 06:43

        А топикстартер и задает. Вон, спросил там на форуме, как ему отключить масштабирование векторного слоя в Leaflet. А на самом деле, имел в виду, наоборот, включение изменения толщины линий пропорционально масштабу отображения. Так что да, учиться, учиться, учиться.


    1. Chikiro
      13.10.2015 01:33

      У мапсервера есть удобная обвязка на python — mascript, с помощью котрого можно формировать мап конфиги и скармливать их мапсерверу (можно файл с конфигом получить, а можно прямо ответ от библиотеки).

      Тайлы нарезали по требованию с помощью TileCache (gdal — для геопривязанных проверенных данных, PIL — для любых изображений, чтобы показывать исходные данные).

      Есть еще mapnik, его мы использовали для отбражения sxf и s57.
      Сначала хотели и с другими типами карт работать напрямую, пришлось возиться с shp, потом решили все в geotiff перегонять, и уже в вебе с помощью мапскрипта отображать необходимые карты по WMS или TMS.

      Я тоже не профи, но попробуйте посмотреть в сторону gdal, mapnik, maserver, почитайте про geotiff, про сервисы TMS, WMS, WFS. Для работы с проекциями есть proj4. Это все больше к серверной части относится, на клиентской известные мне инструменты: OpenLayers, leaflet и proj4.js.

      Не совсем понятно, у вас shp с координатами в какой-то проекции? WGS 84? Или вообще географических координат нет?


      1. Moskus
        13.10.2015 06:38

        Не стоит путать человека, если вы сами в теме не ориентируетесь.
        WGS84 — это что угодно, но не проекция. Вы хотели спросить не про проекцию, а про систему координат.
        Системы координат у этих шейпов нет — они (вероятнее всего) в той системе, которую российские специалисты привыкли называть «план-схема». То есть масштаб там соблюдается (между точками, расстояние между которыми в реальности — метр, тоже будет разница координат в метр, точнее — в одну единицу или в сто единиц — смотря какую точность они там используют). А кроме масштаба, больше ничего нет.


        1. Chikiro
          13.10.2015 10:26

          Вот тут все это называется проекциями spatialreference.org/ref/epsg/wgs-84 Любая двумерная географическая карта земли — это проекция.

          У меня были данные, где в shp были координаты четырехугольника в какой-т системе координат, а изображение, которое было в этой же директории, надо было вписать в этот четырехугольник.


          1. Moskus
            13.10.2015 18:30

            У, как все у вас запущено… Еще и спорить продолжаете.

            Проекция — это метод отображения поверхности Земли на плоскость (а не карта).
            Так называемая «географическая проекция», о которой вы твердите — это вырожденный случай, когда географические координаты, определенные на эллипсоиде (то есть сферические, а следовательно — не являющиеся координатами плоской прямоугольной системы) отображаются на плоскости без преобразования. О координатах на эллипсоиде (например, WGS84) также говорят, как о «неспроецированных».

            Марш изучать мат. часть.


            1. KoGor
              13.10.2015 20:30

              По этой же причине люди обычно делают квадратные глаза, когда им говоришь, что можно набросать карту в excel / calc.


      1. Moskus
        13.10.2015 07:36

        И прежде чем сыпать всем ассортиментом средств и стандартов, подумайте, а нужны ли они все?
        Вот зачем человеку MapServer (и вообще, любая прослойка между базой с геометрией и клиентом), если данные не будут обновляться постоянно (учитывая характер исходных данных — сканированные планшеты городской топосъемки)?
        В этом случае достаточно использовать любую настольную ГИС, из которой можно вывести растреризованное изображение или сразу тайлы.


        1. Chikiro
          13.10.2015 10:20

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


  1. yurash
    13.10.2015 12:07

    Выбор пал на leaflet.js, так как это оболочка с открытым кодом, на ней сделаны OSM и мой любимый 2GIS.

    Откуда вы взяли про 2GIS? У них своя библиотека


    1. Zverik
      13.10.2015 14:40

      Веб-часть сделана на Leaflet, пусть и сильно дополненном.


      1. yurash
        13.10.2015 16:20

        А ведь верно.

        API 2.0 основан на open-source библиотеке Leaflet.

        Забыл про это