diff --git a/docs/notebooks/116_hls_nasa_earthdata.ipynb b/docs/notebooks/116_hls_nasa_earthdata.ipynb new file mode 100644 index 0000000000..d3ae49a684 --- /dev/null +++ b/docs/notebooks/116_hls_nasa_earthdata.ipynb @@ -0,0 +1,318 @@ +{ + "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/116_hls_nasa_earthdata.ipynb)\n", + "[![image](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/opengeos/leafmap/blob/master/docs/notebooks/116_hls_nasa_earthdata.ipynb)\n", + "[![image](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/opengeos/leafmap/HEAD)\n", + "\n", + "**Searching and Visualizing HLS Data from NASA Earthdata**\n", + "\n", + "The [Harmonized Landsat and Sentinel-2](https://www.earthdata.nasa.gov/data/projects/hls) (HLS) project provides consistent 30-meter surface reflectance products from Landsat and Sentinel-2. The HLSL30 and HLSS30 products are distributed through NASA Earthdata as Cloud Optimized GeoTIFFs (COGs), which makes them suitable for cloud-native search, download, and interactive visualization.\n", + "\n", + "This notebook demonstrates how to search HLS granules with `earthaccess` through leafmap, visualize granule footprints, and stream HLS true color and NDVI layers on an interactive map with TiTiler CMR." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Installation\n", + "\n", + "Uncomment the following line to install the required packages if needed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# %pip install -U \"leafmap[raster]\" earthaccess geopandas mapclassify" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import leafmap" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sign in to NASA Earthdata\n", + "\n", + "Searching public metadata does not always require authentication, but downloading protected HLS assets does. Create a NASA Earthdata Login account at [urs.earthdata.nasa.gov](https://urs.earthdata.nasa.gov) if you do not already have one." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "leafmap.nasa_data_login()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define the search parameters\n", + "\n", + "The example below searches for HLS granules near San Francisco, California, USA during summer 2025. HLSL30 is the Landsat product and HLSS30 is the Sentinel-2 product." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hls_collections = {\n", + " \"HLSL30\": \"C2021957657-LPCLOUD\",\n", + " \"HLSS30\": \"C2021957295-LPCLOUD\",\n", + "}\n", + "\n", + "bbox = (-122.55, 37.68, -122.30, 37.84)\n", + "temporal = (\"2025-06-01\", \"2025-08-31\")\n", + "map_center = [37.7749, -122.4194]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Search HLSL30 granules\n", + "\n", + "Set `return_gdf=True` to return the granule footprints as a GeoDataFrame in addition to the Earthaccess search results. The `cloud_cover=(0, 10)` parameter limits the results to granules with less than 10% cloud cover." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "landsat_results, landsat_gdf = leafmap.nasa_data_search(\n", + " short_name=\"HLSL30\",\n", + " version=\"2.0\",\n", + " cloud_hosted=True,\n", + " bounding_box=bbox,\n", + " temporal=temporal,\n", + " cloud_cover=(0, 10),\n", + " count=20,\n", + " return_gdf=True,\n", + ")\n", + "\n", + "landsat_gdf[[\"native-id\", \"BeginningDateTime\", \"EndingDateTime\", \"GranuleUR\"]].head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Search the matching Sentinel-2 HLS collection for the same area and date range." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sentinel_results, sentinel_gdf = leafmap.nasa_data_search(\n", + " short_name=\"HLSS30\",\n", + " version=\"2.0\",\n", + " cloud_hosted=True,\n", + " bounding_box=bbox,\n", + " temporal=temporal,\n", + " cloud_cover=(0, 10),\n", + " count=20,\n", + " return_gdf=True,\n", + ")\n", + "\n", + "sentinel_gdf[[\"native-id\", \"BeginningDateTime\", \"EndingDateTime\", \"GranuleUR\"]].head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualize the search footprints\n", + "\n", + "The footprints show the HLS MGRS tiles returned by NASA CMR for the selected area and time range." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=map_center, zoom=9, height=\"700px\")\n", + "m.add_basemap(\"Satellite\")\n", + "m.add_gdf(\n", + " landsat_gdf,\n", + " layer_name=\"HLSL30 footprints\",\n", + " style={\"color\": \"#d7191c\", \"weight\": 2, \"fillOpacity\": 0.05},\n", + ")\n", + "m.add_gdf(\n", + " sentinel_gdf,\n", + " layer_name=\"HLSS30 footprints\",\n", + " style={\"color\": \"#2c7bb6\", \"weight\": 2, \"fillOpacity\": 0.05},\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualize HLS true color imagery\n", + "\n", + "Use the HLS collection concept ID with `add_cmr_layer()` to stream COG assets from NASA Earthdata through TiTiler CMR. The HLS true color composite uses red, green, and blue bands." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "titiler_cmr_endpoint = \"https://staging.openveda.cloud/api/titiler-cmr\"\n", + "landsat_datetime = \"2025-08-31T00:00:00Z/2025-08-31T23:59:59Z\"\n", + "\n", + "m = leafmap.Map(center=map_center, zoom=10, height=\"700px\")\n", + "m.add_cmr_layer(\n", + " concept_id=hls_collections[\"HLSL30\"],\n", + " datetime=landsat_datetime,\n", + " backend=\"rasterio\",\n", + " bands=[\"B04\", \"B03\", \"B02\"],\n", + " bands_regex=\"B[0-9][0-9]\",\n", + " color_formula=\"Gamma RGB 3.5 Saturation 1.7 Sigmoidal RGB 15 0.35\",\n", + " name=\"HLSL30 true color\",\n", + " titiler_cmr_endpoint=titiler_cmr_endpoint,\n", + " zoom_to_layer=False,\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualize NDVI\n", + "\n", + "Band math expressions can be sent directly to TiTiler CMR. For HLSL30, NDVI uses near infrared band B05 and red band B04." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=map_center, zoom=10, height=\"700px\")\n", + "m.add_cmr_layer(\n", + " concept_id=hls_collections[\"HLSL30\"],\n", + " datetime=landsat_datetime,\n", + " backend=\"rasterio\",\n", + " expression=\"(B05-B04)/(B05+B04)\",\n", + " bands_regex=\"B[0-9][0-9]\",\n", + " rescale=\"-1,1\",\n", + " colormap_name=\"rdylgn\",\n", + " name=\"HLSL30 NDVI\",\n", + " titiler_cmr_endpoint=titiler_cmr_endpoint,\n", + " zoom_to_layer=False,\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compare Landsat and Sentinel-2 HLS layers\n", + "\n", + "Because HLSL30 and HLSS30 are harmonized to the same 30-meter grid, you can add both products to the same map and use the layer control to compare acquisition dates." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sentinel_datetime = \"2025-08-31T00:00:00Z/2025-08-31T23:59:59Z\"\n", + "\n", + "m = leafmap.Map(center=map_center, zoom=10, height=\"700px\")\n", + "for short_name, datetime in [\n", + " (\"HLSL30\", landsat_datetime),\n", + " (\"HLSS30\", sentinel_datetime),\n", + "]:\n", + " m.add_cmr_layer(\n", + " concept_id=hls_collections[short_name],\n", + " datetime=datetime,\n", + " backend=\"rasterio\",\n", + " bands=[\"B04\", \"B03\", \"B02\"],\n", + " bands_regex=\"B[0-9][0-9]\",\n", + " color_formula=\"Gamma RGB 3.5 Saturation 1.7 Sigmoidal RGB 15 0.35\",\n", + " name=f\"{short_name} true color\",\n", + " titiler_cmr_endpoint=titiler_cmr_endpoint,\n", + " zoom_to_layer=False,\n", + " )\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Download selected HLS assets\n", + "\n", + "Use `keywords` to download only selected band files from the returned granules. The example below is commented out to avoid downloading files unintentionally." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "leafmap.nasa_data_download(\n", + " landsat_results[:1],\n", + " out_dir=\"data\",\n", + " keywords=[\".B04.tif\", \".B03.tif\", \".B02.tif\", \".B05.tif\"],\n", + ")" + ] + } + ], + "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.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/mkdocs.yml b/mkdocs.yml index 259e795bdb..2a9e3b0196 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -103,6 +103,7 @@ plugins: "notebooks/101_nasa_opera.ipynb", "notebooks/102_fused.ipynb", "notebooks/107_copernicus.ipynb", + "notebooks/116_hls_nasa_earthdata.ipynb", "maplibre/3d_pmtiles.ipynb", "maplibre/animate_a_line.ipynb", "maplibre/copernicus.ipynb", @@ -416,3 +417,4 @@ nav: - notebooks/113_titiler_cmr.ipynb - notebooks/114_nasa_fire.ipynb - notebooks/115_terrascope.ipynb + - notebooks/116_hls_nasa_earthdata.ipynb