https://
No geospatial data stack is complete without a mechanism for serving vector features, either as GeoJSON, or as vector tiles for effcient use in web maps. tipg is the component that fills that niche in eoAPI.
From the tipg README:
tipg, pronounced T[ee]pg, is a Python package that helps create lightweight OGC Features and Tiles API with a PostGIS Database backend. The API has been designed for OGC Features and OGC Tiles specifications.
In addition to serving existing features from a pre-defined set of tables in a PostGIS-enabled PostgreSQL database, it can serve features from custom views defined in user-defined PostgreSQL functions.
5.1 Configuration¶
In an eoAPI stack, tipg can be connected to any PostgreSQL with PostGIS database including the existing pgstac database. This is controlled by the POSTGRES_* environment variables in the application runtime.
When deploying tipg, you can specify which schemas in your database will be exposed to the tipg API. This is controlled by the TIPG_DB_SCHEMAS environment variable.
For the workshop we have created a schema in the pgstac database called features that you will be working with. To expose this schema to tipg we set TIPG_DB_SCHEMAS=["features"] in the application runtime (see line 178 in infrastructurefeatures.ecoregions) for the examples in this notebook.
Additional resources:¶
tipg configuration docs: https://
developmentseed .org /tipg /user _guide /configuration
5.2 OGC API - Features¶
tipg contains an OGC Features API that is interoperable with many existing applications.
https://
Each table in the PostgreSQL schema represents a single collection. The list of collections available to tipg can be obtained with a GET request to the /collections endpoint
import json
import os
import httpx
tipg_endpoint = os.getenv("TIPG_API_ENDPOINT")
# browser-facing URL for the IFrame/viewer cells (the user's browser can't
# reach the server-side endpoint above when running on Kubernetes)
tipg_browser_endpoint = os.getenv("TIPG_BROWSER_URL") or tipg_endpoint.replace(
"tipg", "localhost"
)
collections_request = httpx.get(f"{tipg_endpoint}/collections")
print(json.dumps(collections_request.json(), indent=2))Each collection has a set of links associated with it including:
/collections/{collection_id}/queryables: describes the fields that can be used for filtering features/collections/{collection_id}/items: where features can be accessed/collections/{collection_id}/tiles: list of tile matrix set IDs that are available for tile requests/collections/{collection_id}/tiles/{tileMatrixSetId}: returns a tilejson for a vector tile layer/collections/{collection_id}/tiles/{tileMatrixSetId}/map.html: interactive map of the collection
The /items, /tiles/{tileMatrixSetId}, and /tiles/{tileMatrixSetId}/map.html endpoints will all accept field filters in the form of {queryable}={value} where queryable is one of the fields listed in the /queryables response for that collection.
5.2.2 Queryables¶
The /collections/{collection_id/queryables endpoint returns a list of fields that can be used to filter features in requests for a collection:
collection_id = "features.ecoregions"
queryables_request = httpx.get(
f"{tipg_endpoint}/collections/{collection_id}/queryables"
)
print(json.dumps(queryables_request.json(), indent=2))5.2.3 Items¶
The /items endpoint for a collection can be used to retrieve paginated lists of features in a number of formats:
GeoJSON
CSV
JSON
GeoJSON Sequence
NDJSON (new-line-delimited JSON)
HTML (for viewing in a browser)
Try retrieving a page of results from the features.terrestrial_ecoregions collection. This will return a GeoJSON FeatureCollection with two features (limit=2):
geojson_request = httpx.get(
f"{tipg_endpoint}/collections/{collection_id}/items",
params={"f": "geojson", "limit": 2},
)
geojson_response = geojson_request.json()
print(json.dumps(geojson_response, indent=2))You can request a sequence of GeoJSON features separated by new lines with f=geojsonseq
geojsonseq_request = httpx.get(
f"{tipg_endpoint}/collections/{collection_id}/items",
params={"f": "geojsonseq", "limit": 2},
)
print(geojsonseq_request.text)5.2.3.1 Filter Items¶
You can apply a filter using the fields returned in the /queryables endpoint for a collection:
filtered_request = httpx.get(
f"{tipg_endpoint}/collections/{collection_id}/items",
params={
"na_l2name": "MEDITERRANEAN CALIFORNIA",
"f": "geojson",
"limit": 2,
},
)
filtered_response = filtered_request.json()
print(
f"{filtered_response['numberMatched']} features match this filtered request",
f"out of {geojson_response['numberMatched']} features in the entire collection",
)In addition to field-based filters, you can use other standard filter mechanisms:
ids: limit to a comma-separated list of feature idsbbox: filter by bounding boxdatetime: filter by datetime (use withdatetime-columnparameter)filter: apply a CQL2 filter (use withfilter-langparameter set to cql2-text or cql2-json) for more complex filter operations
# filter by bounding box
bbox_filtered_request = httpx.get(
f"{tipg_endpoint}/collections/{collection_id}/items",
params={"bbox": "-77,39,-76,40", "f": "geojson", "limit": 2},
)
bbox_filtered_response = bbox_filtered_request.json()
print(
f"{bbox_filtered_response['numberMatched']} features match this filtered request",
f"out of {geojson_response['numberMatched']} features in the entire collection",
)5.2.3.2 As a Map¶
tipg also comes with a convenient HTML response type which makes it possible to interact with the endpoints in your browser. The returned geojson features from a /items request will be displayed in a map!
from IPython.display import IFrame
bbox_filtered_request = httpx.get(
f"{tipg_endpoint}/collections/{collection_id}/items",
params={
"bbox": "-77,39,-76,40",
"f": "html",
},
)
local_url = str(bbox_filtered_request.url).replace(tipg_endpoint, tipg_browser_endpoint)
IFrame(
local_url,
width=1200,
height=800,
)Here is a view of the full API docs for the /collections/{collection_id}/items endpoint:
local_tipg_endpoint = tipg_browser_endpoint
IFrame(
f"{local_tipg_endpoint}/api.html#OGC Features API/items_collections__collectionId__items_get",
width=1200,
height=800,
)5.3 OGC API - Tiles¶
tipg also serves an OGC Tiles API for vector tiles.
The Tiles API works exactly like the Features API but instead of taking requests for entire features it accepts requests for XYZ vector tiles that are very useful for streaming vector data into map client applications. This is useful because it will become impractical or impossible to stream all of a collection’s features into a map application as a geojson - tipg moves the simplification and filtering operations up to the PostGIS database and returns the minimum required data to the map client.
5.3.1 Tilejson¶
The tilejson endpoint /collections/{collection_id/tiles/{tileMatrixSetId}/tilejson.json is the most useful for adding vector tile layers to a map application. The response contains information about the available fields (which can be used for styling the vector tiles), the full collection extent, and the XYZ tile url that can be loaded as a layer in a map.
tilejson_request = httpx.get(
f"{tipg_endpoint}/collections/{collection_id}/tiles/WebMercatorQuad/tilejson.json",
)
tilejson_response = tilejson_request.json()
print(json.dumps(tilejson_response, indent=2))All of the same rules for queryables and query parameters from the /items endpoint apply to the /tiles endpoints, too. The query parameters will be tacked onto the end of the XYZ tile URL:
filtered_tilejson_request = httpx.get(
f"{tipg_endpoint}/collections/{collection_id}/tiles/WebMercatorQuad/tilejson.json",
params={
"eco_name": "Northern Mesoamerican Pacific mangroves",
},
)
filtered_tilejson_response = filtered_tilejson_request.json()
print(json.dumps(filtered_tilejson_response, indent=2))5.3.2 Map Viewer¶
Use /collections/{collection_id/tiles/{tileMatrixSetId}/ for a quick demonstration of how vector tiles enable visualization of massive feature collections. Check out this map of the terrestrial_ecoregions table that lives in our database that has 14,000+ features, which we would never dream of downloading to view in a web map. Instead, we let our map client make requests for simplified features for each XYZ tile as we explore the map.
viewer_request = httpx.get(
f"{tipg_endpoint}/collections/{collection_id}/tiles/WebMercatorQuad/map.html",
)
IFrame(
str(viewer_request.url).replace(tipg_endpoint, tipg_browser_endpoint),
width=1200,
height=800,
)You can apply a field-based filter to limit the features to a subset of the full collection:
filtered_viewer_request = httpx.get(
f"{tipg_endpoint}/collections/{collection_id}/tiles/WebMercatorQuad/map.html",
params={
"na_l2name": "MEDITERRANEAN CALIFORNIA",
},
)
IFrame(
str(filtered_viewer_request.url).replace(tipg_endpoint, tipg_browser_endpoint),
width=1200,
height=800,
)