Skip to content

Commit 3d4e39f

Browse files
authored
feat: add HLS data visualization notebook for NASA Earthdata (#1323)
1 parent 8047cfa commit 3d4e39f

2 files changed

Lines changed: 320 additions & 0 deletions

File tree

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"[![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",
8+
"[![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",
9+
"[![image](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/opengeos/leafmap/HEAD)\n",
10+
"\n",
11+
"**Searching and Visualizing HLS Data from NASA Earthdata**\n",
12+
"\n",
13+
"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",
14+
"\n",
15+
"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."
16+
]
17+
},
18+
{
19+
"cell_type": "markdown",
20+
"metadata": {},
21+
"source": [
22+
"## Installation\n",
23+
"\n",
24+
"Uncomment the following line to install the required packages if needed."
25+
]
26+
},
27+
{
28+
"cell_type": "code",
29+
"execution_count": null,
30+
"metadata": {},
31+
"outputs": [],
32+
"source": [
33+
"# %pip install -U \"leafmap[raster]\" earthaccess geopandas mapclassify"
34+
]
35+
},
36+
{
37+
"cell_type": "code",
38+
"execution_count": null,
39+
"metadata": {},
40+
"outputs": [],
41+
"source": [
42+
"import leafmap"
43+
]
44+
},
45+
{
46+
"cell_type": "markdown",
47+
"metadata": {},
48+
"source": [
49+
"## Sign in to NASA Earthdata\n",
50+
"\n",
51+
"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."
52+
]
53+
},
54+
{
55+
"cell_type": "code",
56+
"execution_count": null,
57+
"metadata": {},
58+
"outputs": [],
59+
"source": [
60+
"leafmap.nasa_data_login()"
61+
]
62+
},
63+
{
64+
"cell_type": "markdown",
65+
"metadata": {},
66+
"source": [
67+
"## Define the search parameters\n",
68+
"\n",
69+
"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."
70+
]
71+
},
72+
{
73+
"cell_type": "code",
74+
"execution_count": null,
75+
"metadata": {},
76+
"outputs": [],
77+
"source": [
78+
"hls_collections = {\n",
79+
" \"HLSL30\": \"C2021957657-LPCLOUD\",\n",
80+
" \"HLSS30\": \"C2021957295-LPCLOUD\",\n",
81+
"}\n",
82+
"\n",
83+
"bbox = (-122.55, 37.68, -122.30, 37.84)\n",
84+
"temporal = (\"2025-06-01\", \"2025-08-31\")\n",
85+
"map_center = [37.7749, -122.4194]"
86+
]
87+
},
88+
{
89+
"cell_type": "markdown",
90+
"metadata": {},
91+
"source": [
92+
"## Search HLSL30 granules\n",
93+
"\n",
94+
"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."
95+
]
96+
},
97+
{
98+
"cell_type": "code",
99+
"execution_count": null,
100+
"metadata": {},
101+
"outputs": [],
102+
"source": [
103+
"landsat_results, landsat_gdf = leafmap.nasa_data_search(\n",
104+
" short_name=\"HLSL30\",\n",
105+
" version=\"2.0\",\n",
106+
" cloud_hosted=True,\n",
107+
" bounding_box=bbox,\n",
108+
" temporal=temporal,\n",
109+
" cloud_cover=(0, 10),\n",
110+
" count=20,\n",
111+
" return_gdf=True,\n",
112+
")\n",
113+
"\n",
114+
"landsat_gdf[[\"native-id\", \"BeginningDateTime\", \"EndingDateTime\", \"GranuleUR\"]].head()"
115+
]
116+
},
117+
{
118+
"cell_type": "markdown",
119+
"metadata": {},
120+
"source": [
121+
"Search the matching Sentinel-2 HLS collection for the same area and date range."
122+
]
123+
},
124+
{
125+
"cell_type": "code",
126+
"execution_count": null,
127+
"metadata": {},
128+
"outputs": [],
129+
"source": [
130+
"sentinel_results, sentinel_gdf = leafmap.nasa_data_search(\n",
131+
" short_name=\"HLSS30\",\n",
132+
" version=\"2.0\",\n",
133+
" cloud_hosted=True,\n",
134+
" bounding_box=bbox,\n",
135+
" temporal=temporal,\n",
136+
" cloud_cover=(0, 10),\n",
137+
" count=20,\n",
138+
" return_gdf=True,\n",
139+
")\n",
140+
"\n",
141+
"sentinel_gdf[[\"native-id\", \"BeginningDateTime\", \"EndingDateTime\", \"GranuleUR\"]].head()"
142+
]
143+
},
144+
{
145+
"cell_type": "markdown",
146+
"metadata": {},
147+
"source": [
148+
"## Visualize the search footprints\n",
149+
"\n",
150+
"The footprints show the HLS MGRS tiles returned by NASA CMR for the selected area and time range."
151+
]
152+
},
153+
{
154+
"cell_type": "code",
155+
"execution_count": null,
156+
"metadata": {},
157+
"outputs": [],
158+
"source": [
159+
"m = leafmap.Map(center=map_center, zoom=9, height=\"700px\")\n",
160+
"m.add_basemap(\"Satellite\")\n",
161+
"m.add_gdf(\n",
162+
" landsat_gdf,\n",
163+
" layer_name=\"HLSL30 footprints\",\n",
164+
" style={\"color\": \"#d7191c\", \"weight\": 2, \"fillOpacity\": 0.05},\n",
165+
")\n",
166+
"m.add_gdf(\n",
167+
" sentinel_gdf,\n",
168+
" layer_name=\"HLSS30 footprints\",\n",
169+
" style={\"color\": \"#2c7bb6\", \"weight\": 2, \"fillOpacity\": 0.05},\n",
170+
")\n",
171+
"m"
172+
]
173+
},
174+
{
175+
"cell_type": "markdown",
176+
"metadata": {},
177+
"source": [
178+
"## Visualize HLS true color imagery\n",
179+
"\n",
180+
"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."
181+
]
182+
},
183+
{
184+
"cell_type": "code",
185+
"execution_count": null,
186+
"metadata": {},
187+
"outputs": [],
188+
"source": [
189+
"titiler_cmr_endpoint = \"https://staging.openveda.cloud/api/titiler-cmr\"\n",
190+
"landsat_datetime = \"2025-08-31T00:00:00Z/2025-08-31T23:59:59Z\"\n",
191+
"\n",
192+
"m = leafmap.Map(center=map_center, zoom=10, height=\"700px\")\n",
193+
"m.add_cmr_layer(\n",
194+
" concept_id=hls_collections[\"HLSL30\"],\n",
195+
" datetime=landsat_datetime,\n",
196+
" backend=\"rasterio\",\n",
197+
" bands=[\"B04\", \"B03\", \"B02\"],\n",
198+
" bands_regex=\"B[0-9][0-9]\",\n",
199+
" color_formula=\"Gamma RGB 3.5 Saturation 1.7 Sigmoidal RGB 15 0.35\",\n",
200+
" name=\"HLSL30 true color\",\n",
201+
" titiler_cmr_endpoint=titiler_cmr_endpoint,\n",
202+
" zoom_to_layer=False,\n",
203+
")\n",
204+
"m"
205+
]
206+
},
207+
{
208+
"cell_type": "markdown",
209+
"metadata": {},
210+
"source": [
211+
"## Visualize NDVI\n",
212+
"\n",
213+
"Band math expressions can be sent directly to TiTiler CMR. For HLSL30, NDVI uses near infrared band B05 and red band B04."
214+
]
215+
},
216+
{
217+
"cell_type": "code",
218+
"execution_count": null,
219+
"metadata": {},
220+
"outputs": [],
221+
"source": [
222+
"m = leafmap.Map(center=map_center, zoom=10, height=\"700px\")\n",
223+
"m.add_cmr_layer(\n",
224+
" concept_id=hls_collections[\"HLSL30\"],\n",
225+
" datetime=landsat_datetime,\n",
226+
" backend=\"rasterio\",\n",
227+
" expression=\"(B05-B04)/(B05+B04)\",\n",
228+
" bands_regex=\"B[0-9][0-9]\",\n",
229+
" rescale=\"-1,1\",\n",
230+
" colormap_name=\"rdylgn\",\n",
231+
" name=\"HLSL30 NDVI\",\n",
232+
" titiler_cmr_endpoint=titiler_cmr_endpoint,\n",
233+
" zoom_to_layer=False,\n",
234+
")\n",
235+
"m"
236+
]
237+
},
238+
{
239+
"cell_type": "markdown",
240+
"metadata": {},
241+
"source": [
242+
"## Compare Landsat and Sentinel-2 HLS layers\n",
243+
"\n",
244+
"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."
245+
]
246+
},
247+
{
248+
"cell_type": "code",
249+
"execution_count": null,
250+
"metadata": {},
251+
"outputs": [],
252+
"source": [
253+
"sentinel_datetime = \"2025-08-31T00:00:00Z/2025-08-31T23:59:59Z\"\n",
254+
"\n",
255+
"m = leafmap.Map(center=map_center, zoom=10, height=\"700px\")\n",
256+
"for short_name, datetime in [\n",
257+
" (\"HLSL30\", landsat_datetime),\n",
258+
" (\"HLSS30\", sentinel_datetime),\n",
259+
"]:\n",
260+
" m.add_cmr_layer(\n",
261+
" concept_id=hls_collections[short_name],\n",
262+
" datetime=datetime,\n",
263+
" backend=\"rasterio\",\n",
264+
" bands=[\"B04\", \"B03\", \"B02\"],\n",
265+
" bands_regex=\"B[0-9][0-9]\",\n",
266+
" color_formula=\"Gamma RGB 3.5 Saturation 1.7 Sigmoidal RGB 15 0.35\",\n",
267+
" name=f\"{short_name} true color\",\n",
268+
" titiler_cmr_endpoint=titiler_cmr_endpoint,\n",
269+
" zoom_to_layer=False,\n",
270+
" )\n",
271+
"m"
272+
]
273+
},
274+
{
275+
"cell_type": "markdown",
276+
"metadata": {},
277+
"source": [
278+
"## Download selected HLS assets\n",
279+
"\n",
280+
"Use `keywords` to download only selected band files from the returned granules. The example below is commented out to avoid downloading files unintentionally."
281+
]
282+
},
283+
{
284+
"cell_type": "code",
285+
"execution_count": null,
286+
"metadata": {},
287+
"outputs": [],
288+
"source": [
289+
"leafmap.nasa_data_download(\n",
290+
" landsat_results[:1],\n",
291+
" out_dir=\"data\",\n",
292+
" keywords=[\".B04.tif\", \".B03.tif\", \".B02.tif\", \".B05.tif\"],\n",
293+
")"
294+
]
295+
}
296+
],
297+
"metadata": {
298+
"kernelspec": {
299+
"display_name": "geo",
300+
"language": "python",
301+
"name": "python3"
302+
},
303+
"language_info": {
304+
"codemirror_mode": {
305+
"name": "ipython",
306+
"version": 3
307+
},
308+
"file_extension": ".py",
309+
"mimetype": "text/x-python",
310+
"name": "python",
311+
"nbconvert_exporter": "python",
312+
"pygments_lexer": "ipython3",
313+
"version": "3.12.12"
314+
}
315+
},
316+
"nbformat": 4,
317+
"nbformat_minor": 4
318+
}

mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ plugins:
103103
"notebooks/101_nasa_opera.ipynb",
104104
"notebooks/102_fused.ipynb",
105105
"notebooks/107_copernicus.ipynb",
106+
"notebooks/116_hls_nasa_earthdata.ipynb",
106107
"maplibre/3d_pmtiles.ipynb",
107108
"maplibre/animate_a_line.ipynb",
108109
"maplibre/copernicus.ipynb",
@@ -416,3 +417,4 @@ nav:
416417
- notebooks/113_titiler_cmr.ipynb
417418
- notebooks/114_nasa_fire.ipynb
418419
- notebooks/115_terrascope.ipynb
420+
- notebooks/116_hls_nasa_earthdata.ipynb

0 commit comments

Comments
 (0)