Skip to content

Chapter 12: Geospatial APIs, Standards, and Interoperability

Geospatial standards and interoperability

Interoperability is one of the oldest and hardest geospatial software problems. Spatial data is produced by many agencies, vendors, sensors, communities, and systems. Standards help independent tools exchange data without losing meaning.

Learning Goals

  • Explain why geospatial standards matter.
  • Compare classic OGC services with modern OGC API standards.
  • Design APIs for spatial filters, tiles, pagination, CRS policy, and metadata.
  • Understand licensing, authentication, and compatibility testing.

Theory

Interoperability requires more than a shared file extension. Systems must agree about geometry, coordinates, attributes, units, time, metadata, semantics, and error behavior. A useful API tells clients not only what data exists, but how it can be queried, transformed, trusted, and cited.

Classic standards such as WMS, WFS, WCS, and WMTS helped establish service interoperability. Modern OGC API standards use web-native patterns such as HTTP, JSON, OpenAPI, links, and collections.

Research and Standards Foundations

The OGC describes its modern API standards as taking advantage of JSON, REST, and OpenAPI. OGC API - Features defines building blocks for discovering and querying feature collections on the web. Part 1 focuses on core feature access in WGS 84 longitude/latitude order; Part 2 addresses coordinate reference systems by reference; additional parts cover filtering and richer query behavior.

This matters because API design is a contract. A geospatial API should state CRS behavior, supported filters, paging strategy, geometry simplification behavior, datetime semantics, authentication requirements, response format, and links to metadata. Without those details, clients can appear to work while quietly making incorrect spatial assumptions.

Coding Examples

The old OGC Web Services pattern often used query-string operations such as GetMap and GetFeature. The modern OGC API pattern uses linked web resources such as /collections, /collections/{collectionId}/items, /tiles, and /processes. Both approaches are still common in production.

Classic WMS GetMap Request

WMS returns rendered map images. The client asks for a layer, style, bounding box, CRS, output size, and image format.

https://example.org/geoserver/wms?
  SERVICE=WMS&
  VERSION=1.3.0&
  REQUEST=GetMap&
  LAYERS=public:parcels&
  STYLES=&
  CRS=EPSG:4326&
  BBOX=39.0,-105.5,40.0,-104.5&
  WIDTH=800&
  HEIGHT=600&
  FORMAT=image/png&
  TRANSPARENT=true

The response is a PNG, JPEG, or other map image. It is useful for display, but not for editing feature attributes. Watch axis order carefully: WMS 1.3.0 uses the axis order defined by the CRS, so EPSG:4326 can mean latitude/longitude in some WMS contexts.

Classic WFS GetFeature Request

WFS returns feature data rather than a rendered image. A simple request can ask for GeoJSON features inside a bounding box:

https://example.org/geoserver/wfs?
  SERVICE=WFS&
  VERSION=2.0.0&
  REQUEST=GetFeature&
  TYPENAMES=public:hydrants&
  OUTPUTFORMAT=application/json&
  SRSNAME=EPSG:4326&
  BBOX=-105.5,39.0,-104.5,40.0,EPSG:4326

The response should be a feature collection. In modern systems, the equivalent design is usually easier to expose through OGC API - Features.

OGC API - Features: Discover Collections

OGC API - Features starts with discoverable resources. A client should not hard-code every URL if the API publishes links.

curl -s "https://demo.pygeoapi.io/master/collections?f=json"

A typical response advertises collections and links:

{
  "links": [
    {
      "href": "https://example.org/api",
      "rel": "self",
      "type": "application/json",
      "title": "This document"
    }
  ],
  "collections": [
    {
      "id": "parcels",
      "title": "Tax parcels",
      "extent": {
        "spatial": {
          "bbox": [[-105.5, 39.0, -104.5, 40.0]]
        }
      },
      "itemType": "feature"
    }
  ]
}

OGC API - Features: Query Items with bbox, datetime, and limit

curl -s "https://example.org/api/collections/parcels/items?bbox=-105.5,39.0,-104.5,40.0&datetime=2025-01-01T00:00:00Z/2025-12-31T23:59:59Z&limit=100&f=json"

Client-side JavaScript:

const params = new URLSearchParams({
  bbox: "-105.5,39.0,-104.5,40.0",
  datetime: "2025-01-01T00:00:00Z/2025-12-31T23:59:59Z",
  limit: "100",
  f: "json"
});

