Week 29 · Space GIS Architect

Geospatial APIs: PostGIS + FastAPI + spatial REST

Most geospatial work ends with: someone wants a JSON API. This week you build it.

Learning objectives

Primer

The endpoint where space GIS meets the rest of the world is almost always a REST API. Someone downstream — another team, a paying customer, a frontend app — wants geospatial data over HTTP, with predictable URLs, ergonomic parameters, and JSON responses. This week you build that endpoint properly.

The shape of a spatial REST endpoint

The four query patterns that show up everywhere:

  1. By ID — single feature by its identifier.
  2. By bounding box — features inside a lon_min, lat_min, lon_max, lat_max box. Useful for map tiles.
  3. By point and radius — features within radius_km of a lat, lon. Useful for near-me queries.
  4. By polygon — features inside an arbitrary GeoJSON polygon. POST it as the request body.

Common parameters across all of them: time range, pagination, attribute filtering, sorting.

FastAPI plus PostGIS

The minimal stack:

from fastapi import FastAPI, Query
from sqlalchemy import create_engine, text

engine = create_engine("postgresql://...")
app = FastAPI(title="LaunchDetect API")

@app.get("/detections")
def detections(bbox: str | None = None, limit: int = 50):
    if bbox:
        lon_min, lat_min, lon_max, lat_max = map(float, bbox.split(","))
        sql = text("SELECT id, ST_AsGeoJSON(position) AS geom FROM detections "
                   "WHERE position && ST_MakeEnvelope(:l1,:l2,:l3,:l4,4326) "
                   "ORDER BY detected_at DESC LIMIT :lim")
        with engine.begin() as conn:
            rows = conn.execute(sql, dict(l1=lon_min,l2=lat_min,l3=lon_max,l4=lat_max,lim=limit)).all()
        return rows

OpenAPI for free

FastAPI auto-generates a full OpenAPI 3.0 specification from your function signatures and Pydantic models. Visit /docs for interactive Swagger UI, /redoc for ReDoc. Other teams can generate client SDKs in any language from the OpenAPI spec.

Pagination

Spatial endpoints often return many features. Two approaches:

For high-traffic endpoints, cursor is the right default.

Caching

Spatial responses cache well. Set Cache-Control: public, max-age=60 on bbox queries. Use a CDN in front. For Lambda-fronted APIs, this can drop your origin load by 95 percent or more.

The lab

You will build a FastAPI app that wraps a PostGIS detections table and exposes the four endpoint patterns above. Returns GeoJSON. Documented via auto-generated OpenAPI at /docs. This is the architecture of launchdetect.com/space-data-api/ in production.

Hands-on lab: Spatial REST API for launch detections

Build a FastAPI app with bbox / radius / id endpoints over a PostGIS detections table. Returns GeoJSON.

Quiz

Test yourself. Answer key on the certificate-track page (Gold-tier feature: progress tracking and auto-grading).

Q1. FastAPI generates what for free?
  1. OpenAPI spec and interactive Swagger UI at /docs
  2. Database migration
  3. Frontend
  4. Hosting
Q2. A bbox query in PostGIS uses:
  1. ST_MakeEnvelope to build the bbox geometry, then ST_Intersects or && operator
  2. Just SQL LIKE
  3. JSON parsing
  4. Random sampling
Q3. GeoJSON FeatureCollection structure is:
  1. A FeatureCollection containing an array of Feature objects
  2. Just a list of coordinates
  3. A binary blob
  4. XML
Q4. Pagination for spatial endpoints typically uses:
  1. Cursor-based with cursor parameter or HTTP Link header
  2. Offset only
  3. No pagination
  4. Random
Q5. Returning EWKB vs GeoJSON tradeoff:
  1. GeoJSON is human-readable and web-friendly; EWKB is more compact
  2. EWKB is always better
  3. Same thing
  4. Neither matters