Documentation

tileserver-rs speaks the OGC API Features standard end-to-end. Any PostgreSQL table with a PostGIS geometry column automatically becomes an OGC collection — usable directly from QGIS, ArcGIS, FME, or any OGC client.

What you get

CapabilityFeatureEndpoint
Core (OGC Part 1)List / get features as GeoJSONGET /ogc/collections/{id}/items
CRS (OGC Part 2)Reprojection on demand (PostGIS ST_Transform)?crs=…&bbox-crs=…
Filter (OGC Part 3)CQL2 text + JSON, safe from SQL injection?filter=…&filter-lang=…
Transactions (OGC Part 4)Create / replace / update / deletePOST, PUT, PATCH, DELETE /items[/{id}]
Schemas (OGC Part 5)JSON Schema metadata/queryables, /sortables, /schema

Quick Start

Point tileserver-rs at a PostGIS database in config.toml:

[postgres]
connection_string = "postgresql://user:pass@localhost:5432/gis"

[[postgres.tables]]
id = "cities"
schema = "public"
table = "cities"
geometry_column = "geom"
# Opt-in: enable write operations (POST/PUT/PATCH/DELETE) for this table
writable = true

Start the server, then browse to:

http://localhost:8080/ogc               # landing page
http://localhost:8080/ogc/conformance   # conformance classes
http://localhost:8080/ogc/collections   # collection list

Core read operations

Every PostGIS table becomes a collection with a TileJSON 3.0-ish metadata document:

curl http://localhost:8080/ogc/collections/cities/items?limit=3

Returns a GeoJSON FeatureCollection with numberMatched, numberReturned, and next/prev pagination links.

Query parameters:

  • bbox=minx,miny,maxx,maxy — spatial filter (4 or 6 values, per spec)
  • limit=N — max features (default 10)
  • offset=N — pagination offset

Coordinate reference systems

Request output in any CRS your PostGIS install supports:

# Web Mercator output
curl 'http://localhost:8080/ogc/collections/cities/items?crs=http://www.opengis.net/def/crs/EPSG/0/3857'

# British National Grid
curl 'http://localhost:8080/ogc/collections/cities/items?crs=http://www.opengis.net/def/crs/EPSG/0/27700'

Responses include a Content-Crs header so clients know what they got:

Content-Crs: <http://www.opengis.net/def/crs/EPSG/0/3857>

Collection metadata advertises supported CRSs and the storage CRS:

{
  "crs": [
    "http://www.opengis.net/def/crs/OGC/1.3/CRS84",
    "http://www.opengis.net/def/crs/EPSG/0/4326"
  ],
  "storageCrs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
}

Identity transforms are elided — if you request data in the storage CRS, no ST_Transform runs.

CQL2 filter expressions

Filter features with the OGC Common Query Language 2 (CQL2), in either text or JSON form.

# Text: simple equality
curl "http://localhost:8080/ogc/collections/cities/items?filter=country='Brazil'"

# Text: numeric range + boolean
curl "http://localhost:8080/ogc/collections/cities/items?filter=population%20%3E%2010000000%20AND%20is_capital%3Dtrue"

# JSON form
curl "http://localhost:8080/ogc/collections/cities/items?filter-lang=cql2-json&filter=%7B%22op%22%3A%22%3D%22%2C%22args%22%3A%5B%7B%22property%22%3A%22country%22%7D%2C%22Brazil%22%5D%7D"

Supported operators: =, <>, <, <=, >, >=, LIKE, BETWEEN … AND …, IN (…), IS NULL, AND, OR, NOT, spatial ops (S_INTERSECTS, S_WITHIN, S_CONTAINS, …).

SQL injection defence

CQL2 translates to raw PostgreSQL SQL, so tileserver-rs applies two defences:

  1. Property allow-list — every Expr::Property in the parsed AST is checked against the table's actual column list from information_schema.columns. References to pg_catalog.pg_shadow, information_schema.*, or any non-existing column return HTTP 400.
  2. Round-trip canonicalisation gate — parsed CQL2 is re-serialised and compared to the input (normalising parens/whitespace/case). Any trailing garbage (; DROP TABLE …, -- comments) causes a mismatch and HTTP 400.

Write operations (CRUD)

Opt-in per table. Add writable = true to the [[postgres.tables]] entry:

# Create a feature (expects application/geo+json body)
curl -X POST http://localhost:8080/ogc/collections/cities/items \
  -H 'Content-Type: application/geo+json' \
  -d '{"type":"Feature","geometry":{"type":"Point","coordinates":[-46.63,-23.55]},"properties":{"name":"São Paulo","country":"Brazil","population":12330000,"is_capital":false,"elevation_m":760}}'
# → 201 Created, Location: /ogc/collections/cities/items/18

# Full replace (PUT)
curl -X PUT http://localhost:8080/ogc/collections/cities/items/18 \
  -H 'Content-Type: application/geo+json' -d '{…}'
# → 204 No Content

# Partial merge (PATCH, RFC 7396 merge-patch)
curl -X PATCH http://localhost:8080/ogc/collections/cities/items/18 \
  -H 'Content-Type: application/merge-patch+json' \
  -d '{"properties":{"population":99999999}}'
# → 204 No Content

# Delete
curl -X DELETE http://localhost:8080/ogc/collections/cities/items/18
# → 204 No Content

All values are bound as parameterised queries ($N placeholders) — no string interpolation of user data. Geometries go through ST_GeomFromGeoJSON.

Tables with writable = false (the default) return HTTP 405 Method Not Allowed on any mutation verb.

Schema introspection

Introspect collection structure without reading data:

# Filterable columns
curl http://localhost:8080/ogc/collections/cities/queryables

# Sortable columns (excludes jsonb + array columns that pg can't ORDER BY)
curl http://localhost:8080/ogc/collections/cities/sortables

# Full GeoJSON Feature schema ($ref to geojson.org for the geometry shape)
curl http://localhost:8080/ogc/collections/cities/schema

Returns JSON Schema 2020-12 documents with per-column types derived from information_schema.columns:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "http://localhost:8080/ogc/collections/cities/queryables",
  "type": "object",
  "properties": {
    "name":        { "type": "string",                    "title": "name" },
    "country":     { "type": "string",                    "title": "country" },
    "population":  { "type": ["integer", "null"],         "title": "population" },
    "is_capital":  { "type": "boolean",                   "title": "is_capital" },
    "founded_year":{ "type": ["integer", "null"],         "title": "founded_year" },
    "elevation_m": { "type": ["number", "null"],          "title": "elevation_m" }
  }
}

Using with QGIS / ArcGIS / FME

Any OGC Features-compliant client can connect to /ogc:

  • QGIS: Browser panel → WFS / OGC API - Features → add http://localhost:8080/ogc
  • ArcGIS Pro: Catalog → Connections → OGC API Features → http://localhost:8080/ogc
  • FME: OGCAPIFeaturesConnector reader, base URL http://localhost:8080/ogc

QGIS and ArcGIS natively round-trip the feature editing workflow when writable = true — users draw/edit features in the GIS client and tileserver-rs applies the changes via PATCH/DELETE.

Interactive API reference

Scalar docs render all OGC endpoints with try-it-now interactivity at:

http://localhost:8080/_openapi

Specifications

SpecLink
Part 1 – CoreOGC 17-069r4
Part 2 – CRSOGC 18-058
Part 3 – FilterOGC 19-079r2
Part 4 – Create-Replace-DeleteOGC 20-002
Part 5 – SchemasOGC 23-058
CQL2OGC 21-065