Web Feature Services
Overview
In this exercise we'll create a Mapfile that can be used to serve data as a WFS. We'll be using the "Populated Places" from Natural Earth as the data source.
Configuring a MAP for WFS
Similar to other OGC services, setting up a Mapfile to serve a WFS uses
keywords in METADATA
blocks.
By default WFS output is XML. We can however configure it to output other formats such as GeoJSON by adding an OUTPUTFORMAT block to the Mapfile:
OUTPUTFORMAT
NAME "geojson"
DRIVER "OGR/GEOJSON"
MIMETYPE "application/json; subtype=geojson; charset=utf-8"
FORMATOPTION "FORM=SIMPLE"
FORMATOPTION "STORAGE=memory"
FORMATOPTION "LCO:NATIVE_MEDIA_TYPE=application/vnd.geo+json"
FORMATOPTION "USE_FEATUREID=true" # ensure GeoJSON output has an id property
END
We then need to add this format to the list of formats returned by the service:
WEB
METADATA
...
"wfs_getfeature_formatlist" "geojson" # we could also return more complex types such as shapezip
END
END
We also need to make sure that any projection requested by a client application is allowed:
WEB
METADATA
...
"wfs_srs" "EPSG:4326 EPSG:3857"
# we can also use ows_ to set these properties for all OWS services
# such as WMS, WFS, and WCS
# "ows_srs" "EPSG:4326 EPSG:3857"
END
END
See the WFS Server documentation for more details.
Configuring a LAYER for WFS
At the LAYER
level there are some additional settings that need to be configured.
Tip
It is good practice to set an EXTENT
on the LAYER
. If not set then MapServer tries to calculate this dynamically so it can return the extent in requests such as GetCapabilities
. This can dramatically slow down the performance of the layer.
We set a unique field name in the METADATA
using gml_featureid
. Without this not all features may be returned.
We also need to configure which feature properties are returned by the service.
We can provide a list of field names, or we can use the all
keyword to return all properties.
We can also manually define the field type for each property, or we can let MapServer calculate these from the source dataset using "gml_types" "auto"
.
The METADATA
blocks are very flexible, and allow different titles to be applied to the layer for different services, for example:
Other Mapfile Notes
The Mapfile contains a LAYER FILTER to limit the features in the layer.
Requesting a WFS in OpenLayers
In OpenLayers we create a VectorLayer with a VectorSource.
The URL for the layer specifies GeoJSON as the format to use: &outputFormat=geojson
.
The code used for this example is based on the WFS example. Every time the OpenLayers map is moved a request is made to return features.
const vectorSource = new VectorSource({
format: new GeoJSON(),
url: function (extent) {
const url = mapserverUrl + mapfilesPath + 'wfs.map&service=WFS&' +
'version=2.0.0&request=GetFeature&typename=places&' +
'outputFormat=geojson&crsName=EPSG:3857&' +
'bbox=' +
extent.join(',') +
',EPSG:3857';
return url;
},
strategy: bboxStrategy,
});
As a WFS returns raw features we need to apply styling in the client. In this example we create a function that returns a circle style:
function createStyle(feature) {
return new Style({
image: new CircleStyle({
radius: 5 + feature.get('rank_min'),
fill: new Fill({
color: [255, 153, 0, 0.8],
}),
}),
...
});
}
...
new VectorLayer({
style: createStyle,
source: vectorSource
}),
Testing on the Command Line
We can test the Mapfile and WFS responses on the command line as follows:
docker exec -it mapserver /bin/bash
mapserv -nh "QUERY_STRING=map=/etc/mapserver/wfs.map&service=WFS&version=2.0.0&request=GetFeature&typeName=places&outputFormat=geojson&crsName=EPSG:3857&bbox=-59223902.72157662,-3903081.7252075593,-14974405.131250374,19995821.45447336,EPSG:3857"
Code
wfs.js
import '../css/style.css';
import GeoJSON from 'ol/format/GeoJSON.js';
import Map from 'ol/Map.js';
import OSM from 'ol/source/OSM.js';
import VectorSource from 'ol/source/Vector.js';
import View from 'ol/View.js';
import {
Circle as CircleStyle,
Fill,
Stroke,
Style,
Text,
} from 'ol/style.js';
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer.js';
import { bbox as bboxStrategy } from 'ol/loadingstrategy.js';
// based on the example at https://openlayers.org/en/latest/examples/vector-wfs.html
const mapserverUrl = import.meta.env.VITE_MAPSERVER_BASE_URL;
const mapfilesPath = import.meta.env.VITE_MAPFILES_PATH;
const vectorSource = new VectorSource({
format: new GeoJSON(),
url: function (extent) {
const url = mapserverUrl + mapfilesPath + 'wfs.map&service=WFS&' +
'version=2.0.0&request=GetFeature&typename=places&' +
'outputFormat=geojson&crsName=EPSG:3857&' +
'bbox=' +
extent.join(',') +
',EPSG:3857';
console.log(url);
return url;
},
strategy: bboxStrategy,
});
const textFill = new Fill({
color: '#fff',
});
const textStroke = new Stroke({
color: 'rgba(0, 0, 0, 0.6)',
width: 3,
});
function createStyle(feature) {
return new Style({
image: new CircleStyle({
radius: 5 + feature.get('rank_min'),
fill: new Fill({
color: [255, 153, 0, 0.8],
}),
}),
text: new Text({
text: feature.get('name'),
fill: textFill,
stroke: textStroke,
}),
});
}
const layers = [
new TileLayer({
source: new OSM(),
className: 'bw',
}),
new VectorLayer({
style: createStyle,
source: vectorSource
}),
];
const map = new Map({
layers: layers,
target: 'map',
view: new View({
center: [2975862.75916499, 8046369.8646329],
zoom: 5,
}),
});
wfs.map
MAP
NAME "WFS"
EXTENT -180 -90 180 90
SIZE 400 400 #
PROJECTION
"init=epsg:4326"
END
OUTPUTFORMAT
NAME "geojson"
DRIVER "OGR/GEOJSON"
MIMETYPE "application/json; subtype=geojson; charset=utf-8"
FORMATOPTION "FORM=SIMPLE"
FORMATOPTION "STORAGE=memory"
FORMATOPTION "LCO:NATIVE_MEDIA_TYPE=application/vnd.geo+json"
FORMATOPTION "USE_FEATUREID=true" # ensure GeoJSON output has an id property
END
WEB
METADATA
"ows_enable_request" "*" # this enables all OGC requests
"wfs_getfeature_formatlist" "geojson"
"wfs_srs" "EPSG:4326 EPSG:3857"
"ows_onlineresource" "http://localhost:5000/"
END
END
LAYER
NAME "places"
TYPE POINT
PROJECTION
"init=epsg:4326"
END
EXTENT -180.0 -90.0 180.0 90
STATUS OFF
METADATA
"gml_featureid" "ne_id"
"gml_include_items" "all"
"gml_types" "auto"
END
FILTER ([pop_max] > 1000000) # only return places with a population > 1 million
CONNECTIONTYPE OGR
CONNECTION "data/naturalearth"
DATA "ne_50m_populated_places_simple"
CLASS
STYLE
COLOR 60 179 113
OUTLINECOLOR 255 255 255
OUTLINEWIDTH 0.1
END
END
END
END
Exercises
- Change the
MAP
andLAYER
WFS metadata, and view the GetCapabilities document. - Try limiting the
gml_include_items
to a single attributename
. -
Try adding a new
shapezip
OUTPUTFORMAT
, and testing the response on the command line. You can redirect it to a file using> test.zip
. Remember to add the format towfs_getfeature_formatlist
in the Mapfile, and tooutputFormat
in the request string.