From c814f80ec2250284fcf28db673937a881765a5d7 Mon Sep 17 00:00:00 2001 From: Harshini Date: Mon, 2 Mar 2026 17:14:28 -0600 Subject: [PATCH 1/2] draft1 of nisar and biomass --- .../NISAR, BIOMASS & AGBD Extraction.ipynb | 1002 +++++++++++++++++ 1 file changed, 1002 insertions(+) create mode 100644 nisar_biomass/NISAR, BIOMASS & AGBD Extraction.ipynb diff --git a/nisar_biomass/NISAR, BIOMASS & AGBD Extraction.ipynb b/nisar_biomass/NISAR, BIOMASS & AGBD Extraction.ipynb new file mode 100644 index 0000000..1b4ba32 --- /dev/null +++ b/nisar_biomass/NISAR, BIOMASS & AGBD Extraction.ipynb @@ -0,0 +1,1002 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "92563beb-f857-4547-9a20-27c1ca0a6407", + "metadata": {}, + "source": [ + "# NISAR, BIOMASS & AGBD Extraction " + ] + }, + { + "cell_type": "markdown", + "id": "1f0131d2-5059-4841-8ca6-4d05a3375b11", + "metadata": {}, + "source": [ + "**Date:** March 2nd,2026\n", + "\n", + "**Author**:Harshini Girish(UAH), Alex Mandel(Development Seed)\n", + "\n", + "**Description**:This notebook checks whether our AOI is covered by the public catalogs for **NISAR** (via CMR STAC/ASF) and **ESA BIOMASS** (via the ESA MAAP STAC catalogue), and then uses **GEDI L4A AGBD** (footprint-level aboveground biomass density) to extract biomass samples near the AOI/sites. We load the site geometry (KML/GeoJSON), compute the AOI bounding box (with an optional buffer for discovery), run STAC searches to confirm coverage and visualize footprints, then download a small set of GEDI granules and extract quality-filtered AGBD shots. Finally, we spatially join GEDI shots to each site (optionally using a configurable meter-based buffer when sites are small or GEDI sampling is sparse) and produce a per-site summary table (count, mean/median, quantiles, min/max) that can be exported for downstream analysis and fusion planning. \n" + ] + }, + { + "cell_type": "markdown", + "id": "ca1e0d9e-3e5b-4cfa-b0cf-a1d2040a2dcb", + "metadata": {}, + "source": [ + "## Run This Notebook\n", + "\n", + "To access and run this tutorial within MAAP’s Algorithm Development Environment (ADE), please refer to the [Getting started with the MAAP](#) section of our documentation.\n", + "\n", + "**Disclaimer**: It is highly recommended to run this tutorial within MAAP’s ADE, which already includes packages specific to MAAP, such as maap-py. Running the tutorial outside of the MAAP ADE may lead to errors.\n" + ] + }, + { + "cell_type": "markdown", + "id": "bc45e1a4-862a-488d-bca8-d6d78364f4b9", + "metadata": {}, + "source": [ + "## Install/Import Packages\n", + " \n", + "Let's install and load the packages necessary for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "038cced6-b2d8-4975-a2ed-57ed29347450", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from shapely.geometry import box, mapping, Point\n", + "import pandas as pd\n", + "import geopandas as gpd\n", + "import folium\n", + "import pystac_client\n", + "import earthaccess\n", + "import h5py\n", + "import numpy as np\n", + "from pystac import ItemCollection" + ] + }, + { + "cell_type": "markdown", + "id": "c1d55648-869c-4914-aeb0-53fb9e662d3f", + "metadata": {}, + "source": [ + "## Inputs \n", + "Edit these paths/parameters to match your AOI and the collections you want to check." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "11741cb7-41db-436d-ad91-a9e923b9c11f", + "metadata": {}, + "outputs": [], + "source": [ + "# --- AOI / Sites input (KML/GeoJSON/Shapefile/etc.) ---\n", + "SITES_PATH = \"amacayacu_100m.kml\" # change if needed\n", + "SITE_ID_COL = None # set to an existing column name, or leave None to auto-create site_id\n", + "\n", + "# --- STAC endpoints ---\n", + "NISAR_STAC_URL = \"https://cmr.earthdata.nasa.gov/stac/ASF\"\n", + "BIOMASS_STAC_URL = \"https://catalog.maap.eo.esa.int/catalogue/\"\n", + "\n", + "# --- Collections (use exact STAC collection IDs) ---\n", + "NISAR_COLLECTION = \"NISAR_L2_GCOV_BETA_V1_1\"\n", + "BIOMASS_COLLECTIONS = [\"BiomassLevel1b\"]\n", + "\n", + "# --- Optional datetime filters (ISO8601 interval), or None ---\n", + "NISAR_DT = None\n", + "BIOMASS_DT = None\n", + "\n", + "# --- GEDI AGBD dataset (from dataset discovery) ---\n", + "GEDI_SHORT = \"GEDI_L4A_AGB_Density_V2_1_2056\"\n", + "\n", + "# Use a buffer for GEDI discovery/extraction (degrees). Keep modest; increase if needed.\n", + "BBOX_BUFFER_DEG = 0.25\n", + "\n", + "# Buffer radius for \"site neighborhood\" summaries (meters). Set 0 for strict in-polygon only.\n", + "SITE_BUFFER_M = 50_000\n" + ] + }, + { + "cell_type": "markdown", + "id": "054970c6-ef0b-4783-81c7-9eb24c110805", + "metadata": {}, + "source": [ + "## Load AOI / sites and compute bbox \n", + "We read the sites file into a GeoDataFrame (EPSG:4326), ensure a `site_id`, and compute the AOI bbox for catalog searches." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d777bc06-1490-4e2f-a6a7-780e1ecd25ce", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AOI bbox: (-70.2926180592577, -3.81826995097037, -70.2404260082027, -3.78652050791343)\n", + "Buffered bbox: (-70.5426180592577, -4.06826995097037, -69.9904260082027, -3.53652050791343)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
site_idgeometry
0site_1MULTIPOLYGON (((-70.29262 -3.7921, -70.29262 -...
\n", + "
" + ], + "text/plain": [ + " site_id geometry\n", + "0 site_1 MULTIPOLYGON (((-70.29262 -3.7921, -70.29262 -..." + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sites_gdf = gpd.read_file(SITES_PATH)\n", + "\n", + "# Ensure WGS84 lon/lat\n", + "if sites_gdf.crs is None:\n", + " sites_gdf = sites_gdf.set_crs(\"EPSG:4326\")\n", + "else:\n", + " sites_gdf = sites_gdf.to_crs(\"EPSG:4326\")\n", + "\n", + "# Ensure a site_id column\n", + "if SITE_ID_COL and SITE_ID_COL in sites_gdf.columns:\n", + " sites_gdf = sites_gdf.rename(columns={SITE_ID_COL: \"site_id\"})\n", + "elif \"site_id\" not in sites_gdf.columns:\n", + " sites_gdf[\"site_id\"] = [f\"site_{i+1}\" for i in range(len(sites_gdf))]\n", + "\n", + "aoi_geom = sites_gdf.geometry.union_all()\n", + "minx, miny, maxx, maxy = aoi_geom.bounds\n", + "BBOX = (minx, miny, maxx, maxy)\n", + "\n", + "# buffered bbox for discovery\n", + "buf = BBOX_BUFFER_DEG\n", + "BBOX_BUF = (minx-buf, miny-buf, maxx+buf, maxy+buf)\n", + "\n", + "print(\"AOI bbox:\", BBOX)\n", + "print(\"Buffered bbox:\", BBOX_BUF)\n", + "sites_gdf[[\"site_id\", \"geometry\"]].head()\n" + ] + }, + { + "cell_type": "markdown", + "id": "41fdea40-ba12-4d7c-878f-e84687377189", + "metadata": {}, + "source": [ + "## STAC helpers \n", + "- `stac_search`: runs a bbox+collection STAC search \n", + "- `items_to_featurecollection`: converts STAC Items directly to a GeoJSON FeatureCollection " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "6a6a5675-7f84-4b6b-86be-ed6faa4d686c", + "metadata": {}, + "outputs": [], + "source": [ + "def stac_search(catalog_url, collections, bbox, datetime=None, limit=200):\n", + " \"\"\"Run a STAC bbox search and return a PySTAC ItemCollection.\"\"\"\n", + " cat = pystac_client.Client.open(catalog_url)\n", + " search = cat.search(collections=collections, bbox=bbox, datetime=datetime, max_items=limit)\n", + " items = list(search.items())\n", + " return ItemCollection(items)\n", + "\n", + "def stac_items_to_gdf(items):\n", + " \"\"\"Convert a PySTAC ItemCollection to a GeoDataFrame (EPSG:4326).\"\"\"\n", + " return gpd.GeoDataFrame.from_features(items.to_dict(), crs=\"EPSG:4326\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "ca24fae4-5fde-452c-89a3-d8abb0ffbeac", + "metadata": {}, + "source": [ + "## NISAR coverage \n", + "Searches the NISAR STAC catalog for the specified collection intersecting the AOI bbox." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "474de714-4718-4b70-ade0-be617b4ab667", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NISAR items found for NISAR_L2_GCOV_BETA_V1_1: 9\n" + ] + } + ], + "source": [ + "nisar_items = stac_search(\n", + " catalog_url=NISAR_STAC_URL,\n", + " collections=[NISAR_COLLECTION],\n", + " bbox=BBOX,\n", + " datetime=NISAR_DT,\n", + " limit=500\n", + ")\n", + "print(f\"NISAR items found for {NISAR_COLLECTION}: {len(nisar_items)}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "4dad8088-fa80-4f96-bed6-3eb4c2690eb3", + "metadata": {}, + "source": [ + "## BIOMASS coverage \n", + "Searches the BIOMASS STAC catalog for the selected BIOMASS collections intersecting the AOI bbox." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "6d2acbe9-bd83-427d-98e1-29ef4f491cfc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BIOMASS items found (['BiomassLevel1b']): 7\n" + ] + } + ], + "source": [ + "biomass_items = stac_search(\n", + " catalog_url=BIOMASS_STAC_URL,\n", + " collections=BIOMASS_COLLECTIONS,\n", + " bbox=BBOX,\n", + " datetime=BIOMASS_DT,\n", + " limit=500\n", + ")\n", + "print(f\"BIOMASS items found ({BIOMASS_COLLECTIONS}): {len(biomass_items)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "ab01e52c-d8e2-4fd6-b89e-32002c6b81f3", + "metadata": {}, + "source": [ + "## Quick map: AOI + catalog footprints \n", + "This creates a Folium map with:\n", + "- AOI polygon(s)\n", + "- NISAR item footprints (if any)\n", + "- BIOMASS item footprints (if any)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "9888dbcc-1ae0-4bfb-bf6e-a04121fa0705", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m = folium.Map(location=[(miny+maxy)/2, (minx+maxx)/2], zoom_start=8, tiles=\"OpenStreetMap\")\n", + "\n", + "# AOI\n", + "folium.GeoJson(\n", + " data={\"type\":\"Feature\",\"geometry\":mapping(aoi_geom),\"properties\":{\"name\":\"AOI\"}},\n", + " name=\"AOI\",\n", + ").add_to(m)\n", + "\n", + "# NISAR footprints (GeoDataFrame.from_features(items.to_dict()))\n", + "if len(nisar_items) > 0:\n", + " nisar_gdf = stac_items_to_gdf(nisar_items)\n", + " folium.GeoJson(nisar_gdf.__geo_interface__, name=\"NISAR\").add_to(m)\n", + "\n", + "# BIOMASS footprints (GeoDataFrame.from_features(items.to_dict()))\n", + "if len(biomass_items) > 0:\n", + " biomass_gdf = stac_items_to_gdf(biomass_items)\n", + " folium.GeoJson(biomass_gdf.__geo_interface__, name=\"BIOMASS\").add_to(m)\n", + "\n", + "folium.LayerControl().add_to(m)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "5b6fdb5c-9a01-490c-8b40-d2064a4a9396", + "metadata": {}, + "source": [ + "## GEDI L4A AGBD: search and download granules \n", + "We use `earthaccess` to search for GEDI L4A AGBD granules intersecting the **buffered bbox** (to avoid missing sparse tracks). Then we download a small batch first; increase the slice once you’re happy with results." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "f83beb86-5d53-4eb5-9765-731489a5878a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "GEDI granules found: 138\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e9a2ee0e86b84adab48a732454a9308e", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "QUEUEING TASKS | : 0%| | 0/10 [00:00\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
beamagbdlatlonl4_quality_flagdegrade_flagfilegeometry
0BEAM0000102.984283-3.713737-70.54003510gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04...POINT (-70.54004 -3.71374)
1BEAM0000181.824799-3.714160-70.53973310gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04...POINT (-70.53973 -3.71416)
2BEAM0000154.345688-3.714583-70.53943310gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04...POINT (-70.53943 -3.71458)
3BEAM0000101.812408-3.715001-70.53913810gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04...POINT (-70.53914 -3.715)
4BEAM0000147.984161-3.715424-70.53883610gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04...POINT (-70.53884 -3.71542)
\n", + "" + ], + "text/plain": [ + " beam agbd lat lon l4_quality_flag degrade_flag \\\n", + "0 BEAM0000 102.984283 -3.713737 -70.540035 1 0 \n", + "1 BEAM0000 181.824799 -3.714160 -70.539733 1 0 \n", + "2 BEAM0000 154.345688 -3.714583 -70.539433 1 0 \n", + "3 BEAM0000 101.812408 -3.715001 -70.539138 1 0 \n", + "4 BEAM0000 147.984161 -3.715424 -70.538836 1 0 \n", + "\n", + " file \\\n", + "0 gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04... \n", + "1 gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04... \n", + "2 gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04... \n", + "3 gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04... \n", + "4 gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04... \n", + "\n", + " geometry \n", + "0 POINT (-70.54004 -3.71374) \n", + "1 POINT (-70.53973 -3.71416) \n", + "2 POINT (-70.53943 -3.71458) \n", + "3 POINT (-70.53914 -3.715) \n", + "4 POINT (-70.53884 -3.71542) " + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "AOI_MINX, AOI_MINY, AOI_MAXX, AOI_MAXY = BBOX_BUF\n", + "\n", + "def extract_l4a_agbd_points_aoi(h5_path):\n", + " rows = []\n", + " with h5py.File(h5_path, \"r\") as f:\n", + " beams = [k for k in f.keys() if k.startswith(\"BEAM\")]\n", + " for beam in beams:\n", + " g = f[beam]\n", + " if not all(k in g for k in [\"agbd\", \"lat_lowestmode\", \"lon_lowestmode\"]):\n", + " continue\n", + "\n", + " agbd = g[\"agbd\"][:]\n", + " lat = g[\"lat_lowestmode\"][:]\n", + " lon = g[\"lon_lowestmode\"][:]\n", + "\n", + " q4 = g[\"l4_quality_flag\"][:] if \"l4_quality_flag\" in g else None\n", + " degr = g[\"degrade_flag\"][:] if \"degrade_flag\" in g else None\n", + "\n", + " m = (agbd > -9000) & np.isfinite(agbd) & np.isfinite(lat) & np.isfinite(lon)\n", + " m &= (lon >= AOI_MINX) & (lon <= AOI_MAXX) & (lat >= AOI_MINY) & (lat <= AOI_MAXY)\n", + " if q4 is not None:\n", + " m &= (q4 == 1)\n", + " if degr is not None:\n", + " m &= (degr == 0)\n", + "\n", + " idx = np.where(m)[0]\n", + " for i in idx:\n", + " rows.append({\n", + " \"beam\": beam,\n", + " \"agbd\": float(agbd[i]),\n", + " \"lat\": float(lat[i]),\n", + " \"lon\": float(lon[i]),\n", + " \"l4_quality_flag\": int(q4[i]) if q4 is not None else None,\n", + " \"degrade_flag\": int(degr[i]) if degr is not None else None,\n", + " \"file\": str(h5_path),\n", + " })\n", + "\n", + " df = pd.DataFrame(rows)\n", + " if df.empty:\n", + " return gpd.GeoDataFrame(df, geometry=[], crs=\"EPSG:4326\")\n", + "\n", + " return gpd.GeoDataFrame(\n", + " df,\n", + " geometry=[Point(xy) for xy in zip(df[\"lon\"], df[\"lat\"])],\n", + " crs=\"EPSG:4326\"\n", + " )\n", + "\n", + "shots_list = []\n", + "for fp in downloaded:\n", + " gdf = extract_l4a_agbd_points_aoi(fp)\n", + " print(fp, \"AOI shots:\", len(gdf))\n", + " if len(gdf) > 0:\n", + " shots_list.append(gdf)\n", + "\n", + "gedi_aoi = gpd.GeoDataFrame(pd.concat(shots_list, ignore_index=True), crs=\"EPSG:4326\") if shots_list else gpd.GeoDataFrame(columns=[\"agbd\"], geometry=[], crs=\"EPSG:4326\")\n", + "print(\"Total AOI shots:\", len(gedi_aoi))\n", + "gedi_aoi.head()\n" + ] + }, + { + "cell_type": "markdown", + "id": "289e7294-9340-4e57-b0ca-ccccc585e607", + "metadata": {}, + "source": [ + "## Site-level AGBD summary (optional buffer) \n", + "We join GEDI shots to site geometries. \n", + "- If `SITE_BUFFER_M = 0`, the join is strict (shots must fall within the site polygon). \n", + "- If `SITE_BUFFER_M > 0`, we buffer sites in meters first (useful when sites are small and GEDI footprints are sparse)." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "7f4826be-8748-4e0a-a2d9-c54f32e9ee3b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Shots matched to sites: 19678\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
site_idnmeanmedianstdp10p90minmaxbuffer_m
0site_119678229.596929203.909927178.79109768.343047375.7263180.05391.29394550000
\n", + "
" + ], + "text/plain": [ + " site_id n mean median std p10 p90 \\\n", + "0 site_1 19678 229.596929 203.909927 178.791097 68.343047 375.726318 \n", + "\n", + " min max buffer_m \n", + "0 0.0 5391.293945 50000 " + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Buffer sites if requested\n", + "if SITE_BUFFER_M and SITE_BUFFER_M > 0:\n", + " sites_m = sites_gdf.to_crs(\"EPSG:3857\")\n", + " sites_m[\"geometry\"] = sites_m.geometry.buffer(SITE_BUFFER_M)\n", + " sites_join = sites_m.to_crs(\"EPSG:4326\")\n", + "else:\n", + " sites_join = sites_gdf\n", + "\n", + "joined = gpd.sjoin(\n", + " gedi_aoi,\n", + " sites_join[[\"site_id\", \"geometry\"]],\n", + " predicate=\"within\",\n", + " how=\"inner\"\n", + ")\n", + "\n", + "print(\"Shots matched to sites:\", len(joined))\n", + "\n", + "site_agbd = (\n", + " joined.groupby(\"site_id\")[\"agbd\"]\n", + " .agg(\n", + " n=\"count\",\n", + " mean=\"mean\",\n", + " median=\"median\",\n", + " std=\"std\",\n", + " p10=lambda x: x.quantile(0.10),\n", + " p90=lambda x: x.quantile(0.90),\n", + " min=\"min\",\n", + " max=\"max\",\n", + " )\n", + " .reset_index()\n", + ")\n", + "\n", + "site_agbd[\"buffer_m\"] = SITE_BUFFER_M\n", + "site_agbd\n" + ] + }, + { + "cell_type": "markdown", + "id": "4f2aac40-5e5b-4bec-bd6d-6723ebe786eb", + "metadata": {}, + "source": [ + "## Export results \n", + "Writes the per-site AGBD summary table to CSV." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "3a8b3476-8553-4d8e-bd54-176a39bf0b99", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Wrote: gedi_agbd_by_site_buffer_50000m.csv\n" + ] + } + ], + "source": [ + "out_csv = \"gedi_agbd_by_site.csv\" if not SITE_BUFFER_M else f\"gedi_agbd_by_site_buffer_{int(SITE_BUFFER_M)}m.csv\"\n", + "site_agbd.to_csv(out_csv, index=False)\n", + "print(\"Wrote:\", out_csv)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "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.12.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From b4b54b19d9a76201d649c34aaebc73e2fd9df2df Mon Sep 17 00:00:00 2001 From: Harshini Date: Tue, 3 Mar 2026 13:01:25 -0600 Subject: [PATCH 2/2] Addressed comments --- .../NISAR, BIOMASS & AGBD Extraction.ipynb | 1002 ------------- nisar_biomass/NISAR, BIOMASS & GEDI.ipynb | 1261 +++++++++++++++++ 2 files changed, 1261 insertions(+), 1002 deletions(-) delete mode 100644 nisar_biomass/NISAR, BIOMASS & AGBD Extraction.ipynb create mode 100644 nisar_biomass/NISAR, BIOMASS & GEDI.ipynb diff --git a/nisar_biomass/NISAR, BIOMASS & AGBD Extraction.ipynb b/nisar_biomass/NISAR, BIOMASS & AGBD Extraction.ipynb deleted file mode 100644 index 1b4ba32..0000000 --- a/nisar_biomass/NISAR, BIOMASS & AGBD Extraction.ipynb +++ /dev/null @@ -1,1002 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "92563beb-f857-4547-9a20-27c1ca0a6407", - "metadata": {}, - "source": [ - "# NISAR, BIOMASS & AGBD Extraction " - ] - }, - { - "cell_type": "markdown", - "id": "1f0131d2-5059-4841-8ca6-4d05a3375b11", - "metadata": {}, - "source": [ - "**Date:** March 2nd,2026\n", - "\n", - "**Author**:Harshini Girish(UAH), Alex Mandel(Development Seed)\n", - "\n", - "**Description**:This notebook checks whether our AOI is covered by the public catalogs for **NISAR** (via CMR STAC/ASF) and **ESA BIOMASS** (via the ESA MAAP STAC catalogue), and then uses **GEDI L4A AGBD** (footprint-level aboveground biomass density) to extract biomass samples near the AOI/sites. We load the site geometry (KML/GeoJSON), compute the AOI bounding box (with an optional buffer for discovery), run STAC searches to confirm coverage and visualize footprints, then download a small set of GEDI granules and extract quality-filtered AGBD shots. Finally, we spatially join GEDI shots to each site (optionally using a configurable meter-based buffer when sites are small or GEDI sampling is sparse) and produce a per-site summary table (count, mean/median, quantiles, min/max) that can be exported for downstream analysis and fusion planning. \n" - ] - }, - { - "cell_type": "markdown", - "id": "ca1e0d9e-3e5b-4cfa-b0cf-a1d2040a2dcb", - "metadata": {}, - "source": [ - "## Run This Notebook\n", - "\n", - "To access and run this tutorial within MAAP’s Algorithm Development Environment (ADE), please refer to the [Getting started with the MAAP](#) section of our documentation.\n", - "\n", - "**Disclaimer**: It is highly recommended to run this tutorial within MAAP’s ADE, which already includes packages specific to MAAP, such as maap-py. Running the tutorial outside of the MAAP ADE may lead to errors.\n" - ] - }, - { - "cell_type": "markdown", - "id": "bc45e1a4-862a-488d-bca8-d6d78364f4b9", - "metadata": {}, - "source": [ - "## Install/Import Packages\n", - " \n", - "Let's install and load the packages necessary for this tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "038cced6-b2d8-4975-a2ed-57ed29347450", - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "from shapely.geometry import box, mapping, Point\n", - "import pandas as pd\n", - "import geopandas as gpd\n", - "import folium\n", - "import pystac_client\n", - "import earthaccess\n", - "import h5py\n", - "import numpy as np\n", - "from pystac import ItemCollection" - ] - }, - { - "cell_type": "markdown", - "id": "c1d55648-869c-4914-aeb0-53fb9e662d3f", - "metadata": {}, - "source": [ - "## Inputs \n", - "Edit these paths/parameters to match your AOI and the collections you want to check." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "11741cb7-41db-436d-ad91-a9e923b9c11f", - "metadata": {}, - "outputs": [], - "source": [ - "# --- AOI / Sites input (KML/GeoJSON/Shapefile/etc.) ---\n", - "SITES_PATH = \"amacayacu_100m.kml\" # change if needed\n", - "SITE_ID_COL = None # set to an existing column name, or leave None to auto-create site_id\n", - "\n", - "# --- STAC endpoints ---\n", - "NISAR_STAC_URL = \"https://cmr.earthdata.nasa.gov/stac/ASF\"\n", - "BIOMASS_STAC_URL = \"https://catalog.maap.eo.esa.int/catalogue/\"\n", - "\n", - "# --- Collections (use exact STAC collection IDs) ---\n", - "NISAR_COLLECTION = \"NISAR_L2_GCOV_BETA_V1_1\"\n", - "BIOMASS_COLLECTIONS = [\"BiomassLevel1b\"]\n", - "\n", - "# --- Optional datetime filters (ISO8601 interval), or None ---\n", - "NISAR_DT = None\n", - "BIOMASS_DT = None\n", - "\n", - "# --- GEDI AGBD dataset (from dataset discovery) ---\n", - "GEDI_SHORT = \"GEDI_L4A_AGB_Density_V2_1_2056\"\n", - "\n", - "# Use a buffer for GEDI discovery/extraction (degrees). Keep modest; increase if needed.\n", - "BBOX_BUFFER_DEG = 0.25\n", - "\n", - "# Buffer radius for \"site neighborhood\" summaries (meters). Set 0 for strict in-polygon only.\n", - "SITE_BUFFER_M = 50_000\n" - ] - }, - { - "cell_type": "markdown", - "id": "054970c6-ef0b-4783-81c7-9eb24c110805", - "metadata": {}, - "source": [ - "## Load AOI / sites and compute bbox \n", - "We read the sites file into a GeoDataFrame (EPSG:4326), ensure a `site_id`, and compute the AOI bbox for catalog searches." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "d777bc06-1490-4e2f-a6a7-780e1ecd25ce", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "AOI bbox: (-70.2926180592577, -3.81826995097037, -70.2404260082027, -3.78652050791343)\n", - "Buffered bbox: (-70.5426180592577, -4.06826995097037, -69.9904260082027, -3.53652050791343)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
site_idgeometry
0site_1MULTIPOLYGON (((-70.29262 -3.7921, -70.29262 -...
\n", - "
" - ], - "text/plain": [ - " site_id geometry\n", - "0 site_1 MULTIPOLYGON (((-70.29262 -3.7921, -70.29262 -..." - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sites_gdf = gpd.read_file(SITES_PATH)\n", - "\n", - "# Ensure WGS84 lon/lat\n", - "if sites_gdf.crs is None:\n", - " sites_gdf = sites_gdf.set_crs(\"EPSG:4326\")\n", - "else:\n", - " sites_gdf = sites_gdf.to_crs(\"EPSG:4326\")\n", - "\n", - "# Ensure a site_id column\n", - "if SITE_ID_COL and SITE_ID_COL in sites_gdf.columns:\n", - " sites_gdf = sites_gdf.rename(columns={SITE_ID_COL: \"site_id\"})\n", - "elif \"site_id\" not in sites_gdf.columns:\n", - " sites_gdf[\"site_id\"] = [f\"site_{i+1}\" for i in range(len(sites_gdf))]\n", - "\n", - "aoi_geom = sites_gdf.geometry.union_all()\n", - "minx, miny, maxx, maxy = aoi_geom.bounds\n", - "BBOX = (minx, miny, maxx, maxy)\n", - "\n", - "# buffered bbox for discovery\n", - "buf = BBOX_BUFFER_DEG\n", - "BBOX_BUF = (minx-buf, miny-buf, maxx+buf, maxy+buf)\n", - "\n", - "print(\"AOI bbox:\", BBOX)\n", - "print(\"Buffered bbox:\", BBOX_BUF)\n", - "sites_gdf[[\"site_id\", \"geometry\"]].head()\n" - ] - }, - { - "cell_type": "markdown", - "id": "41fdea40-ba12-4d7c-878f-e84687377189", - "metadata": {}, - "source": [ - "## STAC helpers \n", - "- `stac_search`: runs a bbox+collection STAC search \n", - "- `items_to_featurecollection`: converts STAC Items directly to a GeoJSON FeatureCollection " - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "6a6a5675-7f84-4b6b-86be-ed6faa4d686c", - "metadata": {}, - "outputs": [], - "source": [ - "def stac_search(catalog_url, collections, bbox, datetime=None, limit=200):\n", - " \"\"\"Run a STAC bbox search and return a PySTAC ItemCollection.\"\"\"\n", - " cat = pystac_client.Client.open(catalog_url)\n", - " search = cat.search(collections=collections, bbox=bbox, datetime=datetime, max_items=limit)\n", - " items = list(search.items())\n", - " return ItemCollection(items)\n", - "\n", - "def stac_items_to_gdf(items):\n", - " \"\"\"Convert a PySTAC ItemCollection to a GeoDataFrame (EPSG:4326).\"\"\"\n", - " return gpd.GeoDataFrame.from_features(items.to_dict(), crs=\"EPSG:4326\")\n" - ] - }, - { - "cell_type": "markdown", - "id": "ca24fae4-5fde-452c-89a3-d8abb0ffbeac", - "metadata": {}, - "source": [ - "## NISAR coverage \n", - "Searches the NISAR STAC catalog for the specified collection intersecting the AOI bbox." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "474de714-4718-4b70-ade0-be617b4ab667", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "NISAR items found for NISAR_L2_GCOV_BETA_V1_1: 9\n" - ] - } - ], - "source": [ - "nisar_items = stac_search(\n", - " catalog_url=NISAR_STAC_URL,\n", - " collections=[NISAR_COLLECTION],\n", - " bbox=BBOX,\n", - " datetime=NISAR_DT,\n", - " limit=500\n", - ")\n", - "print(f\"NISAR items found for {NISAR_COLLECTION}: {len(nisar_items)}\")\n" - ] - }, - { - "cell_type": "markdown", - "id": "4dad8088-fa80-4f96-bed6-3eb4c2690eb3", - "metadata": {}, - "source": [ - "## BIOMASS coverage \n", - "Searches the BIOMASS STAC catalog for the selected BIOMASS collections intersecting the AOI bbox." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "6d2acbe9-bd83-427d-98e1-29ef4f491cfc", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "BIOMASS items found (['BiomassLevel1b']): 7\n" - ] - } - ], - "source": [ - "biomass_items = stac_search(\n", - " catalog_url=BIOMASS_STAC_URL,\n", - " collections=BIOMASS_COLLECTIONS,\n", - " bbox=BBOX,\n", - " datetime=BIOMASS_DT,\n", - " limit=500\n", - ")\n", - "print(f\"BIOMASS items found ({BIOMASS_COLLECTIONS}): {len(biomass_items)}\")" - ] - }, - { - "cell_type": "markdown", - "id": "ab01e52c-d8e2-4fd6-b89e-32002c6b81f3", - "metadata": {}, - "source": [ - "## Quick map: AOI + catalog footprints \n", - "This creates a Folium map with:\n", - "- AOI polygon(s)\n", - "- NISAR item footprints (if any)\n", - "- BIOMASS item footprints (if any)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "9888dbcc-1ae0-4bfb-bf6e-a04121fa0705", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "m = folium.Map(location=[(miny+maxy)/2, (minx+maxx)/2], zoom_start=8, tiles=\"OpenStreetMap\")\n", - "\n", - "# AOI\n", - "folium.GeoJson(\n", - " data={\"type\":\"Feature\",\"geometry\":mapping(aoi_geom),\"properties\":{\"name\":\"AOI\"}},\n", - " name=\"AOI\",\n", - ").add_to(m)\n", - "\n", - "# NISAR footprints (GeoDataFrame.from_features(items.to_dict()))\n", - "if len(nisar_items) > 0:\n", - " nisar_gdf = stac_items_to_gdf(nisar_items)\n", - " folium.GeoJson(nisar_gdf.__geo_interface__, name=\"NISAR\").add_to(m)\n", - "\n", - "# BIOMASS footprints (GeoDataFrame.from_features(items.to_dict()))\n", - "if len(biomass_items) > 0:\n", - " biomass_gdf = stac_items_to_gdf(biomass_items)\n", - " folium.GeoJson(biomass_gdf.__geo_interface__, name=\"BIOMASS\").add_to(m)\n", - "\n", - "folium.LayerControl().add_to(m)\n", - "m" - ] - }, - { - "cell_type": "markdown", - "id": "5b6fdb5c-9a01-490c-8b40-d2064a4a9396", - "metadata": {}, - "source": [ - "## GEDI L4A AGBD: search and download granules \n", - "We use `earthaccess` to search for GEDI L4A AGBD granules intersecting the **buffered bbox** (to avoid missing sparse tracks). Then we download a small batch first; increase the slice once you’re happy with results." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "f83beb86-5d53-4eb5-9765-731489a5878a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "GEDI granules found: 138\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "e9a2ee0e86b84adab48a732454a9308e", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "QUEUEING TASKS | : 0%| | 0/10 [00:00\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
beamagbdlatlonl4_quality_flagdegrade_flagfilegeometry
0BEAM0000102.984283-3.713737-70.54003510gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04...POINT (-70.54004 -3.71374)
1BEAM0000181.824799-3.714160-70.53973310gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04...POINT (-70.53973 -3.71416)
2BEAM0000154.345688-3.714583-70.53943310gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04...POINT (-70.53943 -3.71458)
3BEAM0000101.812408-3.715001-70.53913810gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04...POINT (-70.53914 -3.715)
4BEAM0000147.984161-3.715424-70.53883610gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04...POINT (-70.53884 -3.71542)
\n", - "" - ], - "text/plain": [ - " beam agbd lat lon l4_quality_flag degrade_flag \\\n", - "0 BEAM0000 102.984283 -3.713737 -70.540035 1 0 \n", - "1 BEAM0000 181.824799 -3.714160 -70.539733 1 0 \n", - "2 BEAM0000 154.345688 -3.714583 -70.539433 1 0 \n", - "3 BEAM0000 101.812408 -3.715001 -70.539138 1 0 \n", - "4 BEAM0000 147.984161 -3.715424 -70.538836 1 0 \n", - "\n", - " file \\\n", - "0 gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04... \n", - "1 gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04... \n", - "2 gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04... \n", - "3 gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04... \n", - "4 gedi_l4a_agbd/GEDI04_A_2019144073502_O02523_04... \n", - "\n", - " geometry \n", - "0 POINT (-70.54004 -3.71374) \n", - "1 POINT (-70.53973 -3.71416) \n", - "2 POINT (-70.53943 -3.71458) \n", - "3 POINT (-70.53914 -3.715) \n", - "4 POINT (-70.53884 -3.71542) " - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "AOI_MINX, AOI_MINY, AOI_MAXX, AOI_MAXY = BBOX_BUF\n", - "\n", - "def extract_l4a_agbd_points_aoi(h5_path):\n", - " rows = []\n", - " with h5py.File(h5_path, \"r\") as f:\n", - " beams = [k for k in f.keys() if k.startswith(\"BEAM\")]\n", - " for beam in beams:\n", - " g = f[beam]\n", - " if not all(k in g for k in [\"agbd\", \"lat_lowestmode\", \"lon_lowestmode\"]):\n", - " continue\n", - "\n", - " agbd = g[\"agbd\"][:]\n", - " lat = g[\"lat_lowestmode\"][:]\n", - " lon = g[\"lon_lowestmode\"][:]\n", - "\n", - " q4 = g[\"l4_quality_flag\"][:] if \"l4_quality_flag\" in g else None\n", - " degr = g[\"degrade_flag\"][:] if \"degrade_flag\" in g else None\n", - "\n", - " m = (agbd > -9000) & np.isfinite(agbd) & np.isfinite(lat) & np.isfinite(lon)\n", - " m &= (lon >= AOI_MINX) & (lon <= AOI_MAXX) & (lat >= AOI_MINY) & (lat <= AOI_MAXY)\n", - " if q4 is not None:\n", - " m &= (q4 == 1)\n", - " if degr is not None:\n", - " m &= (degr == 0)\n", - "\n", - " idx = np.where(m)[0]\n", - " for i in idx:\n", - " rows.append({\n", - " \"beam\": beam,\n", - " \"agbd\": float(agbd[i]),\n", - " \"lat\": float(lat[i]),\n", - " \"lon\": float(lon[i]),\n", - " \"l4_quality_flag\": int(q4[i]) if q4 is not None else None,\n", - " \"degrade_flag\": int(degr[i]) if degr is not None else None,\n", - " \"file\": str(h5_path),\n", - " })\n", - "\n", - " df = pd.DataFrame(rows)\n", - " if df.empty:\n", - " return gpd.GeoDataFrame(df, geometry=[], crs=\"EPSG:4326\")\n", - "\n", - " return gpd.GeoDataFrame(\n", - " df,\n", - " geometry=[Point(xy) for xy in zip(df[\"lon\"], df[\"lat\"])],\n", - " crs=\"EPSG:4326\"\n", - " )\n", - "\n", - "shots_list = []\n", - "for fp in downloaded:\n", - " gdf = extract_l4a_agbd_points_aoi(fp)\n", - " print(fp, \"AOI shots:\", len(gdf))\n", - " if len(gdf) > 0:\n", - " shots_list.append(gdf)\n", - "\n", - "gedi_aoi = gpd.GeoDataFrame(pd.concat(shots_list, ignore_index=True), crs=\"EPSG:4326\") if shots_list else gpd.GeoDataFrame(columns=[\"agbd\"], geometry=[], crs=\"EPSG:4326\")\n", - "print(\"Total AOI shots:\", len(gedi_aoi))\n", - "gedi_aoi.head()\n" - ] - }, - { - "cell_type": "markdown", - "id": "289e7294-9340-4e57-b0ca-ccccc585e607", - "metadata": {}, - "source": [ - "## Site-level AGBD summary (optional buffer) \n", - "We join GEDI shots to site geometries. \n", - "- If `SITE_BUFFER_M = 0`, the join is strict (shots must fall within the site polygon). \n", - "- If `SITE_BUFFER_M > 0`, we buffer sites in meters first (useful when sites are small and GEDI footprints are sparse)." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "7f4826be-8748-4e0a-a2d9-c54f32e9ee3b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Shots matched to sites: 19678\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
site_idnmeanmedianstdp10p90minmaxbuffer_m
0site_119678229.596929203.909927178.79109768.343047375.7263180.05391.29394550000
\n", - "
" - ], - "text/plain": [ - " site_id n mean median std p10 p90 \\\n", - "0 site_1 19678 229.596929 203.909927 178.791097 68.343047 375.726318 \n", - "\n", - " min max buffer_m \n", - "0 0.0 5391.293945 50000 " - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Buffer sites if requested\n", - "if SITE_BUFFER_M and SITE_BUFFER_M > 0:\n", - " sites_m = sites_gdf.to_crs(\"EPSG:3857\")\n", - " sites_m[\"geometry\"] = sites_m.geometry.buffer(SITE_BUFFER_M)\n", - " sites_join = sites_m.to_crs(\"EPSG:4326\")\n", - "else:\n", - " sites_join = sites_gdf\n", - "\n", - "joined = gpd.sjoin(\n", - " gedi_aoi,\n", - " sites_join[[\"site_id\", \"geometry\"]],\n", - " predicate=\"within\",\n", - " how=\"inner\"\n", - ")\n", - "\n", - "print(\"Shots matched to sites:\", len(joined))\n", - "\n", - "site_agbd = (\n", - " joined.groupby(\"site_id\")[\"agbd\"]\n", - " .agg(\n", - " n=\"count\",\n", - " mean=\"mean\",\n", - " median=\"median\",\n", - " std=\"std\",\n", - " p10=lambda x: x.quantile(0.10),\n", - " p90=lambda x: x.quantile(0.90),\n", - " min=\"min\",\n", - " max=\"max\",\n", - " )\n", - " .reset_index()\n", - ")\n", - "\n", - "site_agbd[\"buffer_m\"] = SITE_BUFFER_M\n", - "site_agbd\n" - ] - }, - { - "cell_type": "markdown", - "id": "4f2aac40-5e5b-4bec-bd6d-6723ebe786eb", - "metadata": {}, - "source": [ - "## Export results \n", - "Writes the per-site AGBD summary table to CSV." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "3a8b3476-8553-4d8e-bd54-176a39bf0b99", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Wrote: gedi_agbd_by_site_buffer_50000m.csv\n" - ] - } - ], - "source": [ - "out_csv = \"gedi_agbd_by_site.csv\" if not SITE_BUFFER_M else f\"gedi_agbd_by_site_buffer_{int(SITE_BUFFER_M)}m.csv\"\n", - "site_agbd.to_csv(out_csv, index=False)\n", - "print(\"Wrote:\", out_csv)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "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.12.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/nisar_biomass/NISAR, BIOMASS & GEDI.ipynb b/nisar_biomass/NISAR, BIOMASS & GEDI.ipynb new file mode 100644 index 0000000..03eb93c --- /dev/null +++ b/nisar_biomass/NISAR, BIOMASS & GEDI.ipynb @@ -0,0 +1,1261 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "92563beb-f857-4547-9a20-27c1ca0a6407", + "metadata": {}, + "source": [ + "# GEDI, BIOMASS, and NISAR: Visualization for an AOI" + ] + }, + { + "cell_type": "markdown", + "id": "1f0131d2-5059-4841-8ca6-4d05a3375b11", + "metadata": {}, + "source": [ + "**Date:** March 2nd, 2026\n", + "\n", + "**Author**: Harshini Girish(UAH), Alex Mandel(Development Seed), Henry Rodman(Development Seed), Chuck Daniels(Development Seed)\n", + "\n", + "\n", + "**Description**:This notebook demonstrates a lightweight, map-first workflow for visualizing where three Earth observation datasets overlap for a given Area of Interest (AOI). We begin by importing required libraries, loading the AOI geometry (sites polygon layer), ensuring it is in WGS84 (EPSG:4326), and computing both a tight AOI bounding box and a buffered bounding box for data discovery. Using a single STAC helper function, we then perform bounding-box searches for NISAR, ESA BIOMASS, and GEDI collections from their respective STAC catalogs and convert the returned STAC items into GeoDataFrames for mapping. Finally, we render an interactive Folium map that first previews the AOI polygon and then overlays the three datasets as separate, color-coded layers with a layer control, enabling quick visual inspection of coverage and overlap before any downstream subsetting or analysis." + ] + }, + { + "cell_type": "markdown", + "id": "ca1e0d9e-3e5b-4cfa-b0cf-a1d2040a2dcb", + "metadata": {}, + "source": [ + "## Run This Notebook\n", + "\n", + "To access and run this tutorial within MAAP’s Algorithm Development Environment (ADE), please refer to the [Getting started with the MAAP](#) section of our documentation.\n", + "\n", + "**Disclaimer**: It is highly recommended to run this tutorial within MAAP’s ADE, which already includes packages specific to MAAP, such as maap-py. Running the tutorial outside of the MAAP ADE may lead to errors.\n" + ] + }, + { + "cell_type": "markdown", + "id": "bc45e1a4-862a-488d-bca8-d6d78364f4b9", + "metadata": {}, + "source": [ + "## Install/Import Packages\n", + " \n", + "Let's install and load the packages necessary for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "038cced6-b2d8-4975-a2ed-57ed29347450", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from shapely.geometry import box, mapping, Point\n", + "import pandas as pd\n", + "import geopandas as gpd\n", + "import folium\n", + "\n", + "import pystac_client\n", + "from pystac import ItemCollection" + ] + }, + { + "cell_type": "markdown", + "id": "c1d55648-869c-4914-aeb0-53fb9e662d3f", + "metadata": {}, + "source": [ + "## Inputs \n", + "Edit these paths/parameters to match your AOI and the collections you want to check." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "11741cb7-41db-436d-ad91-a9e923b9c11f", + "metadata": {}, + "outputs": [], + "source": [ + "# --- AOI / Sites input (KML/GeoJSON/Shapefile/etc.) ---\n", + "SITES_PATH = \"amacayacu_100m.kml\" # change if needed\n", + "SITE_ID_COL = None # set to an existing column name, or leave None to auto-create site_id\n", + "\n", + "# --- STAC endpoints ---\n", + "NISAR_STAC_URL = \"https://cmr.earthdata.nasa.gov/stac/ASF\"\n", + "BIOMASS_STAC_URL = \"https://catalog.maap.eo.esa.int/catalogue/\"\n", + "GEDI_STAC_URL = \"https://cmr.earthdata.nasa.gov/stac/ORNL_CLOUD\"\n", + "\n", + "# --- Collections (use exact STAC collection IDs) ---\n", + "NISAR_COLLECTION = \"NISAR_L2_GCOV_BETA_V1_1\"\n", + "BIOMASS_COLLECTIONS = [\"BiomassLevel1b\"]\n", + "GEDI_COLLECTION = \"GEDI_L4A_AGB_Density_V2_1_2056_2.1\"\n", + "\n", + "# --- Optional datetime filters (ISO8601 interval), or None ---\n", + "NISAR_DT = None\n", + "BIOMASS_DT = None\n", + "GEDI_DT = None\n", + "\n", + "# --- Buffer for discovery/search (degrees; EPSG:4326 lon/lat) ---\n", + "BBOX_BUFFER_DEG = 0.25\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "054970c6-ef0b-4783-81c7-9eb24c110805", + "metadata": {}, + "source": [ + "## Load AOI / sites and compute bbox \n", + "We read the sites file into a GeoDataFrame (EPSG:4326), ensure a `site_id`, and compute the AOI bbox for catalog searches." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d777bc06-1490-4e2f-a6a7-780e1ecd25ce", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AOI bbox: (-70.2926180592577, -3.81826995097037, -70.2404260082027, -3.78652050791343)\n", + "Buffered bbox: (-70.5426180592577, -4.06826995097037, -69.9904260082027, -3.53652050791343)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
site_idgeometry
0site_1MULTIPOLYGON (((-70.29262 -3.7921, -70.29262 -...
\n", + "
" + ], + "text/plain": [ + " site_id geometry\n", + "0 site_1 MULTIPOLYGON (((-70.29262 -3.7921, -70.29262 -..." + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sites_gdf = gpd.read_file(SITES_PATH)\n", + "\n", + "# Ensure WGS84 lon/lat\n", + "if sites_gdf.crs is None:\n", + " sites_gdf = sites_gdf.set_crs(\"EPSG:4326\")\n", + "else:\n", + " sites_gdf = sites_gdf.to_crs(\"EPSG:4326\")\n", + "\n", + "# Ensure a site_id column\n", + "if SITE_ID_COL and SITE_ID_COL in sites_gdf.columns:\n", + " sites_gdf = sites_gdf.rename(columns={SITE_ID_COL: \"site_id\"})\n", + "elif \"site_id\" not in sites_gdf.columns:\n", + " sites_gdf[\"site_id\"] = [f\"site_{i+1}\" for i in range(len(sites_gdf))]\n", + "\n", + "aoi_geom = sites_gdf.geometry.union_all()\n", + "minx, miny, maxx, maxy = aoi_geom.bounds\n", + "BBOX = (minx, miny, maxx, maxy)\n", + "\n", + "# buffered bbox for discovery\n", + "buf = BBOX_BUFFER_DEG\n", + "BBOX_BUF = (minx-buf, miny-buf, maxx+buf, maxy+buf)\n", + "\n", + "print(\"AOI bbox:\", BBOX)\n", + "print(\"Buffered bbox:\", BBOX_BUF)\n", + "sites_gdf[[\"site_id\", \"geometry\"]].head()\n" + ] + }, + { + "cell_type": "markdown", + "id": "cc34cbe5-39c9-40a2-ab8c-3df637f351f0", + "metadata": {}, + "source": [ + "## AOI preview map\n", + "Visualize the AOI polygons before running any STAC searches." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "938ff8b8-f786-4293-8fd5-ee54ca3b09d4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Quick AOI preview map (sites + AOI outline)\n", + "\n", + "aoi_map = folium.Map(\n", + " location=[(miny + maxy) / 2, (minx + maxx) / 2],\n", + " zoom_start=10,\n", + " tiles=\"OpenStreetMap\",\n", + ")\n", + "\n", + "# Site polygons (purple)\n", + "folium.GeoJson(\n", + " sites_gdf,\n", + " name=\"Sites\",\n", + " style_function=lambda _: {\"color\": \"purple\", \"weight\": 2, \"fillOpacity\": 0.10},\n", + ").add_to(aoi_map)\n", + "\n", + "# AOI outline (red)\n", + "folium.GeoJson(\n", + " data={\"type\": \"Feature\", \"geometry\": mapping(aoi_geom), \"properties\": {\"name\": \"AOI\"}},\n", + " name=\"AOI\",\n", + " style_function=lambda _: {\"color\": \"red\", \"weight\": 3, \"fillOpacity\": 0.0},\n", + ").add_to(aoi_map)\n", + "\n", + "folium.LayerControl(collapsed=False).add_to(aoi_map)\n", + "aoi_map\n" + ] + }, + { + "cell_type": "markdown", + "id": "41fdea40-ba12-4d7c-878f-e84687377189", + "metadata": {}, + "source": [ + "## STAC helper\n", + "\n", + "Single helper to run a STAC bbox search and directly return a GeoDataFrame for mapping." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "6a6a5675-7f84-4b6b-86be-ed6faa4d686c", + "metadata": {}, + "outputs": [], + "source": [ + "def stac_search_to_gdf(catalog_url, collections, bbox, datetime=None, limit=200):\n", + " \"\"\"Run a STAC bbox search and return results as a GeoDataFrame (EPSG:4326).\"\"\"\n", + " cat = pystac_client.Client.open(catalog_url)\n", + " search = cat.search(collections=collections, bbox=bbox, datetime=datetime, max_items=limit)\n", + " items = list(search.items())\n", + " return gpd.GeoDataFrame.from_features([it.to_dict() for it in items], crs=\"EPSG:4326\")\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "ca24fae4-5fde-452c-89a3-d8abb0ffbeac", + "metadata": {}, + "source": [ + "## NISAR coverage\n", + "Search the NISAR STAC catalog for the chosen NISAR collection intersecting the AOI bbox." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "474de714-4718-4b70-ade0-be617b4ab667", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NISAR items found for NISAR_L2_GCOV_BETA_V1_1: 9\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
geometrydatetimestorage:schemesstart_datetimeend_datetime
0POLYGON ((-69.99474 -3.94381, -70.45989 -1.795...2025-11-06T10:27:57Z{'aws': {'type': 'aws-s3', 'platform': 'https:...2025-11-06T10:27:57.000Z2025-11-06T10:28:32.999Z
1POLYGON ((-67.8897 -3.94315, -68.34109 -1.8547...2025-11-11T10:19:38Z{'aws': {'type': 'aws-s3', 'platform': 'https:...2025-11-11T10:19:38.000Z2025-11-11T10:20:12.999Z
2POLYGON ((-70.35039 -1.73374, -70.81558 -3.881...2025-11-30T22:58:08Z{'aws': {'type': 'aws-s3', 'platform': 'https:...2025-11-30T22:58:08.000Z2025-11-30T22:58:43.999Z
3POLYGON ((-67.89068 -3.93366, -68.34215 -1.845...2025-12-05T10:19:39Z{'aws': {'type': 'aws-s3', 'platform': 'https:...2025-12-05T10:19:39.000Z2025-12-05T10:20:13.999Z
4POLYGON ((-69.98483 -3.99241, -70.4499 -1.8445...2025-12-12T10:27:58Z{'aws': {'type': 'aws-s3', 'platform': 'https:...2025-12-12T10:27:58.000Z2025-12-12T10:28:33.999Z
\n", + "
" + ], + "text/plain": [ + " geometry datetime \\\n", + "0 POLYGON ((-69.99474 -3.94381, -70.45989 -1.795... 2025-11-06T10:27:57Z \n", + "1 POLYGON ((-67.8897 -3.94315, -68.34109 -1.8547... 2025-11-11T10:19:38Z \n", + "2 POLYGON ((-70.35039 -1.73374, -70.81558 -3.881... 2025-11-30T22:58:08Z \n", + "3 POLYGON ((-67.89068 -3.93366, -68.34215 -1.845... 2025-12-05T10:19:39Z \n", + "4 POLYGON ((-69.98483 -3.99241, -70.4499 -1.8445... 2025-12-12T10:27:58Z \n", + "\n", + " storage:schemes \\\n", + "0 {'aws': {'type': 'aws-s3', 'platform': 'https:... \n", + "1 {'aws': {'type': 'aws-s3', 'platform': 'https:... \n", + "2 {'aws': {'type': 'aws-s3', 'platform': 'https:... \n", + "3 {'aws': {'type': 'aws-s3', 'platform': 'https:... \n", + "4 {'aws': {'type': 'aws-s3', 'platform': 'https:... \n", + "\n", + " start_datetime end_datetime \n", + "0 2025-11-06T10:27:57.000Z 2025-11-06T10:28:32.999Z \n", + "1 2025-11-11T10:19:38.000Z 2025-11-11T10:20:12.999Z \n", + "2 2025-11-30T22:58:08.000Z 2025-11-30T22:58:43.999Z \n", + "3 2025-12-05T10:19:39.000Z 2025-12-05T10:20:13.999Z \n", + "4 2025-12-12T10:27:58.000Z 2025-12-12T10:28:33.999Z " + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nisar_gdf = stac_search_to_gdf(\n", + " catalog_url=NISAR_STAC_URL,\n", + " collections=[NISAR_COLLECTION],\n", + " bbox=BBOX,\n", + " datetime=NISAR_DT,\n", + " limit=500,\n", + ")\n", + "print(f\"NISAR items found for {NISAR_COLLECTION}: {len(nisar_gdf)}\")\n", + "nisar_gdf.head()\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "4dad8088-fa80-4f96-bed6-3eb4c2690eb3", + "metadata": {}, + "source": [ + "## BIOMASS coverage\n", + "Search the ESA BIOMASS STAC catalog for the chosen BIOMASS collection(s) intersecting the AOI bbox." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "6d2acbe9-bd83-427d-98e1-29ef4f491cfc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BIOMASS items found (['BiomassLevel1b']): 7\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
geometryeofeos:repeat_cycle_idstart_datetimeend_datetimeprocessing:facilityproduct:typeeofeos:global_coverage_idsat:anx_datetimetitleplatform...sar:observation_directionsar:polarizationsauth:schemessar:instrument_modeeofeos:orbit_drift_flagprocessing:datetimeeofeos:mission_phaseupdatedsat:absolute_orbitproduct:acquisition_type
0POLYGON ((-70.63902 -4.03723, -70.09614 -4.150...42026-02-20T22:49:07.442Z2026-02-20T22:49:27.731ZBiomass CPFS1_DGM__1S12026-02-20T21:59:28.073ZBIO_S1_DGM__1S_20260220T224907_20260220T224927...Biomass...left[HH, HV, VH, VV]{'s3': {'type': 's3'}, 'oidc': {'openIdConnect...SMFalse2026-02-21T10:04:09ZTOMOGRAPHIC2026-02-27T14:23:39Z4365nominal
1POLYGON ((-70.64701 -4.03701, -70.10399 -4.150...52026-02-23T22:49:09.093Z2026-02-23T22:49:29.382ZBiomass CPFS1_DGM__1S12026-02-23T21:59:29.731ZBIO_S1_DGM__1S_20260223T224909_20260223T224929...Biomass...left[HH, HV, VH, VV]{'s3': {'type': 's3'}, 'oidc': {'openIdConnect...SMFalse2026-02-24T11:00:18ZTOMOGRAPHIC2026-02-27T14:28:20Z4409nominal
2POLYGON ((-70.66071 -4.03709, -70.11783 -4.150...72026-03-01T22:49:12.783Z2026-03-01T22:49:33.071ZBiomass CPFS1_DGM__1S12026-03-01T21:59:33.396ZBIO_S1_DGM__1S_20260301T224912_20260301T224933...Biomass...left[HH, HV, VH, VV]{'s3': {'type': 's3'}, 'oidc': {'openIdConnect...SMFalse2026-03-02T11:02:12ZTOMOGRAPHIC2026-03-02T13:01:34Z4497nominal
3POLYGON ((-70.62412 -4.03726, -70.08149 -4.150...22026-02-14T22:49:03.840Z2026-02-14T22:49:24.128ZBiomass CPFS1_DGM__1S12026-02-14T21:59:24.479ZBIO_S1_DGM__1S_20260214T224903_20260214T224924...Biomass...left[HH, HV, VH, VV]{'s3': {'type': 's3'}, 'oidc': {'openIdConnect...SMFalse2026-02-15T10:26:08ZTOMOGRAPHIC2026-02-27T14:14:10Z4277nominal
4POLYGON ((-70.6162 -4.03741, -70.07372 -4.1507...12026-02-11T22:49:02.000Z2026-02-11T22:49:22.289ZBiomass CPFS1_DGM__1S12026-02-11T21:59:22.648ZBIO_S1_DGM__1S_20260211T224902_20260211T224922...Biomass...left[HH, HV, VH, VV]{'s3': {'type': 's3'}, 'oidc': {'openIdConnect...SMFalse2026-02-12T10:02:06ZTOMOGRAPHIC2026-02-27T14:09:30Z4233nominal
\n", + "

5 rows × 35 columns

\n", + "
" + ], + "text/plain": [ + " geometry eofeos:repeat_cycle_id \\\n", + "0 POLYGON ((-70.63902 -4.03723, -70.09614 -4.150... 4 \n", + "1 POLYGON ((-70.64701 -4.03701, -70.10399 -4.150... 5 \n", + "2 POLYGON ((-70.66071 -4.03709, -70.11783 -4.150... 7 \n", + "3 POLYGON ((-70.62412 -4.03726, -70.08149 -4.150... 2 \n", + "4 POLYGON ((-70.6162 -4.03741, -70.07372 -4.1507... 1 \n", + "\n", + " start_datetime end_datetime processing:facility \\\n", + "0 2026-02-20T22:49:07.442Z 2026-02-20T22:49:27.731Z Biomass CPF \n", + "1 2026-02-23T22:49:09.093Z 2026-02-23T22:49:29.382Z Biomass CPF \n", + "2 2026-03-01T22:49:12.783Z 2026-03-01T22:49:33.071Z Biomass CPF \n", + "3 2026-02-14T22:49:03.840Z 2026-02-14T22:49:24.128Z Biomass CPF \n", + "4 2026-02-11T22:49:02.000Z 2026-02-11T22:49:22.289Z Biomass CPF \n", + "\n", + " product:type eofeos:global_coverage_id sat:anx_datetime \\\n", + "0 S1_DGM__1S 1 2026-02-20T21:59:28.073Z \n", + "1 S1_DGM__1S 1 2026-02-23T21:59:29.731Z \n", + "2 S1_DGM__1S 1 2026-03-01T21:59:33.396Z \n", + "3 S1_DGM__1S 1 2026-02-14T21:59:24.479Z \n", + "4 S1_DGM__1S 1 2026-02-11T21:59:22.648Z \n", + "\n", + " title platform ... \\\n", + "0 BIO_S1_DGM__1S_20260220T224907_20260220T224927... Biomass ... \n", + "1 BIO_S1_DGM__1S_20260223T224909_20260223T224929... Biomass ... \n", + "2 BIO_S1_DGM__1S_20260301T224912_20260301T224933... Biomass ... \n", + "3 BIO_S1_DGM__1S_20260214T224903_20260214T224924... Biomass ... \n", + "4 BIO_S1_DGM__1S_20260211T224902_20260211T224922... Biomass ... \n", + "\n", + " sar:observation_direction sar:polarizations \\\n", + "0 left [HH, HV, VH, VV] \n", + "1 left [HH, HV, VH, VV] \n", + "2 left [HH, HV, VH, VV] \n", + "3 left [HH, HV, VH, VV] \n", + "4 left [HH, HV, VH, VV] \n", + "\n", + " auth:schemes sar:instrument_mode \\\n", + "0 {'s3': {'type': 's3'}, 'oidc': {'openIdConnect... SM \n", + "1 {'s3': {'type': 's3'}, 'oidc': {'openIdConnect... SM \n", + "2 {'s3': {'type': 's3'}, 'oidc': {'openIdConnect... SM \n", + "3 {'s3': {'type': 's3'}, 'oidc': {'openIdConnect... SM \n", + "4 {'s3': {'type': 's3'}, 'oidc': {'openIdConnect... SM \n", + "\n", + " eofeos:orbit_drift_flag processing:datetime eofeos:mission_phase \\\n", + "0 False 2026-02-21T10:04:09Z TOMOGRAPHIC \n", + "1 False 2026-02-24T11:00:18Z TOMOGRAPHIC \n", + "2 False 2026-03-02T11:02:12Z TOMOGRAPHIC \n", + "3 False 2026-02-15T10:26:08Z TOMOGRAPHIC \n", + "4 False 2026-02-12T10:02:06Z TOMOGRAPHIC \n", + "\n", + " updated sat:absolute_orbit product:acquisition_type \n", + "0 2026-02-27T14:23:39Z 4365 nominal \n", + "1 2026-02-27T14:28:20Z 4409 nominal \n", + "2 2026-03-02T13:01:34Z 4497 nominal \n", + "3 2026-02-27T14:14:10Z 4277 nominal \n", + "4 2026-02-27T14:09:30Z 4233 nominal \n", + "\n", + "[5 rows x 35 columns]" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "biomass_gdf = stac_search_to_gdf(\n", + " catalog_url=BIOMASS_STAC_URL,\n", + " collections=BIOMASS_COLLECTIONS,\n", + " bbox=BBOX,\n", + " datetime=BIOMASS_DT,\n", + " limit=500,\n", + ")\n", + "print(f\"BIOMASS items found ({BIOMASS_COLLECTIONS}): {len(biomass_gdf)}\")\n", + "biomass_gdf.head()\n" + ] + }, + { + "cell_type": "markdown", + "id": "110ce4ab-5aa8-46b7-8398-c85185a090ac", + "metadata": {}, + "source": [ + "## GEDI coverage\n", + "Search the GEDI STAC catalog for GEDI L4A AGBD intersecting the buffered AOI bbox (GEDI tracks can be sparse)." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "80fcde43-f814-4693-8d8e-16e0d0982592", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "GEDI items found for GEDI_L4A_AGB_Density_V2_1_2056_2.1: 138\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
geometrydatetimestorage:schemesstart_datetimeend_datetime
0POLYGON ((-90.92339 -28.63784, -88.13659 -25.8...2019-04-20T09:42:53Z{'aws': {'type': 'aws-s3', 'platform': 'https:...2019-04-20T09:42:53.000Z2019-04-20T09:52:33.000Z
1POLYGON ((-72.56963 -0.28696, -70.40899 -3.350...2019-05-17T11:26:30Z{'aws': {'type': 'aws-s3', 'platform': 'https:...2019-05-17T11:26:30.000Z2019-05-17T11:36:36.000Z
2POLYGON ((-73.19654 -0.02675, -71.0381 -3.0880...2019-05-24T08:44:16Z{'aws': {'type': 'aws-s3', 'platform': 'https:...2019-05-24T08:44:16.000Z2019-05-24T08:54:31.000Z
3POLYGON ((-90.70751 -29.22035, -87.8886 -26.44...2019-06-12T12:34:08Z{'aws': {'type': 'aws-s3', 'platform': 'https:...2019-06-12T12:34:08.000Z2019-06-12T12:44:00.000Z
4POLYGON ((-72.85289 -0.06564, -70.69566 -3.124...2019-06-18T22:34:32Z{'aws': {'type': 'aws-s3', 'platform': 'https:...2019-06-18T22:34:32.000Z2019-06-18T22:44:43.000Z
\n", + "
" + ], + "text/plain": [ + " geometry datetime \\\n", + "0 POLYGON ((-90.92339 -28.63784, -88.13659 -25.8... 2019-04-20T09:42:53Z \n", + "1 POLYGON ((-72.56963 -0.28696, -70.40899 -3.350... 2019-05-17T11:26:30Z \n", + "2 POLYGON ((-73.19654 -0.02675, -71.0381 -3.0880... 2019-05-24T08:44:16Z \n", + "3 POLYGON ((-90.70751 -29.22035, -87.8886 -26.44... 2019-06-12T12:34:08Z \n", + "4 POLYGON ((-72.85289 -0.06564, -70.69566 -3.124... 2019-06-18T22:34:32Z \n", + "\n", + " storage:schemes \\\n", + "0 {'aws': {'type': 'aws-s3', 'platform': 'https:... \n", + "1 {'aws': {'type': 'aws-s3', 'platform': 'https:... \n", + "2 {'aws': {'type': 'aws-s3', 'platform': 'https:... \n", + "3 {'aws': {'type': 'aws-s3', 'platform': 'https:... \n", + "4 {'aws': {'type': 'aws-s3', 'platform': 'https:... \n", + "\n", + " start_datetime end_datetime \n", + "0 2019-04-20T09:42:53.000Z 2019-04-20T09:52:33.000Z \n", + "1 2019-05-17T11:26:30.000Z 2019-05-17T11:36:36.000Z \n", + "2 2019-05-24T08:44:16.000Z 2019-05-24T08:54:31.000Z \n", + "3 2019-06-12T12:34:08.000Z 2019-06-12T12:44:00.000Z \n", + "4 2019-06-18T22:34:32.000Z 2019-06-18T22:44:43.000Z " + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gedi_gdf = stac_search_to_gdf(\n", + " catalog_url=GEDI_STAC_URL,\n", + " collections=[GEDI_COLLECTION],\n", + " bbox=BBOX_BUF, # use buffered bbox for GEDI discovery\n", + " datetime=GEDI_DT,\n", + " limit=500,\n", + ")\n", + "print(f\"GEDI items found for {GEDI_COLLECTION}: {len(gedi_gdf)}\")\n", + "gedi_gdf.head()\n" + ] + }, + { + "cell_type": "markdown", + "id": "ab01e52c-d8e2-4fd6-b89e-32002c6b81f3", + "metadata": {}, + "source": [ + "## Map: AOI + NISAR + BIOMASS + GEDI\n", + "Interactive map showing all three datasets as different colored layers." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "16646ba5-6849-44eb-8d82-2c0e9fdfe774", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# One map with AOI + NISAR + BIOMASS + GEDI (different colors)\n", + "\n", + "m = folium.Map(location=[(miny + maxy) / 2, (minx + maxx) / 2], zoom_start=8, tiles=\"OpenStreetMap\")\n", + "\n", + "def _style(color, weight=2, fill_opacity=0.05):\n", + " return lambda _: {\"color\": color, \"weight\": weight, \"fillOpacity\": fill_opacity}\n", + "\n", + "# AOI outline (red)\n", + "folium.GeoJson(\n", + " data={\"type\": \"Feature\", \"geometry\": mapping(aoi_geom), \"properties\": {\"name\": \"AOI\"}},\n", + " name=\"AOI\",\n", + " style_function=_style(\"red\", weight=3, fill_opacity=0.0),\n", + ").add_to(m)\n", + "\n", + "# NISAR footprints (blue)\n", + "if len(nisar_gdf) > 0:\n", + " folium.GeoJson(\n", + " nisar_gdf,\n", + " name=\"NISAR\",\n", + " style_function=_style(\"blue\", weight=2, fill_opacity=0.05),\n", + " ).add_to(m)\n", + "\n", + "# BIOMASS footprints (green)\n", + "if len(biomass_gdf) > 0:\n", + " folium.GeoJson(\n", + " biomass_gdf,\n", + " name=\"BIOMASS\",\n", + " style_function=_style(\"green\", weight=2, fill_opacity=0.05),\n", + " ).add_to(m)\n", + "\n", + "# GEDI footprints (orange)\n", + "if len(gedi_gdf) > 0:\n", + " folium.GeoJson(\n", + " gedi_gdf,\n", + " name=\"GEDI L4A AGBD\",\n", + " style_function=_style(\"orange\", weight=1, fill_opacity=0.001),\n", + " ).add_to(m)\n", + "\n", + "folium.LayerControl(collapsed=False).add_to(m)\n", + "m" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "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.12.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}