From c56626dfdff4b0cb5eda9d279417826634e89eb9 Mon Sep 17 00:00:00 2001 From: Felix Delattre Date: Thu, 2 Jul 2026 16:07:18 +0200 Subject: [PATCH] chore: update local stack services --- docker-compose.yml | 26 +++++++++++----------- docs/02-database.ipynb | 16 ++++++++++--- docs/03-stac_fastapi_pgstac.ipynb | 9 ++++---- docs/04-titiler_pgstac.ipynb | 37 ++++++++++++++++++------------- docs/05-tipg.ipynb | 21 +++++++++++------- 5 files changed, 66 insertions(+), 43 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4884cc4..0cb9980 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: database: - image: ghcr.io/stac-utils/pgstac:v0.9.8 + image: ghcr.io/stac-utils/pgstac:v0.9.10 environment: - POSTGRES_USER=username - POSTGRES_PASSWORD=password @@ -56,11 +56,11 @@ services: interval: 10s timeout: 5s retries: 3 - start_period: 10s + start_period: 120s stac-fastapi: - image: ghcr.io/stac-utils/stac-fastapi-pgstac:6.0.2 + image: ghcr.io/stac-utils/stac-fastapi-pgstac:6.2.2 ports: - 8081:8081 environment: @@ -80,21 +80,19 @@ services: condition: service_healthy command: bash -c "uvicorn stac_fastapi.pgstac.app:app --host 0.0.0.0 --port 8081" - volumes: - - ./dockerfiles/scripts:/tmp/scripts titiler-pgstac: platform: linux/amd64 - image: ghcr.io/stac-utils/titiler-pgstac:1.9.0 + image: ghcr.io/stac-utils/titiler-pgstac:3.0.0 ports: - 8082:8082 environment: # Postgres connection - - POSTGRES_USER=username - - POSTGRES_PASS=password - - POSTGRES_DBNAME=postgis - - POSTGRES_HOST=database - - POSTGRES_PORT=5432 + - PGUSER=username + - PGPASSWORD=password + - PGDATABASE=postgis + - PGHOST=database + - PGPORT=5432 - DB_MIN_CONN_SIZE=1 - DB_MAX_CONN_SIZE=10 # - DB_MAX_QUERIES=10 @@ -112,9 +110,11 @@ services: # TiTiler Config - MOSAIC_CONCURRENCY=1 - TITILER_PGSTAC_API_ENABLE_EXTERNAL_DATASET_ENDPOINTS=True - # AWS S3 endpoint config + # AWS S3 endpoint config — the workshop's s3:// assets (glad collection) + # are in public buckets; read unsigned unless real credentials are provided - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + - AWS_NO_SIGN_REQUEST=${AWS_NO_SIGN_REQUEST:-YES} depends_on: database: condition: service_started @@ -124,7 +124,7 @@ services: bash -c "uvicorn titiler.pgstac.main:app --host 0.0.0.0 --port 8082" tipg: - image: ghcr.io/developmentseed/tipg:1.1.2 + image: ghcr.io/developmentseed/tipg:1.4.0 ports: - 8083:8083 environment: diff --git a/docs/02-database.ipynb b/docs/02-database.ipynb index 6f22ec1..0b82a2f 100644 --- a/docs/02-database.ipynb +++ b/docs/02-database.ipynb @@ -325,10 +325,20 @@ "source": [ "from IPython.display import IFrame\n", "\n", - "stac_api_endpoint = os.getenv(\"STAC_API_ENDPOINT\").replace(\"stac-fastapi\", \"localhost\")\n", + "# Use the stack's own STAC Browser when deployed (its catalog is already this\n", + "# STAC API); otherwise fall back to the public STAC Browser in external mode.\n", + "stac_browser_endpoint = os.getenv(\"STAC_BROWSER_ENDPOINT\")\n", + "\n", + "if stac_browser_endpoint:\n", + " browser_url = f\"{stac_browser_endpoint}/#/collections/{my_collection.id}\"\n", + "else:\n", + " browser_stac_url = os.getenv(\"STAC_API_ENDPOINT\").replace(\n", + " \"stac-auth-proxy:8000\", \"localhost:8084\"\n", + " )\n", + " browser_url = f\"https://radiantearth.github.io/stac-browser/#/external/{browser_stac_url}/collections/{my_collection.id}\"\n", "\n", "IFrame(\n", - " f\"https://radiantearth.github.io/stac-browser/#/external/{stac_api_endpoint}/collections/{my_collection.id}\",\n", + " browser_url,\n", " 1200,\n", " 800,\n", ")" @@ -467,7 +477,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.11" + "version": "3.12.13" } }, "nbformat": 4, diff --git a/docs/03-stac_fastapi_pgstac.ipynb b/docs/03-stac_fastapi_pgstac.ipynb index 7880724..53eb2b7 100644 --- a/docs/03-stac_fastapi_pgstac.ipynb +++ b/docs/03-stac_fastapi_pgstac.ipynb @@ -14,7 +14,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "id": "c8ec4c5b-1156-4608-8ab9-b2cdcbcc47e1", "metadata": {}, @@ -49,7 +48,7 @@ "### 3.1.2 Authentication\n", "stac-fastapi-pgstac does not contain any authentication mechanism out-of-the-box, meaning your STAC API will be accessible to anyone if it is deployed to a public web address. If you want to make your STAC API accessible only with a username/password or token, check out the [FastAPI docs](https://fastapi.tiangolo.com/tutorial/security) for examples of how to add them to the application in a custom runtime.\n", "\n", - "There is a new project called [stac-auth-proxy](https://github.com/developmentseed/stac-auth-proxy) that can provide fine-grained access controls to a STAC API by adding a proxy layer between users and the actual STAC API.\n", + "There is a new project called [stac-auth-proxy](https://github.com/developmentseed/stac-auth-proxy) that can provide fine-grained access controls to a STAC API by adding a proxy layer between users and the actual STAC API. In this workshop stack, the STAC API is exposed through stac-auth-proxy at `http://localhost:8084`.\n", "\n", "### 3.1.3 STAC API interface\n", "Once your STAC API is up and running, its capabilities will be described in the `/conformance` endpoint response:" @@ -196,7 +195,9 @@ "source": [ "from IPython.display import IFrame\n", "\n", - "local_stac_api_endpoint = stac_api_endpoint.replace(\"stac-fastapi\", \"localhost\")\n", + "local_stac_api_endpoint = os.getenv(\n", + " \"STAC_API_BROWSER_URL\"\n", + ") or stac_api_endpoint.replace(\"stac-auth-proxy:8000\", \"localhost:8084\")\n", "api_docs = (\n", " f\"{local_stac_api_endpoint}/api.html#/default/Get_Collections_collections_get\"\n", ")\n", @@ -544,7 +545,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.11" + "version": "3.12.13" } }, "nbformat": 4, diff --git a/docs/04-titiler_pgstac.ipynb b/docs/04-titiler_pgstac.ipynb index 95a9bb9..3cd9921 100644 --- a/docs/04-titiler_pgstac.ipynb +++ b/docs/04-titiler_pgstac.ipynb @@ -70,10 +70,13 @@ "\n", "from IPython.display import IFrame, Image\n", "\n", - "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", + "titiler_pgstac_endpoint = os.getenv(\"TITILER_PGSTAC_API_ENDPOINT\")\n", + "# browser-facing URL for the IFrame/map cells (the user's browser can't reach\n", + "# the server-side endpoint above when running on Kubernetes or docker-compose)\n", + "titiler_browser_endpoint = os.getenv(\n", + " \"TITILER_BROWSER_URL\"\n", + ") or titiler_pgstac_endpoint.replace(\"titiler-pgstac\", \"localhost\")\n", + "api_docs = f\"{titiler_browser_endpoint}/api.html\"\n", "print(api_docs)\n", "\n", "IFrame(\n", @@ -152,7 +155,7 @@ " - Leaflet\n", " - Mapbox\n", "\n", - "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", + "For now you can take a shortcut to view the map directly in this notebook using the `/map.html` 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. 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", @@ -167,7 +170,7 @@ "outputs": [], "source": [ "IFrame(\n", - " f\"{titiler_pgstac_endpoint}/collections/{collection_id}/WebMercatorQuad/map?{urlencode(params, doseq=True)}\",\n", + " f\"{titiler_browser_endpoint}/collections/{collection_id}/WebMercatorQuad/map.html?{urlencode(params, doseq=True)}\",\n", " 1200,\n", " 800,\n", ")" @@ -180,8 +183,8 @@ "source": [ "### How does it work?\n", "\n", - "- titiler-pgstac is running as a Lambda (serverless) function in AWS that started up when you made the request for the `/map` endpoint.\n", - "- The `/map` endpoint returns an HTML file that is pre-populated with some map code that includes the layer that you specified with the request parameters\n", + "- titiler-pgstac is running as a Lambda (serverless) function in AWS that started up when you made the request for the `/map.html` endpoint.\n", + "- The `/map.html` endpoint returns an HTML file that is pre-populated with some map code that includes the layer that you specified with the request parameters\n", "- As you browse the map, the map is sending XYZ tile requests to titiler-pgstac function in AWS\n", "- Each request contains the information titiler-pgstac needs to search for items in the pgstac database and how to construct an image from the items' assets\n", " - `collection_id`: by specifying the collection ID in the request path you are instructing titiler-pgstac to search for items from a specific STAC collection. Unless otherwise specified, pgstac will retrieve the STAC items in descending order by datetime and it will stop returning results when a tile's geometry is completely covered.\n", @@ -247,7 +250,7 @@ "source": [ "The response comes back with an `id` which uniquely identifies this search and a handful of useful links associated with our newly registered search.\n", "\n", - "Now you can browse the results of this search with the `/map` endpoint like you did earlier." + "Now you can browse the results of this search with the `/map.html` endpoint like you did earlier." ] }, { @@ -267,7 +270,7 @@ ")\n", "\n", "IFrame(\n", - " f\"{titiler_pgstac_endpoint}/searches/{search_id}/WebMercatorQuad/map?{urlencode(params, doseq=True)}\",\n", + " f\"{titiler_browser_endpoint}/searches/{search_id}/WebMercatorQuad/map.html?{urlencode(params, doseq=True)}\",\n", " 1200,\n", " 800,\n", ")" @@ -303,6 +306,8 @@ "outputs": [], "source": [ "params = (\n", + " (\"assets\", \"nir\"),\n", + " (\"assets\", \"red\"),\n", " (\"asset_as_band\", \"True\"),\n", " (\"expression\", \"(nir - red) / (nir + red)\"),\n", " (\"colormap_name\", \"viridis\"),\n", @@ -310,7 +315,7 @@ ")\n", "\n", "IFrame(\n", - " f\"{titiler_pgstac_endpoint}/searches/{search_id}/WebMercatorQuad/map?{urlencode(params, doseq=True)}\",\n", + " f\"{titiler_browser_endpoint}/searches/{search_id}/WebMercatorQuad/map.html?{urlencode(params, doseq=True)}\",\n", " 1200,\n", " 800,\n", ")" @@ -366,6 +371,7 @@ " params={\n", " \"url\": cog_href,\n", " },\n", + " timeout=None,\n", ")\n", "\n", "print(json.dumps(cog_info_request.json(), indent=2))" @@ -394,6 +400,7 @@ " \"url\": cog_href,\n", " \"maxsize\": 2048,\n", " },\n", + " timeout=None,\n", ")\n", "\n", "Image(preview_request.content)" @@ -506,7 +513,7 @@ "outputs": [], "source": [ "map_request = httpx.get(\n", - " f\"{titiler_pgstac_endpoint}/external/WebMercatorQuad/map\",\n", + " f\"{titiler_pgstac_endpoint}/external/WebMercatorQuad/map.html\",\n", " params={\n", " \"url\": cog_href,\n", " \"maxsize\": 2048,\n", @@ -517,7 +524,7 @@ "\n", "\n", "IFrame(\n", - " map_request.url,\n", + " str(map_request.url).replace(titiler_pgstac_endpoint, titiler_browser_endpoint),\n", " 1200,\n", " 800,\n", ")" @@ -541,7 +548,7 @@ "outputs": [], "source": [ "map_request = httpx.get(\n", - " f\"{titiler_pgstac_endpoint}/collections/glad-global-forest-change-1.11/WebMercatorQuad/map\",\n", + " f\"{titiler_pgstac_endpoint}/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", @@ -551,7 +558,7 @@ "\n", "\n", "IFrame(\n", - " map_request.url,\n", + " str(map_request.url).replace(titiler_pgstac_endpoint, titiler_browser_endpoint),\n", " 1200,\n", " 800,\n", ")" diff --git a/docs/05-tipg.ipynb b/docs/05-tipg.ipynb index 800f0e8..b3b9b90 100644 --- a/docs/05-tipg.ipynb +++ b/docs/05-tipg.ipynb @@ -60,6 +60,11 @@ "import httpx\n", "\n", "tipg_endpoint = os.getenv(\"TIPG_API_ENDPOINT\")\n", + "# browser-facing URL for the IFrame/viewer cells (the user's browser can't\n", + "# reach the server-side endpoint above when running on Kubernetes)\n", + "tipg_browser_endpoint = os.getenv(\"TIPG_BROWSER_URL\") or tipg_endpoint.replace(\n", + " \"tipg\", \"localhost\"\n", + ")\n", "\n", "collections_request = httpx.get(f\"{tipg_endpoint}/collections\")\n", "\n", @@ -76,9 +81,9 @@ "- `/collections/{collection_id}/items`: where features can be accessed\n", "- `/collections/{collection_id}/tiles`: list of tile matrix set IDs that are available for tile requests\n", "- `/collections/{collection_id}/tiles/{tileMatrixSetId}`: returns a tilejson for a vector tile layer\n", - "- `/collections/{collection_id}/tiles/{tileMatrixSetId}/viewer`: interactive map of the collection\n", + "- `/collections/{collection_id}/tiles/{tileMatrixSetId}/map.html`: interactive map of the collection\n", "\n", - "The `/items`, `/tiles/{tileMatrixSetId}`, and `/tiles/{tileMatrixSetId}/viewer` 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." + "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." ] }, { @@ -261,7 +266,7 @@ " },\n", ")\n", "\n", - "local_url = str(bbox_filtered_request.url).replace(\"tipg\", \"localhost\")\n", + "local_url = str(bbox_filtered_request.url).replace(tipg_endpoint, tipg_browser_endpoint)\n", "\n", "IFrame(\n", " local_url,\n", @@ -285,7 +290,7 @@ "metadata": {}, "outputs": [], "source": [ - "local_tipg_endpoint = tipg_endpoint.replace(\"tipg\", \"localhost\")\n", + "local_tipg_endpoint = tipg_browser_endpoint\n", "IFrame(\n", " f\"{local_tipg_endpoint}/api.html#OGC Features API/items_collections__collectionId__items_get\",\n", " width=1200,\n", @@ -374,11 +379,11 @@ "outputs": [], "source": [ "viewer_request = httpx.get(\n", - " f\"{tipg_endpoint}/collections/{collection_id}/tiles/WebMercatorQuad/viewer\",\n", + " f\"{tipg_endpoint}/collections/{collection_id}/tiles/WebMercatorQuad/map.html\",\n", ")\n", "\n", "IFrame(\n", - " str(viewer_request.url).replace(\"tipg\", \"localhost\"),\n", + " str(viewer_request.url).replace(tipg_endpoint, tipg_browser_endpoint),\n", " width=1200,\n", " height=800,\n", ")" @@ -400,14 +405,14 @@ "outputs": [], "source": [ "filtered_viewer_request = httpx.get(\n", - " f\"{tipg_endpoint}/collections/{collection_id}/tiles/WebMercatorQuad/viewer\",\n", + " f\"{tipg_endpoint}/collections/{collection_id}/tiles/WebMercatorQuad/map.html\",\n", " params={\n", " \"na_l2name\": \"MEDITERRANEAN CALIFORNIA\",\n", " },\n", ")\n", "\n", "IFrame(\n", - " str(filtered_viewer_request.url).replace(\"tipg\", \"localhost\"),\n", + " str(filtered_viewer_request.url).replace(tipg_endpoint, tipg_browser_endpoint),\n", " width=1200,\n", " height=800,\n", ")"