02.04.2017 Подключаемся к MapServer WMS из OpenLayers

Всупление

Данная статья является логическим продолжением цикла статей, посвященных настройке MapServer и теме разработок веб-картографии: Установка и начальная настройка MapServer и Подключаем MapServer к MS SQL, Изучаем и экспериментируем с MapServer, Настройка WMS сервиса на платформе MapServer. Поэтому я буду предполагать, что у читателя уже установлен действующий экземпляр MapServer, читатель знает, что такое конфигурационные map-файлы, а также в MS SQL Server загружены пространственные данные из shape-файлов TM_WORLD_BORDERS_SIMPL-0.3.shp и ne_10m_populated_places-0.3.shp. Если что-то из вышеперечисленного вас смущает, то настоятельно рекомендую ознакомиться с вышеуказанными статьями. Также все материалы, касающиеся практического использования MapServer, доступны в разделе MapServer - практика.

А сейчас давайте создадим нашего первого WMS-клиента, который будет представлять собой обычное веб-приложение, по факту единственную html-страничку с нашей интерактивной картой. Обращаться к WMS серверу мы будем из javascript с использованием библиотеки OpenLayers, которая и осуществляет всю самую сложную работу за нас по правильной загрузке тайлов и их склейке для формирования правильного изображения карты. На момент написания статьи последней версией была 4.0.1, поэтому в примере буду использовать ее. С официального сайта вы всегда можете загрузить актуальную версию библиотеки. Причем, есть возможность загрузки только библиотеки с сопутствующим css-файлом или же можно загрузить библиотеку с документацией, примерами и исходниками. Саму библиотеку OpenLayers v4.0.1 вы также можете скачать с моего ресурса здесь. При желании вы можете включить библиотеку в свою страничку прямо с сайта openlayers.org.

Что такое OpenLayers?

OpenLayers - библиотека с открытым исходным кодом, написанная на javascript. Она предоставляет API для создания интерактивных карт в html-страницы. Библиотека может отображать тайлы, векторные данные и маркеры, загружаемые с любого ресурса. OpenLayers создавалась для продвижения использования всех видов картографической информации. Библиотека выпущена под 2 пунктом лицензии BSD (также известная как FreeBSD).

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

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

Библиотека спроектирована таким образом, что новые основные особенности, такие как отображение 3D карт, использование WebGL для быстрого отображения больших объемов векторных наборов данных, могут быть добавлены в следующих выпусках.

Подключаемся к MapServer WMS с помощью OpenLayers

Вначале немного настроим наш MapServer. Для этого создадим новый map-файл с именем wms_ol.map в нашем каталоге хорошо знакомом каталоге ms4w\Apache\htdocs\mydemo. И добавим в него следующе содержимое:

MAP

  NAME "WMS"
  IMAGETYPE      PNG
  EXTENT -180 -90 180 90 # Geographic
  SIZE 800 400
  IMAGECOLOR 220 221 239  
  SYMBOLSET "./symbols/symbols.txt"
  
  WEB
    METADATA
      wms_title                  "WMS Demo"
      wms_abstract               "Demo WMS Server"
      wms_onlineresource         "http://localhost:8888/cgi-bin/mapserv.exe?map=../htdocs/mydemo/wms_ol.map&"
      wms_srs                    "EPSG:4326"
      wms_getfeatureinfo         "http://localhost:8888/cgi-bin/mapserv.exe?map=../htdocs/mydemo/wms_ol.map&"
      wms_featureinfoformat      "text/plain"
      wms_enable_request          "*"
    END
  END  
  
  PROJECTION
	"init=epsg:4326"
  END 

  LAYER
    NAME "pop_places"
    TYPE POINT
    STATUS ON
    ####
    CONNECTIONTYPE OGR
    CONNECTION "MSSQL:server=.\SQLEXPRESS;trusted_connection=yes;database=MySpatialDb;tables=ne_10m_populated_places(ogr_geometry)"
    ####
    PROJECTION
      "init=epsg:4326"
    END
    CLASS
      NAME "Pop Places"
      STYLE
        COLOR 10 100 50
        SYMBOL 'circle'
        SIZE 6		
      END
    END
    PROCESSING 'CLOSE_CONNECTION=DEFER'
	  
    METADATA
      wms_title "Popular Places"
      wms_abstract "Popular Places of the world"
      wms_srs "EPSG:4326"
      wms_include_items "all"
      wms_enable_request "*"
    END	  
	  
   END # layer  

  LAYER
    NAME "world_poly"
    TYPE POLYGON
    STATUS ON
    ####
    CONNECTIONTYPE OGR
    CONNECTION "MSSQL:server=.\SQLEXPRESS;trusted_connection=yes;database=MySpatialDb;tables=tm_world_borders_simpl(ogr_geometry)"
    ####
    PROJECTION
      "init=epsg:4326"
    END
    CLASS
      NAME "The World"
      STYLE
        COLOR 240 240 240
        OUTLINECOLOR 100 100 100
      END
    END
    PROCESSING 'CLOSE_CONNECTION=DEFER'
	
    METADATA
      wms_title "World Map"
      wms_abstract "World Map Countries"
      wms_srs "EPSG:4326"
      wms_include_items "all"
      wms_enable_request "*"
    END	
	
  END # layer 
  
