diff --git a/notebooks/GeoCatalog_Tutorial.ipynb b/notebooks/GeoCatalog_Tutorial.ipynb index 9aa5f85..5cab142 100644 --- a/notebooks/GeoCatalog_Tutorial.ipynb +++ b/notebooks/GeoCatalog_Tutorial.ipynb @@ -5,15 +5,15 @@ "id": "890c12f2", "metadata": {}, "source": [ - "# Tutorial: Using the Microsoft Planetary Computer Pro APIs to ingest and visualize data\n", + "# Tutorial: Using the Microsoft Planetary Computer Pro SDK to ingest and visualize data\n", "\n", - "STAC (SpatioTemporal Asset Catalog) Collections are used within a GeoCatalog to index and store related spatiotemporal assets. In this end-to-end tutorial, you'll create a new STAC collection, ingest Sentinel-2 images into the collection, and query those images via GeoCatalog's APIs.\n", + "STAC (SpatioTemporal Asset Catalog) Collections are used within a GeoCatalog to index and store related spatiotemporal assets. In this end-to-end tutorial, you'll use the **[azure-planetarycomputer SDK](https://learn.microsoft.com/python/api/overview/azure/planetarycomputer-readme)** to create a new STAC collection, ingest Sentinel-2 images into the collection, and query those images via GeoCatalog's APIs.\n", "\n", "In this tutorial, you:\n", - "* Will create your very own STAC collection within a Planetary Computer Pro GeoCatalog\n", + "* Will create your very own STAC collection within a Planetary Computer Pro GeoCatalog using the Python SDK\n", "* Ingest satellite imagery into that collection from the European Space Agency\n", "* Configure the collection so the imagery in the collection can be visualized in the Planetary Computer Pro's web interface\n", - "* Query data from within the STAC collection using the Planetary Computer Pro's STAC API\n", + "* Query data from within the STAC collection using the SDK's STAC API client\n", "\n", "## Prerequisites\n", "\n", @@ -21,7 +21,7 @@ "\n", "* Python 3.10 or later\n", "* Azure CLI is installed, and you have run az login to log into your Azure account\n", - "* The necessary requirements listed in the Tutorial Options section are installed\n", + "* The [azure-planetarycomputer](https://pypi.org/project/azure-planetarycomputer/) SDK package and other necessary requirements listed in the Tutorial Options section are installed\n", "\n", "## Open a Jupyter notebook in Azure Machine Learning or VS Code\n", "\n", @@ -46,7 +46,9 @@ "source": [ "## Select tutorial options\n", "\n", - "Before running this tutorial, you need contributor access to an existing GeoCatalog instance. Enter the url of your GeoCatalog instance in the geocatalog_url variable. In this tutorial, you'll create a collection for Sentinel-2 imagery provided by the European Space Agency (ESA) that is currently stored in Microsoft's Planetary Computer Data Catalog.\n" + "Before running this tutorial, you need contributor access to an existing GeoCatalog instance. Enter the url of your GeoCatalog instance in the geocatalog_url variable. This URL will be used to initialize the PlanetaryComputerProClient from the azure-planetarycomputer SDK.\n", + "\n", + "In this tutorial, you'll create a collection for Sentinel-2 imagery provided by the European Space Agency (ESA) that is currently stored in Microsoft's Planetary Computer Data Catalog." ] }, { @@ -62,8 +64,6 @@ ")\n", "geocatalog_url = geocatalog_url.rstrip(\"/\") # Remove trailing slash if present\n", "\n", - "api_version = \"2025-04-30-preview\"\n", - "\n", "# User selections for demo\n", "\n", "# Collection within the Planetary Computer\n", @@ -79,6 +79,14 @@ "param_max_items = 6" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "aba4488e", + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "id": "d6e17578", @@ -86,7 +94,7 @@ "source": [ "### Import the required packages\n", "\n", - "Before you can create a STAC collection you need to import a few python packages and define helper functions to retrieve the required access token." + "Before you can create a STAC collection you need to import a few python packages including the **[azure-planetarycomputer SDK](https://learn.microsoft.com/python/api/overview/azure/planetarycomputer-readme)**. The SDK simplifies interaction with the Planetary Computer Pro APIs by providing strongly-typed Python classes and methods." ] }, { @@ -96,7 +104,7 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install pystac-client azure-identity requests pillow" + "!pip install pystac-client azure-identity azure-planetarycomputer requests pillow" ] }, { @@ -118,23 +126,25 @@ "from typing import Any, Optional, Dict\n", "\n", "import requests\n", - "from azure.identity import AzureCliCredential\n", + "from azure.core.exceptions import ResourceNotFoundError, HttpResponseError\n", + "from azure.identity import DefaultAzureCredential\n", + "from azure.planetarycomputer import PlanetaryComputerProClient\n", + "from azure.planetarycomputer.models import (\n", + " IngestionSourceType,\n", + " SharedAccessSignatureTokenConnection,\n", + " SharedAccessSignatureTokenIngestionSource,\n", + " StacMosaic,\n", + " StacSearchParameters,\n", + " StacAssetUrlSigningMode,\n", + ")\n", "from IPython.display import Markdown as md\n", "from IPython.display import clear_output\n", "from PIL import Image\n", "from pystac_client import Client\n", "\n", - "# Function to get a bearer token for the Planetary Computer Pro API\n", - "MPC_APP_ID = \"https://geocatalog.spatio.azure.com\"\n", - "\n", - "_access_token = None\n", - "def getBearerToken():\n", - " global _access_token\n", - " if not _access_token or datetime.fromtimestamp(_access_token.expires_on) < datetime.now() + timedelta(minutes=5):\n", - " credential = AzureCliCredential()\n", - " _access_token = credential.get_token(f\"{MPC_APP_ID}/.default\")\n", - "\n", - " return {\"Authorization\": f\"Bearer {_access_token.token}\"}\n", + "# Initialize the Planetary Computer Pro client\n", + "credential = DefaultAzureCredential()\n", + "pc_client = PlanetaryComputerProClient(endpoint=geocatalog_url, credential=credential)\n", "\n", "# Method to print error messages when checking response status\n", "def raise_for_status(r: requests.Response) -> None:\n", @@ -157,7 +167,7 @@ "## Create a STAC collection\n", "\n", "### Define a STAC Collection JSON\n", - "Next, you define a STAC collection as a JSON item. For this tutorial, use an existing STAC collection JSON for the Sentinel-2-l2a collection within Microsoft's Planetary Computer. Your collection is assigned a random ID and title so as not to conflict with other existing collections.\n" + "Next, you define a STAC collection as a JSON item. For this tutorial, use an existing STAC collection JSON for the Sentinel-2-l2a collection within Microsoft's Planetary Computer. Your collection is assigned a random ID and title so as not to conflict with other existing collections. The SDK will handle the API calls to create this collection in your GeoCatalog." ] }, { @@ -224,21 +234,10 @@ "metadata": {}, "outputs": [], "source": [ - "# Create a STAC collection by posting to the STAC collections API\n", - "\n", - "collections_endpoint = f\"{geocatalog_url}/stac/collections\"\n", - "\n", - "response = requests.post(\n", - " collections_endpoint,\n", - " json=stac_collection,\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version}\n", - ")\n", - "\n", - "if response.status_code==201:\n", - " print(\"STAC Collection created named:\",stac_collection['title'])\n", - "else:\n", - " raise_for_status(response)" + "# Create a STAC collection using the SDK\n", + "collection_create_operation = pc_client.stac.begin_create_collection(body=stac_collection, polling=True)\n", + "collection_create_operation.result()\n", + "print(\"STAC Collection created named:\", stac_collection[\"title\"])" ] }, { @@ -275,7 +274,7 @@ "source": [ "### Add thumbnail to your Planetary Computer Pro GeoCatalog\n", "\n", - "After reading the thumbnail, you can add it to our collection in by posting it to your GeoCatalogs's collection assets API endpoint along with the required asset json.\n" + "After reading the thumbnail, you can add it to our collection using the SDK's `create_collection_asset` method, which handles the upload automatically." ] }, { @@ -285,31 +284,27 @@ "metadata": {}, "outputs": [], "source": [ - "# Define the GeoCatalog collections API endpoint\n", - "collection_assets_endpoint = f\"{geocatalog_url}/stac/collections/{collection_id}/assets\"\n", + "# Add thumbnail to collection using the SDK\n", "\n", - "# Read the example thumbnail from this collection from the Planetary Computer\n", - "thumbnail = {\"file\": (\"lulc.png\", thumbnail_response.content)}\n", - "\n", - "# Define the STAC collection asset type - thumbnail in this case\n", - "asset = {\n", - " \"data\": '{\"key\": \"thumbnail\", \"href\":\"\", \"type\": \"image/png\", '\n", - " '\"roles\": [\"test_asset\"], \"title\": \"test_asset\"}'\n", + "# Define thumbnail asset metadata\n", + "asset_data = {\n", + " \"key\": \"thumbnail\",\n", + " \"href\": \"\",\n", + " \"type\": \"image/png\",\n", + " \"roles\": [\"thumbnail\"],\n", + " \"title\": \"Thumbnail\"\n", "}\n", "\n", - "# Post the thumbnail to the GeoCatalog collections asset endpoint\n", - "response = requests.post(\n", - " collection_assets_endpoint,\n", - " data=asset,\n", - " files=thumbnail,\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version}\n", + "# Prepare thumbnail file for upload\n", + "thumbnail_file = (\"thumbnail.png\", thumbnail_response.content)\n", + "\n", + "# Post the thumbnail to the GeoCatalog collections asset endpoint using SDK\n", + "pc_client.stac.create_collection_asset(\n", + " collection_id=collection_id,\n", + " body={\"data\": asset_data, \"file\": thumbnail_file}\n", ")\n", "\n", - "if response.status_code==201:\n", - " print(\"STAC Collection thumbnail updated for:\",stac_collection['title'])\n", - "else:\n", - " raise_for_status(response)" + "print(\"STAC Collection thumbnail updated for:\", stac_collection['title'])\n" ] }, { @@ -319,7 +314,7 @@ "source": [ "### Read new collection from within your Planetary Computer Pro GeoCatalog\n", "\n", - "Refresh your browser and you should be able to see the thumbnail. You can also retrieve the collection JSON programmatically by making the following call to the collections endpoint:\n" + "Refresh your browser and you should be able to see the thumbnail. You can also retrieve the collection JSON programmatically using the SDK's `get_collection` method:" ] }, { @@ -331,22 +326,11 @@ }, "outputs": [], "source": [ - "# Request the collection JSON from your GeoCatalog\n", - "collection_endpoint = f\"{geocatalog_url}/stac/collections/{stac_collection['id']}\"\n", + "# Request the collection JSON from your GeoCatalog using the SDK\n", "\n", - "response = requests.get(\n", - " collection_endpoint,\n", - " json={'collection_id':stac_collection['id']},\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version}\n", - ")\n", - "\n", - "if response.status_code==200:\n", - " print(\"STAC Collection successfully read:\",stac_collection['title'])\n", - "else:\n", - " raise_for_status(response)\n", - "\n", - "response.json()" + "collection = pc_client.stac.get_collection(collection_id=collection_id)\n", + "print(\"STAC Collection successfully read:\", collection.title)\n", + "collection.as_dict()" ] }, { @@ -369,73 +353,12 @@ "source": [ "## Ingest STAC items & assets\n", "\n", - "After creating this collection, you're ready to ingest new STAC items into your STAC collection using your GeoCatalog's Items API! Accomplish this process by:\n", + "After creating this collection, you're ready to ingest new STAC items into your STAC collection using the SDK's ingestion management methods! Accomplish this process by:\n", "\n", "1. Obtaining a SAS token from Microsoft's Planetary Computer\n", - "2. Register that token as an Ingestion Source within GeoCatalog\n", - "3. Post STAC Items from that collection to GeoCatalog's Item API\n", - "4. Verify the Items were ingested successfully\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3d5029a9", - "metadata": {}, - "outputs": [], - "source": [ - "ingestion_sources_endpoint = f\"{geocatalog_url}/inma/ingestion-sources\"\n", - "ingestion_source_endpoint = lambda id: f\"{geocatalog_url}/inma/ingestion-sources/{id}\"\n", - "\n", - "\n", - "def find_ingestion_source(container_url: str) -> Optional[Dict[str, Any]]:\n", - "\n", - " response = requests.get(\n", - " ingestion_sources_endpoint,\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version},\n", - " )\n", - "\n", - " for source in response.json()[\"value\"]:\n", - " ingestion_source_id = source[\"id\"]\n", - "\n", - " response = requests.get(\n", - " ingestion_source_endpoint(ingestion_source_id),\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version},\n", - " )\n", - " raise_for_status(response)\n", - "\n", - " response = response.json()\n", - "\n", - " if response[\"connectionInfo\"][\"containerUrl\"] == container_url:\n", - " return response\n", - "\n", - "\n", - "def create_ingestion_source(container_url: str, sas_token: str):\n", - " response = requests.post(\n", - " ingestion_sources_endpoint,\n", - " json={\n", - " \"kind\": \"SasToken\",\n", - " \"connectionInfo\": {\n", - " \"containerUrl\": container_url,\n", - " \"sasToken\": sas_token,\n", - " },\n", - " },\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version},\n", - " )\n", - " raise_for_status(response)\n", - "\n", - "\n", - "def remove_ingestion_source(ingestion_source_id: str):\n", - " response = requests.delete(\n", - " ingestion_source_endpoint(ingestion_source_id),\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version},\n", - " )\n", - " raise_for_status(response)" + "2. Register that token as an Ingestion Source within GeoCatalog using the SDK\n", + "3. Ingest STAC Items from that collection using the SDK's item creation methods\n", + "4. Verify the Items were ingested successfully" ] }, { @@ -514,7 +437,7 @@ "### Register an ingestion source\n", "Before you can ingest these STAC items and their related assets (images) into a GeoCatalog collection you need to determine if you need to register a new ingestion source. Ingestion Sources are used by GeoCatalog to track which storage locations (Azure Blob Storage containers) it has access to. \n", "\n", - "Registering an ingestion source is accomplished by providing GeoCatalog the location of the storage container and a SAS token with read permissions to access the container. If STAC items or their related assets are located in a storage container your GeoCatalog hasn't been given access to the ingest will fail.\n", + "Registering an ingestion source is accomplished using the SDK by providing the location of the storage container and a SAS token with read permissions to access the container. If STAC items or their related assets are located in a storage container your GeoCatalog hasn't been given access to, the ingest will fail.\n", "\n", "To start this process, you first request a SAS token from the Planetary Computer that grants us read access to the container where the Sentinel-2 images reside.\n" ] @@ -550,21 +473,36 @@ "metadata": {}, "outputs": [], "source": [ - "existing_ingestion_source: Optional[Dict[str, Any]] = find_ingestion_source(pc_collection_asset_container)\n", - "\n", - "if existing_ingestion_source:\n", - " connection_info = existing_ingestion_source[\"connectionInfo\"]\n", - " expiration = datetime.fromisoformat(connection_info[\"expiration\"].split('.')[0]) # works in all Python 3.X versions\n", - " expiration = expiration.replace(tzinfo=timezone.utc) # set timezone to UTC\n", + "# Find existing ingestion source for this container\n", + "existing_source = None\n", + "for source_summary in pc_client.ingestion.list_sources():\n", + " if source_summary.kind == IngestionSourceType.SHARED_ACCESS_SIGNATURE_TOKEN:\n", + " source = pc_client.ingestion.get_source(source_summary.id)\n", + " if source.connection_info.container_uri == pc_collection_asset_container:\n", + " existing_source = source\n", + " break\n", + "\n", + "# Check if we need to create or recreate the source\n", + "should_create = True\n", + "if existing_source:\n", + " expiration = existing_source.connection_info.expiration.replace(tzinfo=timezone.utc)\n", " if expiration < datetime.now(tz=timezone.utc) + timedelta(minutes=15):\n", - " print(f\"Recreating existing ingestion source for {pc_collection_asset_container}\")\n", - " remove_ingestion_source(existing_ingestion_source[\"id\"])\n", - " create_ingestion_source(pc_collection_asset_container, pc_token[\"token\"])\n", + " print(f\"Deleting expired ingestion source for {pc_collection_asset_container}\")\n", + " pc_client.ingestion.delete_source(id=existing_source.id)\n", " else:\n", - " print(f\"Using existing ingestion source for {pc_collection_asset_container} with expiration {expiration}\")\n", - "else:\n", + " print(f\"Using existing ingestion source with expiration {expiration}\")\n", + " should_create = False\n", + "\n", + "if should_create:\n", " print(f\"Creating ingestion source for {pc_collection_asset_container}\")\n", - " create_ingestion_source(pc_collection_asset_container, pc_token[\"token\"])\n" + " pc_client.ingestion.create_source(\n", + " body=SharedAccessSignatureTokenIngestionSource(\n", + " connection_info=SharedAccessSignatureTokenConnection(\n", + " container_uri=pc_collection_asset_container,\n", + " shared_access_signature_token=pc_token[\"token\"]\n", + " )\n", + " )\n", + " )" ] }, { @@ -572,8 +510,8 @@ "id": "e8a119c0", "metadata": {}, "source": [ - "### Ingest STAC items using GeoCatalog's Items API\n", - "Now that you registered an ingestion source or validated that a source exists you'll ingest the STAC items you found within the Planetary Computer using GeoCatalog's Items API. Accomplish this by posting each item to the Items API which creates a new ingestion operation within GeoCatalog.\n" + "### Ingest STAC items using the SDK\n", + "Now that you registered an ingestion source or validated that a source exists, you'll ingest the STAC items you found within the Planetary Computer using the SDK's `begin_create_item` method. This creates a new ingestion operation within GeoCatalog for each item." ] }, { @@ -585,9 +523,7 @@ "source": [ "# Ingest items\n", "\n", - "items_endpoint = f\"{geocatalog_url}/stac/collections/{collection_id}/items\"\n", - "\n", - "operation_ids = []\n", + "operations = []\n", "\n", "for item in items:\n", "\n", @@ -599,15 +535,15 @@ " del(item_json['assets']['preview'])\n", " del(item_json['assets']['tilejson'])\n", "\n", - " response = requests.post(\n", - " items_endpoint,\n", - " json=item_json,\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version}\n", + " # Use SDK to create item\n", + " operation = pc_client.stac.begin_create_item(\n", + " collection_id=collection_id,\n", + " body=item_json,\n", + " polling=True\n", " )\n", "\n", - " operation_ids.append(response.json()[\"id\"])\n", - " print(f\"Ingesting item {item_json['id']} with operation id {response.json()['id']}\")\n" + " operations.append(operation)\n", + " print(f\"Ingesting item {item_json['id']}\")" ] }, { @@ -615,7 +551,7 @@ "id": "3c7e81d9", "metadata": {}, "source": [ - "Given that Sentinel-2 item ingestion can take a little time, you can run this code to check the status of your ingestion operations using GeoCatalog's Operations API.\n" + "Given that Sentinel-2 item ingestion can take a little time, you can run this code to check the status of your ingestion operations by calling `.status()` on the poller objects returned from `begin_create_item`.\n" ] }, { @@ -626,27 +562,21 @@ "outputs": [], "source": [ "# Check the status of the operations\n", - "operations_endpoint = f\"{geocatalog_url}/inma/operations\"\n", - "# Loop through all the operations ids until the status of each operation ids is \"Finished\"\n", "pending=True\n", "\n", "start = time.time()\n", "\n", "while pending:\n", - " # Count the number of operation ids that are finished vs unfinished\n", + " # Count the number of operations that are finished vs unfinished\n", " num_running = 0\n", " num_finished = 0\n", " num_failed = 0\n", " clear_output(wait=True)\n", - " for operation_id in operation_ids:\n", - " response = requests.get(\n", - " f\"{operations_endpoint}/{operation_id}\",\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version},\n", - " )\n", - " raise_for_status(response)\n", - " status = response.json()[\"status\"]\n", - " print(f\"Operation id {operation_id} status: {status}\")\n", + " for operation in operations:\n", + " status = operation.status()\n", + " print(f\"Operation status: {status}\")\n", + " if status == \"Pending\":\n", + " num_running+=1\n", " if status == \"Running\":\n", " num_running+=1\n", " elif status == \"Failed\":\n", @@ -671,6 +601,23 @@ " time.sleep(5)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "e6463b65", + "metadata": {}, + "outputs": [], + "source": [ + "# Show detailed error messages\n", + "for operation in operations:\n", + " try:\n", + " operation.result()\n", + " except HttpResponseError as e:\n", + " exception_body = json.loads(e.response.body())\n", + " error = exception_body[\"statusHistory\"][-1]\n", + " print(json.dumps(error, indent=2))\n" + ] + }, { "cell_type": "markdown", "id": "08872749", @@ -704,7 +651,7 @@ "id": "83038008", "metadata": {}, "source": [ - "After reading this render options config from the Planetary Computer, you can enable these render options for the collection by posting this config to the render-options endpoint.\n" + "After reading this render options config from the Planetary Computer, you can enable these render options for the collection using the SDK's `create_render_option` method." ] }, { @@ -714,21 +661,17 @@ "metadata": {}, "outputs": [], "source": [ - "# Post render options config to GeoCatalog render-options API\n", - "\n", - "render_config_endpoint = f\"{geocatalog_url}/stac/collections/{collection_id}/configurations/render-options\"\n", + "# Post render options config to GeoCatalog using the SDK\n", "\n", "for render_option in render_json['renderOptions']:\n", "\n", " # Rename render configs such that they can be stored by GeoCatalog\n", - " render_option['id'] = render_option['name'].translate(str.maketrans('', '', string.punctuation)).lower().replace(\" \",\"-\")[:30]\n", - "\n", - " # Post render definition\n", - " response = requests.post(\n", - " render_config_endpoint,\n", - " json=render_option,\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version}\n", + " render_option['id'] = render_option['name'].translate(str.maketrans('', '', string.punctuation)).lower().replace(\" \", \"-\")[:30]\n", + "\n", + " # Post render definition using SDK\n", + " pc_client.stac.create_render_option(\n", + " collection_id=collection_id,\n", + " body=render_option\n", " )" ] }, @@ -749,19 +692,16 @@ "metadata": {}, "outputs": [], "source": [ - "# Post mosaic definition\n", + "# Post mosaic definition using the SDK\n", "\n", - "mosiacs_config_endpoint = f\"{geocatalog_url}/stac/collections/{collection_id}/configurations/mosaics\"\n", - "\n", - "response = requests.post(\n", - " mosiacs_config_endpoint,\n", - " json={\"id\": \"mos1\",\n", - " \"name\": \"Most recent available\",\n", - " \"description\": \"Most recent available imagery in this collection\",\n", - " \"cql\": []\n", - " },\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version}\n", + "pc_client.stac.add_mosaic(\n", + " collection_id=collection_id,\n", + " body=StacMosaic(\n", + " id=\"mos1\",\n", + " name=\"Most recent available\",\n", + " description=\"Most recent available imagery in this collection\",\n", + " cql=[]\n", + " )\n", ")" ] }, @@ -772,17 +712,17 @@ "source": [ "### Open GeoCatalog web interface\n", "\n", - "Congrats! You created a collection, added STAC items and assets, and updated your collection to include the required configuration files so it can be viewed through the Explorer within the GeoCatalog web interface.\n", + "Congrats! You created a collection using the azure-planetarycomputer SDK, added STAC items and assets, and updated your collection to include the required configuration files so it can be viewed through the Explorer within the GeoCatalog web interface.\n", "\n", "**Navigate back to the GeoCatalog Explorer in the web interface to view your collection!**\n", "\n", "## Query collection via STAC API\n", "\n", - "Now that viewed your collection in the GeoCatalog Explorer, you'll walk through how to use GeoCatalog's STAC APIs to search for and retrieve STAC items and assets for further analysis.\n", + "Now that you've viewed your collection in the GeoCatalog Explorer, you'll walk through how to use the SDK's STAC search methods to search for and retrieve STAC items and assets for further analysis.\n", "\n", - "This process starts by posting a search to your GeoCatalog's STAC API. Specifically, you'll search for imagery within your collection that falls within the original bounding box you used to extract imagery from the Planetary Computer.\n", + "This process uses the SDK's `search` method to search your GeoCatalog's STAC API. Specifically, you'll search for imagery within your collection that falls within the original bounding box you used to extract imagery from the Planetary Computer.\n", "\n", - "Unsurprisingly this query returns all the STAC items you previously placed within your collection.\n" + "Unsurprisingly this query returns all the STAC items you previously placed within your collection." ] }, { @@ -792,18 +732,17 @@ "metadata": {}, "outputs": [], "source": [ - "stac_search_endpoint = f\"{geocatalog_url}/stac/search\"\n", + "# Search for items using the SDK with signed URLs\n", "\n", - "response = requests.post(\n", - " stac_search_endpoint,\n", - " json={\"collections\":[collection_id],\n", - " \"bbox\":bbox_aoi\n", - " },\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version, \"sign\": \"true\"}\n", + "search_params = StacSearchParameters(\n", + " collections=[collection_id],\n", + " bounding_box=bbox_aoi,\n", + " sign=StacAssetUrlSigningMode.TRUE\n", ")\n", "\n", - "matching_items = response.json()['features']\n", + "search_result = pc_client.stac.search(body=search_params, params={\"sign\": \"true\"})\n", + "\n", + "matching_items = search_result.features\n", "print(len(matching_items))" ] }, @@ -812,7 +751,7 @@ "id": "254149be", "metadata": {}, "source": [ - "In your prior query, you also provided another parameter: **sign:true**. This instructs GeoCatalog to return a signed href (item href + SAS token) which allows you to read the given assets from Azure Blob Storage.\n" + "In your prior query, you also provided another parameter: **sign=True**. This instructs the SDK to return signed hrefs (item href + SAS token) which allows you to read the given assets from Azure Blob Storage." ] }, { @@ -824,8 +763,8 @@ }, "outputs": [], "source": [ - "# Download one of the assets bands, band 09\n", - "asset_href = matching_items[0]['assets']['B09']['href']\n", + "# Download one of the asset bands, band 09\n", + "asset_href = matching_items[0].as_dict()['assets']['B09']['href']\n", "print(asset_href)\n", "\n", "response = requests.get(asset_href)\n", @@ -841,7 +780,7 @@ "## Clean up resources\n", "### Delete items\n", "\n", - "At this point, you have created a GeoCatalog Collection, added items and assets to the collection, and retrieved those items and assets using GeoCatalog's STAC API. For the final phase of this tutorial, you're going to remove these items and delete your collection.\n" + "At this point, you have created a GeoCatalog Collection using the azure-planetarycomputer SDK, added items and assets to the collection, and retrieved those items and assets using the SDK's search methods. For the final phase of this tutorial, you're going to remove these items and delete your collection using the SDK." ] }, { @@ -851,13 +790,13 @@ "metadata": {}, "outputs": [], "source": [ - "# Delete all items\n", + "# Delete all items using the SDK\n", "\n", "for item in matching_items:\n", - " response = requests.delete(\n", - " f\"{items_endpoint}/{item['id']}\",\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version}\n", + " pc_client.stac.begin_delete_item(\n", + " collection_id=collection_id,\n", + " item_id=item.id,\n", + " polling=False\n", " )" ] }, @@ -877,16 +816,15 @@ "outputs": [], "source": [ "# Confirm that all the items have been deleted\n", - "response = requests.post(\n", - " stac_search_endpoint,\n", - " json={\"collections\":[stac_collection['id']],\n", - " \"bbox\": bbox_aoi\n", - " },\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version, \"sign\": \"true\"}\n", + "search_params = StacSearchParameters(\n", + " collections=[collection_id],\n", + " bounding_box=bbox_aoi,\n", + " sign=StacAssetUrlSigningMode.TRUE\n", ")\n", "\n", - "matching_items = response.json()['features']\n", + "search_result = pc_client.stac.search(body=search_params)\n", + "\n", + "matching_items = search_result.features\n", "print(len(matching_items))" ] }, @@ -902,19 +840,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 28, "id": "845d3b33", "metadata": {}, - "outputs": [], - "source": [ - "# Delete the collection\n", - "response = requests.delete(\n", - " f\"{collections_endpoint}/{collection_id}\",\n", - " headers=getBearerToken(),\n", - " params={\"api-version\": api_version}\n", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "STAC Collection deleted: sentinel-2-l2a-tutorial-52\n" + ] + } + ], + "source": [ + "# Delete the collection using the SDK\n", + "delete_collection_operation = pc_client.stac.begin_delete_collection(\n", + " collection_id=collection_id,\n", + " polling=True\n", ")\n", + "delete_collection_operation.result()\n", "\n", - "raise_for_status(response)\n", "print(f\"STAC Collection deleted: {collection_id}\")" ] }, @@ -942,9 +887,9 @@ "notebook_metadata_filter": "-all" }, "kernelspec": { - "display_name": "Python 3.8 - AzureML", + "display_name": ".venv", "language": "python", - "name": "python38-azureml" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -956,7 +901,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.5" + "version": "3.9.13" } }, "nbformat": 4,