const url = `https://example.org/api/collections/parcels/items?${params}`;
const response = await fetch(url);

if (!response.ok) {
  throw new Error(`OGC API request failed: ${response.status}`);
}

const featureCollection = await response.json();
console.log(featureCollection.features.length);

Python client:

import requests

params = {
    "bbox": "-105.5,39.0,-104.5,40.0",
    "datetime": "2025-01-01T00:00:00Z/2025-12-31T23:59:59Z",
    "limit": 100,
    "f": "json",
}

response = requests.get(
    "https://example.org/api/collections/parcels/items",
    params=params,
    timeout=30,
)
response.raise_for_status()

features = response.json()["features"]
print(f"received {len(features)} features")

Minimal OGC API - Features Endpoint Sketch

This is not a full compliant server, but it shows the shape of an endpoint that respects common OGC API - Features ideas: collection IDs, bounding boxes, limits, GeoJSON output, and pagination links.

from fastapi import FastAPI, HTTPException, Query, Request
from pydantic import BaseModel

app = FastAPI(title="Example OGC API - Features service")


class BBox(BaseModel):
    west: float
    south: float
    east: float
    north: float


def parse_bbox(value: str) -> BBox:
    parts = [float(part) for part in value.split(",")]
    if len(parts) != 4:
        raise ValueError("bbox must contain west,south,east,north")

    west, south, east, north = parts
    if west >= east or south >= north:
        raise ValueError("bbox min values must be less than max values")

    return BBox(west=west, south=south, east=east, north=north)


@app.get("/collections/{collection_id}/items")
def get_collection_items(
    request: Request,
    collection_id: str,
    bbox: str | None = None,
    datetime: str | None = None,
    limit: int = Query(default=100, ge=1, le=1000),
    offset: int = Query(default=0, ge=0),
):
    if collection_id != "parcels":
        raise HTTPException(status_code=404, detail="Unknown collection")

    parsed_bbox = parse_bbox(bbox) if bbox else None

    # In production, push bbox, datetime, limit, and offset into SQL.
    # Example PostGIS predicate:
    # ST_Intersects(geom, ST_MakeEnvelope(west, south, east, north, 4326))
    rows = query_parcels(parsed_bbox, datetime, limit, offset)

    next_offset = offset + limit
    next_url = str(
        request.url.include_query_params(
            bbox=bbox,
            datetime=datetime,
            limit=limit,
            offset=next_offset,
        )
    )

    return {
        "type": "FeatureCollection",
        "features": [row_to_geojson_feature(row) for row in rows],
        "links": [
            {
                "href": str(request.url),
                "rel": "self",
                "type": "application/geo+json",
                "title": "This page",
            },
            {
                "href": next_url,
                "rel": "next",
                "type": "application/geo+json",
                "title": "Next page",
            },
        ],
        "numberReturned": len(rows),
    }

Important server-side checks:

  • Validate bbox order and coordinate ranges.
  • State whether coordinates are WGS 84 longitude/latitude or a different CRS.
  • Cap limit to prevent accidental full-table downloads.
  • Return self, next, collection, service-desc, and metadata links where appropriate.
  • Push spatial filtering into the database or spatial index; do not load all features and filter in application memory.

OpenAPI Sketch for a Feature Collection

paths:
  /collections/{collectionId}/items:
    get:
      summary: Query features in a collection
      parameters:
        - name: collectionId
          in: path
          required: true
          schema:
            type: string
        - name: bbox
          in: query
          required: false
          schema:
            type: string
          example: "-105.5,39.0,-104.5,40.0"
        - name: datetime
          in: query
          required: false
          schema:
            type: string
          example: "2025-01-01T00:00:00Z/2025-12-31T23:59:59Z"
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 1000
            default: 100
      responses:
        "200":
          description: GeoJSON feature collection
          content:
            application/geo+json:
              schema:
                type: object

OGC API - Tiles URL Pattern

OGC API - Tiles uses tile matrix sets and tile coordinates. A common URL template looks like this:

/collections/{collectionId}/tiles/{tileMatrixSetId}/{tileMatrix}/{tileRow}/{tileCol}

Example vector tile request:

curl -o roads.mvt \
  "https://example.org/api/collections/roads/tiles/WebMercatorQuad/12/1542/965?f=mvt"

