diff --git a/docs/source/science/NISAR/NISAR_DEM.ipynb b/docs/source/science/NISAR/NISAR_DEM.ipynb new file mode 100644 index 00000000..10918fea --- /dev/null +++ b/docs/source/science/NISAR/NISAR_DEM.ipynb @@ -0,0 +1,15650 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "eb30718a-017a-445f-9518-be1e9e99b1d6", + "metadata": {}, + "source": [ + "# NISAR DEM: Cloud-Optimized Access and Visualization" + ] + }, + { + "cell_type": "markdown", + "id": "b93405e4-684b-4bf4-bf25-fc99a8d398df", + "metadata": {}, + "source": [ + "Authors: Harshini Girish(UAH), Alex Mandel(Development Seed)\n", + "\n", + "Date:Febuary 12, 2026\n", + "\n", + "Description:This notebook shows how to avoid full downloads by querying CMR for NISAR DEM granules (collection C3803703055-ASF), extracting each granule’s bounding-box footprint + data link, then using Earthdata login + temporary S3 credentials to open the DEM directly from S3 via /vsis3/ and stream only a small window of pixels with Rasterio/GDAL, finally visualizing the footprints and the streamed preview on an interactive Folium map." + ] + }, + { + "cell_type": "markdown", + "id": "bad7654e-2ec5-4a68-8d6e-0280c2d68236", + "metadata": {}, + "source": [ + "## Run This Notebook\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." + ] + }, + { + "cell_type": "markdown", + "id": "6e621e6d-3d09-40a1-80b9-8476706b85fd", + "metadata": {}, + "source": [ + "## Additional Resources\n", + "- [OPERA Surface Displacement](https://docs.maap-project.org/en/latest/science/OPERA/OPERA_Surface_Displacement.html)\n", + "- [NISAR Access](https://docs.maap-project.org/en/latest/science/NISAR/NISAR_access.html)\n", + "- [Searching NISAR–BIOMASS Overlapping Data](https://docs.maap-project.org/en/develop/technical_tutorials/search/searching_NISAR_BIOMASS_overlapping_data.html)\n" + ] + }, + { + "cell_type": "markdown", + "id": "3bade9ac-0e9c-4aab-95ae-d6ba66f5ec7a", + "metadata": {}, + "source": [ + "## About the Dataset\n", + "\n", + "The CMR collection C3803703055-ASF is the ASF-hosted NISAR DEM distribution, which provides the Modified Copernicus Digital Elevation Model products used by the NISAR mission. It contains global elevation data packaged for cloud delivery (including DEM tiles and supporting index/metadata files) and is distributed through the NISAR EarthdataCloud endpoints. The collection supports both standard HTTP access and direct in-region S3 access (via an S3 bucket/prefix and a temporary credentials endpoint), which makes it well-suited for “streaming” workflows where you read only the needed byte ranges/windows from remote GeoTIFF/COG tiles instead of downloading full rasters." + ] + }, + { + "cell_type": "markdown", + "id": "8a471361-f522-4c9f-a888-fe905f46b85d", + "metadata": {}, + "source": [ + "## Import and Install Packages" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "508578d2-862a-4e3c-9ca7-c0269981484a", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import io\n", + "import base64\n", + "from urllib.parse import urlparse\n", + "\n", + "import requests\n", + "import numpy as np\n", + "\n", + "import earthaccess\n", + "import rasterio as rio\n", + "from rasterio.windows import from_bounds\n", + "\n", + "import geopandas as gpd\n", + "from shapely.geometry import box, mapping\n", + "\n", + "import folium\n", + "from PIL import Image" + ] + }, + { + "cell_type": "markdown", + "id": "d10be82c-0793-4672-9048-a4ab3059f1ff", + "metadata": {}, + "source": [ + "## Input\n", + "Define an Area of Interest (AOI)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "6ee943c8-4f8d-455e-bfe2-28e996376b57", + "metadata": {}, + "outputs": [], + "source": [ + "# AOI inside the chosen tile (S90_W180) \n", + "target_aoi_bounds = (-179.8, -89.8, -179.2, -89.2)\n" + ] + }, + { + "cell_type": "markdown", + "id": "a7404665-9ea2-414e-a0cc-c14084efe6cb", + "metadata": {}, + "source": [ + "## Searching the Data " + ] + }, + { + "cell_type": "markdown", + "id": "651db75d-8ebd-4386-98bb-3a58478a2211", + "metadata": {}, + "source": [ + "This cell queries NASA’s Common Metadata Repository using the `granules.umm_json` endpoint to retrieve granule metadata for a specific collection, `fetch_granules()` sends an HTTP request (waiting up to `timeout=60` seconds) and returns the list of granules under `[\"items\"]`. For each granule’s UMM metadata, `bounding_boxes()` extracts any `BoundingRectangles` and converts them into simple tuples that can later be turned into footprint polygons for mapping/intersection. `data_link()` then looks through `RelatedUrls` and picks the best file access link—preferring `s3://` direct access** for streaming via `/vsis3`, and otherwise falling back to the first HTTP link that looks like a raster/index file. Finally, the last lines run the query for the NISAR DEM collection and print how many granules were returned on that page." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2a973cfd-1dad-4d08-a70d-cc02f2ea9705", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Granules fetched: 200\n" + ] + } + ], + "source": [ + "CMR_URL = \"https://cmr.earthdata.nasa.gov/search/granules.umm_json\"\n", + "\n", + "def fetch_granules(concept_id, n=200, page=1):\n", + " r = requests.get(\n", + " CMR_URL,\n", + " params={\"collection_concept_id\": concept_id, \"page_size\": n, \"page_num\": page},\n", + " timeout=60,\n", + " )\n", + " r.raise_for_status()\n", + " return r.json()[\"items\"]\n", + "\n", + "def bounding_boxes(umm):\n", + " rects = (\n", + " umm.get(\"SpatialExtent\", {})\n", + " .get(\"HorizontalSpatialDomain\", {})\n", + " .get(\"Geometry\", {})\n", + " .get(\"BoundingRectangles\", [])\n", + " )\n", + " return [(r[\"WestBoundingCoordinate\"], r[\"SouthBoundingCoordinate\"],\n", + " r[\"EastBoundingCoordinate\"], r[\"NorthBoundingCoordinate\"]) for r in rects]\n", + "\n", + "def data_link(umm):\n", + " # Prefer direct s3:// access; otherwise first http(s) .tif/.tiff/.vrt link\n", + " urls = [ru.get(\"URL\", \"\") for ru in umm.get(\"RelatedUrls\", [])]\n", + " for u in urls:\n", + " if u.startswith(\"s3://\"):\n", + " return u\n", + " for u in urls:\n", + " if u.lower().startswith(\"http\") and u.lower().endswith((\".tif\", \".tiff\", \".vrt\")):\n", + " return u\n", + " return None\n", + "\n", + "concept_id = \"C3803703055-ASF\"\n", + "items = fetch_granules(concept_id, n=200, page=1)\n", + "print(\"Granules fetched:\", len(items))\n" + ] + }, + { + "cell_type": "markdown", + "id": "a1fecb19-cecc-4da9-b0a3-2debd2e4bf35", + "metadata": {}, + "source": [ + "### Build Footprints GeoDataFrame" + ] + }, + { + "cell_type": "markdown", + "id": "00181775-44e5-4e5d-85ab-1b22375c13cd", + "metadata": {}, + "source": [ + "This cell builds a GeoDataFrame of granule footprints for the CMR collection `C3803703055-ASF`. It loops through each returned granule (`items`), pulls out the UMM metadata and the granule concept-id, then uses `bounding_boxes(umm)` to extract one or more bounding rectangles for that granule. For every bounding box, it appends a row containing the granule id, a readable title (`GranuleUR`), a preferred access link, and a Shapely polygon created from the bounds (`box(*b)`). Finally, it converts the list of rows into a GeoPandas `GeoDataFrame` with the geometry column set to `\"geometry\"` and CRS set to `EPSG:4326`, so you can map the footprints, filter by AOI overlap, and later use `data_url` to stream the selected raster without downloading the full file.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "60b3685f-de77-46bb-9c47-c108c9f78656", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Footprints: 200\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", + "
concept_idtitledata_urlgeometry
0G3964549387-ASFv1-2-EPSG4326-S90-S90_W180-DEM_S90_00_W180_00_...s3://sds-n-cumulus-prod-nisar-products/DEM/v1....POLYGON ((-179 -90, -179 -89, -180 -89, -180 -...
1G3964549482-ASFv1-2-EPSG4326-S90-S90_W180-DEM_S90_00_W179_00_...s3://sds-n-cumulus-prod-nisar-products/DEM/v1....POLYGON ((-178 -90, -178 -89, -179 -89, -179 -...
2G3964549304-ASFv1-2-EPSG4326-S90-S90_W180-DEM_S90_00_W178_00_...s3://sds-n-cumulus-prod-nisar-products/DEM/v1....POLYGON ((-177 -90, -177 -89, -178 -89, -178 -...
3G3964549526-ASFv1-2-EPSG4326-S90-S90_W180-DEM_S90_00_W177_00_...s3://sds-n-cumulus-prod-nisar-products/DEM/v1....POLYGON ((-176 -90, -176 -89, -177 -89, -177 -...
4G3964549467-ASFv1-2-EPSG4326-S90-S90_W180-DEM_S90_00_W176_00_...s3://sds-n-cumulus-prod-nisar-products/DEM/v1....POLYGON ((-175 -90, -175 -89, -176 -89, -176 -...
\n", + "
" + ], + "text/plain": [ + " concept_id title \\\n", + "0 G3964549387-ASF v1-2-EPSG4326-S90-S90_W180-DEM_S90_00_W180_00_... \n", + "1 G3964549482-ASF v1-2-EPSG4326-S90-S90_W180-DEM_S90_00_W179_00_... \n", + "2 G3964549304-ASF v1-2-EPSG4326-S90-S90_W180-DEM_S90_00_W178_00_... \n", + "3 G3964549526-ASF v1-2-EPSG4326-S90-S90_W180-DEM_S90_00_W177_00_... \n", + "4 G3964549467-ASF v1-2-EPSG4326-S90-S90_W180-DEM_S90_00_W176_00_... \n", + "\n", + " data_url \\\n", + "0 s3://sds-n-cumulus-prod-nisar-products/DEM/v1.... \n", + "1 s3://sds-n-cumulus-prod-nisar-products/DEM/v1.... \n", + "2 s3://sds-n-cumulus-prod-nisar-products/DEM/v1.... \n", + "3 s3://sds-n-cumulus-prod-nisar-products/DEM/v1.... \n", + "4 s3://sds-n-cumulus-prod-nisar-products/DEM/v1.... \n", + "\n", + " geometry \n", + "0 POLYGON ((-179 -90, -179 -89, -180 -89, -180 -... \n", + "1 POLYGON ((-178 -90, -178 -89, -179 -89, -179 -... \n", + "2 POLYGON ((-177 -90, -177 -89, -178 -89, -178 -... \n", + "3 POLYGON ((-176 -90, -176 -89, -177 -89, -177 -... \n", + "4 POLYGON ((-175 -90, -175 -89, -176 -89, -176 -... " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rows = []\n", + "for it in items:\n", + " umm = it[\"umm\"]\n", + " gran_id = it[\"meta\"][\"concept-id\"]\n", + " title = umm.get(\"GranuleUR\", gran_id)\n", + " url = data_link(umm)\n", + "\n", + " for b in bounding_boxes(umm):\n", + " rows.append({\"concept_id\": gran_id, \"title\": title, \"data_url\": url, \"geometry\": box(*b)})\n", + "\n", + "gdf = gpd.GeoDataFrame(rows, geometry=\"geometry\", crs=\"EPSG:4326\")\n", + "print(\"Footprints:\", len(gdf))\n", + "gdf.head()\n" + ] + }, + { + "cell_type": "markdown", + "id": "01e0ec81-ef8b-4c79-92c6-a5fb19ff28f5", + "metadata": {}, + "source": [ + "### Interactive map \n", + "This cell builds an interactive Folium map centered on your AOI (`target_aoi_bounds`). It first computes the AOI center and initializes a basemap. Then it loops through each row of `gdf` (your granule footprints GeoDataFrame) and adds the footprint geometry as a `GeoJson` layer styled in green, with a tooltip showing the granule `title`. After that, it adds the AOI itself as a red outline by converting the AOI bounds into a Shapely `box()` polygon and passing it through `mapping()` so Folium can render it. Finally, `LayerControl()` is added so you can toggle layers, and the last line outputs `m` to display the map.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "526f3b98-5b92-42ec-a090-8469e289f5e8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "minx, miny, maxx, maxy = target_aoi_bounds\n", + "m = folium.Map(location=[(miny + maxy) / 2, (minx + maxx) / 2], zoom_start=6, tiles=\"OpenStreetMap\")\n", + "\n", + "for _, r in gdf.iterrows():\n", + " folium.GeoJson(\n", + " r.geometry,\n", + " style_function=lambda _: {\"color\": \"green\", \"weight\": 2, \"fillOpacity\": 0.05},\n", + " tooltip=r[\"title\"],\n", + " ).add_to(m)\n", + "\n", + "folium.GeoJson(\n", + " mapping(box(*target_aoi_bounds)),\n", + " style_function=lambda _: {\"color\": \"red\", \"weight\": 3, \"fillOpacity\": 0.0},\n", + " name=\"AOI\",\n", + ").add_to(m)\n", + "\n", + "folium.LayerControl().add_to(m)\n", + "m\n" + ] + }, + { + "cell_type": "markdown", + "id": "f31156c3-de9d-4306-b5a4-8f5eb4c3bdcc", + "metadata": {}, + "source": [ + "## Cloud Optimized Remote Access " + ] + }, + { + "cell_type": "markdown", + "id": "4629eaa5-7db1-472b-85c8-18f1e2373240", + "metadata": {}, + "source": [ + "### Set up Access\n", + "This cell logs you into NASA Earthdata (via `earthaccess`) and then calls the NISAR EarthdataCloud S3 credentials endpoint to obtain temporary AWS credentials (access key, secret key, session token). Those credentials are placed into environment variables so GDAL/Rasterio can authenticate when reading files directly from the NISAR S3 bucket using `/vsis3/...`. This is what makes “remote/streamed” reads work without downloading the full GeoTIFF locally. (`timeout=60` just means the request will wait up to 60 seconds before failing.)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "62902d97-caa3-4360-804a-376aa84527f6", + "metadata": {}, + "outputs": [], + "source": [ + "earthaccess.login()\n", + "sess = earthaccess.get_requests_https_session()\n", + "\n", + "resp = sess.get(\"https://nisar.asf.earthdatacloud.nasa.gov/s3credentials\", timeout=60)\n", + "resp.raise_for_status()\n", + "creds = resp.json()\n", + "\n", + "\n", + "os.environ[\"AWS_ACCESS_KEY_ID\"] = creds[\"accessKeyId\"]\n", + "os.environ[\"AWS_SECRET_ACCESS_KEY\"] = creds[\"secretAccessKey\"]\n", + "os.environ[\"AWS_SESSION_TOKEN\"] = creds[\"sessionToken\"]\n", + "os.environ[\"AWS_DEFAULT_REGION\"] = \"us-west-2\"\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "716d3293-a103-44b2-b8e4-249ae9a9541b", + "metadata": {}, + "source": [ + "### Streaming Preview \n", + "\n", + "This cell defines the smallest set of helpers needed to (a) convert an `s3://...` URL into a GDAL `/vsis3/...` path, (b) set a couple of GDAL options to reduce slow directory listing behavior, and (c) read only a small window from the middle of the raster (e.g., 15% of the width/height). It then turns that window into a transparent PNG (NoData masked out) and computes the exact geographic bounds of the streamed window using the window’s transform. The output is a PNG data URL plus `[south, west]` and `[north, east]` bounds that Folium can use to align an overlay correctly on a web map.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "091bd276-f020-4571-af49-bea50a1f38e8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Chosen: v1-2-EPSG4326-S90-S90_W180-DEM_S90_00_W180_00_C01-tif\n", + "Data URL: s3://sds-n-cumulus-prod-nisar-products/DEM/v1.2/EPSG4326/S90/S90_W180/DEM_S90_00_W180_00_C01.tif\n" + ] + } + ], + "source": [ + "#Minimal converter (only S3 direct access)\n", + "def s3_to_vsis3(s3_url: str) -> str:\n", + " return \"/vsis3/\" + s3_url[len(\"s3://\"):]\n", + "\n", + "\n", + "gdal_opts = {\n", + " \"GDAL_DISABLE_READDIR_ON_OPEN\": \"EMPTY_DIR\",\n", + " \"AWS_REGION\": \"us-west-2\",\n", + "}\n", + "\n", + "# Helper: pick a small center window + turn it into a PNG overlay\n", + "def stream_center_preview_as_png(src, frac=0.15):\n", + " left, bottom, right, top = src.bounds\n", + " cx, cy = (left + right) / 2, (bottom + top) / 2\n", + " w, h = (right - left) * frac, (top - bottom) * frac\n", + " preview_bounds = (cx - w/2, cy - h/2, cx + w/2, cy + h/2)\n", + "\n", + " win = rio.windows.from_bounds(*preview_bounds, transform=src.transform)\n", + " arr = src.read(1, window=win, masked=True)\n", + "\n", + " vals = arr.compressed()\n", + " if vals.size == 0:\n", + " return None, None # empty window\n", + "\n", + " vmin, vmax = np.percentile(vals, [2, 98])\n", + " img = np.clip((arr - vmin) / (vmax - vmin + 1e-9), 0, 1)\n", + " img = (img * 255).astype(\"uint8\")\n", + "\n", + " alpha = np.where(np.ma.getmaskarray(arr), 0, 220).astype(\"uint8\")\n", + " rgba = np.dstack([img, img, img, alpha])\n", + "\n", + " buf = io.BytesIO()\n", + " Image.fromarray(rgba, mode=\"RGBA\").save(buf, format=\"PNG\")\n", + " data_url = \"data:image/png;base64,\" + base64.b64encode(buf.getvalue()).decode(\"utf-8\")\n", + "\n", + "# compute exact lat/lon bounds of the streamed window for proper alignment\n", + " t = src.window_transform(win)\n", + " h_px, w_px = arr.shape\n", + " win_left, win_top = t.c, t.f\n", + " win_right = win_left + t.a * w_px\n", + " win_bottom = win_top + t.e * h_px \n", + "\n", + " return data_url, [[win_bottom, win_left], [win_top, win_right]]\n", + "\n", + "\n", + "#run: choose first granule and stream it \n", + "\n", + "chosen = gdf.iloc[0] # first tile\n", + "print(\"Chosen:\", chosen[\"title\"])\n", + "print(\"Data URL:\", chosen[\"data_url\"])\n", + "\n", + "vsis3_path = s3_to_vsis3(chosen[\"data_url\"])\n", + "\n", + "with rio.Env(**gdal_opts):\n", + " with rio.open(vsis3_path) as src:\n", + " overlay_png, overlay_bounds = stream_center_preview_as_png(src, frac=0.15)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "2c8868f4-4a7c-44fe-b2e9-1b26d4159088", + "metadata": {}, + "source": [ + "### Interactive map \n", + "\n", + "This cell builds a Folium map and centers it on the streamed window if it exists (otherwise it falls back to the chosen tile’s footprint center). It then draws: (1) all available granule footprints in green for context, (2) the selected granule footprint in blue so you can see which tile you streamed from, and (3) the streamed PNG preview as an `ImageOverlay` positioned using the exact `[south, west]` / `[north, east]` bounds computed earlier. Finally, it adds a layer control so you can toggle the overlay and footprint layers on/off and visually confirm the streamed raster window is aligned on the basemap.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "27e35c4d-60a9-4b26-badb-8a65fcd282f6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# --- map ---\n", + "# center map on streamed window (or on chosen footprint center if window empty)\n", + "if overlay_bounds:\n", + " (south, west), (north, east) = overlay_bounds\n", + " center = [(south + north) / 2, (west + east) / 2]\n", + "else:\n", + " # fallback: center on chosen footprint\n", + " minx, miny, maxx, maxy = chosen.geometry.bounds\n", + " center = [(miny + maxy) / 2, (minx + maxx) / 2]\n", + "\n", + "m = folium.Map(location=center, zoom_start=6, tiles=\"OpenStreetMap\")\n", + "\n", + "# footprints \n", + "for _, r in gdf.iterrows():\n", + " folium.GeoJson(\n", + " r.geometry,\n", + " style_function=lambda _: {\"color\": \"green\", \"weight\": 2, \"fillOpacity\": 0.05},\n", + " tooltip=r[\"title\"],\n", + " ).add_to(m)\n", + "\n", + "# chosen footprint\n", + "folium.GeoJson(\n", + " chosen.geometry,\n", + " style_function=lambda _: {\"color\": \"blue\", \"weight\": 3, \"fillOpacity\": 0},\n", + " tooltip=f\"CHOSEN: {chosen['title']}\",\n", + " name=\"Chosen tile\",\n", + ").add_to(m)\n", + "\n", + "# streamed preview overlay\n", + "if overlay_png is None:\n", + " print(\"Preview window is empty (all NoData). Pick a different granule.\")\n", + "else:\n", + " folium.raster_layers.ImageOverlay(\n", + " image=overlay_png,\n", + " bounds=overlay_bounds,\n", + " opacity=0.75,\n", + " name=\"Streamed DEM preview\",\n", + " ).add_to(m)\n", + "\n", + "folium.LayerControl().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 +} diff --git a/docs/source/science_examples.rst b/docs/source/science_examples.rst index dcb11ac9..a2d3f28c 100644 --- a/docs/source/science_examples.rst +++ b/docs/source/science_examples.rst @@ -20,6 +20,7 @@ You can also find links to Open Source Science guidelines for the MAAP platform. science/ATL03/ATL03.ipynb science/ATL08/ATL08.ipynb science/NISAR/NISAR_access.ipynb + science/NISAR/NISAR_dem.ipynb science/ESA_BIOMASS/ESA_BIOMASS_Data_Access.ipynb science/ESA_BIOMASS/ESA_BIOMASS_Simulated_Data_Access.ipynb science/OPERA/OPERA_Surface_Displacement.ipynb