Skip to content

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).

gdal raster info /etc/mapserver/data/raster/54752_dtm_1m.tif

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
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

  1. From the command line, test the WCS 2.0.1 protocol by making a GetCoverage request and saving the output as a GeoTIFF using the configured OUTPUTFORMAT (MapServer format name, not a MIME type). Then use gdal raster info to 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
    
  2. Add the COG output format to the Mapfile and make a GetCoverage request to download a COG-formatted output. Check the output file with gdal raster info to see the difference in metadata.

  3. Update the JavaScript code to test the WCS 1.0.0 protocol. This requires different parameters to be passed in the requests, for example COVERAGEID becomes COVERAGE, and the CRS parameters are different. You can also remove the entire imageLoadFunction as WCS 1.0.0 more closely matches the WMS protocol, using BBOX,WIDTH, and HEIGHT parameters to specify the area and size of the output image.

    params: {
        SERVICE: 'WCS',
        VERSION: '1.0.0',
        REQUEST: 'GetCoverage',
        FORMAT: 'image/png',
        COVERAGE: 'dtm',
        CRS: 'EPSG:3857',
        RESPONSE_CRS: 'EPSG:3857',
    },
    

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.

Further Reading