END

Мы добавили в WMS сервис два слоя: полигоны стран и самые населенные места планеты (слой точечных объектов). Давайте проверим, как работает наш сервер. Для этого я воспользуюсь программой QGIS и добавлю 2 этих слоя. О том, как добавить слой в QGIS рассказывал в конце статьи Настройка WMS сервиса на платформе MapServer. После того как мы создадим наше WMS подключение, мы можем добавить два слоя на карту. Для этого, нажав клавишу Ctrl выделим с помощью левой кнопки мыши наши два слоя и жмем кнопку "Добавить".

Добавление WMS слоев на карту в QGIS
Добавление WMS слоев на карту в QGIS

Убеждаемся, что карта наши слои отлично работают в программе. Если вы все сделали верно, то должны получить примерно такую картину:

Отображение WMS слоев в QGIS
Отображение WMS слоев в QGIS

Далее давайте распакуем архив с библиотекой OpenLayers v4.0.1 в директорию ms4w\Apache\htdocs\apps\ol. Таким образом, в директории ms4w\Apache\htdocs\apps\ol\v4.0.1-dist должно оказаться 3 файла: ol.js, ol-debug.js и ol.css. Далее создадим в директории ms4w\Apache\htdocs\apps\ol файл с именем index.html. За основу нашей страницы я взял пример из OpenLayers Quick Start, немного его изменив. Вот полное содержимое этого файла:

<!doctype html>
<html lang="ru">
  <head>
    <link rel="stylesheet" href="v4.0.1-dist/ol.css" type="text/css">
    <style>
    html, body { 
        margin:0; 
        padding:0; 
        height:100%; 
    }
    #info { 
        position:absolute; 
        z-index:10; 
        background-color:#cce6ff; 
        right:0;
    }
    #map { 
        height:100%; 
    }
    </style>
    <script src="v4.0.1-dist/ol.js" type="text/javascript"></script>
    <title>Мой первый WMS клиент!</title>
  </head>
  <body>
    <div id="map" class="map">
        <div id="info">Мой первый WMS клиент!</div>
    </div>
    <script type="text/javascript">	
      var map = new ol.Map({
        target: 'map',
        layers: [
          new ol.layer.Tile({
            source: new ol.source.OSM()
          })
        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([37.41, 8.82]),
          zoom: 4
        })
      });
    </script>
  </body>
</html>

Изменения, в основном, коснулись только внешнего вида. Я растянул карту на всю ширину и высоту страницы с помощью стилей CSS. Также я добавил блок для отображения информации (элемент <div> с идентификаторм "info"). Он нам пригодится позже для отображения некоторой информации. Обратите внимание на атрибут href тега link и src тега script. В них я указал путь (относительно самого файла index.html) к нашей библиотеке OpenLayers и ее таблице стилей. Откроем теперь нашу страницу в браузере по адресу http://localhost:8888/apps/ol/index.html или даже можно не указывать файл, т.е. http://localhost:8888/apps/ol, т.к. он является документом по умолчанию для сервера Apache. Если все прошло успешно, то страница в браузере должна выглядеть таким образом:

Отображение карты OpenStreetMap
Отображение карты OpenStreetMap

Рассмотрим, что же делает наш JavaScript код. Переменной map присваивается создаваемый объект карты с некоторыми заданными параметрами. Для создания объекта, необходимо задать, как минимум, три параметра: target, layers и view.

  • target - контейнер для карты, либо это - сам элемент, либо же - идентификатор id этого элемента. Если параметр не задан при создании объекта, тогда необходимо вызвать метод setTarget для отрисовки карты.
  • layers - массив слоев. Если не указан, то будет отрисована пустая карта. Обратите внимание, что слои рендерятся в указанном в массиве порядке. Если вы, например, хотите, чтобы векторный слой находился поверх тайлового слоя, то он должен находится после тайлового слоя. В нашем случае мы создали тайловый слой (с помощью new ol.layer.Tile({...})), а в качестве источника данных используется сервис OpenStreetMap (с помощью new ol.source.OSM()).
  • view - отображение карты. Ни один слой не будет запрошен из источника данных до тех пор, пока это свойство не будет указано либо во время создания, либо с помощью метода setView. Свойство center определяет центр карты, а zoom задает текущий масштаб.

