NISAR DEM (v1.2): Access and Visualize

Date: March 4, 2026

Author: Harshini Girish (UAH), Rajat Shinde (UAH), Alex Mandel (Development Seed), Jamison French (Development Seed), Brian Freitag (NASA MSFC), Sheyenne Kirkland (UAH), Henry Rodman (Development Seed), Zac Deziel (Development Seed), Chuck Daniels (Development Seed)

Description: This notebook subsets a small Area of Interest (AOI) from the NISAR DEM VRT stored in ASF’s S3 bucket using rasterio’s AWSSession together with rasterio.Env for authenticated requester-pays S3 access. It logs in via Earthdata, requests temporary S3 credentials, opens the DEM directly from S3, clips the AOI in memory with rioxarray, and plots the result for quick inspection. This example intentionally focuses on access and in-memory subsetting; for a related MAAP example that writes an Xarray object to a cloud-optimized GeoTIFF (COG), see the Additional Resources section below.

Run This Notebook

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.

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.

About the Data

This notebook uses the NISAR DEM v1.2 collection hosted by the NASA Alaska Satellite Facility (ASF) DAAC. It is a global digital elevation model (DEM) prepared for NISAR processing workflows and is not derived from NISAR measurements; instead, it is modified from the Copernicus DEM 30 m source product. Key modifications include converting the vertical reference from the EGM2008 geoid to the WGS84 ellipsoid, filling gaps (e.g., over ocean / missing tiles) to provide complete global coverage, and publishing the data through a hierarchical VRT (Virtual Raster) structure for fast, unified access. The top-level VRT file (``EPSG4326.vrt``) points to a second tier of VRTs organized by latitude bands, which in turn reference the underlying tiles; this makes it easy to access the DEM as one “virtual mosaic” without manually managing tile lists. Note: because elevations are ellipsoid-referenced, this dataset should not be used for applications that specifically require geoid-based elevations.

Additional Resources

For a related MAAP science example that shows writing a cloud-optimized GeoTIFF (COG) from an Xarray-backed workflow, see ESA CCI Biomass V5.01 — Saving Tile as COG above.

Imports

Load all required libraries (earthaccess for authentication, rasterio for S3 access/session handling, rioxarray for in-memory clipping, and matplotlib for previewing the subset).

[ ]:
import time

import matplotlib.pyplot as plt
import rasterio
import rioxarray
from rasterio.session import AWSSession

import earthaccess

Parameters

Define the DEM version/projection directory and the AOI bounding box to subset.

[2]:
# --- DEM version + projection directory ---
DEM_VERSION = "v1.2"
EPSG_DIR = "EPSG4326"  # also available: "EPSG3413" (Arctic), "EPSG3031" (Antarctic)

# --- AOI bbox ---
# For EPSG4326: lon/lat bbox (min_lon, min_lat, max_lon, max_lat)
BBOX = (-116.7, 35.9, -116.6, 36.0)

VRT paths

Construct the VRT path in ASF’s S3 bucket. The /vsis3/ path is what rasterio/GDAL will read directly from S3.

[3]:
# --- ASF S3 VRT (DEM mosaic as a VRT) ---
ASF_S3_BUCKET = "sds-n-cumulus-prod-nisar-products"
S3_KEY = f"DEM/{DEM_VERSION}/{EPSG_DIR}/{EPSG_DIR}.vrt"

S3_VRT_S3URI = f"s3://{ASF_S3_BUCKET}/{S3_KEY}"
S3_VRT_VSI = f"/vsis3/{ASF_S3_BUCKET}/{S3_KEY}"

S3_VRT_S3URI
[3]:
's3://sds-n-cumulus-prod-nisar-products/DEM/v1.2/EPSG4326/EPSG4326.vrt'

Set up Access

Log in via Earthdata and request short-lived S3 credentials from ASF so the S3 VRT can be accessed.

[4]:
#Fetch temporary S3 credentials via Earthdata login
ASF_S3CREDS_ENDPOINT = "https://nisar.asf.earthdatacloud.nasa.gov/s3credentials"

auth = earthaccess.login()

creds = None
for attempt in range(1, 6):
    try:
        creds = auth.get_s3_credentials(endpoint=ASF_S3CREDS_ENDPOINT)
        print(f"Got S3 creds on attempt {attempt}")
        break
    except Exception as e:
        print(f"Attempt {attempt} failed:", type(e).__name__, e)
        time.sleep(5)

if creds is None:
    raise RuntimeError("Could not obtain ASF S3 credentials after retries.")
Got S3 creds on attempt 1

AWS session setup

Create a rasterio AWSSession from the temporary ASF S3 credentials and use it with rasterio.Env for requester-pays S3 access.

[11]:
session = AWSSession(
    aws_access_key_id=creds["accessKeyId"],
    aws_secret_access_key=creds["secretAccessKey"],
    aws_session_token=creds["sessionToken"],
    region_name="us-west-2",
    requester_pays=True,
)

Subsetting helper

Define a helper function that opens the DEM from S3 inside a rasterio environment and clips the AOI in memory with rioxarray.

[16]:
def open_dem_subset(dataset_path: str, bbox, session: AWSSession):
    """Open a DEM from S3 and clip the requested bounding box in memory."""
    minx, miny, maxx, maxy = bbox

    with rasterio.Env(session=session):
        rds = rioxarray.open_rasterio(dataset_path, mask_and_scale=True)
        clipped = rds.rio.clip_box(minx=minx, miny=miny, maxx=maxx, maxy=maxy).load()

    return clipped

Run subset in memory

Open the S3 VRT with the AWS session and clip the AOI in memory.

[17]:
try:
    clipped = open_dem_subset(S3_VRT_S3URI, BBOX, session)
    print("Subset loaded in memory")
    print("CRS:", clipped.rio.crs)
    print("Bounds:", clipped.rio.bounds())
except Exception as e:
    print("S3 subset failed:", type(e).__name__, e)
    raise


Subset loaded in memory
CRS: EPSG:4326
Bounds: (-116.70013888888889, 35.89986111111111, -116.59986111111111, 36.00013888888889)

Preview

Plot the clipped DEM directly from the in-memory Xarray object.

[18]:
plt.figure(figsize=(7, 5))
clipped.squeeze().plot.imshow()
plt.title("DEM Elevation (m)")
plt.xlabel(f"{clipped.squeeze().dims[-1]} (longitude, degrees)")
plt.ylabel(f"{clipped.squeeze().dims[-2]} (latitude, degrees)")
plt.tight_layout()
plt.show()
../../_images/science_NISAR_NISAR_DEM_19_0.png