Skip to content

OGCAPI Features Part 3 Filtering

Filtering allows requests to an OGC API - Features service to return only the features needed. This can be based on feature properties, location, or a combination of both. In this tutorial, you'll learn how filtering is supported in MapServer and how to use it in a sample web application.

Let's take a look at the Timișoara OGC API Landing Page. This contains a link to a page listing all the supported Conformance Classes.

The three we are interested in for this tutorial are:

Note

As this is an unreleased feature (due as part of the MapServer 8.8 release), documentation can only be seen on the documentation preview web site.

We can see which attributes in the buildings layer are filterable by opening the queryables endpoint:

http://localhost:7000/TIMISOARA/ogcapi/collections/buildings/queryables?f=html

This list is controlled in MapServer by the oga_queryable_items keyword in the layer METADATA block. In this example we use the special value all, which exposes all layer attributes as queryables (similar to gml_include_items when set to all). If we only want selected attributes we can use a comma-separated list.

    LAYER
        NAME "buildings"
        TYPE POLYGON
        # allow the OGR FeatureId to be returned and used for querying
        # see https://github.com/MapServer/MapServer/pull/7533
        PROCESSING "OGR_EXPOSE_FID=TRUE"
        METADATA
            "ows_title" "Buildings"
            "gml_include_items" "all"
            "gml_featureid" "fid"
            "gml_types" "auto"
            "oga_queryable_items" "all"
            # or a list of properties
            # "oga_queryable_items" "name,osm_id"
        ...

The default MapServer Bootstrap templates then expose these queryables as simple filter inputs in the HTML interface. You can try this directly on the items page:

http://localhost:7000/TIMISOARA/ogcapi/collections/buildings/items?f=html

Filter interface

Filter Types

CQL2 stands for Common Query Language 2, an OGC standard for defining filters on feature data.

CQL2-text and CQL2-JSON are two equivalent encodings of the same filtering language. CQL2-text is a human-readable syntax. CQL2-JSON represents the same logic as a structured JSON object, making it easier for software to generate, validate, and manipulate programmatically.

When you use the Bootstrap template above and apply a filter, you will see the query in the URL, for example:

http://localhost:7000/TIMISOARA/ogcapi/collections/buildings/items?f=html&filter=building+LIKE+%27%25hospital%25%27&filter-lang=cql2-text

The filter-lang=cql2-text parameter indicates that the filter is written in CQL2-text. The filter itself is URL-encoded (for example spaces become + and special characters are percent-encoded), which is required because it is being passed in a URL query string.

We can also write the same query using CQL2-JSON:

{
  "op": "like",
  "args": [
    { "property": "building" },
    "%hospital%"
  ]
}

Both expressions mean the same thing: return features where the building attribute contains "hospital".

The JSON filter looks as follows as an encoded-URL:

http://localhost:7000/TIMISOARA/ogcapi/collections/buildings/items?f=json&filter=%7B%22op%22%3A%22like%22%2C%22args%22%3A%5B%7B%22property%22%3A%22building%22%7D%2C%22%25hospital%25%22%5D%7D&filter-lang=cql2-json

If you open this in a browser, you will see a list of matching features. These are returned in JSON because we supplied the f=json parameter. We also have to add the &filter-lang=cql2-json parameter, as by default MapServer assumes cql2-text.

In addition to CQL2 filters, MapServer also supports simple query parameter filtering on queryable attributes. For example, &building=hospital is a shorthand for an equality-style filter on the building attribute. This is convenient for quick lookups but lacks the expressiveness of full CQL2.

http://localhost:7000/TIMISOARA/ogcapi/collections/buildings/items?f=json&building=hospital

Custom Application with Filtering

MapServer's filtering support allows us to quickly build powerful applications, using simple API calls. http://localhost:7001/timisoara.html links to a custom OpenLayers application, using the OGC filtering API to filter buildings by type, and highlight them on a map.