Давайте добавим на карту наш тайловый слой WMS pop_places. Для этого добавим в массив слоев новый объект тайлового слоя. Секция <script> ... </script> будет выглядеть следующим образом.

<script type="text/javascript">	
    var map = new ol.Map({
    target: 'map',
    layers: [
        new ol.layer.Tile({
            source: new ol.source.OSM()
        }),
        new ol.layer.Tile({
            source: new ol.source.TileWMS({
                projection: 'EPSG:4326',
                url: 'http://localhost:8888/cgi-bin/mapserv.exe?map=../htdocs/mydemo/wms_ol.map',
                params: { 'LAYERS': 'pop_places', 'TILED': true, 'VERSION': '1.1.1' },
            })
        })
    ],
    view: new ol.View({
        center: ol.proj.fromLonLat([37.41, 8.82]),
        zoom: 4
        })
    });
</script>

В новом коде я создал новый объект ol.source.TileWMS, при создании которого были указаны следующие параметры:

  • projection - проекция слоя
  • url - url WMS службы
  • params - параметры WMS запроса. Как минимум нужно указать параметр LAYERS. Параметр STYLE по умолчанию установлен в ''. По умолчанию VERSION имеет значение 1.3.0. WIDTH, HEIGHT, BBOX и CRS (SRS для WMS версии < 1.3.0) будет установлены динамически.

Если открыть и немного подвигать карту, то можно увидеть вот такие артефакты отрисовки карты:

Отображение MapServer WMS слоя точечных объектов
Отображение MapServer WMS слоя точечных объектов

Самое интересное, что изображения с сервера приходят с точечными объектами одинаковых размеров, а в процессе рендеринга изображения на элемент canvas происходят вот такие ошибки отрисовки. Давайте проверим, как отрисовывается наш слой с полигонами границ стран. Для этого добавим в параметр 'LAYERS' название слоя - 'world_poly', а также установим прозрачность (свойство opacity) WMS слоя в 0.7. По умолчанию opacity = 1, что обозначает, что слой абсолютно непрозрачен. Новый вид секции <script> ... </script>:

<script type="text/javascript">	
    var map = new ol.Map({
    target: 'map',
    layers: [
        new ol.layer.Tile({
            source: new ol.source.OSM()            
        }),
        new ol.layer.Tile({
            source: new ol.source.TileWMS({
                projection: 'EPSG:4326',
                url: 'http://localhost:8888/cgi-bin/mapserv.exe?map=../htdocs/mydemo/wms_ol.map',
                params: { 'LAYERS': 'world_poly,pop_places', 'TILED': true, 'VERSION': '1.1.1' },
            }),
            opacity: 0.7
        })
    ],
    view: new ol.View({
        center: ol.proj.fromLonLat([37.41, 8.82]),
        zoom: 4
        })
    });
</script>

Как видим, в полигональном слое стран толщина линий контуров тоже несколько различается, хотя это и не так заметно.

Отображение MapServer WMS полигонального слоя границ стран и слоя точечных объектов
Отображение MapServer WMS полигонального слоя границ стран и слоя точечных объектов

Ранее в своем проекте я активно использовал библиотеку OpenLayers 3 для создания векторных слоев на клиенте с помощью ol.layer.Vector, но никаких нареканий к библиотеке не было. К слову, пробовал подключать свою старую библиотеку OpenLayers v3.14.2, однако проблема с точечными объектами осталась. По поводу ошибки рендеринга WMS слоев задал вопрос на Stackoverflow, поскольку объяснение в интернете обнаружить не удалось. Так что ждем-с )

07.04.2017 Проблема с отрисовкой в OpenLayers. Разбор полетов (обновление)

Итак, выяснилось в чем заключается проблема: выходное разрешение изменяется в зависимости от широты при проецировании из EPSG:4326 в EPSG:3857. В противном случае были бы получены размытые изображения.

Что же с этим поделать? Есть решение на серверной стороне. Все, что нужно - это просто возвращать изображения в нужной проекции! Исправим соответствующим образом наш map-файл.

