{ "cells": [ { "cell_type": "markdown", "id": "0d9243d6-3aaf-47ef-852b-d81174bf5e6e", "metadata": {}, "source": [ "# OPERA Surface Displacement from Sentinel-1: Access and Visualize" ] }, { "cell_type": "markdown", "id": "73064a66-73a5-4004-9731-65b4f023ff26", "metadata": {}, "source": [ "Authors: Harshini Girish (UAH), Rajat Shinde (UAH), Alex Mandel (Development Seed), Chuck Daniels (Development Seed), Julia Signell (Element84)\n", "\n", "Date:July 28, 2025\n", "\n", "Description: This tutorial aims to provide information and code to help users get started working with the OPERA Sentinel-1 Surface Displacement product using the MAAP. We will search for the data within NASA’s Common Metadata Repository (CMR)." ] }, { "cell_type": "markdown", "id": "a0ca2d41-6b47-4ff9-9405-ce0ed60dff18", "metadata": {}, "source": [ "## Run This Notebook" ] }, { "cell_type": "markdown", "id": "f61c7f88-f9a4-4f24-bb85-ec5222d42631", "metadata": {}, "source": [ "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 that you 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", "\n" ] }, { "cell_type": "markdown", "id": "f8d01bb3-368c-4eca-a4e3-73ce89fcd56c", "metadata": {}, "source": [ "## About the Data\n", "\n", "> The Level-3 OPERA Sentinel-1 Surface Displacement (DISP) product is generated through interferometric time-series analysis of Level-2 Coregistered Sentinel-1 Single Look Complex (CSLC) datasets. Using a hybrid Persistent Scatterer (PS) and Distributed Scatterer (DS) approach, this product quantifies Earth's surface displacement in the radar line-of-sight. The DISP products enable the detection of anthropogenic and natural surface changes, including subsidence, tectonic deformation, and landslides. \n", "\n", "> The OPERA DISP suite comprises complementary datasets derived from Sentinel-1 and NISAR inputs, designated as DISP-S1 and DISP-NI, respectively. Each product, created per acquisition, adheres to a consistent structure, HDF5 file format, file-naming convention, and a 30 m spatial posting. This collection specifically includes DISP-S1 products, derived from Sentinel-1 data. For visualization and quick exploration, the Pangeo Image can be used for these datasets. \n" ] }, { "cell_type": "markdown", "id": "dbb122dd-1079-465c-8348-8a97eb46130e", "metadata": {}, "source": [ "Source: [OPERA Surface Displacement from Sentinel-1](https://cmr.earthdata.nasa.gov/search/concepts/C3294057315-ASF.html)" ] }, { "cell_type": "markdown", "id": "7bae3cbe-f6f6-4591-8fcf-8d6f558bbc2e", "metadata": {}, "source": [ "## Importing Packages" ] }, { "cell_type": "code", "execution_count": 3, "id": "a0147000-e8a1-458f-acb0-b253960aabb7", "metadata": {}, "outputs": [], "source": [ "# --- MAAP & Cloud Access ---\n", "from maap.maap import MAAP\n", "import earthaccess\n", "from s3fs import S3FileSystem\n", "\n", "# File Access & Processing -\n", "import os\n", "import fsspec\n", "import h5py\n", "import re\n", "import numpy as np\n", "import xarray as xr\n", "import dask\n", "\n", "# Plotting & Visualization \n", "import matplotlib.pyplot as plt\n", "import folium\n", "\n", "# Geospatial \n", "import geopandas as gpd\n", "from shapely.geometry import box, Polygon\n", "\n", "# Misc\n", "import requests\n", "\n", "# Initialize MAAP\n", "maap = MAAP()\n" ] }, { "cell_type": "markdown", "id": "79bc1350-2cec-4a39-91ef-30f3def98514", "metadata": {}, "source": [ "## Searching the Data" ] }, { "cell_type": "markdown", "id": "9a84f593-66c9-4bba-9d84-4f376ba1ffa5", "metadata": {}, "source": [ "This performs a granule search using the `maap.searchGranule()` function on the OPERA Sentinel-1 displacement product collection." ] }, { "cell_type": "code", "execution_count": 16, "id": "ba83ee4e-75fd-4443-ae92-d6a356bee759", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "collection = maap.searchCollection(\n", " cmr_host=\"cmr.earthdata.nasa.gov\",\n", " short_name=\"OPERA_L3_DISP-S1_V1\"\n", ")\n", "len(collection)\n", "\n" ] }, { "cell_type": "code", "execution_count": 17, "id": "ebde15fe-125e-480f-883c-10bf86236494", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Found 100 granules with polygons.\n" ] } ], "source": [ "results = maap.searchGranule(\n", " short_name=\"OPERA_L3_DISP-S1_V1\",\n", " cmr_host=\"cmr.earthdata.nasa.gov\",\n", " limit=100\n", ")\n", "\n", "records = []\n", "for r in results:\n", " granule = r[\"Granule\"]\n", " try:\n", " points = granule[\"Spatial\"][\"HorizontalSpatialDomain\"][\"Geometry\"][\"GPolygon\"][\"Boundary\"][\"Point\"]\n", " coords = [(float(p[\"PointLongitude\"]), float(p[\"PointLatitude\"])) for p in points]\n", " if len(coords) >= 3:\n", " polygon = Polygon(coords)\n", " records.append({\n", " \"GranuleUR\": granule[\"GranuleUR\"],\n", " \"geometry\": polygon\n", " })\n", " except KeyError:\n", " continue\n", "if records:\n", " gdf = gpd.GeoDataFrame(records, crs=\"EPSG:4326\")\n", " print(f\"Found {len(gdf)} granules with polygons.\")\n", "\n", " " ] }, { "cell_type": "markdown", "id": "ba0a22e8-aab6-4043-a409-086240addd65", "metadata": {}, "source": [ "## Visualizing with Bounding Boxes" ] }, { "cell_type": "markdown", "id": "5ae9d96a-3ff6-42ba-940c-8fe2334fd575", "metadata": {}, "source": [ "This code creates an interactive map showing bounding boxes for each granule using `folium`. It extracts geometry bounds from a `GeoDataFrame`, constructs a new `GeoDataFrame` of bounding boxes, and overlays them on a Leaflet map with tooltips displaying each granule's ID.\n" ] }, { "cell_type": "code", "execution_count": 18, "id": "ea2ac1d2-ee56-4a75-be51-28ee6445600d", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
<xarray.Dataset> Size: 4GB\n",
"Dimensions: (y: 7915, x: 9548, time: 1)\n",
"Coordinates:\n",
" * y (y) float64 63kB 1.995e+06 ... 1.758e+06\n",
" * x (x) float64 76kB 7.682e+04 ... 3.632e+05\n",
" * time (time) datetime64[ns] 8B 2016-08-18T00:55...\n",
"Data variables: (12/13)\n",
" spatial_ref int64 8B ...\n",
" reference_time (time) datetime64[ns] 8B dask.array<chunksize=(1,), meta=np.ndarray>\n",
" displacement (y, x) float64 605MB dask.array<chunksize=(4096, 4096), meta=np.ndarray>\n",
" short_wavelength_displacement (y, x) float32 302MB dask.array<chunksize=(5632, 5632), meta=np.ndarray>\n",
" recommended_mask (y, x) float32 302MB dask.array<chunksize=(5632, 5632), meta=np.ndarray>\n",
" connected_component_labels (y, x) float32 302MB dask.array<chunksize=(5632, 5632), meta=np.ndarray>\n",
" ... ...\n",
" estimated_phase_quality (y, x) float32 302MB dask.array<chunksize=(5632, 5632), meta=np.ndarray>\n",
" persistent_scatterer_mask (y, x) float32 302MB dask.array<chunksize=(5632, 5632), meta=np.ndarray>\n",
" shp_counts (y, x) float32 302MB dask.array<chunksize=(5632, 5632), meta=np.ndarray>\n",
" water_mask (y, x) float32 302MB dask.array<chunksize=(5632, 5632), meta=np.ndarray>\n",
" phase_similarity (y, x) float32 302MB dask.array<chunksize=(5632, 5632), meta=np.ndarray>\n",
" timeseries_inversion_residuals (y, x) float32 302MB dask.array<chunksize=(5632, 5632), meta=np.ndarray>\n",
"Attributes:\n",
" Conventions: CF-1.8\n",
" contact: opera-sds-ops@jpl.nasa.gov\n",
" institution: NASA JPL\n",
" mission_name: OPERA\n",
" reference_document: JPL D-108765\n",
" title: OPERA_L3_DISP-S1 Product