Skip to content
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,57 @@ supports research, field operations, and public data delivery for the Bureau of
- 🔐 Optional authentication and role-based access
- 🧾 Interactive API documentation via OpenAPI and ReDoc

---

## 🗺️ OGC API - Features

The API exposes OGC API - Features endpoints under `/ogc`.

### Landing & metadata

```bash
curl http://localhost:8000/ogc
curl http://localhost:8000/ogc/conformance
curl http://localhost:8000/ogc/collections
curl http://localhost:8000/ogc/collections/locations
```

### Items (GeoJSON)

```bash
curl "http://localhost:8000/ogc/collections/locations/items?limit=10&offset=0"
curl "http://localhost:8000/ogc/collections/wells/items?limit=5"
curl "http://localhost:8000/ogc/collections/springs/items?limit=5"
curl "http://localhost:8000/ogc/collections/locations/items/123"
```

### BBOX + datetime filters

```bash
curl "http://localhost:8000/ogc/collections/locations/items?bbox=-107.9,33.8,-107.8,33.9"
curl "http://localhost:8000/ogc/collections/wells/items?datetime=2020-01-01/2024-01-01"
```

### Polygon filter (CQL2 text)

Use `filter` + `filter-lang=cql2-text` with `WITHIN(...)`:

```bash
curl "http://localhost:8000/ogc/collections/locations/items?filter=WITHIN(geometry,POLYGON((-107.9 33.8,-107.8 33.8,-107.8 33.9,-107.9 33.9,-107.9 33.8)))&filter-lang=cql2-text"
```

### Property filter (CQL)

Basic property filters are supported with `properties`:

```bash
curl "http://localhost:8000/ogc/collections/wells/items?properties=thing_type='water well' AND well_depth>=100 AND well_depth<=200"
curl "http://localhost:8000/ogc/collections/wells/items?properties=well_purposes IN ('domestic','irrigation')"
curl "http://localhost:8000/ogc/collections/wells/items?properties=well_casing_materials='PVC'"
curl "http://localhost:8000/ogc/collections/wells/items?properties=well_screen_type='Steel'"
```


---

## 🛠️ Getting Started
Expand Down
1 change: 1 addition & 0 deletions api/ogc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# ============= OGC API package =============================================
92 changes: 92 additions & 0 deletions api/ogc/collections.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from __future__ import annotations

from typing import Dict

from fastapi import Request

from api.ogc.schemas import Collection, CollectionExtent, CollectionExtentSpatial, Link


BASE_CRS = "http://www.opengis.net/def/crs/OGC/1.3/CRS84"


COLLECTIONS: Dict[str, dict] = {
"locations": {
"title": "Locations",
"description": "Sample locations",
"itemType": "feature",
},
"wells": {
"title": "Wells",
"description": "Things filtered to water wells",
"itemType": "feature",
},
"springs": {
"title": "Springs",
"description": "Things filtered to springs",
"itemType": "feature",
},
}


def _collection_links(request: Request, collection_id: str) -> list[Link]:
base = str(request.base_url).rstrip("/")
return [
Link(
href=f"{base}/ogc/collections/{collection_id}",
rel="self",
type="application/json",
),
Link(
href=f"{base}/ogc/collections/{collection_id}/items",
rel="items",
type="application/geo+json",
),
Link(
href=f"{base}/ogc/collections",
rel="collection",
type="application/json",
),
]


def list_collections(request: Request) -> list[Collection]:
collections = []
for cid, meta in COLLECTIONS.items():
extent = CollectionExtent(
spatial=CollectionExtentSpatial(
bbox=[[-180.0, -90.0, 180.0, 90.0]], crs=BASE_CRS
)
)
collections.append(
Collection(
id=cid,
title=meta["title"],
description=meta.get("description"),
itemType=meta.get("itemType", "feature"),
crs=[BASE_CRS],
links=_collection_links(request, cid),
extent=extent,
)
)
return collections


def get_collection(request: Request, collection_id: str) -> Collection | None:
meta = COLLECTIONS.get(collection_id)
if not meta:
return None
extent = CollectionExtent(
spatial=CollectionExtentSpatial(
bbox=[[-180.0, -90.0, 180.0, 90.0]], crs=BASE_CRS
)
)
return Collection(
id=collection_id,
title=meta["title"],
description=meta.get("description"),
itemType=meta.get("itemType", "feature"),
crs=[BASE_CRS],
links=_collection_links(request, collection_id),
extent=extent,
)
8 changes: 8 additions & 0 deletions api/ogc/conformance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CONFORMANCE_CLASSES = [
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core",
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30",
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson",
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/collections",
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/features",
"http://www.opengis.net/spec/cql2/1.0/conf/cql2-text",
]
Loading
Loading