demo
titiler.PgSTAC Demo¶
This Notebook aims to show the different features provided by titiler.pgstac application
In order to run this demo you'll need to have a PgSTAC database and the titiler.pgstac application running. The easiest way to launch them is to use the repo's docker-compose.yml
docker compose up tiler
Python requirements¶
pip install httpx folium pypgstac psycopg psycopg-pool geojson-pydantic
Populate the PgSTAC db with data¶
$ pypgstac load collections tests/fixtures/noaa-emergency-response.json --dsn postgresql://username:password@localhost:5439/postgis --method insert
$ pypgstac load items tests/fixtures/noaa-eri-nashville2020.json --dsn postgresql://username:password@localhost:5439/postgis --method insert
In [19]:
Copied!
import json
import httpx
from folium import Map, TileLayer, GeoJson
from geojson_pydantic import Feature, Polygon
endpoint = "http://127.0.0.1:8081"
print(httpx.get(f"{endpoint}/healthz").json())
import json
import httpx
from folium import Map, TileLayer, GeoJson
from geojson_pydantic import Feature, Polygon
endpoint = "http://127.0.0.1:8081"
print(httpx.get(f"{endpoint}/healthz").json())
{'database_online': True, 'versions': {'titiler': '2.0.2', 'titiler.pgstac': '2.1.0', 'rasterio': '1.5.0', 'gdal': '3.12.1', 'proj': '9.7.1', 'geos': '3.14.1'}}
In [20]:
Copied!
# bounds of the noaa-eri-nashville2020.json items
bounds = (-87.0251, 36.0999, -85.4249, 36.2251)
poly = Polygon.from_bounds(*bounds)
geojson = Feature(type="Feature", geometry=poly, properties=None).model_dump(
exclude_none=True
)
m = Map(
tiles="OpenStreetMap",
location=((bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2),
zoom_start=9,
)
geo_json = GeoJson(
data=geojson,
style_function=lambda x: {
"opacity": 1,
"dashArray": "1",
"fillOpacity": 0,
"weight": 3,
},
)
geo_json.add_to(m)
m
# bounds of the noaa-eri-nashville2020.json items
bounds = (-87.0251, 36.0999, -85.4249, 36.2251)
poly = Polygon.from_bounds(*bounds)
geojson = Feature(type="Feature", geometry=poly, properties=None).model_dump(
exclude_none=True
)
m = Map(
tiles="OpenStreetMap",
location=((bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2),
zoom_start=9,
)
geo_json = GeoJson(
data=geojson,
style_function=lambda x: {
"opacity": 1,
"dashArray": "1",
"fillOpacity": 0,
"weight": 3,
},
)
geo_json.add_to(m)
m
Out[20]:
Make this Notebook Trusted to load map: File -> Trust Notebook
Register Search query¶
In [21]:
Copied!
search_request = {
# Filter collection
"collections": ["noaa-emergency-response"],
# limit bounds of the known items (note: the bbox will also be used in the tilejson response)
"bbox": bounds,
"filter-lang": "cql2-json",
}
response = httpx.post(
f"{endpoint}/searches/register",
json=search_request,
).json()
print(response)
searchid = response["id"]
search_request = {
# Filter collection
"collections": ["noaa-emergency-response"],
# limit bounds of the known items (note: the bbox will also be used in the tilejson response)
"bbox": bounds,
"filter-lang": "cql2-json",
}
response = httpx.post(
f"{endpoint}/searches/register",
json=search_request,
).json()
print(response)
searchid = response["id"]
{'id': 'ea0c57eac8d1ad974d89ee406847d9b6', 'links': [{'href': 'http://127.0.0.1:8081/searches/ea0c57eac8d1ad974d89ee406847d9b6/info', 'rel': 'metadata', 'title': 'Mosaic metadata'}, {'href': 'http://127.0.0.1:8081/searches/ea0c57eac8d1ad974d89ee406847d9b6/{tileMatrixSetId}/tilejson.json', 'rel': 'tilejson', 'templated': True, 'title': 'Link for TileJSON (Template URL)'}, {'href': 'http://127.0.0.1:8081/searches/ea0c57eac8d1ad974d89ee406847d9b6/{tileMatrixSetId}/map.html', 'rel': 'map', 'templated': True, 'title': 'Link for Map viewer (Template URL)'}, {'href': 'http://127.0.0.1:8081/searches/ea0c57eac8d1ad974d89ee406847d9b6/WMTSCapabilities.xml', 'rel': 'wmts', 'title': 'WMTS Capabilities link.'}]}
Show list of Mosaics¶
In [22]:
Copied!
response = httpx.get(f"{endpoint}/searches/").json()
print(
json.dumps(
[
(search["search"]["hash"], search["search"]["metadata"].get("name"))
for search in response["searches"]
],
indent=4,
)
)
response = httpx.get(f"{endpoint}/searches/").json()
print(
json.dumps(
[
(search["search"]["hash"], search["search"]["metadata"].get("name"))
for search in response["searches"]
],
indent=4,
)
)
[
[
"cd576c2d50cdd09e454c24010bb79028",
"Mosaic for 'noaa-emergency-response' Collection"
],
[
"43a44a482f99dfe682a2b7bd3e6aa604",
"Mosaic for 'world' Collection"
],
[
"1b42336515fdeecabc6b7c1f7a6218c1",
"Mosaic for 'MAXAR_Marshall_Fire_21_Update' Collection"
],
[
"1409af735738d4502e27e45a9dc5a280",
"Mosaic for 'MAXAR_kentucky_flooding_7_29_2022' Collection"
],
[
"7f18b96b2e31b68972ec1e3b65d43d45",
null
],
[
"58b83e95cf5dbf7305dd1cc7917eccb8",
"Mosaic for 'world' Collection"
],
[
"862cc7dbaf98dc90c6135cb0b7ea1767",
null
],
[
"6bf03e6b8cbf8b68443cf2123e901f44",
null
],
[
"bb877b5a9882ee9c0fe7a6223321e354",
"Mosaic for 'MAXAR_Morocco_Earthquake_Sept_2023' Collection"
],
[
"7176438ae4ef4ec7c456dba5259d6376",
"Mosaic for 'MAXAR_Hurricane_Idalia_Florida_Aug23' Collection"
]
]
Get Search Metadata¶
In [23]:
Copied!
info_response = httpx.get(f"{endpoint}/searches/{searchid}/info").json()
print(json.dumps(info_response, indent=4))
info_response = httpx.get(f"{endpoint}/searches/{searchid}/info").json()
print(json.dumps(info_response, indent=4))
{
"search": {
"hash": "ea0c57eac8d1ad974d89ee406847d9b6",
"search": {
"bbox": [
-87.0251,
36.0999,
-85.4249,
36.2251
],
"collections": [
"noaa-emergency-response"
],
"filter-lang": "cql2-json"
},
"lastused": "2026-05-12T09:34:34.497906Z",
"usecount": 2,
"metadata": {
"type": "mosaic"
}
},
"links": [
{
"href": "http://127.0.0.1:8081/searches/ea0c57eac8d1ad974d89ee406847d9b6/info",
"rel": "self",
"title": "Mosaic metadata"
},
{
"href": "http://127.0.0.1:8081/searches/ea0c57eac8d1ad974d89ee406847d9b6/{tileMatrixSetId}/tilejson.json",
"rel": "tilejson",
"templated": true,
"title": "TileJSON link (Template URL)."
},
{
"href": "http://127.0.0.1:8081/searches/ea0c57eac8d1ad974d89ee406847d9b6/{tileMatrixSetId}/map.html",
"rel": "map",
"templated": true,
"title": "Map viewer link (Template URL)."
},
{
"href": "http://127.0.0.1:8081/searches/ea0c57eac8d1ad974d89ee406847d9b6/WMTSCapabilities.xml",
"rel": "wmts",
"title": "WMTS Capabilities link."
}
]
}
Get TileJSON¶
Note: to return a valid tilejson document you'll need to pass either the assets or expression option.
In [24]:
Copied!
tj_response = httpx.get(
f"{endpoint}/searches/{searchid}/WebMercatorQuad/tilejson.json?assets=cog&tilesize=256&minzoom=14&maxzoom=18"
).json()
print(json.dumps(tj_response, indent=4))
tj_response = httpx.get(
f"{endpoint}/searches/{searchid}/WebMercatorQuad/tilejson.json?assets=cog&tilesize=256&minzoom=14&maxzoom=18"
).json()
print(json.dumps(tj_response, indent=4))
{
"tilejson": "3.0.0",
"version": "1.0.0",
"scheme": "xyz",
"tiles": [
"http://127.0.0.1:8081/searches/ea0c57eac8d1ad974d89ee406847d9b6/tiles/WebMercatorQuad/{z}/{x}/{y}?assets=cog&tilesize=256"
],
"minzoom": 14,
"maxzoom": 18,
"bounds": [
-87.0251,
36.0999,
-85.4249,
36.2251
],
"center": [
-86.225,
36.162499999999994,
14
]
}
Load tiles¶
In [29]:
Copied!
m = Map(
location=((bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2), zoom_start=14
)
geo_json = GeoJson(
data=geojson,
style_function=lambda x: {
"opacity": 1,
"dashArray": "1",
"fillOpacity": 0,
"weight": 1,
},
)
geo_json.add_to(m)
aod_layer = TileLayer(
tiles=tj_response["tiles"][0],
attr="Mosaic",
min_zoom=14,
max_zoom=18,
max_native_zoom=18,
)
aod_layer.add_to(m)
m
m = Map(
location=((bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2), zoom_start=14
)
geo_json = GeoJson(
data=geojson,
style_function=lambda x: {
"opacity": 1,
"dashArray": "1",
"fillOpacity": 0,
"weight": 1,
},
)
geo_json.add_to(m)
aod_layer = TileLayer(
tiles=tj_response["tiles"][0],
attr="Mosaic",
min_zoom=14,
max_zoom=18,
max_native_zoom=18,
)
aod_layer.add_to(m)
m
Out[29]:
Make this Notebook Trusted to load map: File -> Trust Notebook
Register a Mosaic with Metadata¶
In [26]:
Copied!
search_request = {
# Filter collection
"collections": ["noaa-emergency-response"],
# limit bounds of the known items (note: the bbox will also be used in the tilejson response)
"bbox": bounds,
"filter-lang": "cql2-json",
"metadata": {
"bounds": [
-87.0251,
36.0999,
-85.4249,
36.2251,
], # This is redondant because it's in the bbox filter
"minzoom": 14,
"maxzoom": 18,
"assets": ["cog"],
"defaults": {
"true_color": {
"assets": ["cog|bidx=1,2,3"],
},
"b1": {
"assets": ["cog|bidx=1"],
},
},
},
}
response = httpx.post(
f"{endpoint}/searches/register",
json=search_request,
).json()
print(json.dumps(response, indent=4))
searchid = response["id"]
search_request = {
# Filter collection
"collections": ["noaa-emergency-response"],
# limit bounds of the known items (note: the bbox will also be used in the tilejson response)
"bbox": bounds,
"filter-lang": "cql2-json",
"metadata": {
"bounds": [
-87.0251,
36.0999,
-85.4249,
36.2251,
], # This is redondant because it's in the bbox filter
"minzoom": 14,
"maxzoom": 18,
"assets": ["cog"],
"defaults": {
"true_color": {
"assets": ["cog|bidx=1,2,3"],
},
"b1": {
"assets": ["cog|bidx=1"],
},
},
},
}
response = httpx.post(
f"{endpoint}/searches/register",
json=search_request,
).json()
print(json.dumps(response, indent=4))
searchid = response["id"]
{
"id": "7f18b96b2e31b68972ec1e3b65d43d45",
"links": [
{
"href": "http://127.0.0.1:8081/searches/7f18b96b2e31b68972ec1e3b65d43d45/info",
"rel": "metadata",
"title": "Mosaic metadata"
},
{
"href": "http://127.0.0.1:8081/searches/7f18b96b2e31b68972ec1e3b65d43d45/{tileMatrixSetId}/tilejson.json",
"rel": "tilejson",
"templated": true,
"title": "Link for TileJSON (Template URL)"
},
{
"href": "http://127.0.0.1:8081/searches/7f18b96b2e31b68972ec1e3b65d43d45/{tileMatrixSetId}/map.html",
"rel": "map",
"templated": true,
"title": "Link for Map viewer (Template URL)"
},
{
"href": "http://127.0.0.1:8081/searches/7f18b96b2e31b68972ec1e3b65d43d45/WMTSCapabilities.xml",
"rel": "wmts",
"title": "WMTS Capabilities link."
},
{
"href": "http://127.0.0.1:8081/searches/7f18b96b2e31b68972ec1e3b65d43d45/{tileMatrixSetId}/tilejson.json?assets=cog%7Cbidx%3D1",
"rel": "tilejson",
"templated": true,
"title": "TileJSON link for `b1` layer (Template URL)."
},
{
"href": "http://127.0.0.1:8081/searches/7f18b96b2e31b68972ec1e3b65d43d45/{tileMatrixSetId}/tilejson.json?assets=cog%7Cbidx%3D1%2C2%2C3",
"rel": "tilejson",
"templated": true,
"title": "TileJSON link for `true_color` layer (Template URL)."
}
]
}
In [30]:
Copied!
tj_response = httpx.get(
f"{endpoint}/searches/{searchid}/WebMercatorQuad/tilejson.json?assets=cog&tilesize=256"
).json()
print(json.dumps(tj_response, indent=4))
tj_response = httpx.get(
f"{endpoint}/searches/{searchid}/WebMercatorQuad/tilejson.json?assets=cog&tilesize=256"
).json()
print(json.dumps(tj_response, indent=4))
{
"tilejson": "3.0.0",
"version": "1.0.0",
"scheme": "xyz",
"tiles": [
"http://127.0.0.1:8081/searches/7f18b96b2e31b68972ec1e3b65d43d45/tiles/WebMercatorQuad/{z}/{x}/{y}?assets=cog&tilesize=256"
],
"minzoom": 14,
"maxzoom": 18,
"bounds": [
-87.0251,
36.0999,
-85.4249,
36.2251
],
"center": [
-86.225,
36.162499999999994,
14
]
}
In [31]:
Copied!
m = Map(
location=((bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2), zoom_start=14
)
geo_json = GeoJson(
data=geojson,
style_function=lambda x: {
"opacity": 1,
"dashArray": "1",
"fillOpacity": 0,
"weight": 1,
},
)
geo_json.add_to(m)
aod_layer = TileLayer(
tiles=tj_response["tiles"][0],
attr="Mosaic",
min_zoom=tj_response["minzoom"],
max_zoom=tj_response["maxzoom"],
max_native_zoom=tj_response["maxzoom"],
)
aod_layer.add_to(m)
m
m = Map(
location=((bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2), zoom_start=14
)
geo_json = GeoJson(
data=geojson,
style_function=lambda x: {
"opacity": 1,
"dashArray": "1",
"fillOpacity": 0,
"weight": 1,
},
)
geo_json.add_to(m)
aod_layer = TileLayer(
tiles=tj_response["tiles"][0],
attr="Mosaic",
min_zoom=tj_response["minzoom"],
max_zoom=tj_response["maxzoom"],
max_native_zoom=tj_response["maxzoom"],
)
aod_layer.add_to(m)
m
Out[31]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [ ]:
Copied!