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 выделим с помощью левой кнопки мыши наши два слоя и жмем кнопку "Добавить".
Убеждаемся, что карта наши слои отлично работают в программе. Если вы все сделали верно, то должны получить примерно такую картину:
Далее давайте распакуем архив с библиотекой 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. Если все прошло успешно, то страница в браузере должна выглядеть таким образом:
Рассмотрим, что же делает наш 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) будет установлены динамически.
Если открыть и немного подвигать карту, то можно увидеть вот такие артефакты отрисовки карты:
Самое интересное, что изображения с сервера приходят с точечными объектами одинаковых размеров, а в процессе рендеринга изображения на элемент 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>
Как видим, в полигональном слое стран толщина линий контуров тоже несколько различается, хотя это и не так заметно.
Ранее в своем проекте я активно использовал библиотеку 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>
Обозначенную строку я даже не удалял, а просто закомментировал. Давайте теперь посмотрим на результат:
В нашем случае мы имели доступ к серверной части, поэтому достаточно просто исправить эту фичу OpenLayers, однако далеко не всегда мы имеем доступ к WMS серверу, а иногда по тем или иным причинам просто не можем изменить выходную проекцию. В этих случаях нам остается только смериться с этой фичей. На момент написания этого примечания, я уже запилил статью "Подключаемся к MapServer WMS с помощью Leaflet". И, если сравнить эти две библиотеки в исследуемом аспекте, то Leaflet свободна от этого недостатка, что является несомненным плюсом.
Файлы для загрузки:
- OpenLayers v4.0.1 - архив с библиотекой OpenLayers версии 4.0.1, содержащий полный и минифицированный js-файлы, а также css-файл.
Предыдущие статьи по теме разработки веб-ГИС и настройки MapServer: