Web Coverage Services (WCS)
Overview
This tutorial demonstrates the WCS Server capabilities of MapServer. We'll be using WCS 2.0 for this tutorial, and will serve a Cloud-Optimized GeoTIFF (COG) from the Estonian Land Board as the source dataset. The dataset is a Digital Terrain Model (DTM) with a 1 m resolution.
WCS Requests
Some sample MapServer requests for testing the WCS service are listed below. You can test these in your browser.
You can also connect to the MapServer Docker container and use mapserv to test the requests from the command line.
docker exec -it mapserver bash
mapserv -nh "QUERY_STRING=map=/etc/mapserver/wcs.map&SERVICE=WCS&REQUEST=GetCapabilities&VERSION=2.0.1"
Source Dataset
Let's get some information about the source dataset using the GDAL CLI commandgdal raster info
(the modern equivalent of gdalinfo).
The truncated output is shown below.
Driver: GTiff/GeoTIFF
Files: /etc/mapserver/data/raster/54752_dtm_1m.tif
/etc/mapserver/data/raster/54752_dtm_1m.tif.aux.xml
Size is 5000, 5000
Coordinate System is:
PROJCRS["Estonian Coordinate System of 1997",
...
ID["EPSG",3301]]
Origin = (655000.000000000000000,6475000.000000000000000)
Pixel Size = (1.000000000000000,-1.000000000000000)
...
Image Structure Metadata:
LAYOUT=COG
...
Center ( 657500.000, 6472500.000) ( 26d41'30.35"E, 58d21'52.44"N)
Band 1 Block=512x512 Type=Float32, ColorInterp=Gray
Min=30.680 Max=83.205
Minimum=30.680, Maximum=83.205, Mean=60.461, StdDev=9.347
NoData Value=-9999
Overviews: 2500x2500, 1250x1250, 625x625, 312x312
Metadata:
STATISTICS_MAXIMUM=83.205001831055
...
From the output we can see that the dataset is in the EPSG:3301 coordinate reference system, with an origin at (655000, 6475000)
and a pixel size of 1 × 1 (with a negative Y resolution, as is typical for north-up rasters). The LAYOUT=COG indicates that the file is structured as a Cloud-Optimized GeoTIFF (COG).
Configuring a Mapfile for WCS
The Mapfile for the WCS service is similar to a WMS Mapfile, but with some differences. The LAYER type is set to RASTER, and the METADATA section contains
keywords prefixed with wcs_ to specify the WCS parameters.
WEB
METADATA
"wcs_enable_request" "*"
"wcs_srs" "EPSG:4326 EPSG:3857"
"wcs_title" "Example WCS Mapfile"
"wcs_description" "Test description"
"wcs_onlineresource" "http://localhost:7000/"
END
END
If the Mapfile is used for multiple services such as WMS and WCS, a single metadata item can be specified using the ows_ prefix, for example ows_title.
LAYER METADATA can also be used to specify additional information about the coverage, but is not required for this tutorial.
The OUTPUTFORMAT defines the properties of the output format.
In this case we are defining a custom output format for GeoTIFFs with a FLOAT32 data type to match the source raster
and ensure that the full precision of the source raster is preserved in WCS responses.
OUTPUTFORMAT
NAME "GEOTIFF"
DRIVER "GDAL/GTiff"
MIMETYPE "image/tiff"
IMAGEMODE FLOAT32
EXTENSION "tif"
END
We can use the full power of GDAL to define custom output formats. For example, we could define a COG output format
by switching to the COG Driver, and add statistics to the output file by adding the STATISTICS=YES format option:
OUTPUTFORMAT
NAME "GEOTIFF_COG"
DRIVER "GDAL/COG"
MIMETYPE "image/tiff"
IMAGEMODE FLOAT32
EXTENSION "tif"
FORMATOPTION "STATISTICS=YES"
END
Requesting a WCS in OpenLayers
Typically WCS requests are made from client applications such as QGIS, ArcGIS Pro, or custom JS code in web applications to download the raw raster data, rather than to display it as a map image. However, for the purposes of this tutorial we will be using OpenLayers to make requests to the WCS and display the results. WCS is not natively supported in OpenLayers, but we can use the ImageWMS source as a workaround by overriding the request parameters to call WCS, and display the results as an image layer on the map.
Typically WCS is used to return raw raster formats such as GeoTIFFs, but viewing these in a browser requires rendering the raw pixel values using additional JavaScript libraries such as geotiff.js.
In this tutorial we instead request a rendered image (FORMAT: 'image/png') and use CSS filters
to visually confirm that data is being returned correctly.
const wcsSource = new ImageWMS({
url,
params: {
SERVICE: 'WCS',
VERSION: '2.0.1',
REQUEST: 'GetCoverage',
FORMAT: 'image/png',
COVERAGEID: 'dtm',
SUBSETTINGCRS: 'http://www.opengis.net/def/crs/EPSG/0/3857',
OUTPUTCRS: 'http://www.opengis.net/def/crs/EPSG/0/3857',
}
Tip
The COVERAGEID corresponds to the MapServer LAYER NAME
Code
wcs.js
import '../css/style.css';
import Map from 'ol/Map';
import View from 'ol/View';
import ImageLayer from 'ol/layer/Image';
import ImageWMS from 'ol/source/ImageWMS';
const mapserverUrl = import.meta.env.VITE_MAPSERVER_BASE_URL;
const mapfilesPath = import.meta.env.VITE_MAPFILES_PATH;
const url = mapserverUrl + mapfilesPath + 'wcs.map';
const wcsSource = new ImageWMS({
url,
params: {
SERVICE: 'WCS',
VERSION: '2.0.1',
REQUEST: 'GetCoverage',
FORMAT: 'image/png',
COVERAGEID: 'dtm',
SUBSETTINGCRS: 'http://www.opengis.net/def/crs/EPSG/0/3857',
OUTPUTCRS: 'http://www.opengis.net/def/crs/EPSG/0/3857',
},
projection: 'EPSG:3857',
imageLoadFunction: (image, src) => {
const srcUrl = new URL(src);
const params = srcUrl.searchParams;
// Get the ImageWMS params
const bbox = params.get('BBOX').split(',');
const width = params.get('WIDTH');
const height = params.get('HEIGHT');
// Replace with WCS 2.0.1 equivalents
params.append('SUBSET', `x(${bbox[0]},${bbox[2]})`);
params.append('SUBSET', `y(${bbox[1]},${bbox[3]})`);
params.set('SCALESIZE', `x(${width}),y(${height})`);
// Remove the WMS params
params.delete('BBOX');
params.delete('WIDTH');
params.delete('HEIGHT');
params.delete('CRS');
image.getImage().src = srcUrl.toString();
},
ratio: 1,
});
const map = new Map({
target: 'map',
layers: [
new ImageLayer({
source: wcsSource
}),
],
view: new View({
projection: 'EPSG:3857',
center: [2975862, 8046369],
zoom: 14,
}),
});
// apply CSS filters to enhance the contrast in the DTM
map.once('rendercomplete', () => {
document.querySelector('.ol-layer canvas').style.filter =
'brightness(2.2) contrast(2) sepia(1) hue-rotate(90deg) saturate(3)';
});
wcs.map
MAP
NAME "WCS"
EXTENT 26.668678 58.339241 26.796582 58.40941
UNITS DD
SIZE 800 600
MAXSIZE 5000
PROJECTION
"epsg:4326"
END
OUTPUTFORMAT
NAME "GEOTIFF"
DRIVER "GDAL/GTiff"
MIMETYPE "image/tiff"
IMAGEMODE FLOAT32
EXTENSION "tif"
END
WEB
METADATA
"wcs_enable_request" "*"
"wcs_srs" "EPSG:4326 EPSG:3857"
"wcs_title" "Example WCS Mapfile"
"wcs_description" "Test description"
"wcs_onlineresource" "http://localhost:7000/"
END
END
LAYER
NAME "dtm"
EXTENT 655000 6470000 660000 6475000
STATUS OFF
TYPE RASTER
DATA "data/raster/54752_dtm_1m.tif" # Map data: Estonian Land Board 2024
PROJECTION
"epsg:3301"
END
END
END
Exercises
-
From the command line, test the WCS 2.0.1 protocol by making a
GetCoveragerequest and saving the output as a GeoTIFF using the configuredOUTPUTFORMAT(MapServer format name, not a MIME type). Then usegdal raster infoto check the output file.mapserv -nh "QUERY_STRING=map=/etc/mapserver/wcs.map&SERVICE=WCS&VERSION=2.0.1&REQUEST=GetCoverage&COVERAGEID=dtm&FORMAT=GEOTIFF&SUBSETTINGCRS=http://www.opengis.net/def/crs/EPSG/0/4326&SUBSET=x(26.6507,26.7362)&SUBSET=y(58.3414,58.3879)&SCALESIZE=x(400),y(400)" \ > output.tif gdal raster info output.tif -
Add the COG output format to the Mapfile and make a
GetCoveragerequest to download a COG-formatted output. Check the output file withgdal raster infoto see the difference in metadata. -
Update the JavaScript code to test the WCS 1.0.0 protocol. This requires different parameters to be passed in the requests, for example
COVERAGEIDbecomesCOVERAGE, and the CRS parameters are different. You can also remove the entireimageLoadFunctionas WCS 1.0.0 more closely matches the WMS protocol, usingBBOX,WIDTH, andHEIGHTparameters to specify the area and size of the output image.
Possible Errors
msWCSGetCoverage20(): WCS server error. Raster size out of range, width and height of resulting coverage must be no more than MAXSIZE=4096.
Set the MAXSIZE directive in the MAP to a larger value. By default, this is set to 4096.