From a2c9f7da958f319dafeeefa8deb495efcebf9429 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Thu, 28 Nov 2024 23:54:14 -0500 Subject: [PATCH 1/8] Add support for NASA OPERA data products --- leafmap/leafmap.py | 4 + leafmap/toolbar.py | 334 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 338 insertions(+) diff --git a/leafmap/leafmap.py b/leafmap/leafmap.py index 9eac06d4b1..140d5ee196 100644 --- a/leafmap/leafmap.py +++ b/leafmap/leafmap.py @@ -233,6 +233,10 @@ def add(self, obj, index=None, **kwargs) -> None: from .toolbar import nasa_data_gui nasa_data_gui(self, **kwargs) + elif obj == "NASA_OPERA": + from .toolbar import nasa_opera_gui + + nasa_opera_gui(self, **kwargs) elif obj == "inspector": from .toolbar import inspector_gui diff --git a/leafmap/toolbar.py b/leafmap/toolbar.py index bbc2f906f5..5a6a4279e2 100644 --- a/leafmap/toolbar.py +++ b/leafmap/toolbar.py @@ -6615,3 +6615,337 @@ def button_clicked(change): m.tool_control = toolbar_control else: return toolbar_widget + + +def nasa_opera_gui( + m, + position: Optional[str] = "topright", + opened: Optional[bool] = True, + default_dataset: Optional[str] = "OPERA_L3_DSWX-HLS_V1", + **kwargs, +): + """Search NASA Earth data interactive + + Args: + m (leafmap.Map, optional): The leaflet Map object. Defaults to None. + position (str, optional): The position of the widget. Defaults to "topright". + opened (bool, optional): Whether to open the widget. Defaults to True. + default_dataset (str, optional): The default dataset. Defaults to "OPERA_L3_DSWX-HLS_V1". + + Returns: + ipywidgets: The tool GUI widget. + """ + import pandas as pd + from datetime import datetime + + widget_width = "400px" + padding = "0px 0px 0px 5px" # upper, right, bottom, left + style = {"description_width": "initial"} + + if not hasattr(m, "_NASA_DATA"): + + data = { + "ShortName": [ + "OPERA_L2_CSLC-S1-STATIC_V1", + "OPERA_L2_CSLC-S1_V1", + "OPERA_L2_RTC-S1-STATIC_V1", + "OPERA_L2_RTC-S1_V1", + "OPERA_L3_DIST-ALERT-HLS_V1", + "OPERA_L3_DIST-ANN-HLS_V1", + "OPERA_L3_DSWX-HLS_V1", + "OPERA_L3_DSWX-S1_V1", + ], + "EntryTitle": [ + "OPERA Coregistered Single-Look Complex from Sentinel-1 Static Layers validated product (Version 1)", + "OPERA Coregistered Single-Look Complex from Sentinel-1 validated product (Version 1)", + "OPERA Radiometric Terrain Corrected SAR Backscatter from Sentinel-1 Static Layers validated product (Version 1)", + "OPERA Radiometric Terrain Corrected SAR Backscatter from Sentinel-1 validated product (Version 1)", + "OPERA Land Surface Disturbance Alert from Harmonized Landsat Sentinel-2 product (Version 1)", + "OPERA Land Surface Disturbance Annual from Harmonized Landsat Sentinel-2 product (Version 1)", + "OPERA Dynamic Surface Water Extent from Harmonized Landsat Sentinel-2 product (Version 1)", + "OPERA Dynamic Surface Water Extent from Sentinel-1 (Version 1)", + ], + } + + df = pd.DataFrame(data) + setattr(m, "_NASA_DATA", df) + names = df["ShortName"].tolist() + setattr(m, "_NASA_DATA_NAMES", names) + + default_title = m._NASA_DATA[m._NASA_DATA["ShortName"] == default_dataset][ + "EntryTitle" + ].values[0] + + output = widgets.Output( + layout=widgets.Layout(width=widget_width, padding=padding, overflow="auto") + ) + + toolbar_button = widgets.ToggleButton( + value=False, + tooltip="Search NASA Earth data", + icon="search", + layout=widgets.Layout(width="28px", height="28px", padding="0px 0px 0px 4px"), + ) + + close_button = widgets.ToggleButton( + value=False, + tooltip="Close the tool", + icon="times", + button_style="primary", + layout=widgets.Layout(height="28px", width="28px", padding="0px 0px 0px 4px"), + ) + + short_name = widgets.Dropdown( + options=m._NASA_DATA_NAMES, + value=default_dataset, + description="Short Name:", + style=style, + layout=widgets.Layout(width=widget_width, padding=padding), + ) + + title = widgets.Text( + value=default_title, + description="Title:", + style=style, + disabled=True, + layout=widgets.Layout(width=widget_width, padding=padding), + ) + + max_items = widgets.IntText( + value=50, + description="Max items:", + style=style, + layout=widgets.Layout(width="125px", padding=padding), + ) + + bbox = widgets.Text( + value="", + description="Bounding box:", + placeholder="xmin, ymin, xmax, ymax", + style=style, + layout=widgets.Layout(width="271px", padding=padding), + ) + + start_date = widgets.DatePicker( + description="Start date:", + disabled=False, + style=style, + layout=widgets.Layout(width="198px", padding=padding), + ) + end_date = widgets.DatePicker( + description="End date:", + disabled=False, + style=style, + layout=widgets.Layout(width="198px", padding=padding), + ) + + dataset = widgets.Dropdown( + value=None, + description="Dataset:", + style=style, + layout=widgets.Layout(width=widget_width, padding=padding), + ) + + buttons = widgets.ToggleButtons( + value=None, + options=["Search", "Display", "Reset", "Close"], + tooltips=["Get Items", "Display Image", "Reset", "Close"], + button_style="primary", + ) + buttons.style.button_width = "65px" + + def change_dataset(change): + title.value = m._NASA_DATA[m._NASA_DATA["ShortName"] == short_name.value][ + "EntryTitle" + ].values[0] + dataset.value = None + dataset.options = [] + + short_name.observe(change_dataset, "value") + + toolbar_widget = widgets.VBox() + toolbar_widget.children = [toolbar_button] + toolbar_header = widgets.HBox() + toolbar_header.children = [close_button, toolbar_button] + toolbar_footer = widgets.VBox() + toolbar_footer.children = [ + short_name, + title, + widgets.HBox([max_items, bbox]), + widgets.HBox([start_date, end_date]), + dataset, + buttons, + output, + ] + + toolbar_event = ipyevents.Event( + source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] + ) + + def handle_toolbar_event(event): + if event["type"] == "mouseenter": + toolbar_widget.children = [toolbar_header, toolbar_footer] + elif event["type"] == "mouseleave": + if not toolbar_button.value: + toolbar_widget.children = [toolbar_button] + toolbar_button.value = False + close_button.value = False + + toolbar_event.on_dom_event(handle_toolbar_event) + + def toolbar_btn_click(change): + if change["new"]: + close_button.value = False + toolbar_widget.children = [toolbar_header, toolbar_footer] + else: + if not close_button.value: + toolbar_widget.children = [toolbar_button] + + toolbar_button.observe(toolbar_btn_click, "value") + + def close_btn_click(change): + if change["new"]: + toolbar_button.value = False + if m is not None: + m.toolbar_reset() + if m.tool_control is not None and m.tool_control in m.controls: + m.remove_control(m.tool_control) + m.tool_control = None + toolbar_widget.close() + + close_button.observe(close_btn_click, "value") + + def button_clicked(change): + if change["new"] == "Search": + with output: + output.clear_output() + with output: + print("Searching...") + + if bbox.value.strip() == "": + if m.user_roi_bounds() is not None: + bounds = tuple(m.user_roi_bounds()) + else: + bounds = ( + m.bounds[0][1], + m.bounds[0][0], + m.bounds[1][1], + m.bounds[1][0], + ) + else: + bounds = tuple(map(float, bbox.value.split(","))) + if len(bounds) != 4: + print("Please provide a valid bounding box.") + bounds = None + + if start_date.value is not None and end_date.value is not None: + date_range = (str(start_date.value), str(end_date.value)) + elif start_date.value is not None: + date_range = ( + str(start_date.value), + datetime.today().strftime("%Y-%m-%d"), + ) + else: + date_range = None + + output.clear_output(wait=True) + try: + results, gdf = nasa_data_search( + count=max_items.value, + short_name=short_name.value, + bbox=bounds, + temporal=date_range, + return_gdf=True, + ) + + if len(results) > 0: + if "Footprints" in m.get_layer_names(): + m.remove(m.find_layer("Footprints")) + if ( + hasattr(m, "_NASA_DATA_CTRL") + and m._NASA_DATA_CTRL in m.controls + ): + m.remove(m._NASA_DATA_CTRL) + + style = { + # "stroke": True, + "color": "#3388ff", + "weight": 2, + "opacity": 1, + "fill": True, + "fillColor": "#3388ff", + "fillOpacity": 0.1, + } + + hover_style = { + "weight": style["weight"] + 2, + "fillOpacity": 0, + "color": "yellow", + } + + m.add_gdf( + gdf, + layer_name="Footprints", + info_mode="on_click", + zoom_to_layer=False, + style=style, + hover_style=hover_style, + ) + setattr(m, "_NASA_DATA_CTRL", m.controls[-1]) + + dataset.options = gdf["native-id"].values.tolist() + dataset.value = dataset.options[0] + + setattr(m, "_NASA_DATA_GDF", gdf) + setattr(m, "_NASA_DATA_RESULTS", results) + output.clear_output() + + except Exception as e: + print(e) + + elif change["new"] == "Display": + output.clear_output() + with output: + print("To be implemented...") + + elif change["new"] == "Reset": + short_name.options = m._NASA_DATA_NAMES + short_name.value = default_dataset + title.value = default_title + max_items.value = 50 + bbox.value = "" + bbox.placeholder = "xmin, ymin, xmax, ymax" + start_date.value = None + end_date.value = None + dataset.options = [] + dataset.value = None + output.clear_output() + + if "Footprints" in m.get_layer_names(): + m.remove(m.find_layer("Footprints")) + if hasattr(m, "_NASA_DATA_CTRL") and m._NASA_DATA_CTRL in m.controls: + m.remove(m._NASA_DATA_CTRL) + + elif change["new"] == "Close": + if m is not None: + m.toolbar_reset() + if m.tool_control is not None and m.tool_control in m.controls: + m.remove_control(m.tool_control) + m.tool_control = None + toolbar_widget.close() + + buttons.value = None + + buttons.observe(button_clicked, "value") + + toolbar_button.value = opened + if m is not None: + toolbar_control = ipyleaflet.WidgetControl( + widget=toolbar_widget, position=position + ) + + if toolbar_control not in m.controls: + m.add(toolbar_control) + m.tool_control = toolbar_control + else: + return toolbar_widget From 36ae301dac37947507eeabe6b2baffb1145b4125 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Fri, 29 Nov 2024 01:05:43 -0500 Subject: [PATCH 2/8] Add support for visualizing raster --- leafmap/toolbar.py | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/leafmap/toolbar.py b/leafmap/toolbar.py index 5a6a4279e2..3f3493f1d5 100644 --- a/leafmap/toolbar.py +++ b/leafmap/toolbar.py @@ -6637,6 +6637,10 @@ def nasa_opera_gui( """ import pandas as pd from datetime import datetime + import boto3 + import rasterio as rio + from rasterio.session import AWSSession + import xarray as xr widget_width = "400px" padding = "0px 0px 0px 5px" # upper, right, bottom, left @@ -6672,6 +6676,31 @@ def nasa_opera_gui( names = df["ShortName"].tolist() setattr(m, "_NASA_DATA_NAMES", names) + # Generates the temporary + s3_cred_endpoint = "https://archive.podaac.earthdata.nasa.gov/s3credentials" + + def get_temp_creds(): + temp_creds_url = s3_cred_endpoint + return requests.get(temp_creds_url).json() + + temp_creds_req = get_temp_creds() + + session = boto3.Session( + aws_access_key_id=temp_creds_req["accessKeyId"], + aws_secret_access_key=temp_creds_req["secretAccessKey"], + aws_session_token=temp_creds_req["sessionToken"], + region_name="us-west-2", + ) + + rio_env = rio.Env( + AWSSession(session), + GDAL_DISABLE_READDIR_ON_OPEN="EMPTY_DIR", + CPL_VSIL_CURL_ALLOWED_EXTENSIONS="TIF, TIFF", + GDAL_HTTP_COOKIEFILE=os.path.expanduser("~/cookies.txt"), + GDAL_HTTP_COOKIEJAR=os.path.expanduser("~/cookies.txt"), + ) + rio_env.__enter__() + default_title = m._NASA_DATA[m._NASA_DATA["ShortName"] == default_dataset][ "EntryTitle" ].values[0] @@ -6906,7 +6935,14 @@ def button_clicked(change): elif change["new"] == "Display": output.clear_output() with output: - print("To be implemented...") + print("Loading...") + links = m._NASA_DATA_RESULTS[ + dataset.options.index(dataset.value) + ].data_links() + ds = xr.open_dataset(links[0], engine="rasterio") + image = array_to_image(ds["band_data"]) + m.add_raster(image, zoom_to_layer=False) + output.clear_output() elif change["new"] == "Reset": short_name.options = m._NASA_DATA_NAMES From 6baeb3d13e33ef2f762236989a67579f47bc48e0 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Fri, 29 Nov 2024 11:40:10 -0500 Subject: [PATCH 3/8] Add DWSx layer options --- leafmap/toolbar.py | 50 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/leafmap/toolbar.py b/leafmap/toolbar.py index 3f3493f1d5..87ff8a5ba9 100644 --- a/leafmap/toolbar.py +++ b/leafmap/toolbar.py @@ -6775,6 +6775,13 @@ def get_temp_creds(): layout=widgets.Layout(width=widget_width, padding=padding), ) + layer = widgets.Dropdown( + value=None, + description="Layer:", + style=style, + layout=widgets.Layout(width=widget_width, padding=padding), + ) + buttons = widgets.ToggleButtons( value=None, options=["Search", "Display", "Reset", "Close"], @@ -6803,6 +6810,7 @@ def change_dataset(change): widgets.HBox([max_items, bbox]), widgets.HBox([start_date, end_date]), dataset, + layer, buttons, output, ] @@ -6927,6 +6935,25 @@ def button_clicked(change): setattr(m, "_NASA_DATA_GDF", gdf) setattr(m, "_NASA_DATA_RESULTS", results) + + if short_name.value == "OPERA_L3_DSWX-HLS_V1": + layer.options = [ + "B01_WTR: Water classification", + "B02_BWTR: Binary water", + "B03_CONF: Confidence", + "B04_DIAG: Diagnostic layer", + "B05_WTR-1: Interpretation of diagnostic layer", + "B06_WTR-2: Interpreted layer refined using land cover", + "B07_LAND: Land cover classification", + "B08_SHAD: Terrain shadow layer", + "B09_CLOUD: Input HLS Fmask cloud classification", + "B10_DEM: Digital elevation model", + ] + layer.value = "B01_WTR: Water classification" + else: + layer.options = [] + layer.value = None + output.clear_output() except Exception as e: @@ -6935,13 +6962,20 @@ def button_clicked(change): elif change["new"] == "Display": output.clear_output() with output: - print("Loading...") - links = m._NASA_DATA_RESULTS[ - dataset.options.index(dataset.value) - ].data_links() - ds = xr.open_dataset(links[0], engine="rasterio") - image = array_to_image(ds["band_data"]) - m.add_raster(image, zoom_to_layer=False) + if ( + short_name.value == "OPERA_L3_DSWX-HLS_V1" + and dataset.value is not None + ): + print("Loading...") + links = m._NASA_DATA_RESULTS[ + dataset.options.index(dataset.value) + ].data_links() + ds = xr.open_dataset(links[layer.index], engine="rasterio") + image = array_to_image(ds["band_data"]) + name_prefix = dataset.value.split("_")[4][:8] + name_suffix = layer.value.split(":")[0][4:] + layer_name = f"{name_prefix}_{name_suffix}" + m.add_raster(image, zoom_to_layer=False, layer_name=layer_name) output.clear_output() elif change["new"] == "Reset": @@ -6955,6 +6989,8 @@ def button_clicked(change): end_date.value = None dataset.options = [] dataset.value = None + layer.options = [] + layer.value = None output.clear_output() if "Footprints" in m.get_layer_names(): From ac2a46493718072ceacee41e3e23942461bfbe2b Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Fri, 29 Nov 2024 16:28:48 -0500 Subject: [PATCH 4/8] Add colormap option --- leafmap/toolbar.py | 72 +++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/leafmap/toolbar.py b/leafmap/toolbar.py index 87ff8a5ba9..a6a4482ff0 100644 --- a/leafmap/toolbar.py +++ b/leafmap/toolbar.py @@ -6641,11 +6641,16 @@ def nasa_opera_gui( import rasterio as rio from rasterio.session import AWSSession import xarray as xr + import matplotlib.pyplot as plt widget_width = "400px" padding = "0px 0px 0px 5px" # upper, right, bottom, left style = {"description_width": "initial"} + colormaps = plt.colormaps() + cmap_options = [cmap for cmap in colormaps if (len(cmap) < 20 and cmap.islower())] + cmap_options.sort() + if not hasattr(m, "_NASA_DATA"): data = { @@ -6779,7 +6784,15 @@ def get_temp_creds(): value=None, description="Layer:", style=style, - layout=widgets.Layout(width=widget_width, padding=padding), + layout=widgets.Layout(width="200px", padding=padding), + ) + + palette = widgets.Dropdown( + options=cmap_options, + value="tab10", + description="Colormap:", + style=style, + layout=widgets.Layout(width="200px", padding=padding), ) buttons = widgets.ToggleButtons( @@ -6796,6 +6809,8 @@ def change_dataset(change): ].values[0] dataset.value = None dataset.options = [] + layer.value = None + layer.options = [] short_name.observe(change_dataset, "value") @@ -6810,7 +6825,7 @@ def change_dataset(change): widgets.HBox([max_items, bbox]), widgets.HBox([start_date, end_date]), dataset, - layer, + widgets.HBox([layer, palette]), buttons, output, ] @@ -6936,20 +6951,10 @@ def button_clicked(change): setattr(m, "_NASA_DATA_GDF", gdf) setattr(m, "_NASA_DATA_RESULTS", results) - if short_name.value == "OPERA_L3_DSWX-HLS_V1": - layer.options = [ - "B01_WTR: Water classification", - "B02_BWTR: Binary water", - "B03_CONF: Confidence", - "B04_DIAG: Diagnostic layer", - "B05_WTR-1: Interpretation of diagnostic layer", - "B06_WTR-2: Interpreted layer refined using land cover", - "B07_LAND: Land cover classification", - "B08_SHAD: Terrain shadow layer", - "B09_CLOUD: Input HLS Fmask cloud classification", - "B10_DEM: Digital elevation model", - ] - layer.value = "B01_WTR: Water classification" + if len(m._NASA_DATA_RESULTS) > 0: + links = m._NASA_DATA_RESULTS[0].data_links() + layer.options = [link.split("_")[-1] for link in links] + layer.value = layer.options[0] else: layer.options = [] layer.value = None @@ -6962,20 +6967,26 @@ def button_clicked(change): elif change["new"] == "Display": output.clear_output() with output: - if ( - short_name.value == "OPERA_L3_DSWX-HLS_V1" - and dataset.value is not None - ): - print("Loading...") - links = m._NASA_DATA_RESULTS[ - dataset.options.index(dataset.value) - ].data_links() - ds = xr.open_dataset(links[layer.index], engine="rasterio") - image = array_to_image(ds["band_data"]) - name_prefix = dataset.value.split("_")[4][:8] - name_suffix = layer.value.split(":")[0][4:] - layer_name = f"{name_prefix}_{name_suffix}" - m.add_raster(image, zoom_to_layer=False, layer_name=layer_name) + print("Loading...") + links = m._NASA_DATA_RESULTS[ + dataset.options.index(dataset.value) + ].data_links() + ds = xr.open_dataset(links[layer.index], engine="rasterio") + setattr(m, "_NASA_DATA_DS", ds) + da = ds["band_data"] + nodata = os.environ.get("NODATA", 0) + da = da.fillna(nodata) + image = array_to_image(da) + name_prefix = dataset.value.split("_")[4][:8] + name_suffix = layer.value.split(".")[0] + layer_name = f"{name_prefix}_{name_suffix}" + m.add_raster( + image, + zoom_to_layer=True, + colormap=palette.value, + nodata=nodata, + layer_name=layer_name, + ) output.clear_output() elif change["new"] == "Reset": @@ -6991,6 +7002,7 @@ def button_clicked(change): dataset.value = None layer.options = [] layer.value = None + palette.value = "tab10" output.clear_output() if "Footprints" in m.get_layer_names(): From 3fae0c47a584400f46163d43e1574872c46fee60 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Fri, 29 Nov 2024 17:38:32 -0500 Subject: [PATCH 5/8] Error handling for h5 --- leafmap/toolbar.py | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/leafmap/toolbar.py b/leafmap/toolbar.py index a6a4482ff0..7c32015897 100644 --- a/leafmap/toolbar.py +++ b/leafmap/toolbar.py @@ -6971,23 +6971,33 @@ def button_clicked(change): links = m._NASA_DATA_RESULTS[ dataset.options.index(dataset.value) ].data_links() - ds = xr.open_dataset(links[layer.index], engine="rasterio") - setattr(m, "_NASA_DATA_DS", ds) - da = ds["band_data"] - nodata = os.environ.get("NODATA", 0) - da = da.fillna(nodata) - image = array_to_image(da) - name_prefix = dataset.value.split("_")[4][:8] - name_suffix = layer.value.split(".")[0] - layer_name = f"{name_prefix}_{name_suffix}" - m.add_raster( - image, - zoom_to_layer=True, - colormap=palette.value, - nodata=nodata, - layer_name=layer_name, - ) - output.clear_output() + link = links[layer.index] + try: + if link.endswith(".tif"): + ds = xr.open_dataset(link, engine="rasterio") + setattr(m, "_NASA_DATA_DS", ds) + da = ds["band_data"] + nodata = os.environ.get("NODATA", 0) + da = da.fillna(nodata) + image = array_to_image(da) + setattr(m, "_NASA_DATA_IMAGE", image) + name_prefix = dataset.value.split("_")[4][:8] + name_suffix = layer.value.split(".")[0] + layer_name = f"{name_prefix}_{name_suffix}" + m.add_raster( + image, + zoom_to_layer=True, + colormap=palette.value, + nodata=nodata, + layer_name=layer_name, + ) + output.clear_output() + else: + output.clear_output() + print("Only GeoTIFF files are supported.") + except Exception as e: + output.clear_output() + print(e) elif change["new"] == "Reset": short_name.options = m._NASA_DATA_NAMES From 8bb2ccca9d720e140f3fab45857804523c8bd4b3 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Fri, 29 Nov 2024 18:20:06 -0500 Subject: [PATCH 6/8] Add NASA OPERA notebook --- docs/notebooks/101_nasa_opera.ipynb | 181 ++++++++++++++++++++++++++++ docs/tutorials.md | 1 + examples/README.md | 1 + mkdocs.yml | 2 + 4 files changed, 185 insertions(+) create mode 100644 docs/notebooks/101_nasa_opera.ipynb diff --git a/docs/notebooks/101_nasa_opera.ipynb b/docs/notebooks/101_nasa_opera.ipynb new file mode 100644 index 0000000000..cf1a97578d --- /dev/null +++ b/docs/notebooks/101_nasa_opera.ipynb @@ -0,0 +1,181 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[![image](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://demo.leafmap.org/lab/index.html?path=notebooks/101_nasa_opera.ipynb)\n", + "[![image](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/opengeos/leafmap/blob/master/docs/notebooks/101_nasa_opera.ipynb)\n", + "[![image](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/opengeos/leafmap/HEAD)\n", + "\n", + "**Searching and Visualizing NASA OPERA Data Products Interactively**\n", + "\n", + "\n", + "Started in April 2021, the Observational Products for End-Users from Remote Sensing Analysis ([OPERA](https://www.jpl.nasa.gov/go/opera)) project at the Jet Propulsion Laboratory collects data from satellite radar and optical instruments to generate six product suites:\n", + "\n", + "* a near-global Surface Water Extent product suite\n", + "* a near-global Surface Disturbance product suite\n", + "* a near-global Radiometric Terrain Corrected product\n", + "* a North America Coregistered Single Look complex product suite\n", + "* a North America Displacement product suite\n", + "* a North America Vertical Land Motion product suite\n", + "\n", + "This notebook demonstrates how to search and visualize NASA OPERA data products interactively using the `leafmap` Python package." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# %pip install -U \"leafmap[raster]\" earthaccess" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import leafmap" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To download and access the data, you will need to create an Earthdata login. You can register for an account at [urs.earthdata.nasa.gov](https://urs.earthdata.nasa.gov)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "leafmap.nasa_data_login()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[36.1711, -114.6581], zoom=10, height=\"700px\")\n", + "m.add_basemap(\"Satellite\")\n", + "m.add(\"NASA_OPERA\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Pan and zoom to your area of interest. Select a dataset from the Short Name dropdown list. Click the \"Search\" button to load the available datasets for the region. The footprints of the datasets will be displayed on the map. Click on a footprint to display the metadata of the dataset. \n", + "\n", + "![image](https://github.com/user-attachments/assets/f3b1b42e-d83d-4f40-b9e6-67082d364367)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The footprints of the datasets can be accessed as a GeoPandas GeoDataFrame:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m._NASA_DATA_GDF.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Select a dataset from the Dataset dropdown list. Then, select a layer from the Layer dropdown list. Choose a appropriate colormap, then click on the \"Display\" button to display the selected layer on the map." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![image](https://github.com/user-attachments/assets/f9800913-c683-4a9f-a3b5-164c911a4486)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The selected layer added to the map can be accessed as a xarray Dataset:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m._NASA_DATA_DS" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To save the displayed layer as a GeoTIFF file:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m._NASA_DATA_DS['band_data'].rio.to_raster(\"data/DSWx.tif\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To download all the available datasets for the region:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "leafmap.nasa_data_download(m._NASA_DATA_RESULTS[:1], out_dir=\"data\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "geo", + "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.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/tutorials.md b/docs/tutorials.md index f0be2aa040..5cc9d692fe 100644 --- a/docs/tutorials.md +++ b/docs/tutorials.md @@ -112,6 +112,7 @@ 98. Retrieving watershed boundaries from the National Hydrography Dataset (NHD) ([notebook](https://leafmap.org/notebooks/98_watershed)) 99. Retrieving wetland boundaries from the National Wetlands Inventory (NWI) ([notebook](https://leafmap.org/notebooks/99_wetlands)) 100. Visualizing the National Land Cover Database (NLCD) data products with Leafmap ([notebook](https://leafmap.org/notebooks/100_nlcd)) +101. Searching and Visualizing NASA OPERA Data Products Interactively ([notebook](https://leafmap.org/notebooks/101_nasa_opera)) ## Demo diff --git a/examples/README.md b/examples/README.md index 2b6ec245c0..163c383637 100644 --- a/examples/README.md +++ b/examples/README.md @@ -119,6 +119,7 @@ 98. Retrieving watershed boundaries from the National Hydrography Dataset (NHD) ([notebook](https://leafmap.org/notebooks/98_watershed)) 99. Retrieving wetland boundaries from the National Wetlands Inventory (NWI) ([notebook](https://leafmap.org/notebooks/99_wetlands)) 100. Visualizing the National Land Cover Database (NLCD) data products with Leafmap ([notebook](https://leafmap.org/notebooks/100_nlcd)) +101. Searching and Visualizing NASA OPERA Data Products Interactively ([notebook](https://leafmap.org/notebooks/101_nasa_opera)) ## Demo diff --git a/mkdocs.yml b/mkdocs.yml index a8b065229d..2d04613a23 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -80,6 +80,7 @@ plugins: "notebooks/88_nasa_earth_data.ipynb", "notebooks/94_mapbox.ipynb", "notebooks/99_wetlands.ipynb", + "notebooks/101_nasa_opera.ipynb", ] markdown_extensions: @@ -335,3 +336,4 @@ nav: - notebooks/98_watershed.ipynb - notebooks/99_wetlands.ipynb - notebooks/100_nlcd.ipynb + - notebooks/101_nasa_opera.ipynb From 24d8609667532c433cb874d815f8e707dd0ad7ce Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 29 Nov 2024 23:20:31 +0000 Subject: [PATCH 7/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/notebooks/101_nasa_opera.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/notebooks/101_nasa_opera.ipynb b/docs/notebooks/101_nasa_opera.ipynb index cf1a97578d..e80c56e6c1 100644 --- a/docs/notebooks/101_nasa_opera.ipynb +++ b/docs/notebooks/101_nasa_opera.ipynb @@ -137,7 +137,7 @@ "metadata": {}, "outputs": [], "source": [ - "m._NASA_DATA_DS['band_data'].rio.to_raster(\"data/DSWx.tif\")" + "m._NASA_DATA_DS[\"band_data\"].rio.to_raster(\"data/DSWx.tif\")" ] }, { From c75b9ef325116c54dcd8b5e1e72267ff3b2d1485 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Fri, 29 Nov 2024 18:25:53 -0500 Subject: [PATCH 8/8] Add DEM layer --- docs/notebooks/101_nasa_opera.ipynb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/notebooks/101_nasa_opera.ipynb b/docs/notebooks/101_nasa_opera.ipynb index e80c56e6c1..d703539faf 100644 --- a/docs/notebooks/101_nasa_opera.ipynb +++ b/docs/notebooks/101_nasa_opera.ipynb @@ -105,7 +105,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "![image](https://github.com/user-attachments/assets/f9800913-c683-4a9f-a3b5-164c911a4486)" + "The water classification layer:\n", + "\n", + "![image](https://github.com/user-attachments/assets/f9800913-c683-4a9f-a3b5-164c911a4486)\n", + "\n", + "The DEM layer:\n", + "\n", + "![image](https://github.com/user-attachments/assets/15690881-d259-474f-9c77-eb6c40027ccc)\n" ] }, {