MAP

  NAME "WMS"
  IMAGETYPE      PNG
  EXTENT -180 -90 180 90 # Geographic
  SIZE 800 400
  IMAGECOLOR 220 221 239  
  SYMBOLSET "./symbols/symbols.txt"
  
  WEB
    METADATA
      wms_title                  "WMS Demo"
      wms_abstract               "Demo WMS Server"
      wms_onlineresource         "http://localhost:8888/cgi-bin/mapserv.exe?map=../htdocs/mydemo/wms_ol.map&"
      wms_srs                    "EPSG:3857"
      wms_getfeatureinfo         "http://localhost:8888/cgi-bin/mapserv.exe?map=../htdocs/mydemo/wms_ol.map&"
      wms_featureinfoformat      "text/plain"
      wms_enable_request          "*"
    END
  END  
  
  PROJECTION
	"init=epsg:3857"
  END 

  LAYER
    NAME "pop_places"
    TYPE POINT
    STATUS ON
    ####
    CONNECTIONTYPE OGR
    CONNECTION "MSSQL:server=.\SQLEXPRESS;trusted_connection=yes;database=MySpatialDb;tables=ne_10m_populated_places(ogr_geometry)"
    ####
    PROJECTION
      "init=epsg:4326"
    END
    CLASS
      NAME "Pop Places"
      STYLE
        COLOR 10 100 50
        SYMBOL 'circle'
        SIZE 6		
      END
    END
    PROCESSING 'CLOSE_CONNECTION=DEFER'
	  
    METADATA
      wms_title "Popular Places"
      wms_abstract "Popular Places of the world"
      wms_srs "EPSG:4326"
      wms_include_items "all"
      wms_enable_request "*"
    END	  
	  
   END # layer  

  LAYER
    NAME "world_poly"
    TYPE POLYGON
    STATUS ON
    ####
    CONNECTIONTYPE OGR
    CONNECTION "MSSQL:server=.\SQLEXPRESS;trusted_connection=yes;database=MySpatialDb;tables=tm_world_borders_simpl(ogr_geometry)"
    ####
    PROJECTION
      "init=epsg:4326"
    END
    CLASS
      NAME "The World"
      STYLE
        COLOR 240 240 240
        OUTLINECOLOR 100 100 100
      END
    END
    PROCESSING 'CLOSE_CONNECTION=DEFER'
	
    METADATA
      wms_title "World Map"
      wms_abstract "World Map Countries"
      wms_srs "EPSG:4326"
      wms_include_items "all"
      wms_enable_request "*"
    END	
	
  END # layer 
  
END

Здесь мы изменили параметр wms_srs в метаданных объекта WEB уровня MAP, а также изменили выходную проекцию в объекте PROJECTION того же уровня MAP. Теперь все, что нам осталось сделать - удалить строчку projection: 'EPSG:4326' в html документе в объекте конфигурации, который передается конструктору ol.source.TileWMS. Я не буду приводить весь html документ, а покажу только измененную секцию <script>.

<script type="text/javascript">	
    var map = new ol.Map({
    target: 'map',
    layers: [
        new ol.layer.Tile({
            source: new ol.source.OSM()            
        }),
        new ol.layer.Tile({
            source: new ol.source.TileWMS({
                //projection: 'EPSG:4326',
                url: 'http://localhost:8888/cgi-bin/mapserv.exe?map=../htdocs/mydemo/wms_ol.map',
                params: { 'LAYERS': 'world_poly,pop_places', 'TILED': true, 'VERSION': '1.1.1' },
            }),
            opacity: 0.7
        })
    ],
    view: new ol.View({
        center: ol.proj.fromLonLat([37.41, 8.82]),
        zoom: 4
        })
    });
</script>

Обозначенную строку я даже не удалял, а просто закомментировал. Давайте теперь посмотрим на результат:

Отображение WMS слоев с помощью OpenLayers без перепроецирования
Отображение WMS слоев с помощью OpenLayers без перепроецирования

В нашем случае мы имели доступ к серверной части, поэтому достаточно просто исправить эту фичу OpenLayers, однако далеко не всегда мы имеем доступ к WMS серверу, а иногда по тем или иным причинам просто не можем изменить выходную проекцию. В этих случаях нам остается только смериться с этой фичей. На момент написания этого примечания, я уже запилил статью "Подключаемся к MapServer WMS с помощью Leaflet". И, если сравнить эти две библиотеки в исследуемом аспекте, то Leaflet свободна от этого недостатка, что является несомненным плюсом.

Файлы для загрузки:

  • OpenLayers v4.0.1 - архив с библиотекой OpenLayers версии 4.0.1, содержащий полный и минифицированный js-файлы, а также css-файл.

Предыдущие статьи по теме разработки веб-ГИС и настройки MapServer: