From 4a3eff698ca34a3e85f4ea3ab24c5ea479ae761d Mon Sep 17 00:00:00 2001 From: Harshini Date: Mon, 2 Feb 2026 15:32:17 -0600 Subject: [PATCH 1/3] visualizing_NISAR_BIOMASS --- .../visualizing_NISAR_BIOMASS.ipynb | 987 ++++++++++++++++++ 1 file changed, 987 insertions(+) create mode 100644 docs/source/technical_tutorials/visualization/visualizing_NISAR_BIOMASS.ipynb diff --git a/docs/source/technical_tutorials/visualization/visualizing_NISAR_BIOMASS.ipynb b/docs/source/technical_tutorials/visualization/visualizing_NISAR_BIOMASS.ipynb new file mode 100644 index 00000000..28dfc824 --- /dev/null +++ b/docs/source/technical_tutorials/visualization/visualizing_NISAR_BIOMASS.ipynb @@ -0,0 +1,987 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "374aa2b1", + "metadata": {}, + "source": [ + "# NISAR and ESA BIOMASS: OVERLAPP\n", + "\n", + "Date: Febuary 2,2026\n", + "\n", + "Authors: Harshini Girish(UAH), Rajat Shinde (UAH), Alex Mandel (Development Seed), Samantha Niemoeller (JPL)\n", + "\n", + "\n", + "Description: This notebook queries NISAR L2 GCOV granules (via earthaccess) and ESA CCI BIOMASS V5.01 tiles (via ESA MAAP STAC) for a chosen AOI and time settings. It converts returned items to footprint polygons and plots them on a single interactive Folium map as two toggleable layers.\n", + "An optional overlap layer highlights where NISAR and BIOMASS footprints intersect (bbox or true geometry). The result quickly shows where data coincides spatially to support fusion workflows." + ] + }, + { + "cell_type": "markdown", + "id": "93b44b7d-f5a5-4048-80e5-737db0998a43", + "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\"](https://docs.maap-project.org/en/latest/getting_started/getting_started.html) section of our documentation.\n", + "\n", + "Disclaimer: it is highly recommended to run a tutorial within MAAP's ADE, which already includes packages specific to MAAP, such as maap-py. Running the tutorial outside of the MAAP ADE may lead to errors. Additionally, it is recommended to use the `Pangeo` workspace within the ADE, since certain packages relevant to this tutorial are already installed." + ] + }, + { + "cell_type": "markdown", + "id": "399aa805-c518-4cde-812d-8729c5e888d9", + "metadata": {}, + "source": [ + "## Additional Resources\n", + "- [NISAR](https://nisar.jpl.nasa.gov/)\n", + "- [BIOMASS](https://docs.maap-project.org/en/develop/science/ESA_CCI/ESA_CCI_V5_Token_Access.html)\n" + ] + }, + { + "cell_type": "markdown", + "id": "a328ae66-198a-4906-b2f5-ffc387ee44b1", + "metadata": {}, + "source": [ + "## Import and Install Packages" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "86047982", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import stat\n", + "import getpass\n", + "import pathlib\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import earthaccess\n", + "from pystac_client import Client\n", + "\n", + "from shapely.geometry import Polygon, box, mapping, shape\n", + "from collections import Counter\n", + "\n", + "from folium import Map, GeoJson, LayerControl\n", + "\n", + "plt.rcParams[\"figure.figsize\"] = (6, 6)\n", + "plt.rcParams[\"axes.grid\"] = False\n" + ] + }, + { + "cell_type": "markdown", + "id": "80529155", + "metadata": {}, + "source": [ + "## Inputs\n", + "This “Inputs” section defines the search settings used later in the notebook: BBOX sets the area of interest as (min_lon, min_lat, max_lon, max_lat) and can be used to spatially filter both datasets, `NISAR_TEMPORAL = (\"2025-10-01\", \"2025-12-31\")` restricts the NISAR search to granules acquired within that date range, and `NISAR_COUNT = 6` limits how many NISAR granules (and footprints) will be plotted. For BIOMASS, `BIOMASS_YEAR = 2010` selects a specific annual layer, and BIOMASS_DT converts it into a STAC datetime range (2010-01-01/2010-12-31) used in the BIOMASS query; the tip warns that choosing a year outside the collection’s indexed range (often 2010–2021) will return zero results.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8ed53932-c0b5-4223-ba86-aed95cbd65d3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NISAR_TEMPORAL: ('2025-10-01', '2025-12-31')\n", + "BIOMASS_DT: 2010-01-01/2010-12-31\n" + ] + } + ], + "source": [ + "NISAR_TEMPORAL = (\"2025-10-01\", \"2025-12-31\")\n", + "\n", + "# How many NISAR granules to plot\n", + "NISAR_COUNT = 6\n", + "\n", + "# BIOMASS year (choose explicitly from available years: 2010..2021)\n", + "BIOMASS_YEAR = 2010\n", + "BIOMASS_DT = f\"{BIOMASS_YEAR}-01-01/{BIOMASS_YEAR}-12-31\"\n", + "BIOMASS_LIMIT = 500 \n", + "\n", + "print(\"NISAR_TEMPORAL:\", NISAR_TEMPORAL)\n", + "print(\"BIOMASS_DT:\", BIOMASS_DT)\n" + ] + }, + { + "cell_type": "markdown", + "id": "e2dc36db-f686-41e7-8cbf-c152fcbf84fc", + "metadata": {}, + "source": [ + "## Access the Data\n" + ] + }, + { + "cell_type": "markdown", + "id": "5b6c52d4", + "metadata": {}, + "source": [ + "### 1) NISAR data" + ] + }, + { + "cell_type": "markdown", + "id": "9a8b1506-d6a1-422b-984c-92e172e298b7", + "metadata": {}, + "source": [ + "This cell sets the NISAR collection short name (`NISAR_L2_GCOV_BETA_V1`), logs in to Earthdata via `earthaccess.login()`, and then queries CMR for matching NISAR granules within the specified `NISAR_TEMPORAL` window, limited to `NISAR_COUNT` results and filtered to cloud-hosted items. Finally, it prints how many granules were returned by the search.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a7711773", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NISAR granules found: 6\n" + ] + } + ], + "source": [ + "NISAR_SHORT_NAME = \"NISAR_L2_GCOV_BETA_V1\"\n", + "\n", + "earthaccess.login()\n", + "\n", + "nisar_results = earthaccess.search_data(\n", + " short_name=NISAR_SHORT_NAME,\n", + " cloud_hosted=True,\n", + " temporal=NISAR_TEMPORAL,\n", + " count=NISAR_COUNT,\n", + ")\n", + "\n", + "print(\"NISAR granules found:\", len(nisar_results))\n" + ] + }, + { + "cell_type": "markdown", + "id": "642c4607-928f-4319-bbfc-6407c84267d0", + "metadata": {}, + "source": [ + "This cell loops through the NISAR search results and converts each granule into a GeoJSON footprint feature using `nisar_granule_to_feature(g)`, skipping any granules that don’t contain usable footprint metadata. It then bundles all successfully created footprint features into a GeoJSON `FeatureCollection` called `nisar_fc`. Finally, it prints how many NISAR footprint polygons were added to the collection for mapping.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "d2c4f32a-66c6-48d3-81aa-005bed1ebfd1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NISAR footprints in FeatureCollection: 6\n" + ] + } + ], + "source": [ + "# Build NISAR FeatureCollection for mapping\n", + "nisar_features = []\n", + "for g in nisar_results:\n", + " try:\n", + " nisar_features.append(nisar_granule_to_feature(g))\n", + " except Exception as e:\n", + " print(\"Skipping granule (no footprint):\", e)\n", + "\n", + "nisar_fc = {\"type\": \"FeatureCollection\", \"features\": nisar_features}\n", + "print(\"NISAR footprints in FeatureCollection:\", len(nisar_fc[\"features\"]))\n" + ] + }, + { + "cell_type": "markdown", + "id": "883e8a62-08b4-46e7-8d98-9edd787f6cb9", + "metadata": {}, + "source": [ + "This cell defines helper functions used before visualization. `_get_umm(g)` safely extracts the UMM metadata dictionary from an `earthaccess` granule. `nisar_granule_to_feature(g)` then converts a single NISAR granule into a GeoJSON Feature by reading its spatial geometry (preferring a polygon from `GPolygons` and falling back to a `BoundingRectangles` box if needed), extracting the granule’s start/end times, and attaching an ID/title in the feature properties. The output Feature objects are later collected into a FeatureCollection and plotted on the interactive map.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0961138d", + "metadata": {}, + "outputs": [], + "source": [ + "def _get_umm(g):\n", + " try:\n", + " return g.get(\"umm\", {})\n", + " except Exception:\n", + " return {}\n", + "\n", + "\n", + "def nisar_granule_to_feature(g):\n", + " umm = _get_umm(g)\n", + "\n", + " geom = (\n", + " umm.get(\"SpatialExtent\", {})\n", + " .get(\"HorizontalSpatialDomain\", {})\n", + " .get(\"Geometry\", {})\n", + " )\n", + "\n", + " poly = None\n", + "\n", + " # Prefer polygon boundary\n", + " gpolys = geom.get(\"GPolygons\", [])\n", + " if gpolys:\n", + " pts = gpolys[0].get(\"Boundary\", {}).get(\"Points\", [])\n", + " if pts:\n", + " coords = [(p[\"Longitude\"], p[\"Latitude\"]) for p in pts]\n", + " if coords and coords[0] != coords[-1]:\n", + " coords = coords + [coords[0]]\n", + " poly = Polygon(coords)\n", + "\n", + " # Fallback to bounding rectangle\n", + " if poly is None:\n", + " rects = geom.get(\"BoundingRectangles\", [])\n", + " if rects:\n", + " r = rects[0]\n", + " poly = box(\n", + " r[\"WestBoundingCoordinate\"],\n", + " r[\"SouthBoundingCoordinate\"],\n", + " r[\"EastBoundingCoordinate\"],\n", + " r[\"NorthBoundingCoordinate\"],\n", + " )\n", + "\n", + " if poly is None:\n", + " raise ValueError(\"Could not extract footprint geometry from NISAR granule metadata\")\n", + "\n", + " # Time\n", + " time_range = (\n", + " umm.get(\"TemporalExtent\", {})\n", + " .get(\"RangeDateTime\", {})\n", + " )\n", + " t0 = time_range.get(\"BeginningDateTime\")\n", + " t1 = time_range.get(\"EndingDateTime\")\n", + "\n", + " # Title/ID-like field\n", + " title = umm.get(\"GranuleUR\") or umm.get(\"Title\") or \"NISAR granule\"\n", + "\n", + " return {\n", + " \"type\": \"Feature\",\n", + " \"geometry\": mapping(poly),\n", + " \"properties\": {\"title\": title, \"t0\": t0, \"t1\": t1},\n", + " }\n" + ] + }, + { + "cell_type": "markdown", + "id": "e94fa9b5", + "metadata": {}, + "source": [ + "### 2) ESA BIOMASS data\n", + "This cell manages your ESA MAAP access token by storing it in a local file (`~/.config/esa_maap/ticket`). If the token file doesn’t exist, it securely prompts you to paste the token, saves it, and sets strict permissions (read/write only for you). It then verifies the file permissions are exactly `600` for security and finally reads the token into `ESA_TOKEN`, confirming where it was loaded from for use in BIOMASS/ESA authenticated requests.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "8c979f77", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Token loaded from file: /projects/.config/esa_maap/ticket\n" + ] + } + ], + "source": [ + "#ESA token file (used if/when you need Authorization for assets) \n", + "TOKEN_FILE = pathlib.Path.home() / \".config\" / \"esa_maap\" / \"ticket\"\n", + "TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True)\n", + "\n", + "if not TOKEN_FILE.exists():\n", + " tok = getpass.getpass(\"Paste ESA portal token (hidden): \").strip()\n", + " if not tok:\n", + " raise ValueError(\"Empty token.\")\n", + " TOKEN_FILE.write_text(tok, encoding=\"utf-8\")\n", + " TOKEN_FILE.chmod(stat.S_IRUSR | stat.S_IWUSR)\n", + "\n", + "st = TOKEN_FILE.stat()\n", + "if (st.st_mode & 0o777) != 0o600:\n", + " raise PermissionError(f\"{TOKEN_FILE} must have mode 600. Fix with: chmod 600 {TOKEN_FILE}\")\n", + "\n", + "ESA_TOKEN = TOKEN_FILE.read_text(encoding=\"utf-8\").strip()\n", + "print(\"Token loaded from file:\", TOKEN_FILE)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "72448e29-8942-40ec-8ab6-e38af08380ae", + "metadata": {}, + "outputs": [], + "source": [ + "USE_BIOMASS_BBOX = False # set True to restrict BIOMASS to AOI, False for more items" + ] + }, + { + "cell_type": "markdown", + "id": "590dcbe8-cd62-4c94-ac11-8d6257f6c8f2", + "metadata": {}, + "source": [ + "This cell queries the ESA MAAP STAC catalog for BIOMASS V5.01 items. It opens the STAC endpoint, builds a `search_kwargs` dictionary with the BIOMASS collection, the selected year/time window (`BIOMASS_DT`), and a result cap (`BIOMASS_LIMIT = 25`). If `USE_BIOMASS_BBOX` is enabled, it also adds your AOI bounding box to restrict results spatially; otherwise it searches more broadly. Finally, it executes the STAC search, collects the returned items into `biomass_items`, and prints how many BIOMASS items were found and whether the bbox filter was applied.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "a8d0f7bb-19b8-41c3-99b1-cde5b9d081b7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BIOMASS items found: 619\n" + ] + } + ], + "source": [ + "STAC_URL = \"https://catalog.maap.eo.esa.int/catalogue/\"\n", + "BIOMASS_COLLECTION = \"CCIBiomassV5.01\"\n", + "\n", + "BIOMASS_LIMIT = 25 \n", + "\n", + "api = Client.open(STAC_URL)\n", + "\n", + "search_kwargs = dict(\n", + " collections=[BIOMASS_COLLECTION],\n", + " datetime=BIOMASS_DT,\n", + " limit=BIOMASS_LIMIT,\n", + ")\n", + "\n", + "if USE_BIOMASS_BBOX:\n", + " search_kwargs[\"bbox\"] = list(BBOX)\n", + "\n", + "search = api.search(**search_kwargs)\n", + "\n", + "biomass_items = list(search.get_items())\n", + "print(\"BIOMASS items found:\", len(biomass_items))" + ] + }, + { + "cell_type": "markdown", + "id": "83a14cb4-1685-4218-8655-424aa2847203", + "metadata": {}, + "source": [ + "This cell converts the BIOMASS STAC search results (`biomass_items`) into a GeoJSON `FeatureCollection` for mapping. It defines `biomass_item_to_feature()` to package each STAC item’s footprint geometry (`item.geometry`) and key metadata fields (item id and temporal fields like `start_datetime`/`end_datetime`) into a GeoJSON Feature. It then applies this conversion to every BIOMASS item, stores the results in `biomass_fc`, and prints how many BIOMASS footprint features are available to plot on the interactive map (here, 619).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "5bfaa8f5-82d9-4a19-aadb-fe5a75c75f81", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BIOMASS footprints in FeatureCollection: 619\n" + ] + } + ], + "source": [ + "# Build BIOMASS FeatureCollection for mapping\n", + "def biomass_item_to_feature(item):\n", + " return {\n", + " \"type\": \"Feature\",\n", + " \"geometry\": item.geometry,\n", + " \"properties\": {\n", + " \"id\": item.id,\n", + " \"start_datetime\": item.properties.get(\"start_datetime\"),\n", + " \"end_datetime\": item.properties.get(\"end_datetime\"),\n", + " \"datetime\": item.properties.get(\"datetime\"),\n", + " },\n", + " }\n", + "\n", + "biomass_features = [biomass_item_to_feature(it) for it in biomass_items]\n", + "biomass_fc = {\"type\": \"FeatureCollection\", \"features\": biomass_features}\n", + "\n", + "print(\"BIOMASS footprints in FeatureCollection:\", len(biomass_fc[\"features\"]))\n" + ] + }, + { + "cell_type": "markdown", + "id": "729cd4a4", + "metadata": {}, + "source": [ + "## Interactive map: NISAR and BIOMASS footprint layers\n", + "\n", + "This section creates a single interactive Leaflet/Folium map centered on the midpoint of the AOI `BBOX`. It then overlays two GeoJSON layers: one showing the footprint polygons for all discovered NISAR granules (`nisar_fc`) with tooltips for the granule title and start/end times, and another showing the footprint polygons for BIOMASS items (`biomass_fc`) with tooltips for tile ID and temporal coverage. Finally, it adds a `LayerControl` so you can toggle the NISAR and BIOMASS layers on/off to visually compare their spatial overlap.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "b85cc1fe", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create base map centered on bbox\n", + "center_lat = (BBOX[1] + BBOX[3]) / 2\n", + "center_lon = (BBOX[0] + BBOX[2]) / 2\n", + "\n", + "m = Map(tiles=\"OpenStreetMap\", location=(center_lat, center_lon), zoom_start=7)\n", + "\n", + "# Add NISAR footprints\n", + "GeoJson(\n", + " data=nisar_fc,\n", + " name=f\"NISAR ({len(nisar_fc['features'])} granules)\",\n", + " tooltip=[\"title\", \"t0\", \"t1\"],\n", + ").add_to(m)\n", + "\n", + "# Add BIOMASS footprints\n", + "GeoJson(\n", + " data=biomass_fc,\n", + " name=f\"BIOMASS {BIOMASS_YEAR} ({len(biomass_fc['features'])} items)\",\n", + " tooltip=[\"id\", \"start_datetime\", \"end_datetime\"],\n", + ").add_to(m)\n", + "\n", + "LayerControl(collapsed=False).add_to(m)\n", + "\n", + "m\n" + ] + }, + { + "cell_type": "markdown", + "id": "2312467f-cdb5-472e-9429-0ddf09af29bf", + "metadata": {}, + "source": [ + "## Overlap of BIOMASS tiles intersecting with NISAR granule" + ] + }, + { + "cell_type": "markdown", + "id": "a5b33a76-2f12-4a22-8904-57ccd1e71983", + "metadata": {}, + "source": [ + "This cell summarizes the overlap results by counting how many overlap pairs are associated with each NISAR granule index `(nisar_i)` in `overlap_fc`. It prints the total number of NISAR granules and BIOMASS items being compared, the total number of overlap pairs found, and then lists each NISAR granule with the number of BIOMASS items that overlap it, along with the granule’s title. This helps explain why the overlap count can be large: it is counting many BIOMASS-to-one-NISAR pairings, not unique NISAR granules." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "5af0f9df-58e8-4514-bbe9-82b93e97375e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NISAR granules: 6\n", + "BIOMASS items: 619\n", + "Total overlaps (pairs): 134\n", + "0 22 - NISAR_L2_PR_GCOV_003_005_D_077_4005_DHDH_A_20251017T132451_20251017T132526_X05007_N_F_J_001\n", + "1 20 - NISAR_L2_PR_GCOV_003_064_D_130_7700_SHNA_A_20251021T160803_20251021T160836_X05007_N_P_J_001\n", + "2 20 - NISAR_L2_PR_GCOV_004_064_D_130_7700_SHNA_A_20251102T160804_20251102T160837_X05007_N_P_J_001\n", + "3 28 - NISAR_L2_PR_GCOV_004_076_A_022_2005_QPDH_A_20251103T110514_20251103T110549_X05007_N_F_J_002\n", + "4 22 - NISAR_L2_PR_GCOV_005_172_A_008_2005_DHDH_A_20251122T024618_20251122T024652_X05007_N_F_J_001\n", + "5 22 - NISAR_L2_PR_GCOV_006_172_A_008_2005_DHDH_A_20251204T024618_20251204T024653_X05007_N_F_J_001\n" + ] + } + ], + "source": [ + "counts = Counter([f[\"properties\"][\"nisar_i\"] for f in overlap_fc[\"features\"]])\n", + "print(\"NISAR granules:\", len(nisar_fc[\"features\"]))\n", + "print(\"BIOMASS items:\", len(biomass_fc[\"features\"]))\n", + "print(\"Total overlaps (pairs):\", len(overlap_fc[\"features\"]))\n", + "\n", + "for i in range(len(nisar_fc[\"features\"])):\n", + " title = nisar_fc[\"features\"][i][\"properties\"].get(\"title\")\n", + " print(i, counts.get(i, 0), \"-\", title)\n" + ] + }, + { + "cell_type": "markdown", + "id": "c383d669-995a-461c-a465-0cc695895c78", + "metadata": {}, + "source": [ + "This cell computes and visualizes a *bounding-box* overlap layer between the two datasets. First, `geom_bbox_polygon()` converts each feature’s footprint geometry into its rectangular bounding box using `.bounds`, so overlap checks are fast and consistent. It then loops over every NISAR feature and every BIOMASS feature, intersects their bounding boxes (`nb.intersection(bb)`), and whenever the intersection is non-empty it records an “overlap pair” as a new GeoJSON Feature with metadata from both items (NISAR title/time and BIOMASS id/time). All overlap features are collected into `overlap_fc`, the total number of bbox-overlap pairs is printed, and the overlap intersections are added as a third toggleable Folium layer (“Overlap (bbox ∩ bbox)”) so you can click/hover to inspect which NISAR and BIOMASS items overlap in space.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "e9dbf54f-5cea-4ad8-be1e-0e5a16ad3601", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BBox overlaps found: 134\n" + ] + }, + { + "data": { + "text/html": [ + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def geom_bbox_polygon(feature):\n", + " g = shape(feature[\"geometry\"])\n", + " minx, miny, maxx, maxy = g.bounds\n", + " return box(minx, miny, maxx, maxy)\n", + "\n", + "overlap_features = []\n", + "\n", + "nisar_feats = nisar_fc[\"features\"]\n", + "biomass_feats = biomass_fc[\"features\"]\n", + "\n", + "for i, nf in enumerate(nisar_feats):\n", + " nb = geom_bbox_polygon(nf)\n", + "\n", + " for j, bf in enumerate(biomass_feats):\n", + " bb = geom_bbox_polygon(bf)\n", + "\n", + " inter = nb.intersection(bb)\n", + " if not inter.is_empty:\n", + " overlap_features.append({\n", + " \"type\": \"Feature\",\n", + " \"geometry\": mapping(inter),\n", + " \"properties\": {\n", + " \"nisar_i\": i,\n", + " \"biomass_j\": j,\n", + " \"nisar_title\": nf[\"properties\"].get(\"title\"),\n", + " \"nisar_t0\": nf[\"properties\"].get(\"t0\"),\n", + " \"nisar_t1\": nf[\"properties\"].get(\"t1\"),\n", + " \"biomass_id\": bf[\"properties\"].get(\"id\"),\n", + " \"biomass_start\": bf[\"properties\"].get(\"start_datetime\"),\n", + " \"biomass_end\": bf[\"properties\"].get(\"end_datetime\"),\n", + " }\n", + " })\n", + "\n", + "overlap_fc = {\"type\": \"FeatureCollection\", \"features\": overlap_features}\n", + "print(\"BBox overlaps found:\", len(overlap_fc[\"features\"]))\n", + "\n", + "\n", + "GeoJson(\n", + " data=overlap_fc,\n", + " name=\"Overlap (bbox ∩ bbox)\",\n", + " tooltip=[\n", + " \"nisar_title\", \"nisar_t0\", \"nisar_t1\",\n", + " \"biomass_id\", \"biomass_start\", \"biomass_end\"\n", + " ],\n", + ").add_to(m)\n", + "\n", + "LayerControl(collapsed=False).add_to(m)\n", + "m\n" + ] + } + ], + "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 0f4d17d99bd1d70681760a129e628f34792caee2 Mon Sep 17 00:00:00 2001 From: Harshini Date: Mon, 2 Feb 2026 15:42:05 -0600 Subject: [PATCH 2/3] Updatefor nisar nav --- docs/source/technical_tutorials/visualizing.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/technical_tutorials/visualizing.rst b/docs/source/technical_tutorials/visualizing.rst index c87a68b0..b4659626 100644 --- a/docs/source/technical_tutorials/visualizing.rst +++ b/docs/source/technical_tutorials/visualizing.rst @@ -12,4 +12,5 @@ Visualize visualization/stac_ipyleaflet.ipynb visualization/visualize_lonboard.ipynb visualization/Visualizing_OPERA-DISP_tile_with_TiTiler-MultiDim.ipynb + visualization/visualizing_NISAR_BIOMASS From b0355820eb0ee3d142cb49b5be4d6684cb32d1b8 Mon Sep 17 00:00:00 2001 From: Harshini Date: Mon, 2 Feb 2026 15:42:23 -0600 Subject: [PATCH 3/3] Update visualizing.rst --- docs/source/technical_tutorials/visualizing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/technical_tutorials/visualizing.rst b/docs/source/technical_tutorials/visualizing.rst index b4659626..bb457e1f 100644 --- a/docs/source/technical_tutorials/visualizing.rst +++ b/docs/source/technical_tutorials/visualizing.rst @@ -12,5 +12,5 @@ Visualize visualization/stac_ipyleaflet.ipynb visualization/visualize_lonboard.ipynb visualization/Visualizing_OPERA-DISP_tile_with_TiTiler-MultiDim.ipynb - visualization/visualizing_NISAR_BIOMASS + visualization/visualizing_NISAR_BIOMASS.ipynb