{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Visualizing MAAP STAC Dataset with titiler-pgstac\n", "\n", "Authors: Henry Rodman (Development Seed)\n", "\n", "Date: September 5, 2024\n", "\n", "Description: In this notebook, we visualize Cloud-Optimized GeoTIFFs (COGs) from the icesat2-boreal collection in MAAP's STAC using the `/collections` and `/searches` endpoints in [titiler-pgstac.maap-project.org](https://titiler-pgstac.maap-project.org)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Run This Notebook\n", "To access and run this tutorial within MAAP's Algorithm Development Environment (ADE), please refer to the [\"Getting started with the MAAP\"](https://docs.maap-project.org/en/latest/getting_started/getting_started.html) section of our documentation.\n", "\n", "Disclaimer: it is highly recommended to run a tutorial within MAAP's ADE, which already includes the required software dependencies. Running the tutorial outside of the MAAP ADE may lead to errors. The `pangeo` workspace in the MAAP ADE is already configured with all of the dependencies and access credentials." ] }, { "cell_type": "markdown", "metadata": { "jp-MarkdownHeadingCollapsed": true }, "source": [ "## Note About the Data\n", "\n", "From the collection description:\n", "> Aboveground biomass density c.2020 gridded to 30m derived from ICESat-2, Harmonized Landsat-Sentinel2 and Copernicus DEM. Band 1 is the data band. Band 2 is the standard deviation.\n", "\n", "These COGs are indexed in the [MAAP STAC](https://stac.maap-project.org/collections/SRTMGL1_COD/items)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Additional Resources\n", "- [MAAP titiler-pgstac API Documentation](https://titiler-pgstac.maap-project.org/api.html)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Importing and Installing Packages\n", "\n", "To be able to run this notebook you'll need the following requirements:\n", "\n", "- folium\n", "- httpx" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If the packages below are not installed already, uncomment the following cell:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "tags": [] }, "outputs": [], "source": [ "# %pip install folium\n", "# %pip install httpx" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "tags": [] }, "outputs": [], "source": [ "import httpx\n", "from folium import GeoJson, LayerControl, Map, TileLayer\n", "from pprint import pprint\n", "from pystac_client import Client\n", "from shapely.geometry import box, mapping" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This demo uses the [MAAP titiler-pgstac deployment](https://titiler-pgstac.maap-project.org) to render tiles for a collection (`icesat2-boreal`) that is indexed in the [MAAP STAC](https://stac.maap-project.org). We will make use of some pre-defined `render` parameters stored in the [`render` extension](https://github.com/stac-extensions/render) metadata for the collection when we generate visualizations." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "tags": [] }, "outputs": [], "source": [ "TITILER_PGSTAC_ENDPOINT = \"https://titiler-pgstac.maap-project.org\" # MAAP titiler-pgstac endpoint\n", "COLLECTION_ID = \"icesat2-boreal\"\n", "RENDERING = \"agb\" # render parameters for aboveground biomass" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'links': [{'href': 'https://titiler-pgstac.maap-project.org/collections/icesat2-boreal/info',\n", " 'rel': 'self',\n", " 'title': 'Mosaic metadata'},\n", " {'href': 'https://titiler-pgstac.maap-project.org/collections/icesat2-boreal/{tileMatrixSetId}/tilejson.json',\n", " 'rel': 'tilejson',\n", " 'templated': True,\n", " 'title': 'TileJSON link (Template URL).'},\n", " {'href': 'https://titiler-pgstac.maap-project.org/collections/icesat2-boreal/{tileMatrixSetId}/tilejson.json?bidx=1&nodata=nan&colormap_name=gist_earth_r&rescale=0%2C400&assets=tif',\n", " 'rel': 'tilejson',\n", " 'templated': True,\n", " 'title': 'TileJSON link for `agb` layer (Template URL).'},\n", " {'href': 'https://titiler-pgstac.maap-project.org/collections/icesat2-boreal/{tileMatrixSetId}/map',\n", " 'rel': 'map',\n", " 'templated': True,\n", " 'title': 'Map viewer link (Template URL).'},\n", " {'href': 'https://titiler-pgstac.maap-project.org/collections/icesat2-boreal/{tileMatrixSetId}/map?bidx=1&nodata=nan&colormap_name=gist_earth_r&rescale=0%2C400&assets=tif',\n", " 'rel': 'map',\n", " 'templated': True,\n", " 'title': 'Map viewer link for `agb` layer (Template URL).'},\n", " {'href': 'https://titiler-pgstac.maap-project.org/collections/icesat2-boreal/{tileMatrixSetId}/WMTSCapabilities.xml',\n", " 'rel': 'wmts',\n", " 'templated': True,\n", " 'title': 'WMTS link (Template URL)'}],\n", " 'search': {'_where': \"collection = ANY ('{icesat2-boreal}') \",\n", " 'hash': '2b1c56237655c787dc5598c8592f5010',\n", " 'lastused': '2024-09-06T20:29:27.945205Z',\n", " 'metadata': {'assets': ['csv', 'tif'],\n", " 'bounds': [-180.0, 51.6, 180.0, 78.0],\n", " 'defaults': {'agb': {'assets': ['tif'],\n", " 'bidx': [1],\n", " 'colormap_name': 'gist_earth_r',\n", " 'nodata': 'nan',\n", " 'rescale': [[0, 400]]}},\n", " 'name': \"Mosaic for 'icesat2-boreal' Collection\",\n", " 'type': 'mosaic'},\n", " 'orderby': 'datetime DESC, id DESC',\n", " 'search': {'collections': ['icesat2-boreal']},\n", " 'usecount': 462}}\n" ] } ], "source": [ "# get some information about the collection including render parameters and spatial extent\n", "collection_info = httpx.get(\n", " f\"{TITILER_PGSTAC_ENDPOINT}/collections/{COLLECTION_ID}/info\",\n", " timeout=10,\n", ").json()\n", "pprint(collection_info)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "Tip: You can also get the render parameters from the /collections/{collection_id} STAC API endpoint but for simplicity we are using the titiler-pgstac endpoints for everything in this example.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Render tiles for the whole collection\n", "The [`/collections` endpoints](https://titiler-pgstac.maap-project.org/api.html#/STAC%20Collection) in a `titiler-pgstac` deployment can be used to render tiles for a mosaic of all items in a collection. This is particularly convenient for collections where all areas are covered by only a single item because you can get everything you need for rendering snazzy tiles with a single `GET` request. The catch is that if your collection contains data for multiple time points, for example, the tiles will load the most recent ones by default and stop adding data once the tile is fully covered by items (in descending order by time).\n", "\n", "The `render` parameters are provided in the collection metadata and can be accessed in the response from the `/collections/{collection_id}/info` endpoint:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'assets': ['tif'],\n", " 'bidx': [1],\n", " 'colormap_name': 'gist_earth_r',\n", " 'maxzoom': 14,\n", " 'minzoom': 7,\n", " 'nodata': 'nan',\n", " 'rescale': [[0, 400]]}\n" ] } ], "source": [ "# load the render parameters from the collection metadata\n", "agb_render_params = collection_info[\"search\"][\"metadata\"][\"defaults\"][RENDERING].copy()\n", "\n", "# add some zoom limits to avoid overloading the tile server\n", "agb_render_params[\"minzoom\"] = 7\n", "agb_render_params[\"maxzoom\"] = 14\n", "\n", "pprint(agb_render_params)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These parameters can be passed along to any `titiler-pgstac` endpoint to make use of pre-defined visualization settings!" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'bounds': [-180.0, 51.6, 180.0, 78.0],\n", " 'center': [0.0, 64.8, 7],\n", " 'maxzoom': 14,\n", " 'minzoom': 7,\n", " 'name': \"Mosaic for 'icesat2-boreal' Collection\",\n", " 'scheme': 'xyz',\n", " 'tilejson': '2.2.0',\n", " 'tiles': ['https://titiler-pgstac.maap-project.org/collections/icesat2-boreal/tiles/WebMercatorQuad/{z}/{x}/{y}?bidx=1&assets=tif&nodata=nan&rescale=%5B0%2C+400%5D&colormap_name=gist_earth_r'],\n", " 'version': '1.0.0'}\n" ] } ], "source": [ "collection_tilejson = httpx.get(\n", " f\"{TITILER_PGSTAC_ENDPOINT}/collections/{COLLECTION_ID}/WebMercatorQuad/tilejson.json\",\n", " params=agb_render_params,\n", ").json()\n", "\n", "pprint(collection_tilejson)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The contents of the `collection_tilejson` dictionary can be passed to the arguments in `TileLayer` to control things like min/max zoom levels for the tiles." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "tags": [] }, "outputs": [ { "data": { "text/html": [ "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# create an XYZ tile layer for the leaflet map\n", "tiles = TileLayer(\n", " name=f\"{COLLECTION_ID} - {RENDERING}\",\n", " tiles=collection_tilejson[\"tiles\"][0],\n", " min_zoom=collection_tilejson[\"minzoom\"],\n", " max_zoom=collection_tilejson[\"maxzoom\"],\n", " opacity=1,\n", " attr=\"NASA\",\n", " overlay=True,\n", " control=True,\n", ")\n", "\n", "# load the collection bounding box so we can plot it on the map\n", "collection_bbox = collection_info[\"search\"][\"metadata\"][\"bounds\"]\n", "geojson = {\"type\": \"Feature\", \"geometry\": mapping(box(*collection_bbox)), \"properties\": {}}\n", "\n", "geojson_layer = GeoJson(\n", " data=geojson,\n", " style_function=lambda x: {\n", " \"opacity\": 1,\n", " \"dashArray\": \"1\",\n", " \"fillOpacity\": 0,\n", " \"weight\": 2,\n", " \"color\": \"orange\",\n", " },\n", " name=f\"{COLLECTION_ID} bounds\",\n", " overlay=True,\n", " control=True,\n", " show=True,\n", ")\n", "\n", "m = Map(\n", " tiles=\"OpenStreetMap\",\n", " location=((collection_bbox[1] + collection_bbox[3]) / 2, (collection_bbox[0] + collection_bbox[2]) / 2),\n", " zoom_start=2,\n", ")\n", "tiles.add_to(m)\n", "geojson_layer.add_to(m)\n", "\n", "LayerControl(collapsed=False).add_to(m)\n", "\n", "m" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Render tiles for a STAC search\n", "You can use the `/searches` endpoints to register a STAC search with `titiler-pgstac` and view tiles from a mosaic formed by the items that are returned by the search. This is useful for limiting tiles to a particular temporal or spatial extent or possibly to items with specific properties.\n", "\n", "This can be done with a `POST` request to the [`/searches/register` endpoint](https://titiler-pgstac.maap-project.org/api.html#/STAC%20Search/register_search_searches_register_post) with the STAC search parameters included as a json request body. You can use any `search` parameters that are implemented in the `pgstac` database backing the `titiler-pgstac` deployment.\n", "\n", "In this example, we will register a STAC search for items that intersect the bounding box of Alaska:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'id': '058af6b76d8fc4e092818a0d1f7bffa8',\n", " 'links': [{'href': 'https://titiler-pgstac.maap-project.org/searches/058af6b76d8fc4e092818a0d1f7bffa8/info',\n", " 'rel': 'metadata',\n", " 'title': 'Mosaic metadata'},\n", " {'href': 'https://titiler-pgstac.maap-project.org/searches/058af6b76d8fc4e092818a0d1f7bffa8/{tileMatrixSetId}/tilejson.json',\n", " 'rel': 'tilejson',\n", " 'templated': True,\n", " 'title': 'Link for TileJSON (Template URL)'},\n", " {'href': 'https://titiler-pgstac.maap-project.org/searches/058af6b76d8fc4e092818a0d1f7bffa8/{tileMatrixSetId}/map',\n", " 'rel': 'map',\n", " 'templated': True,\n", " 'title': 'Link for Map viewer (Template URL)'},\n", " {'href': 'https://titiler-pgstac.maap-project.org/searches/058af6b76d8fc4e092818a0d1f7bffa8/{tileMatrixSetId}/WMTSCapabilities.xml',\n", " 'rel': 'wmts',\n", " 'templated': True,\n", " 'title': 'Link for WMTS (Template URL)'}]}\n" ] } ], "source": [ "ak_bbox = (-180, 50, -128, 73)\n", "ak_search_request = httpx.post(\n", " f\"{TITILER_PGSTAC_ENDPOINT}/searches/register\",\n", " json={\n", " \"collections\": [COLLECTION_ID],\n", " \"bbox\": ak_bbox,\n", " },\n", ").json()\n", "\n", "pprint(ak_search_request)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `/searches` POST request will return a search `id` that represents a unique key for the search parameters that you provided. The `id` can be used in the `/searches/{search_id}` endpoints! However, the `render` parameters from the collection metadata are not be associated with any `search` that is registered in this way so we need to re-use the `agb_render_params` that we pulled from the `/collections/{collection_id}/info` endpoint." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'bounds': [-180.0, 50.0, -128.0, 73.0],\n", " 'center': [-154.0, 61.5, 7],\n", " 'maxzoom': 14,\n", " 'minzoom': 7,\n", " 'name': '058af6b76d8fc4e092818a0d1f7bffa8',\n", " 'scheme': 'xyz',\n", " 'tilejson': '2.2.0',\n", " 'tiles': ['https://titiler-pgstac.maap-project.org/searches/058af6b76d8fc4e092818a0d1f7bffa8/tiles/WebMercatorQuad/{z}/{x}/{y}?bidx=1&assets=tif&nodata=nan&rescale=%5B0%2C+400%5D&colormap_name=gist_earth_r'],\n", " 'version': '1.0.0'}\n" ] } ], "source": [ "search_id = ak_search_request[\"id\"]\n", "ak_tilejson = httpx.get(\n", " f\"{TITILER_PGSTAC_ENDPOINT}/searches/{search_id}/WebMercatorQuad/tilejson.json\",\n", " params=agb_render_params,\n", ").json()\n", "\n", "pprint(ak_tilejson)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tiles = TileLayer(\n", " name=f\"{COLLECTION_ID} - {RENDERING}\",\n", " tiles=ak_tilejson[\"tiles\"][0],\n", " min_zoom=ak_tilejson[\"minzoom\"],\n", " max_zoom=ak_tilejson[\"maxzoom\"],\n", " opacity=1,\n", " attr=\"NASA\",\n", " overlay=True,\n", " control=True,\n", ")\n", "geojson = {\"type\": \"Feature\", \"geometry\": mapping(box(*ak_bbox)), \"properties\": {}}\n", "\n", "zoom_start = 5\n", "\n", "geojson_layer = GeoJson(\n", " data=geojson,\n", " style_function=lambda x: {\n", " \"opacity\": 1,\n", " \"dashArray\": \"1\",\n", " \"fillOpacity\": 0,\n", " \"weight\": 1,\n", " },\n", " name=\"STAC search bounds\",\n", " overlay=True,\n", " control=True,\n", " show=True,\n", ")\n", "\n", "m = Map(\n", " tiles=\"OpenStreetMap\",\n", " location=((ak_bbox[1] + ak_bbox[3]) / 2, (ak_bbox[0] + ak_bbox[2]) / 2),\n", " zoom_start=zoom_start,\n", ")\n", "geojson_layer.add_to(m)\n", "tiles.add_to(m)\n", "LayerControl(collapsed=False).add_to(m)\n", "\n", "m" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python [conda env:pangeo] *", "language": "python", "name": "conda-env-pangeo-py" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.13" } }, "nbformat": 4, "nbformat_minor": 4 }