Difference between revisions of "Using WMS with OpenLayers"

From CUOSGwiki
Jump to navigationJump to search
(Update WMS services and OpenLayers usages)
 
(39 intermediate revisions by one other user not shown)
Line 1: Line 1:
 
== Introduction ==
 
== Introduction ==
   
  +
The goal of this project is to use freely available Web Map Service (WMS) layers with [https://openlayers.org OpenLayers]<ref name="ol">OpenLayers https://openlayers.org</ref> to create a dynamic map for use within a web browser. This tutorial contains a brief outline of WMS layers and using the OpenLayers library. This includes creating a basic OpenLayers web app, adding WMS layers to the map, and working with projections in OpenLayers.
   
  +
== Required Data and Tools ==
The goal of this project is to use freely available Web Map Service (WMS) layers with OpenLayers to create a dynamic map for use within a web browser. In particular, the hope was to include a land use layer for Canada, and the user could browse to a desired location to determine how the land is being used. If the user knows the coordinates of the place they want to view, they can modify the source code by entering the chosen longitude, latitude, and zoom level. Then when loading the map the images will be centered within the map window. For the following example, only GeoBase and Toporama WMS layers are used, and they can be accessed at no cost and without restrictions.
 
   
This tutorial contains a brief outline of Web Map Service (WMS) layers and OpenLayers, and then descriptions of the methods used to create the map displayed in Section 4. This includes discussion of the important aspects of the HTML code that generated the resulting map, as well as why the use of OpenLayers is suitable to achieve the desired goals.
 
   
  +
=== WMS Layers ===
   
  +
A WMS provides an HTTP interface for requesting geo-registered map images from one or more geospatial databases. A WMS request can define the geographic layer(s) and area of interest that will be processed by the service. The service then responds to the request with geo-registered map images (PNG images in this tutorial's example) which can then be displayed in a GIS tool or library like OpenLayers. [https://www.ogc.org/standards/wms/ Web Map Services]<ref>Open Geospatial Consortium WMS Standard https://www.ogc.org/standards/wms/</ref> are an open standard defined by the Open Geospatial Consortium (OGC). All Web Map Services support a GetCapabilities request that returns an XML document that describes the capabilities of the WMS server. This includes the layers that the WMS serves, the supported styles, supported coordinate systems, etc.
   
== Data and Mapping Tools ==
 
   
  +
Requests are made by adding parameters to the URL of a WMS. For example, to make the GetCapabilities request for the canvec_en service: [https://maps.geogratis.gc.ca/wms/canvec_en?service=wms&version=1.3.0&request=GetCapabilities https://maps.geogratis.gc.ca/wms/canvec_en?service=wms&version=1.3.0&request=GetCapabilities].
   
=== WMS Layers ===
 
 
WMS provides a HTTP interface for requesting geo-registered map images from one of more geospatial databases. A WMS request defines the geographic layer(s) and area of interest that will be processed.[http://www.opengeospatial.org/standards/wms] There is then a response to the request where geo-registered map images (in the following example returned as a PNG) can be displayed within a browser.
 
   
 
If the user has included more than one WMS layer, they have the ability to specify which will be the base layer, and which will be overlay layers. If the user then wants multiple images to be combined, the overlay layers need to be designated transparent. This is not necessary, however, as the user may want only one layer to appear at a time. This can be done by making the transparent flag false for each of the overlay layers, and then the user can manually switch between layers through the web browser. With OpenLayers the ‘LayerSwitcher’ control, which can be added into the code, is a popup that allows the user to do this by simply selecting which layer they want to view.
 
If the user has included more than one WMS layer, they have the ability to specify which will be the base layer, and which will be overlay layers. If the user then wants multiple images to be combined, the overlay layers need to be designated transparent. This is not necessary, however, as the user may want only one layer to appear at a time. This can be done by making the transparent flag false for each of the overlay layers, and then the user can manually switch between layers through the web browser. With OpenLayers the ‘LayerSwitcher’ control, which can be added into the code, is a popup that allows the user to do this by simply selecting which layer they want to view.
   
There are six WMS layers being used in the following example. The first layer, which is being used as the base map is obtained from [http://ows.geobase.ca/wms/geobase_en/ Geobase], and it contains foreign landmass political boundaries. The additional overlay layers which encompass all of Canada are as follows:
 
   
  +
Several map services will be used in this tutorial.
Surface water network (layer name: hydrography). Obtained from: [http://wms.ess-ws.nrcan.gc.ca/wms/toporama_en/ NRCan's Toporama]
 
   
  +
{| class="wikitable"
Landcover (layer name: landcover:csc2000): [http://ows.geobase.ca/wms/geobase_en/ Geobase]
 
  +
|+Web Map Services
  +
|-
  +
!Name
  +
!URL
  +
!Layers
  +
!Description
  +
|-
  +
|elevation_en
  +
|https://maps.geogratis.gc.ca/wms/elevation_en?service=WMS&version=1.3.0
  +
|<syntaxhighlight inline>cdsm.color-shaded-relief</syntaxhighlight>
  +
|Shaded relief map of Canada
  +
|-
  +
|CBMT
  +
|https://maps.geogratis.gc.ca/wms/CBMT?service=wms&version=1.3.0
  +
|<syntaxhighlight inline>CBMT</syntaxhighlight>
  +
|Base map of Canada, highlighting transportation networks
  +
|-
  +
|railway_en
  +
|https://maps.geogratis.gc.ca/wms/railway_en?service=WMS&version=1.3.0
  +
|<syntaxhighlight inline>railway</syntaxhighlight>
  +
|Canadian railway network
  +
|-
  +
|canvec_en
  +
|https://maps.geogratis.gc.ca/wms/canvec_en?service=wms&version=1.3.0
  +
|<syntaxhighlight inline>land</syntaxhighlight>
  +
|Outline of land
  +
|}
   
Primary Roads (layer name: nrn:roadnetwork:roadseg_primary): [http://ows.geobase.ca/wms/geobase_en/ Geobase]
 
   
  +
=== OpenLayers ===
Secondary Roads (layer name: nrn:roadnetwork:roadseg_secondary): [http://ows.geobase.ca/wms/geobase_en/ Geobase]
 
  +
OpenLayers is an open source JavaScript library that is used for building interactive mapping applications for use in web browsers. It is an Open Source Geospatial Foundation (OSGeo) project that supports many OGC standards including WMS, Web Feature Services (WFS), and Web Map Tile Services (WMTS). Documentation for OpenLayers can be found at [https://openlayers.org/ https://openlayers.org]<ref name="ol"/>.
   
  +
== Methods ==
Cities (layer name:populated_places): [http://wms.ess-ws.nrcan.gc.ca/wms/toporama_en/ NRCan's Toporama]
 
   
  +
=== Creating the OpenLayers Viewer ===
   
  +
OpenLayers has a tool to create a basic viewer application, which is documented in their [https://openlayers.org/en/latest/doc/tutorials/bundle.html quickstart guide]. The tool requires [https://nodejs.org/en/ NodeJS] to be installed, and some basic command line use. To create the OpenLayers project: create a new directory, and then run the project creation tool inside of the directory via the command line:
The GeoBase and Toporama WMS support mandatory GetCapabilities and GetMap operations as defined in the [http://www.opengeospatial.org/standards/wms/ OGC WMS standard, version 1.1.1]. Get Capabilities is an operation that produces a descriptive XML document that includes general information on the WMS, as well as descriptions of each data layer. An example of this type of request addressed to GeoBase is [http://ows.geobase.ca/wms/geobase_en?service=wms&request=GetCapabilities&version=1.1.1/ http://ows.geobase.ca/wms/geobase_en?service=wms&request=GetCapabilities&version=1.1.1], and to NRCan’s Toporama is [http://wms.ess-ws.nrcan.gc.ca/wms/toporama_en?VERSION=1.1.1&request=GetCapabilities&service=wms/ http://wms.ess-ws.nrcan.gc.ca/wms/toporama_en?VERSION=1.1.1&request=GetCapabilities&service=wms]. This was used for this project and it allowed the user to fully understand what each layer was representing.
 
   
  +
<syntaxhighlight lang="bash">
  +
mkdir wms-tutorial && cd wms-tutorial
   
  +
npx create-ol-app
=== OpenLayers ===
 
  +
</syntaxhighlight>
OpenLayers is an open source JavaScript library that is used to display map data in web browsers. It is an Open Source Geospatial Foundation (OSGeo) project that supports map data from any source that uses Open Geospatial Consortium (OGC) standards as WMS or Web Feature Service (WFS). The OpenLayers library and full documentation can be obtained from [http://openlayers.org/ http://openlayers.org/].
 
   
  +
This creates a number of files in the project directory including an HTML file, a JavaScript (.js) file, and a few other advanced configuration files. We will focus on the JavaScript file as this is where the OpenLayers code is located.
   
  +
The <syntaxhighlight inline>main.js</syntaxhighlight> file created by the project creation tool contains the following code which creates a basic map with an OpenStreetMap layer. You can open this file for editing in any text editor ([https://code.visualstudio.com/ Visual Studio Code] is a good one).
== Methods ==
 
   
  +
<syntaxhighlight lang="javascript" line>
=== WMS Layers ===
 
  +
import {Map, View} from 'ol';
  +
import TileLayer from 'ol/layer/Tile';
  +
import OSM from 'ol/source/OSM';
   
  +
const map = new Map({
The first step was to determine which WMS layers would be used for the project. Six layers were found using GeoBase and Natural Resources Canada’s Toporama data. Within the subsequent HTML code example, they are named ‘basemap,’ ‘water,’ ‘landcovercanada,’ ‘primary roads,’ ‘secondary roads,’ and ‘cities.’ What each layer represents is described below:
 
  +
target: 'map',
  +
layers: [
  +
new TileLayer({
  +
source: new OSM()
  +
})
  +
],
  +
view: new View({
  +
center: [0, 0],
  +
zoom: 2
  +
})
  +
});
  +
</syntaxhighlight>
   
  +
To actually run the application, we again use the command line to start it by running the <syntaxhighlight lang="bash" inline>npm start</syntaxhighlight> command in the command line. Once started, the application can be accessed by navigating to [http://localhost:1234 http://localhost:1234] in your web browser. An interactive map should be visible. Now we can begin modifying it to add our WMS layers.
basemap – Foreign political boundaries at 1:10M scale
 
   
  +
To add layers, we have to modify the default import statements to include image layer and WMS code from OpenLayers. We add an array of layers ''(lines 6–14)'' which will hold our layer and WMS request definitions, and then add the layers to our map ''(line 18)''. Here, we add a single WMS layer to start:
water – Surface water network of Canada
 
   
  +
<syntaxhighlight lang="javascript" line highlight="2-3,6-14,18">
landcovercanada – Land cover information from 2000 for all of Canada. It is the result of vectorization of raster thematic data originating from Landsat 5 and Landsat 7 ortho-images, for agricultural and forest areas of Canada.
 
  +
import {Map, View} from 'ol';
  +
import {Image as ImageLayer, Tile as TileLayer} from 'ol/layer';
  +
import ImageWMS from 'ol/source/ImageWMS';
  +
import TileLayer from 'ol/layer/Tile';
   
  +
const layers = [
primary roads – Primary roads include mainly the Highways and Expressways, although this may vary according to each province.
 
  +
new ImageLayer({
  +
source: new ImageWMS({
  +
url: 'https://maps.geogratis.gc.ca/wms/elevation_en?service=WMS&version=1.3.0',
  +
params: {layers: 'cdsm.color-shaded-relief'},
  +
crossOrigin: 'Anonymous'
  +
})
  +
})
  +
];
   
  +
const map = new Map({
secondary roads – Secondary roads include mainly the Arterial ways, although this may vary according to each province.
 
  +
target: 'map',
  +
layers: layers,
  +
view: new View({
  +
center: [0, 0],
  +
zoom: 2
  +
})
  +
});
  +
</syntaxhighlight>
   
  +
There are several things to note about the layer definition shown on lines 7 through 13 in the code snippet above. We have to provide the URL for the WMS, along with the layer(s) that we want to be loaded in the params. The <syntaxhighlight inline>crossOrigin: 'Anonymous'</syntaxhighlight> parameter is required for some WMS servers due to browser security rules that restrict how web apps can load data from other websites.
cities – Heavily populated areas
 
   
  +
Multiple image layers can be added by adding their definition to the <syntaxhighlight inline>layers</syntaxhighlight> array. The order in which they are placed in the array determines the order in which they are displayed in the viewer, with the first entry being the bottomost layer. We can see this by adding more layers, including a layer that contains transparent images allowing us to see the layer beneath it.
   
  +
<syntaxhighlight lang="javascript" line start="6" highlight="9-29">
Further information regarding how the WMS layers are used within OpenLayers can be seen within the comments of the HTML code, such as how multiple WMS layers can be specified in a single OpenLayers layer. Also, note that the layers are placed in the reverse order in which they want to be viewed. In other words, ‘cities’ is placed at the as the last layer within the ‘layers’ variable so that it will be viewed on top of the other layers. If the landcover layer was instead placed as the final layer, it would cover all of the others.
 
  +
const layers = [
  +
new ImageLayer({
  +
source: new ImageWMS({
  +
url: 'https://maps.geogratis.gc.ca/wms/elevation_en?service=WMS&version=1.3.0',
  +
params: {layers: 'cdsm.color-shaded-relief'},
  +
crossOrigin: "Anonymous"
  +
})
  +
}),
  +
new ImageLayer({
  +
source: new ImageWMS({
  +
url: 'https://maps.geogratis.gc.ca/wms/CBMT?service=wms&version=1.3.0',
  +
params: {layers: 'CBMT'},
  +
crossOrigin: 'Anonymous'
  +
})
  +
}),
  +
new ImageLayer({
  +
source: new ImageWMS({
  +
url: 'https://maps.geogratis.gc.ca/wms/railway_en?service=WMS&version=1.3.0',
  +
params: {layers: 'railway'},
  +
crossOrigin: "Anonymous"
  +
})
  +
}),
  +
new ImageLayer({
  +
source: new ImageWMS({
  +
url: 'https://maps.geogratis.gc.ca/wms/canvec_en?service=wms&version=1.3.0',
  +
params: {layers: 'land'},
  +
crossOrigin: "Anonymous"
  +
})
  +
}),
  +
];
  +
</syntaxhighlight>
   
   
=== OpenLayers Viewer ===
+
=== Projections ===
   
  +
The default projection used by OpenLayers is the EPGS:3857 (WGS84 Pseudo-Mercator) projection. For the majority of use-cases this is sufficient, but OpenLayers supports many other predefined projections, as well as any arbitrary projection definition. The projection of the map view can be changed by adding a <syntaxhighlight inline>projection</syntaxhighlight> parameter to the View initializer. For example, to change the projection to EPSG:4326 (unprojected WGS84):
To build an OpenLayers viewer requires crafting HTML in which the viewer will be seen. This is done through the statement on line 14 of the following code. OpenLayers supports putting a map inside almost any HTML block element on the page. Also required is a script tag which includes the OpenLayers library to the page. This is shown in line 15 of the following code:
 
   
  +
<syntaxhighlight lang="javascript" line start="37" highlight="7">
[[File:Openlayersviewercode.png]]
 
  +
const map = new Map({
  +
target: 'map',
  +
layers: layers,
  +
view: new View({
  +
center: [0, 0],
  +
zoom: 15,
  +
projection: 'EPSG:4326'
  +
})
  +
});
  +
</syntaxhighlight>
   
  +
OpenLayers only has a limited set of projections built in however. For example, if we tried to use the [https://epsg.io/42304 EPSG:42304] (NAD83 Lambert Conformal Conic) projection the map viewer would not load because OpenLayers does not recognize it.
''Figure 1. Displaying the HTML code required to craft the OpenLayers viewer and include the OpenLayers library to the page.''
 
   
  +
To define a new projection, we can use the open source [https://github.com/proj4js/proj4js proj4js] library which can be installed in our project by running <syntaxhighlight inline>npm install proj4</syntaxhighlight> from the command line inside the project directory. Once installed, we can import proj4js into our code and use it to define the projection. The definition of an EPSG projection <ref>EPSG refers to European Petroleum Survey Group, which is now-defunct, but has been absorbed into the International Association of Oil and Gas Producers (OGP) Geomatics Committee. They are an authority for maintaining map projection codes in order for there to be a standard directory of projections.</ref> can be looked up at [https://epsg.io https://epsg.io] (there is even a tab specifically for proj4js). The line of proj4js definition code can be added anywhere in our code, as long as it is before the initialization of the map. We also need to register proj4js with OpenLayers so that it is aware of the projection definition. Once registered, we can use the projection that we defined in any OpenLayers function.
   
  +
<syntaxhighlight lang="javascript" line highlight="5-6, 8-9, 48">
=== HTML Code ===
 
  +
import {Map, View} from 'ol';
  +
import {Image as ImageLayer, Tile as TileLayer} from 'ol/layer';
  +
import ImageWMS from 'ol/source/ImageWMS';
  +
import TileLayer from 'ol/layer/Tile';
  +
import proj4 from 'proj4';
  +
import {register} from 'ol/proj/proj4';
   
  +
proj4.defs("EPSG:42304","+proj=lcc +lat_1=49 +lat_2=77 +lat_0=49 +lon_0=-95 +x_0=0 +y_0=0 +datum=NAD83 +units=m +no_defs");
The following HTML code was used to produce Figure 2 shown below. Within the code there are comments (<nowiki><!-- --></nowiki>) that describe what the subsequent pieces of code are accomplishing.
 
  +
register(proj4);
   
  +
const layers = [
  +
new ImageLayer({
  +
source: new ImageWMS({
  +
url: 'https://maps.geogratis.gc.ca/wms/elevation_en?service=WMS&version=1.3.0',
  +
params: {layers: 'cdsm.color-shaded-relief'},
  +
crossOrigin: "Anonymous"
  +
})
  +
}),
  +
new ImageLayer({
  +
source: new ImageWMS({
  +
url: 'https://maps.geogratis.gc.ca/wms/CBMT?service=wms&version=1.3.0',
  +
params: {layers: 'CBMT'},
  +
crossOrigin: 'Anonymous'
  +
})
  +
}),
  +
new ImageLayer({
  +
source: new ImageWMS({
  +
url: 'https://maps.geogratis.gc.ca/wms/railway_en?service=WMS&version=1.3.0',
  +
params: {layers: 'railway'},
  +
crossOrigin: "Anonymous"
  +
})
  +
}),
  +
new ImageLayer({
  +
source: new ImageWMS({
  +
url: 'https://maps.geogratis.gc.ca/wms/canvec_en?service=wms&version=1.3.0',
  +
params: {layers: 'land'},
  +
crossOrigin: "Anonymous"
  +
})
  +
}),
  +
];
   
  +
const map = new Map({
  +
target: 'map',
  +
layers,
  +
view: new View({
  +
center: [0, 0],
  +
zoom: 2,
  +
projection: 'EPSG:42304'
  +
})
  +
});
  +
</syntaxhighlight>
   
  +
It is important to note that changing the projection of the OpenLayers view will also change the coordinate system used by OpenLayers. For instance, if we wanted to center the map on the Carleton campus, which is located at <syntaxhighlight inline>-75.696254, 45.384792</syntaxhighlight> (longitude, latitude), this will not work because the EPSG:42304 projection's coordinate systems is in meters, not longitude/latitude degrees. OpenLayers has built in functionality that allows us to transform coordinates between coordinate systems, and to convert from latitude and longitude we can import the convenience function <syntaxhighlight inline>fromLonLat</syntaxhighlight>:
<!DOCTYPE html>
 
<html>
 
<head>
 
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
 
<meta name="apple-mobile-web-app-capable" content="yes">
 
<title>OpenLayers WMS Test</title>
 
<link rel="stylesheet" href="../theme/default/style.css" type="text/css">
 
<link rel="stylesheet" href="style.css" type="text/css">
 
</head>
 
<body onload="init()">
 
<nowiki><h1 id="title">OpenLayers WMS Example</h1></nowiki>
 
<nowiki><div id="shortdesc">Displaying WMS layers:</div></nowiki>
 
<nowiki><div id="map" class="mediummap"></div></nowiki>
 
<script type="text/javascript" src="OpenLayers.js"></script>
 
<script type="text/javascript">
 
 
<nowiki> <!-- Enter the desired center location of the map as well as the zoom level. These will be called after the map has been
 
created using the map.setCenter control. --> </nowiki>
 
var lon = -75.6981200;
 
var lat = 45.4111700;
 
var zoom = 9;
 
var map, layer;
 
function init() {
 
<nowiki> <!-- Define the WMS layers that will be used to generate the map. Multiple WMS layers can be placed in a single OpenLayers
 
layer, as is the case here. --> </nowiki>
 
<nowiki> <!-- Each layer could also be set up as its own variable, and then all of the variables could be called at the end during
 
the map.addLayers() step. Both methods will work. --> </nowiki>
 
<nowiki> <!-- The multiple WMS layers within the single OpenLayers layer is all one statement, and the first WMS layer listed will
 
be considered the base layer. --> </nowiki>
 
<nowiki> <!-- The other layers are then considered the overlay layers. These must be made transparent. Note the transparent flag is
 
set as true within each WMS layer. --> </nowiki>
 
<nowiki> <!-- Note the isBaseLayer flag within each of the overlay layers, which is set as false. The first layer does not
 
need this flag as it is understood to be the base layer. --> </nowiki>
 
<nowiki> <!-- Note the use of png images, which support transparency. --> </nowiki>
 
 
var layers =
 
[
 
new OpenLayers.Layer.WMS(
 
"basemap",
 
"http://ows.geobase.ca/wms/geobase_en",
 
{
 
layers: 'reference:landmass:international_10m'
 
}),
 
new OpenLayers.Layer.WMS(
 
"water",
 
"http://wms.ess-ws.nrcan.gc.ca/wms/toporama_en",
 
{
 
layers: 'hydrography',
 
transparent: 'true',
 
format: 'image/png'
 
},
 
{
 
isBaseLayer: false
 
}),
 
new OpenLayers.Layer.WMS(
 
"landcovercanada",
 
"http://ows.geobase.ca/wms/geobase_en",
 
{
 
layers: 'landcover:csc2000',
 
transparent: 'true',
 
format: 'image/png'
 
},
 
{
 
isBaseLayer: false
 
}),
 
new OpenLayers.Layer.WMS(
 
"primaryroads",
 
"http://ows.geobase.ca/wms/geobase_en",
 
{
 
layers: 'nrn:roadnetwork:roadseg_primary',
 
transparent: 'true',
 
format: 'image/png'
 
},
 
{
 
isBaseLayer: false
 
}),
 
new OpenLayers.Layer.WMS(
 
"secondaryroads",
 
"http://ows.geobase.ca/wms/geobase_en",
 
{
 
layers: 'nrn:roadnetwork:roadseg_secondary',
 
transparent: 'true',
 
format: 'image/png'
 
},
 
{
 
isBaseLayer: false
 
}),
 
new OpenLayers.Layer.WMS(
 
"cities",
 
"http://wms.ess-ws.nrcan.gc.ca/wms/toporama_en",
 
{
 
layers: 'populated_places',
 
transparent: 'true',
 
format: 'image/png'
 
},
 
{
 
isBaseLayer: false
 
})
 
];
 
 
<nowiki> <!-- Define the projection of the map. Codes, their associated projections, and documentation can be found at
 
http://trac.osgeo.org/proj/ (PROJ.4) or http:spatialreference.org --> </nowiki>
 
var mapprojection = new OpenLayers.Projection("EPSG:42304");
 
 
<nowiki> <!-- Creating bounds for the layers in projected coordinates --> </nowiki>
 
var full = new OpenLayers.Bounds(-2300000, -1000000, 2700000, 3500000);
 
var init = new OpenLayers.Bounds(300000, -800000, 1300000, 200000);
 
 
<nowiki> <!-- Set up a variable to hold the map object, and create the map. --> </nowiki>
 
var map = new OpenLayers.Map('map',
 
{
 
projection: mapprojection,
 
maxExtent: full,
 
restrictedExtent: full
 
});
 
<nowiki> <!-- Add the layers to the map. In this case only 'layers' has to be called because multiple WMS layers were placed in
 
a single OpenLayers layer --> </nowiki>
 
map.addLayers(layers);
 
map.zoomToExtent(init);
 
map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
 
//map.addControl(new OpenLayers.Control.PanZoomBar);//
 
//map.addControl( new OpenLayers.Control.LayerSwitcher() );//
 
};
 
 
</script>
 
</body>
 
</html>
 
   
  +
<syntaxhighlight lang="javascript" line highlight="7">
  +
import {Map, View} from 'ol';
  +
import {Image as ImageLayer, Tile as TileLayer} from 'ol/layer';
  +
import ImageWMS from 'ol/source/ImageWMS';
  +
import TileLayer from 'ol/layer/Tile';
  +
import proj4 from 'proj4';
  +
import {register} from 'ol/proj/proj4';
  +
import {fromLonLat} from 'ol/proj';
  +
</syntaxhighlight>
   
  +
Next, we can update the View initializer to use the transformed longitude and latitude coordinates. The target projection must be specified (if not, the default is EPSG:3857).
   
  +
<syntaxhighlight lang="javascript" line start="43" highlight="5-6">
=== Projections ===
 
  +
const map = new Map({
  +
target: 'map',
  +
layers,
  +
view: new View({
  +
center: fromLonLat([-75.696254, 45.384792], 'EPSG:42304'),
  +
zoom: 15,
  +
projection: 'EPSG:42304'
  +
})
  +
});
  +
</syntaxhighlight>
   
  +
== Resulting Map ==
As shown within the HTML code, the projection of the map is EPSG:42304. EPSG refers to European Petroleum Survey Group, which is now-defunct, but has been absorbed into the International Association of Oil and Gas Producers (OGP) Geomatics Committee. They are an authority for maintaining map projection codes in order for there to be a standard directory of projections [http://www.epsg.org/].
 
   
  +
[[File:OpenLayersWMS.png|500px]]
As for the code EPSG:42304, this refers to the Lambert Conformal Conic projection, based on the NAD83 datum. The bounds that have been specified for this map were created using these projected coordinates. These bounds are then used to generate the WMS requests by the OpenLayers library.
 
   
  +
''Figure 1. The OpenLayers WMS viewer built in this tutorial''
In order to complete transformations, [http://trac.osgeo.org/proj/ PROJ.4] can be used. It is a Free and Open Source Software (FOSS) library that allows users to perform conversions between cartographic projections. However, it should be noted that the software only transforms from either projected coordinates to unprojected lat/long coordinates, or from unprojected lat/long coordinates to projected coordinates. Thus if you want to transform the data from one projected coordinate system to another, you must undertake a two-step process where you first transform to unprojected lat/long, then transform again to the desired coordinate system.
 
   
  +
[[File:OpenLayersWMSProjection.png|500px]]
== Resulting Maps ==
 
   
  +
''Figure 2. The OpenLayers WMS viewer at a smaller scale, showing the EPSG:42304 projection''
[[File:Openlayersmapzoom9.png‎]]
 
   
  +
== Source Code ==
''Figure 2. Displaying the OpenLayers map window created by the above HTML code.''
 
   
  +
The final version of the JavaScript code can be found below.
   
  +
<syntaxhighlight lang="javascript" line>
  +
import {Map, View} from 'ol';
  +
import {Image as ImageLayer, Tile as TileLayer} from 'ol/layer';
  +
import ImageWMS from 'ol/source/ImageWMS';
  +
import TileLayer from 'ol/layer/Tile';
  +
import proj4 from 'proj4';
  +
import {register} from 'ol/proj/proj4';
  +
import {fromLonLat} from 'ol/proj';
   
  +
proj4.defs("EPSG:42304","+proj=lcc +lat_1=49 +lat_2=77 +lat_0=49 +lon_0=-95 +x_0=0 +y_0=0 +datum=NAD83 +units=m +no_defs");
  +
register(proj4);
   
  +
const layers = [
  +
new ImageLayer({
  +
source: new ImageWMS({
  +
url: 'https://maps.geogratis.gc.ca/wms/elevation_en?service=WMS&version=1.3.0',
  +
params: {layers: 'cdsm.color-shaded-relief'},
  +
crossOrigin: "Anonymous"
  +
})
  +
}),
  +
new ImageLayer({
  +
source: new ImageWMS({
  +
url: 'https://maps.geogratis.gc.ca/wms/CBMT?service=wms&version=1.3.0',
  +
params: {layers: 'CBMT'},
  +
crossOrigin: 'Anonymous'
  +
})
  +
}),
  +
new ImageLayer({
  +
source: new ImageWMS({
  +
url: 'https://maps.geogratis.gc.ca/wms/railway_en?service=WMS&version=1.3.0',
  +
params: {layers: 'railway'},
  +
crossOrigin: "Anonymous"
  +
})
  +
}),
  +
new ImageLayer({
  +
source: new ImageWMS({
  +
url: 'https://maps.geogratis.gc.ca/wms/canvec_en?service=wms&version=1.3.0',
  +
params: {layers: 'land'},
  +
crossOrigin: "Anonymous"
  +
})
  +
}),
  +
];
   
  +
const map = new Map({
  +
target: 'map',
  +
layers,
  +
view: new View({
  +
center: fromLonLat([-75.696254, 45.384792], 'EPSG:42304'),
  +
zoom: 15,
  +
projection: 'EPSG:42304'
  +
})
  +
});
  +
</syntaxhighlight>
   
The data product specifications regarding the land cover layer can be found [http://www.geobase.ca/doc/specs/pdf/GeoBase_LCC2000V_product_specifications_en.pdf/ here], and the land cover classification legend can be found [http://www.geobase.ca/doc/specs/pdf/GeoBase_LCC2000V_Harmonization_Legend.pdf/ here].
 
   
  +
<!--
  +
== Access to Testing Example ==
   
  +
The OpenLayers WMS example used in this tutorial is available to download [http://dges.carleton.ca/courses/GEOM4008/Student/scottpage/OpenLayersTutorialTest.zip here].
  +
-->
   
  +
== References ==
   
  +
<references/>
[[File:Openlayersmapzoom7.png‎]]
 
 
''Figure 3. Displaying the OpenLayers map window of the same area as Figure 2, but at a lesser zoom level.''
 
 
== References ==
 
OGC WMS Standards [http://www.opengeospatial.org/standards/wms]
 

Latest revision as of 18:48, 30 September 2021

Introduction

The goal of this project is to use freely available Web Map Service (WMS) layers with OpenLayers[1] to create a dynamic map for use within a web browser. This tutorial contains a brief outline of WMS layers and using the OpenLayers library. This includes creating a basic OpenLayers web app, adding WMS layers to the map, and working with projections in OpenLayers.

Required Data and Tools

WMS Layers

A WMS provides an HTTP interface for requesting geo-registered map images from one or more geospatial databases. A WMS request can define the geographic layer(s) and area of interest that will be processed by the service. The service then responds to the request with geo-registered map images (PNG images in this tutorial's example) which can then be displayed in a GIS tool or library like OpenLayers. Web Map Services[2] are an open standard defined by the Open Geospatial Consortium (OGC). All Web Map Services support a GetCapabilities request that returns an XML document that describes the capabilities of the WMS server. This includes the layers that the WMS serves, the supported styles, supported coordinate systems, etc.


Requests are made by adding parameters to the URL of a WMS. For example, to make the GetCapabilities request for the canvec_en service: https://maps.geogratis.gc.ca/wms/canvec_en?service=wms&version=1.3.0&request=GetCapabilities.


If the user has included more than one WMS layer, they have the ability to specify which will be the base layer, and which will be overlay layers. If the user then wants multiple images to be combined, the overlay layers need to be designated transparent. This is not necessary, however, as the user may want only one layer to appear at a time. This can be done by making the transparent flag false for each of the overlay layers, and then the user can manually switch between layers through the web browser. With OpenLayers the ‘LayerSwitcher’ control, which can be added into the code, is a popup that allows the user to do this by simply selecting which layer they want to view.


Several map services will be used in this tutorial.

Web Map Services
Name URL Layers Description
elevation_en https://maps.geogratis.gc.ca/wms/elevation_en?service=WMS&version=1.3.0 cdsm.color-shaded-relief Shaded relief map of Canada
CBMT https://maps.geogratis.gc.ca/wms/CBMT?service=wms&version=1.3.0 CBMT Base map of Canada, highlighting transportation networks
railway_en https://maps.geogratis.gc.ca/wms/railway_en?service=WMS&version=1.3.0 railway Canadian railway network
canvec_en https://maps.geogratis.gc.ca/wms/canvec_en?service=wms&version=1.3.0 land Outline of land


OpenLayers

OpenLayers is an open source JavaScript library that is used for building interactive mapping applications for use in web browsers. It is an Open Source Geospatial Foundation (OSGeo) project that supports many OGC standards including WMS, Web Feature Services (WFS), and Web Map Tile Services (WMTS). Documentation for OpenLayers can be found at https://openlayers.org[1].

Methods

Creating the OpenLayers Viewer

OpenLayers has a tool to create a basic viewer application, which is documented in their quickstart guide. The tool requires NodeJS to be installed, and some basic command line use. To create the OpenLayers project: create a new directory, and then run the project creation tool inside of the directory via the command line:

mkdir wms-tutorial && cd wms-tutorial

npx create-ol-app

This creates a number of files in the project directory including an HTML file, a JavaScript (.js) file, and a few other advanced configuration files. We will focus on the JavaScript file as this is where the OpenLayers code is located.

The main.js file created by the project creation tool contains the following code which creates a basic map with an OpenStreetMap layer. You can open this file for editing in any text editor (Visual Studio Code is a good one).

 1 import {Map, View} from 'ol';
 2 import TileLayer from 'ol/layer/Tile';
 3 import OSM from 'ol/source/OSM';
 4 
 5 const map = new Map({
 6   target: 'map',
 7   layers: [
 8     new TileLayer({
 9       source: new OSM()
10     })
11   ],
12   view: new View({
13     center: [0, 0],
14     zoom: 2
15   })
16 });

To actually run the application, we again use the command line to start it by running the npm start command in the command line. Once started, the application can be accessed by navigating to http://localhost:1234 in your web browser. An interactive map should be visible. Now we can begin modifying it to add our WMS layers.

To add layers, we have to modify the default import statements to include image layer and WMS code from OpenLayers. We add an array of layers (lines 6–14) which will hold our layer and WMS request definitions, and then add the layers to our map (line 18). Here, we add a single WMS layer to start:

 1 import {Map, View} from 'ol';
 2 import {Image as ImageLayer, Tile as TileLayer} from 'ol/layer';
 3 import ImageWMS from 'ol/source/ImageWMS';
 4 import TileLayer from 'ol/layer/Tile';
 5 
 6 const layers = [
 7   new ImageLayer({
 8     source: new ImageWMS({
 9       url: 'https://maps.geogratis.gc.ca/wms/elevation_en?service=WMS&version=1.3.0',
10       params: {layers: 'cdsm.color-shaded-relief'},
11       crossOrigin: 'Anonymous'
12     })
13   })
14 ];
15 
16 const map = new Map({
17   target: 'map',
18   layers: layers,
19   view: new View({
20     center: [0, 0],
21     zoom: 2
22   })
23 });

There are several things to note about the layer definition shown on lines 7 through 13 in the code snippet above. We have to provide the URL for the WMS, along with the layer(s) that we want to be loaded in the params. The crossOrigin: 'Anonymous' parameter is required for some WMS servers due to browser security rules that restrict how web apps can load data from other websites.

Multiple image layers can be added by adding their definition to the layers array. The order in which they are placed in the array determines the order in which they are displayed in the viewer, with the first entry being the bottomost layer. We can see this by adding more layers, including a layer that contains transparent images allowing us to see the layer beneath it.

 6 const layers = [
 7   new ImageLayer({
 8     source: new ImageWMS({
 9       url: 'https://maps.geogratis.gc.ca/wms/elevation_en?service=WMS&version=1.3.0',
10       params: {layers: 'cdsm.color-shaded-relief'},
11       crossOrigin: "Anonymous"
12     })
13   }),
14   new ImageLayer({
15     source: new ImageWMS({
16       url: 'https://maps.geogratis.gc.ca/wms/CBMT?service=wms&version=1.3.0',
17       params: {layers: 'CBMT'},
18       crossOrigin: 'Anonymous'
19     })
20   }),
21   new ImageLayer({
22     source: new ImageWMS({
23       url: 'https://maps.geogratis.gc.ca/wms/railway_en?service=WMS&version=1.3.0',
24       params: {layers: 'railway'},
25       crossOrigin: "Anonymous"
26     })
27   }),
28   new ImageLayer({
29     source: new ImageWMS({
30       url: 'https://maps.geogratis.gc.ca/wms/canvec_en?service=wms&version=1.3.0',
31       params: {layers: 'land'},
32       crossOrigin: "Anonymous"
33     })
34   }),
35 ];


Projections

The default projection used by OpenLayers is the EPGS:3857 (WGS84 Pseudo-Mercator) projection. For the majority of use-cases this is sufficient, but OpenLayers supports many other predefined projections, as well as any arbitrary projection definition. The projection of the map view can be changed by adding a projection parameter to the View initializer. For example, to change the projection to EPSG:4326 (unprojected WGS84):

37 const map = new Map({
38   target: 'map',
39   layers: layers,
40   view: new View({
41     center: [0, 0],
42     zoom: 15,
43     projection: 'EPSG:4326'
44   })
45 });

OpenLayers only has a limited set of projections built in however. For example, if we tried to use the EPSG:42304 (NAD83 Lambert Conformal Conic) projection the map viewer would not load because OpenLayers does not recognize it.

To define a new projection, we can use the open source proj4js library which can be installed in our project by running npm install proj4 from the command line inside the project directory. Once installed, we can import proj4js into our code and use it to define the projection. The definition of an EPSG projection [3] can be looked up at https://epsg.io (there is even a tab specifically for proj4js). The line of proj4js definition code can be added anywhere in our code, as long as it is before the initialization of the map. We also need to register proj4js with OpenLayers so that it is aware of the projection definition. Once registered, we can use the projection that we defined in any OpenLayers function.

 1 import {Map, View} from 'ol';
 2 import {Image as ImageLayer, Tile as TileLayer} from 'ol/layer';
 3 import ImageWMS from 'ol/source/ImageWMS';
 4 import TileLayer from 'ol/layer/Tile';
 5 import proj4 from 'proj4';
 6 import {register} from 'ol/proj/proj4';
 7 
 8 proj4.defs("EPSG:42304","+proj=lcc +lat_1=49 +lat_2=77 +lat_0=49 +lon_0=-95 +x_0=0 +y_0=0 +datum=NAD83 +units=m +no_defs");
 9 register(proj4);
10 
11 const layers = [
12   new ImageLayer({
13     source: new ImageWMS({
14       url: 'https://maps.geogratis.gc.ca/wms/elevation_en?service=WMS&version=1.3.0',
15       params: {layers: 'cdsm.color-shaded-relief'},
16       crossOrigin: "Anonymous"
17     })
18   }),
19   new ImageLayer({
20     source: new ImageWMS({
21       url: 'https://maps.geogratis.gc.ca/wms/CBMT?service=wms&version=1.3.0',
22       params: {layers: 'CBMT'},
23       crossOrigin: 'Anonymous'
24     })
25   }),
26   new ImageLayer({
27     source: new ImageWMS({
28       url: 'https://maps.geogratis.gc.ca/wms/railway_en?service=WMS&version=1.3.0',
29       params: {layers: 'railway'},
30       crossOrigin: "Anonymous"
31     })
32   }),
33   new ImageLayer({
34     source: new ImageWMS({
35       url: 'https://maps.geogratis.gc.ca/wms/canvec_en?service=wms&version=1.3.0',
36       params: {layers: 'land'},
37       crossOrigin: "Anonymous"
38     })
39   }),
40 ];
41 
42 const map = new Map({
43   target: 'map',
44   layers,
45   view: new View({
46     center: [0, 0],
47     zoom: 2,
48     projection: 'EPSG:42304'
49   })
50 });

It is important to note that changing the projection of the OpenLayers view will also change the coordinate system used by OpenLayers. For instance, if we wanted to center the map on the Carleton campus, which is located at -75.696254, 45.384792 (longitude, latitude), this will not work because the EPSG:42304 projection's coordinate systems is in meters, not longitude/latitude degrees. OpenLayers has built in functionality that allows us to transform coordinates between coordinate systems, and to convert from latitude and longitude we can import the convenience function fromLonLat:

1 import {Map, View} from 'ol';
2 import {Image as ImageLayer, Tile as TileLayer} from 'ol/layer';
3 import ImageWMS from 'ol/source/ImageWMS';
4 import TileLayer from 'ol/layer/Tile';
5 import proj4 from 'proj4';
6 import {register} from 'ol/proj/proj4';
7 import {fromLonLat} from 'ol/proj';

Next, we can update the View initializer to use the transformed longitude and latitude coordinates. The target projection must be specified (if not, the default is EPSG:3857).

43 const map = new Map({
44   target: 'map',
45   layers,
46   view: new View({
47     center: fromLonLat([-75.696254, 45.384792], 'EPSG:42304'),
48     zoom: 15,
49     projection: 'EPSG:42304'
50   })
51 });

Resulting Map

OpenLayersWMS.png

Figure 1. The OpenLayers WMS viewer built in this tutorial

OpenLayersWMSProjection.png

Figure 2. The OpenLayers WMS viewer at a smaller scale, showing the EPSG:42304 projection

Source Code

The final version of the JavaScript code can be found below.

 1 import {Map, View} from 'ol';
 2 import {Image as ImageLayer, Tile as TileLayer} from 'ol/layer';
 3 import ImageWMS from 'ol/source/ImageWMS';
 4 import TileLayer from 'ol/layer/Tile';
 5 import proj4 from 'proj4';
 6 import {register} from 'ol/proj/proj4';
 7 import {fromLonLat} from 'ol/proj';
 8 
 9 proj4.defs("EPSG:42304","+proj=lcc +lat_1=49 +lat_2=77 +lat_0=49 +lon_0=-95 +x_0=0 +y_0=0 +datum=NAD83 +units=m +no_defs");
10 register(proj4);
11 
12 const layers = [
13   new ImageLayer({
14     source: new ImageWMS({
15       url: 'https://maps.geogratis.gc.ca/wms/elevation_en?service=WMS&version=1.3.0',
16       params: {layers: 'cdsm.color-shaded-relief'},
17       crossOrigin: "Anonymous"
18     })
19   }),
20   new ImageLayer({
21     source: new ImageWMS({
22       url: 'https://maps.geogratis.gc.ca/wms/CBMT?service=wms&version=1.3.0',
23       params: {layers: 'CBMT'},
24       crossOrigin: 'Anonymous'
25     })
26   }),
27   new ImageLayer({
28     source: new ImageWMS({
29       url: 'https://maps.geogratis.gc.ca/wms/railway_en?service=WMS&version=1.3.0',
30       params: {layers: 'railway'},
31       crossOrigin: "Anonymous"
32     })
33   }),
34   new ImageLayer({
35     source: new ImageWMS({
36       url: 'https://maps.geogratis.gc.ca/wms/canvec_en?service=wms&version=1.3.0',
37       params: {layers: 'land'},
38       crossOrigin: "Anonymous"
39     })
40   }),
41 ];
42 
43 const map = new Map({
44   target: 'map',
45   layers,
46   view: new View({
47     center: fromLonLat([-75.696254, 45.384792], 'EPSG:42304'),
48     zoom: 15,
49     projection: 'EPSG:42304'
50   })
51 });


References

  1. 1.0 1.1 OpenLayers https://openlayers.org
  2. Open Geospatial Consortium WMS Standard https://www.ogc.org/standards/wms/
  3. EPSG refers to European Petroleum Survey Group, which is now-defunct, but has been absorbed into the International Association of Oil and Gas Producers (OGP) Geomatics Committee. They are an authority for maintaining map projection codes in order for there to be a standard directory of projections.