The GeoJSON client discussed in the previous
chapter allows reading data from a
static web resource or a local file. However most often geospatial APIs contains
huge datasets, and data items to be queried must be selected and filtered.
What is OGC API Features?
The OGC API Features standard
by the Open Geospatial Consortium (or
OGC) specifies this - how data is discovered and accessed:
OGC API Features provides API building blocks to create, modify and query
features on the Web. OGC API Features is comprised of multiple parts, each of
them is a separate standard. The “Core” part specifies the core
capabilities and is restricted to fetching features where geometries are
represented in the coordinate reference system WGS 84 with axis order
longitude/latitude. Additional capabilities that address more advanced needs
will be specified in additional parts.
1️⃣ Part 1: Core
A compliant (according to OGC API - Features - Part 1: Core
) API service
should provide at least following resources:
Resource Path Description Landing page /
Metadata about the API. Conformance classes /conformance
Conformance classes supported by the API. Feature collections /collections
Metadata about all feature collections provided by the API. Feature collection /collections/{collectionId}
Metadata about a single feature collection provided by the API. Features /collections/{collectionId}/items
Feature items (with geometry and property data) in a specified feature collection provided by the API. Feature (by id) /collections/{collectionId}/items/{featureId}
A single feature item (with geometry and property data) in a specified feature collection provided by the API.
Most services also provide an API definition (ie. an Open API 3.0 document) at
/api
describing the capabilities of the API service.
See
geodata_example.dart
for a sample how to read metadata and feature items from an API service
conforming to
OGC API Features .
Most relevant portions of this sample:
import 'package:geobase/geobase.dart' ;
import 'package:geodata/ogcapi_features_client.dart' ;
Future < void > main ( List < String > args) async {
// create an OGC API Features client for the open pygeoapi demo service
// (see https://pygeoapi.io/ and https://demo.pygeoapi.io for more info)
final client = OGCAPIFeatures . http (
endpoint : Uri . parse ( 'https://demo.pygeoapi.io/master/' ),
// resource meta contains the service title (+ links and optional description)
final meta = await client. meta ();
print ( 'Service: ${ meta . title } ' );
// access OpenAPI definition for the service and check for terms of service
// (OpenAPI contains also other info of service, queries and responses, etc.)
final openAPI = await meta. openAPI ();
final info = openAPI.content[ 'info' ] as Map < String , dynamic >;
print ( 'Terms of service: ${ info [ 'termsOfService' ]} ' );
// conformance classes (text ids) informs the capabilities of the service
final conformance = await client. conformance ();
// service should (at least) be compliant with Part 1 (Core + GeoJSON)
if ( ! conformance. conformsToFeaturesCore (geoJSON : true )) {
print ( 'NOT compliant with Part 1 (Core, GeoJSON).' );
// get a feature source (`OGCFeatureSource`) for Dutch windmill point features
final source = await client. collection ( 'dutch_windmills' );
// the source for the collection also provides some metadata
final collectionMeta = await source. meta ();
print ( 'Collection: ${ collectionMeta . id } / ${ collectionMeta . title } ' );
print ( 'Description: ${ collectionMeta . description } ' );
print ( 'Spatial extent: ${ collectionMeta . extent ?. spatial } ' );
print ( 'Temporal extent: ${ collectionMeta . extent ?. temporal } ' );
// **** next read actual data (wind mills) from this collection
// `itemsAll` lets access all features on source (optionally limited by limit)
final itemsAll = await source. itemsAll (
// (... code omitted ...)
// `itemsAllPaged` helps paginating through a large dataset with many features
// (here each page is limited to 2 features)
Paged < OGCFeatureItems > ? page = await source. itemsAllPaged (limit : 2 );
// (... code omitted ...)
// `items` is used for filtered queries, here bounding box, WGS 84 coordinates
final items = await source. items (
bbox : GeoBox (west : 5.03 , south : 52.21 , east : 5.06 , north : 52.235 ),
// (... code omitted ...)
// `BoundedItemsQuery` provides also following filters:
// - `limit` sets the maximum number of features returned
// - `timeFrame` sets a temporal filter
// - `bboxCrs` sets the CRS used by the `bbox` filter (*)
// - `crs` sets the CRS used by geometry objects of response features (*)
// - `parameters` sets queryable properties as a query parameter filter (#)
// (*) supported by services conforming to Part 2: CRS
// (#) supported by services conforming to Part 3: Filtering
// `itemsPaged` is used for paginated access on filtered queries
// (not demostrated here, see `itemsAllPaged` sample above about paggination)
// samples above accessed feature collections (resuls with 0 to N features)
// it's possible to access also a single specific feature item by ID
final item = await source. itemById ( 'Molens.5' );
// (... code omitted ...)
As mentioned above, see
geodata_example.dart
for the full sample.
2️⃣ Part 2: Coordinate Reference Systems by Reference
The Part 1: Core
defined feature services that support only accessing data
using WGS 84 longitude / latitude coordinates (with optional height or
elevation).
The second part of the OGC API - Features
standard is
Part 2: Coordinate Reference Systems by Reference
that specifies how servers
publish supported coordinate refererence systems (as CRS identifiers) and how
clients request and receive geospatial feature items whose geometries
(coordinates) are in “alternative coordinate reference systems” (other than
WGS 84 longitude/latitude).
The following example demonstrates these capabilities (see the full sample at
ogcapi_features_crs_example.dart ):
// create an OGC API Features client for the open ldproxy demo service
// (see https://demo.ldproxy.net/zoomstack for more info)
final client = OGCAPIFeatures . http (
// an URI to the landing page of the service
endpoint : Uri . parse ( 'https://demo.ldproxy.net/zoomstack' ),
// customize GeoJSON format
format : GeoJSON . featureFormat (
// specify that CRS authorities should be respected for axis order in
// GeoJSON data (actually this is the default - here for demonstration)
crsLogic : GeoRepresentation .crsAuthority,
// get service description and attribution info
final meta = await client. meta ();
print ( 'Service: ${ meta . description } ' );
print ( 'Attribution: ${ meta . attribution } ' );
// service should be compliant with Part 1 (Core, GeoJSON) and Part 2 (CRS)
final conformance = await client. conformance ();
if ( ! (conformance. conformsToFeaturesCore (geoJSON : true ) &&
conformance. conformsToFeaturesCrs ())) {
print ( 'NOT compliant with Part 1 (Core, GeoJSON) and Part 2 (CRS).' );
// get "airports" collection, and print spatial extent and storage CRS
final airports = await client. collection ( 'airports' );
final airportsMeta = await airports. meta ();
final extent = airportsMeta.extent ? .spatial;
final crs = extent.coordRefSys;
print ( 'Spatial bbox list (crs: $ crs ):' );
for ( final box in extent.boxes) {
final storageCrs = airportsMeta.storageCrs;
if (storageCrs != null ) {
print ( 'Storage CRS: $ storageCrs ' );
// get all supported CRS identifiers
final supportedCrs = airportsMeta.crs;
for ( final crs in supportedCrs) {
print ( '---------------------' );
print ( 'query crs: $ crs ' );
// get feature items filtered by name and result geometries in `crs`
final itemsByName = await airports. items (
// output result geometries in crs of the loop
bboxCrs : CoordRefSys . normalized (
'http://www.opengis.net/def/crs/EPSG/0/27700' ,
// print metadata about response
final returned = itemsByName.numberReturned;
final contentCrs = itemsByName.contentCrs;
print ( 'got $ returned items' );
print ( 'content crs: $ contentCrs ' );
// print features items contained in response feature collection
for ( final feature in itemsByName.collection.features) {
final name = feature.properties[ 'name' ];
final geometry = feature.geometry;
if (crs. isGeographic ()) {
final position = Geographic . from (geometry.position);
const dms = Dms (type : DmsType .degMinSec, decimals : 3 );
print ( ' $ id $ name ${ position . lonDms ( dms )} , ${ position . latDms ( dms )} ' );
final position = geometry.position;
print ( ' $ id $ name $ position ' );
3️⃣ Part 3: Filtering
The third part - Part 3: Filtering
of OGC API - Features
- further extends
capabilities of feature services.
For example when client
is OGCFeatureService
and source
is
OGCFeatureSource
, and both are initialized just like in the previous samples,
then it’s possible to check for the support of filtering, get queryable
properties, and utilize properties in simple (queryables as query parameters in
HTTP requests):
// service should be compliant with OGC API Features - Part 3 (Filtering)
final conformance = await client. conformance ();
if ( ! conformance. conformsToFeaturesQueryables (queryParameters : true )) {
'NOT compliant with Part 3 Filtering (Queryables + Query Parameters).' ,
// optional metadata about queryable properties
final queryables = await source. queryables ();
if (queryables != null ) {
print ( 'Queryables for ${ queryables . title } :' );
for ( final prop in queryables.properties.values) {
print ( ' ${ prop . name } ( ${ prop . title } ): ${ prop . type } ' );
// here query parameters is set to define a simple filter by a place name
final itemsByPlace = await source. items (
// queryables as query parameters (`PLAATS` is a queryable property)
// (... code omitted ...)
Queryable properties can also be utilized in more complex filters (based on the
Common Query Language
or CQL2). See the API documentation of items
and
itemsPaged
methods in OGCFeatureSource
for more information.