The key parts of the code relating to filtering are building the filter string (as cql2-text):

const cqlFilter = `building = '${buildingType}'`;
return `${baseUrl}/timisoara/ogcapi/collections/buildings/items?` +
    `filter=${encodeURIComponent(cqlFilter)}&filter-lang=cql2-text&limit=1000&f=json`;

And updating the data source when the selection changes:

document.getElementById('building-select').addEventListener('change', (e) => {
    const newSource = new VectorSource({
        format: new GeoJSON(),
        url: buildOgcUrl(e.target.value),
        strategy: allStrategy,
    });
    vectorLayer.setSource(newSource);
});

The options for the drop-down list are defined in the ./workshop/exercises/app/timisoara.html page.

<select id="building-select">
    <option value="apartments">Apartments</option>
    <option value="church">Church</option>
    <option value="commercial">Commercial</option>
    <option value="hospital">Hospital</option>
    <option value="office">Office</option>
    <option selected value="university">University</option>
</select>

Code

Example

timisoara.js
import '../css/style.css';
import ImageWMS from 'ol/source/ImageWMS.js';
import Map from 'ol/Map.js';
import OSM from 'ol/source/OSM.js';
import View from 'ol/View.js';
import { Image as ImageLayer, Tile as TileLayer } from 'ol/layer.js';
import VectorLayer from 'ol/layer/Vector.js';
import VectorSource from 'ol/source/Vector.js';
import GeoJSON from 'ol/format/GeoJSON.js';
import { Style, Stroke } from 'ol/style.js';
import { all as allStrategy } from 'ol/loadingstrategy.js';

const mapserverUrl = import.meta.env.VITE_MAPSERVER_BASE_URL;
const mapfilesPath = import.meta.env.VITE_MAPFILES_PATH;
const baseUrl = mapserverUrl.replace(/\?+$/, '');

function buildOgcUrl(buildingType) {
    const cqlFilter = `building = '${buildingType}'`;
    return `${baseUrl}/timisoara/ogcapi/collections/buildings/items?` +
        `filter=${encodeURIComponent(cqlFilter)}&filter-lang=cql2-text&limit=1000&f=json`;
}

const select = document.getElementById('building-select');

const vectorSource = new VectorSource({
    format: new GeoJSON(),
    url: buildOgcUrl(select.value),
    strategy: allStrategy,
});

const vectorLayer = new VectorLayer({
    source: vectorSource,
    style: [
        new Style({
            stroke: new Stroke({ color: 'rgba(0,0,0,0.5)', width: 5 }),
        }),
        new Style({
            stroke: new Stroke({ color: 'cyan', width: 3 }),
        }),
    ]
});

const layers = [
    new TileLayer({
        source: new OSM(),
        visible: false,
    }),
    new ImageLayer({
        opacity: 0.2,
        source: new ImageWMS({
            url: mapserverUrl + mapfilesPath + 'timisoara.map&',
            params: { 'LAYERS': 'buildings', 'STYLES': '' },
        }),
    }),
    vectorLayer,
];

const map = new Map({
    layers: layers,
    target: 'map',
    view: new View({
        center: [2363111, 5740066],
        zoom: 16,
    }),
});

document.getElementById('building-select').addEventListener('change', (e) => {
    const newSource = new VectorSource({
        format: new GeoJSON(),
        url: buildOgcUrl(e.target.value),
        strategy: allStrategy,
    });
    vectorLayer.setSource(newSource);
});