The server must document the tile matrix set, tile coordinate convention, tile format, bounds, and available zoom levels. A tile API without tile matrix metadata is only a URL pattern, not an interoperable contract.

OGC API - Processes Job Pattern

OGC API - Processes exposes geospatial operations as web resources. A client submits inputs, receives a job, polls status, and retrieves outputs.

curl -s -X POST "https://example.org/api/processes/buffer/execution" \
  -H "Content-Type: application/json" \
  -d '{
    "inputs": {
      "geometry": {
        "type": "Point",
        "coordinates": [-105.0, 39.7]
      },
      "distanceMeters": 500
    }
  }'

Example response:

{
  "jobID": "buffer-20260512-001",
  "status": "accepted",
  "links": [
    {
      "href": "https://example.org/api/jobs/buffer-20260512-001",
      "rel": "monitor",
      "type": "application/json"
    }
  ]
}

The engineering contract should say which CRS the input geometry uses, what units distanceMeters uses, whether the operation is planar or geodesic, and how long job results are retained.

Math

APIs expose spatial math through bounding boxes, geometry predicates, distance filters, tile coordinates, coordinate transformations, scale denominators, and query tolerances. API designers must decide which calculations happen server-side and which assumptions are visible to clients.

Equation companion: Math and Algorithms Reference

Tools of the Trade

  • OGC API - Features, Tiles, Maps, Processes, Records.
  • WMS, WMTS, WFS, WCS.
  • GeoJSON, GeoPackage, COG, STAC, GeoParquet.
  • OpenAPI, JSON Schema, JSON-LD.
  • GeoServer, pygeoapi, pg_featureserv, stac-fastapi.

Examples of Real-World Solutions

  • A national mapping agency publishes authoritative boundaries through OGC APIs.
  • A climate data platform exposes STAC catalogs and COG assets.
  • A city API supports bounding-box filters, pagination, and links to metadata.
  • A research workflow combines data from several agencies because each publishes standard formats.

Working Practice Examples

  1. Design an API route for querying features by bounding box, datetime, and attribute filters.
  2. Write an OpenAPI sketch for a spatial collection endpoint.
  3. Compare GeoJSON and GeoParquet for API vs analytical workloads.
  4. Publish a tiny static STAC catalog for three raster assets.
  5. Write a client that follows links from /collections to a collection's items endpoint.
  6. Implement bbox, limit, and offset parameters for a small GeoJSON feature API.
  7. Compare a WMS GetMap response with an OGC API - Features items response for the same layer.
  8. Design an OGC API - Processes request for buffering a point, clipping a raster, or summarizing parcels inside a polygon.

Common Failure Modes

  • Returning geometry without CRS assumptions.
  • No pagination for large result sets.
  • Ambiguous units in distance filters.
  • Mixing licensing terms in derived outputs.
  • Treating standards as documentation instead of testable contracts.
  • Hard-coding URLs instead of following published links.
  • Confusing rendered map images with feature data.
  • Implementing bbox but forgetting CRS and axis-order rules.
  • Returning nonstandard JSON while claiming GeoJSON or OGC API compatibility.

Works Cited

"OGC API Standards." Open Geospatial Consortium, https://ogcapi.ogc.org/. Accessed 9 May 2026.

"OGC API - Features Standard." Open Geospatial Consortium, https://www.ogc.org/publications/standard/ogcapi-features/. Accessed 9 May 2026.

"OGC API - Processes." Open Geospatial Consortium, https://ogcapi.ogc.org/processes/. Accessed 12 May 2026.

"OGC API - Tiles Standard." Open Geospatial Consortium, https://www.ogc.org/publications/standard/ogcapi-tiles/. Accessed 12 May 2026.

"OGC Standards." Open Geospatial Consortium, https://www.ogc.org/standards/. Accessed 9 May 2026.

"SpatioTemporal Asset Catalog Specification." STAC, https://stacspec.org/. Accessed 9 May 2026.

Butler, Howard, et al. "The GeoJSON Format." RFC 7946, Internet Engineering Task Force, 2016, https://www.rfc-editor.org/rfc/rfc7946. Accessed 9 May 2026.

"GeoParquet Specification." GeoParquet, https://geoparquet.org/. Accessed 9 May 2026.


GeoInformatica Consulting logo