From 060a40ba2ce0c8c067bd36031ec5e286f569a103 Mon Sep 17 00:00:00 2001 From: Alex Mandel Date: Fri, 24 Oct 2025 23:28:16 +0000 Subject: [PATCH 01/10] fix: review edits to section 2 --- docs/02-database.ipynb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/02-database.ipynb b/docs/02-database.ipynb index 6b1a98a..9c0179a 100644 --- a/docs/02-database.ipynb +++ b/docs/02-database.ipynb @@ -7,10 +7,10 @@ "source": [ "# 2. The Database: pgstac\n", "\n", - "pgstac is a PostgreSQL extension that enables STAC metadata management in a PostgreSQL database.\n", + "**pgstac** is a PostgreSQL extension that enables STAC metadata management in a PostgreSQL database.\n", "eoAPI is useful to many organizations because the other components are configured to work seamlessly with STAC metadata that is stored in your pgstac database.\n", "\n", - "**pypgstac** is a Python package for interacting with a pgstac database. You will learn how to use pypgstac to perform the following operations on a pgstac database:\n", + "**pypgstac** is a Python package for interacting with a pgstac database. You will learn how to use pypgstac to perform the following operations on a pgstac database:\n", "1. Generate STAC collection record\n", "2. Add the record to the `collections` table with `Loader.load_collections`\n", "3. Generate STAC item records\n", @@ -30,7 +30,7 @@ "id": "jivtk3bczsd", "metadata": {}, "source": [ - "# Workshop Setup\n", + "## Workshop Setup\n", "\n", "Run this cell to fetch database credentials. You'll be prompted to enter the workshop access token provided by your instructor.\n", "\n", @@ -117,7 +117,7 @@ "source": [ "## 2.1 Load a collection object\n", "\n", - "Start by creating a collection that will contain items within ~2 degrees of your provided location between January 1 2025 and April 18 2025." + "We'll start by creating a collection, that will eventually contain items, within ~2 degrees of your provided location between January 1 2025 and April 18 2025." ] }, { @@ -178,7 +178,7 @@ "id": "af1fa92d-97b3-4b70-b7b9-b371a06741a3", "metadata": {}, "source": [ - "The `load_collections` method accepts an iterable of STAC collection dictionaries or a file path to a collection.json file or a .ndjson file with multiple collection records. You already have your `pystac.Collection` in memory in this session so you can just write it to a dictionary and upload it (in a list). \n", + "The `load_collections` method accepts an iterable of STAC collection dictionaries, a file path to a collection.json file, or a [.ndjson](https://en.wikipedia.org/wiki/JSON_streaming#Newline-delimited_JSON) file with multiple collection records. You already have your `pystac.Collection` in memory in this session so you can just write it to a dictionary and upload it (in a list, i.e. iterable object). \n", "\n", "The `upsert` method will add your collection to the `collections` table if it does not exist and update any fields with new values if the record does already exist." ] @@ -466,7 +466,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.12" + "version": "3.12.11" } }, "nbformat": 4, From 0ae165047adc5a4f197a7b792d43bf73678cbe26 Mon Sep 17 00:00:00 2001 From: Alex Mandel Date: Fri, 24 Oct 2025 23:28:33 +0000 Subject: [PATCH 02/10] fix: most review edits to section 3 --- docs/03-stac_fastapi_pgstac.ipynb | 241 +++++------------------------- 1 file changed, 41 insertions(+), 200 deletions(-) diff --git a/docs/03-stac_fastapi_pgstac.ipynb b/docs/03-stac_fastapi_pgstac.ipynb index 20cbf5b..a70a3d7 100644 --- a/docs/03-stac_fastapi_pgstac.ipynb +++ b/docs/03-stac_fastapi_pgstac.ipynb @@ -57,47 +57,16 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "3d4e7fe4-c21b-45c8-a56c-9b649f9f4924", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"conformsTo\": [\n", - " \"http://www.opengis.net/spec/cql2/1.0/conf/basic-cql2\",\n", - " \"http://www.opengis.net/spec/cql2/1.0/conf/cql2-json\",\n", - " \"http://www.opengis.net/spec/cql2/1.0/conf/cql2-text\",\n", - " \"http://www.opengis.net/spec/ogcapi-common-2/1.0/conf/simple-query\",\n", - " \"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core\",\n", - " \"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson\",\n", - " \"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30\",\n", - " \"http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/features-filter\",\n", - " \"http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/filter\",\n", - " \"https://api.stacspec.org/v1.0.0-rc.1/collection-search\",\n", - " \"https://api.stacspec.org/v1.0.0-rc.1/collection-search#fields\",\n", - " \"https://api.stacspec.org/v1.0.0-rc.1/collection-search#filter\",\n", - " \"https://api.stacspec.org/v1.0.0-rc.1/collection-search#free-text\",\n", - " \"https://api.stacspec.org/v1.0.0-rc.1/collection-search#query\",\n", - " \"https://api.stacspec.org/v1.0.0-rc.1/collection-search#sort\",\n", - " \"https://api.stacspec.org/v1.0.0-rc.2/item-search#filter\",\n", - " \"https://api.stacspec.org/v1.0.0/collections\",\n", - " \"https://api.stacspec.org/v1.0.0/core\",\n", - " \"https://api.stacspec.org/v1.0.0/item-search\",\n", - " \"https://api.stacspec.org/v1.0.0/item-search#fields\",\n", - " \"https://api.stacspec.org/v1.0.0/item-search#query\",\n", - " \"https://api.stacspec.org/v1.0.0/item-search#sort\",\n", - " \"https://api.stacspec.org/v1.0.0/ogcapi-features\",\n", - " \"https://api.stacspec.org/v1.0.0/ogcapi-features#fields\",\n", - " \"https://api.stacspec.org/v1.0.0/ogcapi-features#query\",\n", - " \"https://api.stacspec.org/v1.0.0/ogcapi-features#sort\"\n", - " ]\n", - "}\n" - ] - } - ], + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], "source": [ "import json\n", "import os\n", @@ -108,6 +77,7 @@ "\n", "conformance_response = httpx.get(f\"{stac_api_endpoint}/conformance\").json()\n", "\n", + "print(stac_api_endpoint)\n", "print(json.dumps(conformance_response, indent=2))" ] }, @@ -135,147 +105,12 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "ca0033ed-4898-4c21-8aa9-aaf79fc8d570", "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"collections\": [\n", - " {\n", - " \"id\": \"falling-silence-5211-sentinel-2-c1-l2a\",\n", - " \"type\": \"Collection\",\n", - " \"links\": [\n", - " {\n", - " \"rel\": \"items\",\n", - " \"type\": \"application/geo+json\",\n", - " \"href\": \"https://eoapiworkshop-stac.eoapi.dev/collections/falling-silence-5211-sentinel-2-c1-l2a/items\"\n", - " },\n", - " {\n", - " \"rel\": \"parent\",\n", - " \"type\": \"application/json\",\n", - " \"href\": \"https://eoapiworkshop-stac.eoapi.dev/\"\n", - " },\n", - " {\n", - " \"rel\": \"root\",\n", - " \"type\": \"application/json\",\n", - " \"href\": \"https://eoapiworkshop-stac.eoapi.dev/\"\n", - " },\n", - " {\n", - " \"rel\": \"self\",\n", - " \"type\": \"application/json\",\n", - " \"href\": \"https://eoapiworkshop-stac.eoapi.dev/collections/falling-silence-5211-sentinel-2-c1-l2a\"\n", - " },\n", - " {\n", - " \"rel\": \"http://www.opengis.net/def/rel/ogc/1.0/queryables\",\n", - " \"type\": \"application/schema+json\",\n", - " \"title\": \"Queryables\",\n", - " \"href\": \"https://eoapiworkshop-stac.eoapi.dev/collections/falling-silence-5211-sentinel-2-c1-l2a/queryables\"\n", - " }\n", - " ],\n", - " \"extent\": {\n", - " \"spatial\": {\n", - " \"bbox\": [\n", - " [\n", - " 141.0,\n", - " -27.0,\n", - " 145.0,\n", - " -23.0\n", - " ]\n", - " ]\n", - " },\n", - " \"temporal\": {\n", - " \"interval\": [\n", - " [\n", - " \"2025-08-20T13:13:52.225580Z\",\n", - " \"2025-10-19T13:13:52.225580Z\"\n", - " ]\n", - " ]\n", - " }\n", - " },\n", - " \"license\": \"other\",\n", - " \"description\": \"falling-silence-5211's personal Sentinel-2 L2A collection\",\n", - " \"stac_version\": \"1.1.0\"\n", - " },\n", - " {\n", - " \"id\": \"polished-wildflower-3104-sentinel-2-c1-l2a\",\n", - " \"type\": \"Collection\",\n", - " \"links\": [\n", - " {\n", - " \"rel\": \"items\",\n", - " \"type\": \"application/geo+json\",\n", - " \"href\": \"https://eoapiworkshop-stac.eoapi.dev/collections/polished-wildflower-3104-sentinel-2-c1-l2a/items\"\n", - " },\n", - " {\n", - " \"rel\": \"parent\",\n", - " \"type\": \"application/json\",\n", - " \"href\": \"https://eoapiworkshop-stac.eoapi.dev/\"\n", - " },\n", - " {\n", - " \"rel\": \"root\",\n", - " \"type\": \"application/json\",\n", - " \"href\": \"https://eoapiworkshop-stac.eoapi.dev/\"\n", - " },\n", - " {\n", - " \"rel\": \"self\",\n", - " \"type\": \"application/json\",\n", - " \"href\": \"https://eoapiworkshop-stac.eoapi.dev/collections/polished-wildflower-3104-sentinel-2-c1-l2a\"\n", - " },\n", - " {\n", - " \"rel\": \"http://www.opengis.net/def/rel/ogc/1.0/queryables\",\n", - " \"type\": \"application/schema+json\",\n", - " \"title\": \"Queryables\",\n", - " \"href\": \"https://eoapiworkshop-stac.eoapi.dev/collections/polished-wildflower-3104-sentinel-2-c1-l2a/queryables\"\n", - " }\n", - " ],\n", - " \"extent\": {\n", - " \"spatial\": {\n", - " \"bbox\": [\n", - " [\n", - " -145.0,\n", - " 52.0,\n", - " -141.0,\n", - " 56.0\n", - " ]\n", - " ]\n", - " },\n", - " \"temporal\": {\n", - " \"interval\": [\n", - " [\n", - " \"2025-08-21T21:27:37.618150Z\",\n", - " \"2025-10-20T21:27:37.618150Z\"\n", - " ]\n", - " ]\n", - " }\n", - " },\n", - " \"license\": \"other\",\n", - " \"description\": \"polished-wildflower-3104's personal Sentinel-2 L2A collection\",\n", - " \"stac_version\": \"1.1.0\"\n", - " }\n", - " ],\n", - " \"links\": [\n", - " {\n", - " \"rel\": \"root\",\n", - " \"type\": \"application/json\",\n", - " \"href\": \"https://eoapiworkshop-stac.eoapi.dev/\"\n", - " },\n", - " {\n", - " \"rel\": \"self\",\n", - " \"type\": \"application/json\",\n", - " \"href\": \"https://eoapiworkshop-stac.eoapi.dev/collections?limit=2\"\n", - " }\n", - " ],\n", - " \"numberMatched\": 2,\n", - " \"numberReturned\": 2\n", - "}\n" - ] - } - ], + "outputs": [], "source": [ "collections_response = httpx.get(\n", " f\"{stac_api_endpoint}/collections\", params={\"limit\": 2}\n", @@ -289,24 +124,16 @@ "id": "ad74c861-7b4b-491e-9e17-82bcafd1cec4", "metadata": {}, "source": [ + "### 3.2.1 All Collections\n", "You can retrieve all of a catalog's collection using the `get_all_collections` method from `pystac-client`:" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "48664f4b-453d-47b8-9e3b-24a13c11cfcd", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "falling-silence-5211-sentinel-2-c1-l2a\n", - "polished-wildflower-3104-sentinel-2-c1-l2a\n" - ] - } - ], + "outputs": [], "source": [ "import pystac_client\n", "\n", @@ -322,6 +149,7 @@ "id": "c066ffe0-5506-4268-be8b-26329b4e70fd", "metadata": {}, "source": [ + "### 3.2.2 Collection Search Query\n", "Some APIs contain many many collections so, if the `collection-search` extension is enabled, it can be helpful to apply filters using the available query parameters like:\n", "- `q`: free-text search parameter\n", "- `datetime`: temporal filters\n", @@ -369,9 +197,11 @@ "from IPython.display import IFrame\n", "\n", "local_stac_api_endpoint = stac_api_endpoint.replace(\"stac-fastapi\", \"localhost\")\n", + "api_docs = f\"{local_stac_api_endpoint}/api.html#/default/Get_Collections_collections_get\"\n", + "print(api_docs)\n", "\n", "IFrame(\n", - " f\"{local_stac_api_endpoint}/api.html#/default/Get_Collections_collections_get\",\n", + " api_docs,\n", " 1200,\n", " 800,\n", ")" @@ -385,10 +215,17 @@ "Try applying the `filter` parameter to do a cql2-text query on the id field to find the collection you created in the `database` exercies.\n", "\n", "
\n", - "Tip: Try out the CQL2 Playground to learn how to write cql2-text or cql2-json queries\n", + "Tip: Try out the CQL2 Playground to learn how to write cql2-text or cql2-json queries\n", "
\n", - "\n", - "" + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "fb94fe39-5091-41c1-8c2c-a227eb1b5c3c", + "metadata": {}, + "source": [ + "If you didn't run Part 2 on Databases, you can either go back to make a username or you can copy one from section 3.2.1 collection ids." ] }, { @@ -402,7 +239,7 @@ "from IPython.display import display\n", "\n", "username_input = widgets.Text(\n", - " value=\"hrodmn\",\n", + " value=None,\n", " placeholder=\"Enter your username\",\n", " description=\"username:\",\n", " disabled=False,\n", @@ -490,7 +327,7 @@ "id": "74d1fb0b-e830-4fb0-8619-f9cfd3019111", "metadata": {}, "source": [ - "### 3.3.1 /search\n", + "### 3.3.1 Item Search\n", "\n", "Use the `/search` endpoint to find all items in your collection with a timestamp after April 4, 2025" ] @@ -592,7 +429,9 @@ "id": "61d01f82-94f8-4071-b398-2508bc84c56a", "metadata": {}, "source": [ - "### 3.3.2 /collections/{collection_id}/items\n", + "### 3.3.2 All Items\n", + "\n", + "The API `/collections/{collection_id}/items` endpoint will get you all items in a collection.\n", "\n", "You can also run the same search but instead of passing `collections` as a query parameter you can include `collection_id` as a path parameter in the request URL itself. All of the other query parameters for the `/search` GET request will be available." ] @@ -610,7 +449,7 @@ " f\"{stac_api_endpoint}/collections/{my_collection.id}/items\",\n", " params={\n", " \"datetime\": f\"{datetime_string}/..\", # open interval from 2025-04-04 forward\n", - " \"limit\": 100,\n", + " \"limit\": 1000,\n", " \"filter\": \"eo:cloud_cover < 10\", # less than 10% cloud cover\n", " },\n", ")\n", @@ -623,7 +462,7 @@ "id": "a1de0ea6-51b5-4b12-88e0-67c2d49012bb", "metadata": {}, "source": [ - "### 3.3.3 /collections/{collection_id}/items/{item_id}\n", + "### 3.3.3 Single Item by ID\n", "\n", "To retrieve a specific item from the catalog, you can use the `/collections/{collection_id}/items/{item_id}` endpoint." ] @@ -681,7 +520,9 @@ "metadata": {}, "outputs": [], "source": [ - "IFrame(f\"{local_stac_api_endpoint}/api.html\", 1200, 800)" + "api_docs = f\"{local_stac_api_endpoint}/api.html\"\n", + "print(api_docs)\n", + "IFrame(api_docs, 1200, 800)" ] } ], @@ -701,9 +542,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.12" + "version": "3.12.11" } }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} From 42f1bb04d2002149577816cbafa7e7fe053dd2fe Mon Sep 17 00:00:00 2001 From: Alex Mandel Date: Mon, 27 Oct 2025 17:23:52 +0000 Subject: [PATCH 03/10] fix: applied ruff to 03 --- docs/03-stac_fastapi_pgstac.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/03-stac_fastapi_pgstac.ipynb b/docs/03-stac_fastapi_pgstac.ipynb index a70a3d7..0860be9 100644 --- a/docs/03-stac_fastapi_pgstac.ipynb +++ b/docs/03-stac_fastapi_pgstac.ipynb @@ -197,7 +197,9 @@ "from IPython.display import IFrame\n", "\n", "local_stac_api_endpoint = stac_api_endpoint.replace(\"stac-fastapi\", \"localhost\")\n", - "api_docs = f\"{local_stac_api_endpoint}/api.html#/default/Get_Collections_collections_get\"\n", + "api_docs = (\n", + " f\"{local_stac_api_endpoint}/api.html#/default/Get_Collections_collections_get\"\n", + ")\n", "print(api_docs)\n", "\n", "IFrame(\n", From 47a1b187f85a0460cd89203fd56df073c7e44a12 Mon Sep 17 00:00:00 2001 From: Alex Mandel Date: Mon, 27 Oct 2025 23:28:54 +0000 Subject: [PATCH 04/10] fix: progress on 04 fixes --- docs/04-titiler_pgstac.ipynb | 62 +++++++++++++++++------------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/docs/04-titiler_pgstac.ipynb b/docs/04-titiler_pgstac.ipynb index 0734ace..8065b0f 100644 --- a/docs/04-titiler_pgstac.ipynb +++ b/docs/04-titiler_pgstac.ipynb @@ -27,30 +27,16 @@ "\n", "You can do all of this with titiler-pgstac with a single copy of the raw data in cloud storage!\n", "\n", - "Start by entering the username you picked when defining your personal Sentinel-2 L2A collection:" + "## Setting up the session\n", + "Start by entering the username you picked when defining your personal Sentinel-2 L2A collection in [chapter 2](02-database.ipynb):" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "97b26130-eef8-4fbc-a404-afe4e6942777", "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "3c371766370f430f8a0f56116e786672", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Text(value='', description='username:', placeholder='Enter your username')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "import ipywidgets as widgets\n", "from IPython.display import display\n", @@ -65,6 +51,14 @@ "display(username_input)" ] }, + { + "cell_type": "markdown", + "id": "034167b5-b9f3-468a-88a5-806c72ff1654", + "metadata": {}, + "source": [ + "Now if you want to explore the Titiler PgSTAC interactive API documentation, let's load the website." + ] + }, { "cell_type": "code", "execution_count": null, @@ -79,9 +73,11 @@ "titiler_pgstac_endpoint = os.getenv(\"TITILER_PGSTAC_API_ENDPOINT\").replace(\n", " \"titiler-pgstac\", \"localhost\"\n", ")\n", + "api_docs = f\"{titiler_pgstac_endpoint}/api.html\"\n", + "print(api_docs)\n", "\n", "IFrame(\n", - " f\"{titiler_pgstac_endpoint}/api.html\",\n", + " api_docs,\n", " 1200,\n", " 800,\n", ")" @@ -92,10 +88,10 @@ "id": "cbed93d9-ba99-4a67-8dcc-68c7d11ec183", "metadata": {}, "source": [ - "## 4.1 /collections/{collection_id}\n", - "Start out with the most basic titiler-pgstac request: RGB visualization of the most recent images in your Sentinel-2 STAC collection.\n", + "## 4.1 Mosaic a Collection\n", + "Start out with the most basic titiler-pgstac request: Red-Green-Blue(RGB), aka standard photograph style, visualization of the most recent images in your Sentinel-2 STAC collection using the `/collections/{collection_id}` endpoint.\n", "\n", - "To get a visualization of the red/green/blue band combination from your collection you need to tell titiler-pgstac which assets you want using the `assets` query parameter (multiple times in this case)." + "To get a visualization of the red/green/blue band combination from your collection you need to tell titiler-pgstac which assets you want using the `assets` query parameter (multiple times in this case). If you're not sure what names to use you can always query the Collection or an Item and read the band properties in the metadata." ] }, { @@ -159,7 +155,7 @@ "For now you can take a shortcut to view the map directly in this notebook using the `/map` endpoint which will generate an HTML file with a Leaflet map that can be rendered directly in the notebook.\n", "\n", "
\n", - "It may take a while to render tiles for the full view because the titiler-pgstac container is downloading data from S3 in order to render the images - zoom in to have a better browsing experience.\n", + "It may take a while to render tiles for the full view because the titiler-pgstac container is downloading data from S3 in order to render the images - zoom in to have a better browsing experience. This performance can be improved when deploying your own eoAPI through careful preparation of data and sourcing of hardware resources, the demo runs on a very small server.\n", "
" ] }, @@ -199,14 +195,14 @@ "id": "4b649d4b-590c-449e-aa2d-b8c50f1d1a70", "metadata": {}, "source": [ - "## 4.2 /searches\n", + "## 4.2 Mosaic a Search \n", "\n", - "Sometimes you will want to render visualizations from a more complex STAC query. titiler-pgstac and pgstac make it possible to pre-register a search query to make it easy to request tiles from a STAC search based on the filter parameters.\n", + "Sometimes you will want to render visualizations from a more complex STAC query. titiler-pgstac and pgstac make it possible to pre-register a `/searches` query to make it easy to request tiles from a STAC search based on the filter parameters.\n", "\n", "The view in the example above may have some clouds visible so you can construct a query that will filter down the STAC item results to items that have low cloud cover.\n", "\n", "To do this you can first send a POST request to the `/searches/register` endpoint with your STAC query parameters like you would send to a STAC API when searching for items directly.\n", - "Once the search is registered, you can make XYZ tile requests like you did for the default `/collections` view." + "Once the search is registered, you can make XYZ tile requests like you did for the default `/collections` view. Registering the search provides for significant convenience when using the same search repeatedly, like in a webite layer definition." ] }, { @@ -294,9 +290,9 @@ "id": "520b08f4-1d21-4459-9bdf-559264b93a62", "metadata": {}, "source": [ - "## 4.3 band math expressions\n", + "## 4.3 Apply Band Math Expressions\n", "\n", - "Perhaps you want to view NDVI or some other band math expression. You can set `asset_as_band=True` then write a mathematical expression that uses the asset keys as variables which titiler-pgstac will evaluate for each tile.\n", + "Perhaps you want to view [NDVI](https://en.wikipedia.org/wiki/Normalized_difference_vegetation_index) or some other band math expression. You can set `asset_as_band=True` then write a mathematical expression that uses the asset keys as variables which titiler-pgstac will evaluate for each tile.\n", "\n", "In the example below you can request tiles for a view of NDVI and specify the colormap from a [list](https://cogeotiff.github.io/rio-tiler/colormap/#intervals-colormaps) of possible values. The results will be calculated on-the-fly by titiler-pgstac for each XYZ tile request, and returned to your map client in this notebook." ] @@ -333,9 +329,9 @@ "\n", "See [maap-eoapi](https://github.com/MAAP-Project/maap-eoapi/tree/main/cdk/runtimes/eoapi/raster/eoapi/raster) for an example of a custom runtime that extends the default settings.\n", "\n", - "### 4.4.1 /external\n", + "### 4.4.1 Using External Data\n", "\n", - "Sometimes you may want to render visualizations of data that are **not** cataloged in your STAC. The `/external` family of endpoints can be enabled by setting the environment variable `TITILER_PGSTAC_API_ENABLE_EXTERNAL_DATASET_ENDPOINTS=True`. This will enable you to create tile endpoints for any file available over HTTP.\n", + "Sometimes you may want to render visualizations of data that are **not** cataloged in your STAC. The `/external` family of endpoints can be enabled by setting the environment variable `TITILER_PGSTAC_API_ENABLE_EXTERNAL_DATASET_ENDPOINTS=True`. This will enable you to create tile endpoints for any file available over HTTP(S).\n", "\n", "We have enabled this feature in the deployment that we set up for the workshop (see Line 154 in [infrastructure/app.py](../infrastructure/app.py#L154)).\n", "\n", @@ -536,7 +532,7 @@ "source": [ "What a nice view of that cloud-optimized geotiff, right? It would be better if you could browse the entire global dataset in a map like this, though. You could do that if you loaded the dataset into your STAC!\n", "\n", - "This particular dataset is loaded in [NASA MAAP](https://maap-project.org)'s STAC and is accessible on that organization's titiler-pgstac deployment so here is a sneak peak of the global view (be patient it can take a few seconds for a titiler-pgstac running on AWS Lambda to warm up!)." + "This particular dataset is loaded in [NASA MAAP](https://maap-project.org)'s STAC and is accessible on that organization's titiler-pgstac deployment so here is a sneak peak of the global view (be patient it can take a few seconds for a titiler-pgstac running on AWS Lambda to warm up!). Reminder, if you zoom in less tiles are needed and it will load faster." ] }, { @@ -547,7 +543,7 @@ "outputs": [], "source": [ "map_request = httpx.get(\n", - " f\"{titiler_pgstac_endpoint}/collections/glad-global-forest-change-1.11/WebMercatorQuad/map.html\",\n", + " f\"https://titiler-pgstac.maap-project.org/collections/glad-global-forest-change-1.11/WebMercatorQuad/map.html\",\n", " params={\n", " \"assets\": \"lossyear\",\n", " \"colormap\": json.dumps({i: rgb for i, rgb in colormap.items()}),\n", @@ -580,7 +576,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.12" + "version": "3.12.11" } }, "nbformat": 4, From 4e78bc8505a5445acc95dbc7adcef9aedb8ff549 Mon Sep 17 00:00:00 2001 From: Alex Mandel Date: Mon, 27 Oct 2025 23:31:31 +0000 Subject: [PATCH 05/10] fix: linter fix f string --- docs/04-titiler_pgstac.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/04-titiler_pgstac.ipynb b/docs/04-titiler_pgstac.ipynb index 8065b0f..41eb70f 100644 --- a/docs/04-titiler_pgstac.ipynb +++ b/docs/04-titiler_pgstac.ipynb @@ -543,7 +543,7 @@ "outputs": [], "source": [ "map_request = httpx.get(\n", - " f\"https://titiler-pgstac.maap-project.org/collections/glad-global-forest-change-1.11/WebMercatorQuad/map.html\",\n", + " \"https://titiler-pgstac.maap-project.org/collections/glad-global-forest-change-1.11/WebMercatorQuad/map.html\",\n", " params={\n", " \"assets\": \"lossyear\",\n", " \"colormap\": json.dumps({i: rgb for i, rgb in colormap.items()}),\n", From a94ca2e2d9aac92564d279b0d8a2d4631f951581 Mon Sep 17 00:00:00 2001 From: Alex Mandel Date: Tue, 28 Oct 2025 17:50:38 +0000 Subject: [PATCH 06/10] feat: revised 05 based on #15 --- docs/05-tipg.ipynb | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/05-tipg.ipynb b/docs/05-tipg.ipynb index 2f548a3..800f0e8 100644 --- a/docs/05-tipg.ipynb +++ b/docs/05-tipg.ipynb @@ -9,7 +9,7 @@ "\n", "\n", "\n", - "No geospatial data stack is complete without a mechanism for serving vector features as GeoJSON or for consumption in web maps as vector tiles. **tipg** is the component that fills that niche in eoAPI.\n", + "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.\n", "\n", "From the tipg README:\n", "> `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](https://ogcapi.ogc.org/features) and [OGC Tiles](https://ogcapi.ogc.org/tiles/) specifications.\n", @@ -24,7 +24,7 @@ "source": [ "## 5.1 Configuration\n", "\n", - "In an eoAPI stack, tipg can be connected to any PostgreSQL database including the existing pgstac database. This is controlled by the `POSTGRES_*` environment variables in the application runtime.\n", + "In an eoAPI stack, **tipg** can be connected to any PostgreSQL with [PostGIS](https://postgis.net/) database including the existing pgstac database. This is controlled by the `POSTGRES_*` environment variables in the application runtime.\n", "\n", "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.\n", "\n", @@ -86,9 +86,9 @@ "id": "1961c2fd-9e02-47ef-a63e-1832b9ce0546", "metadata": {}, "source": [ - "### 5.2.2 /collections/{collection_id/queryables\n", + "### 5.2.2 Queryables\n", "\n", - "The `/queryables` endpoint returns a list of fields that can be used to filter features in requests for a collection:" + "The `/collections/{collection_id/queryables` endpoint returns a list of fields that can be used to filter features in requests for a collection:" ] }, { @@ -114,7 +114,7 @@ "id": "ae165899-69b5-403f-b15f-116e42bd1cd1", "metadata": {}, "source": [ - "### 5.2.2 /collections/{collection_id}/items\n", + "### 5.2.3 Items\n", "\n", "The `/items` endpoint for a collection can be used to retrieve paginated lists of features in a number of formats:\n", " - GeoJSON\n", @@ -174,6 +174,8 @@ "id": "a17c0497-511a-4b7e-9b92-84b0e23dd24b", "metadata": {}, "source": [ + "#### 5.2.3.1 Filter Items\n", + "\n", "You can apply a filter using the fields returned in the `/queryables` endpoint for a collection:" ] }, @@ -237,6 +239,8 @@ "id": "bdb2a8c2-94f4-4064-9cc9-1275fe22c053", "metadata": {}, "source": [ + "#### 5.2.3.2 As a Map\n", + "\n", "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!" ] }, @@ -300,7 +304,7 @@ "\n", "\n", "\n", - "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 becomes impracticalimpossible 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." + "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." ] }, { @@ -308,9 +312,9 @@ "id": "5c1595fd-48db-4bc3-bfb5-774025e001bf", "metadata": {}, "source": [ - "### 5.3.1 /collections/{collection_id/tiles/{tileMatrixSetId}/tilejson.json\n", + "### 5.3.1 Tilejson\n", "\n", - "The `tilejson` endpoint 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." + "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." ] }, { @@ -357,9 +361,9 @@ "id": "8aa43dd5-0e0b-4154-85b8-c582eea80678", "metadata": {}, "source": [ - "### 5.3.1 /collections/{collection_id/tiles/{tileMatrixSetId}/viewer\n", + "### 5.3.2 Map Viewer\n", "\n", - "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. It 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." + "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." ] }, { @@ -426,9 +430,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.12.11" } }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} From 09528f84907daa97530d72d46e45425b063cab0a Mon Sep 17 00:00:00 2001 From: Alex Mandel Date: Fri, 31 Oct 2025 14:44:36 +0000 Subject: [PATCH 07/10] fix: revert maap pgstac url --- docs/04-titiler_pgstac.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/04-titiler_pgstac.ipynb b/docs/04-titiler_pgstac.ipynb index 41eb70f..7dc17f0 100644 --- a/docs/04-titiler_pgstac.ipynb +++ b/docs/04-titiler_pgstac.ipynb @@ -543,7 +543,7 @@ "outputs": [], "source": [ "map_request = httpx.get(\n", - " \"https://titiler-pgstac.maap-project.org/collections/glad-global-forest-change-1.11/WebMercatorQuad/map.html\",\n", + " f\"{titiler_pgstac_endpoint}/collections/glad-global-forest-change-1.11/WebMercatorQuad/map\",\n", " params={\n", " \"assets\": \"lossyear\",\n", " \"colormap\": json.dumps({i: rgb for i, rgb in colormap.items()}),\n", From 089fe0507156074379e32f9a0a42891be26c207c Mon Sep 17 00:00:00 2001 From: Alex Mandel Date: Fri, 31 Oct 2025 15:56:03 +0000 Subject: [PATCH 08/10] feat: temporal extent matches text now --- docs/02-database.ipynb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/02-database.ipynb b/docs/02-database.ipynb index 9c0179a..33ecf7f 100644 --- a/docs/02-database.ipynb +++ b/docs/02-database.ipynb @@ -131,6 +131,7 @@ "\n", "import pystac_client\n", "from pystac import Collection, Extent, SpatialExtent, TemporalExtent\n", + "from pystac.utils import str_to_datetime\n", "from pypgstac.db import PgstacDB\n", "from pypgstac.load import Loader, Methods\n", "from shapely.geometry import Point\n", @@ -141,7 +142,7 @@ "bbox = Point(lon_input.value, lat_input.value).buffer(2).bounds\n", "\n", "today = datetime.today()\n", - "temporal_extent = [today - timedelta(days=60), today]\n", + "temporal_extent = [str_to_datetime('2025-01-01T00:00:00Z'), str_to_datetime('2025-04-18T00:00:00Z')]\n", "\n", "my_collection = Collection(\n", " id=collection_id,\n", From 145938f9c6b34c3d62a7e674f0a24f99feccc4c6 Mon Sep 17 00:00:00 2001 From: Alex Mandel Date: Fri, 31 Oct 2025 16:07:20 +0000 Subject: [PATCH 09/10] chore: ruff format --- docs/02-database.ipynb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/02-database.ipynb b/docs/02-database.ipynb index 33ecf7f..387501f 100644 --- a/docs/02-database.ipynb +++ b/docs/02-database.ipynb @@ -142,7 +142,10 @@ "bbox = Point(lon_input.value, lat_input.value).buffer(2).bounds\n", "\n", "today = datetime.today()\n", - "temporal_extent = [str_to_datetime('2025-01-01T00:00:00Z'), str_to_datetime('2025-04-18T00:00:00Z')]\n", + "temporal_extent = [\n", + " str_to_datetime(\"2025-01-01T00:00:00Z\"),\n", + " str_to_datetime(\"2025-04-18T00:00:00Z\"),\n", + "]\n", "\n", "my_collection = Collection(\n", " id=collection_id,\n", From 3c2e4fc9ba0340357136ce03cd066c040c2392c6 Mon Sep 17 00:00:00 2001 From: Alex Mandel Date: Fri, 31 Oct 2025 18:10:47 +0000 Subject: [PATCH 10/10] fix: remove datetime import, no longer needed --- docs/02-database.ipynb | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/02-database.ipynb b/docs/02-database.ipynb index 387501f..6f22ec1 100644 --- a/docs/02-database.ipynb +++ b/docs/02-database.ipynb @@ -127,8 +127,6 @@ "metadata": {}, "outputs": [], "source": [ - "from datetime import datetime, timedelta\n", - "\n", "import pystac_client\n", "from pystac import Collection, Extent, SpatialExtent, TemporalExtent\n", "from pystac.utils import str_to_datetime\n", @@ -141,7 +139,6 @@ "collection_id = f\"{username_input.value}-sentinel-2-c1-l2a\"\n", "bbox = Point(lon_input.value, lat_input.value).buffer(2).bounds\n", "\n", - "today = datetime.today()\n", "temporal_extent = [\n", " str_to_datetime(\"2025-01-01T00:00:00Z\"),\n", " str_to_datetime(\"2025-04-18T00:00:00Z\"),\n",