const loadingMask = document.getElementById('loading-mask');
map.on('loadstart', () => loadingMask.classList.add('active'));
map.on('loadend', () => loadingMask.classList.remove('active'));
ogcapi-features.map
MAP
    NAME "Timisoara"
    SIZE 800 800
    EXTENT 20.9984994 45.6280885 21.5152955 45.8955907
    PROJECTION
        "EPSG:4326"
    END
    WEB
        IMAGEPATH "/etc/mapserver/tmp/" # end-slash is important!
        METADATA
            "ows_enable_request" "*"
            "ows_srs" "EPSG:4326 EPSG:3857"
            "ows_use_default_extent_for_getfeature" "false"
        END
    END
    LAYER
        NAME "buildings"
        PROJECTION
            "EPSG:4326"
        END
        STATUS OFF
        TYPE POLYGON
        PROCESSING "OGR_EXPOSE_FID=TRUE"
        CONNECTIONTYPE OGR
        CONNECTION "data/osm/timisoara/buildings.gpkg"
        TEMPLATE "ttt"
        METADATA
            "ows_title" "Buildings"
            "gml_include_items" "all"
            "gml_featureid" "fid"
            "gml_types" "auto"
            "oga_queryable_items" "all"
            "oga_sortable_items" "all"
        END
        CLASSITEM "building"
        CLASS
            STYLE
                COLOR 220 80 60
            END
            EXPRESSION {house}
        END
        CLASS
            STYLE
                COLOR 60 130 220
            END
            EXPRESSION {apartments}
        END
        CLASS
            STYLE
                COLOR 180 100 40
            END
            EXPRESSION {industrial}
        END
        CLASS
            STYLE
                COLOR 100 180 80
            END
            EXPRESSION {residential}
        END
        CLASS
            STYLE
                COLOR 240 160 60
            END
            EXPRESSION {detached}
        END
        CLASS
            STYLE
                COLOR 130 110 170
            END
            EXPRESSION {garages}
        END
        CLASS
            STYLE
                COLOR 150 125 185
            END
            EXPRESSION {garage}
        END
        CLASS
            STYLE
                COLOR 60 190 190
            END
            EXPRESSION {commercial}
        END
        CLASS
            STYLE
                COLOR 210 140 90
            END
            EXPRESSION {semidetached_house}
        END
        CLASS
            STYLE
                COLOR 80 160 200
            END
            EXPRESSION {school}
        END
        CLASS
            STYLE
                COLOR 160 140 110
            END
            EXPRESSION {warehouse}
        END
        CLASS
            STYLE
                COLOR 190 190 100
            END
            EXPRESSION {roof}
        END
        CLASS
            STYLE
                COLOR 100 200 120
            END
            EXPRESSION {greenhouse}
        END
        CLASS
            STYLE
                COLOR 220 110 150
            END
            EXPRESSION {retail}
        END
        CLASS
            STYLE
                COLOR 200 160 80
            END
            EXPRESSION {construction}
        END
        CLASS
            STYLE
                COLOR 180 120 100
            END
            EXPRESSION {terrace}
        END
        CLASS
            STYLE
                COLOR 170 130 200
            END
            EXPRESSION {church}
        END
        CLASS
            STYLE
                COLOR 240 180 130
            END
            EXPRESSION {kindergarten}
        END
        CLASS
            STYLE
                COLOR 200 80 100
            END
            EXPRESSION {hospital}
        END
        CLASS
            STYLE
                COLOR 80 110 190
            END
            EXPRESSION {university}
        END
        CLASS
            STYLE
                COLOR 120 170 210
            END
            EXPRESSION {dormitory}
        END
        CLASS
            STYLE
                COLOR 160 155 140
            END
            EXPRESSION {shed}
        END
        CLASS
            STYLE
                COLOR 90 150 170
            END
            EXPRESSION {office}
        END
        CLASS
            STYLE
                COLOR 140 200 160
            END
            EXPRESSION {public}
        END
        CLASS
            STYLE
                COLOR 170 165 150
            END
            EXPRESSION {service}
        END
        CLASS
            STYLE
                COLOR 100 100 160
            END
            EXPRESSION {train_station}
        END
        CLASS
            STYLE
                COLOR 210 150 170
            END
            EXPRESSION {hotel}
        END
        CLASS
            STYLE
                COLOR 220 220 220
            END
        END
    END
END

Exercises