Difference between revisions of "Using WMS with OpenLayers"

From CUOSGwiki
Jump to navigationJump to search
(Update WMS services and OpenLayers usages)
 
(12 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 [http://openlayers.org 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 at that location. For the following example, only [http://www.geobase.ca/geobase/en/index.html GeoBase] and [http://atlas.nrcan.gc.ca/site/english/toporama Natural Resources Canada's Toporama] WMS layers are used, and they can be accessed at no cost and without restrictions.
 
 
This tutorial contains a brief outline of WMS layers and OpenLayers, and then descriptions of the methods used to create the maps displayed in Section 4. This includes discussion of the important aspects of the HTML code that generated the resulting maps, as well as projection transformations and the computing of bounding boxes using [http://trac.osgeo.org/proj/ PROJ.4].
 
 
== Data and Mapping Tools ==
 
   
   
 
=== WMS Layers ===
 
=== 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.
+
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.
   
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.
 
   
  +
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].
   
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 boundaries. The additional overlay layers which encompass all of Canada are as follows:
 
   
  +
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.
Surface water network (layer name: hydrography). Obtained from: [http://wms.ess-ws.nrcan.gc.ca/wms/toporama_en NRCan's Toporama]
 
   
Landcover (layer name: landcover:csc2000): [http://ows.geobase.ca/wms/geobase_en Geobase]
 
   
  +
Several map services will be used in this tutorial.
Primary Roads (layer name: nrn:roadnetwork:roadseg_primary): [http://ows.geobase.ca/wms/geobase_en Geobase]
 
   
  +
{| class="wikitable"
Secondary Roads (layer name: nrn:roadnetwork:roadseg_secondary): [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
  +
|}
   
Cities (layer name: populated_places): [http://wms.ess-ws.nrcan.gc.ca/wms/toporama_en NRCan's Toporama]
 
 
 
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 is representing.
 
   
 
=== OpenLayers ===
 
=== OpenLayers ===
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].
+
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 ==
 
== Methods ==
   
=== WMS Layers ===
+
=== 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 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:
 
   
  +
<syntaxhighlight lang="bash">
basemap – Foreign political boundaries at 1:10M scale
 
  +
mkdir wms-tutorial && cd wms-tutorial
   
  +
npx create-ol-app
water – Surface water network of Canada
 
  +
</syntaxhighlight>
   
  +
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.
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.
 
   
  +
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).
primary roads – Primary roads include mainly the Highways and Expressways, although this may vary according to each province.
 
   
  +
<syntaxhighlight lang="javascript" line>
secondary roads – Secondary roads include mainly the Arterial ways, although this may vary according to each province.
 
  +
import {Map, View} from 'ol';
  +
import TileLayer from 'ol/layer/Tile';
  +
import OSM from 'ol/source/OSM';
   
  +
const map = new Map({
cities – Heavily populated areas
 
  +
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.
   
  +
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:
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.
 
   
  +
<syntaxhighlight lang="javascript" line highlight="2-3,6-14,18">
  +
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 = [
=== OpenLayers Viewer ===
 
  +
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({
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:
 
  +
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.
[[File:Openlayersviewercode.png]]
 
   
  +
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.
''Figure 1. Displaying the HTML code required to craft the OpenLayers viewer and include the OpenLayers library to the page.''
 
   
  +
<syntaxhighlight lang="javascript" line start="6" highlight="9-29">
  +
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>
   
=== HTML Code ===
 
 
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.
 
 
 
 
<!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 = 1511889.26697;//
 
//var lat = -171308.32032;//
 
//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.MousePosition())//
 
//map.addControl( new OpenLayers.Control.LayerSwitcher() );//
 
};
 
 
</script>
 
</body>
 
</html>
 
   
 
=== Projections ===
 
=== 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):
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/].
 
 
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.
 
   
  +
<syntaxhighlight lang="javascript" line start="37" highlight="7">
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.
 
  +
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.
   
  +
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">
=== Computing Bounding Boxes ===
 
  +
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");
If a user wants to recompute bounding boxes from EPSG:42304 (or any other projection) to unprojected lat/long coordinates (EPSG:4269), [http://trac.osgeo.org/proj/wiki/WikiStart#Download/ PROJ.4] can be used. Instructions regarding installation can be found [http://svn.osgeo.org/metacrs/proj/trunk/proj/INSTALL/ here].
 
  +
register(proj4);
   
  +
const layers = [
First, the user should change the projection code in the HTML code. In this case, changing it from EPSG:42304 (Projected Lambert Conformal Conic) to EPSG:4269 (unprojected lat/long). Now the user can computer new 'full' and 'init' unprojected bounding boxes using PROJ.4. The lines within the HTML code that contain the bounding box numbers are shown below:
 
  +
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>:
[[File:Boundingboxcode.png]]
 
   
  +
<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).
In order, the numbers refer to: (left limit, bottom limit, right limit, top limit). The left limit and bottom limit numbers give you the (x,y) coordinates of the bottom left corner, and the right limit and top limit give you the (x,y) coordinates of the top right corner. For each of these points, you can use PROJ.4 to computer inverse projections from those coordinates. This will give you new bounding box numbers in unprojected lat/long angular coordinates. If you then want to transform back to a projected coordinate system, you can complete a forward projection transformation, once again generating new bounding box numbers that are new rectangular coordinates. Full documentation regarding how this is done with PROJ.4 can be found at [ftp://ftp.remotesensing.org/proj/proj.4.3.pdf/ ftp://ftp.remotesensing.org/proj/proj.4.3.pdf]
 
   
  +
<syntaxhighlight lang="javascript" line start="43" highlight="5-6">
== Resulting Maps ==
 
  +
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 ==
[[File:Openlayersmapzoom9.2.png]]
 
   
  +
[[File:OpenLayersWMS.png|500px]]
''Figure 2. Displaying the OpenLayers map window created by the above HTML code.''
 
   
  +
''Figure 1. The OpenLayers WMS viewer built in this tutorial''
   
  +
[[File:OpenLayersWMSProjection.png|500px]]
   
  +
''Figure 2. The OpenLayers WMS viewer at a smaller scale, showing the EPSG:42304 projection''
Figure 2 is the result of the HTML code in Section 3.3. It shows the land cover makeup of the Ottawa area. As this image is rather zoomed in, the land cover, water and roads layers are visible. Figure 3 is included because it shows the Ottawa area at a lower zoom level. From this level, all of the overlay layers are visible, including the populated places layer. This is useful for those who do not know the coordinates of the location they may want to view, or if they want to browse the area surrounding their desired location.
 
   
  +
== Source Code ==
   
  +
The final version of the JavaScript code can be found below.
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].
 
   
  +
<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");
Additionally, the two maps do not have OpenLayers controls added to the map window. These could include a PanZoomBar, mouse position coordinates, and a layer switcher option. If the user desires these controls, the code required to insert them can be found [http://dev.openlayers.org/docs/files/OpenLayers/Control-js.html here].
 
  +
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>
   
   
  +
<!--
[[File:Openlayersmapzoom7.png‎]]
 
  +
== 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].
''Figure 3. Displaying the OpenLayers map window of the same area as Figure 2, but at a lesser zoom level.''
 
  +
-->
   
 
== References ==
 
== References ==
OGC WMS Standards [http://www.opengeospatial.org/standards/wms]
 
   
  +
<references/>
OGP Geomatics Committee [http://www.epsg.org/]
 

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.