Add Urban Heat Island mapping notebook with MODIS LST - #1343
Conversation
Demonstrates querying MODIS MOD11A2 LST via earthaccess, applying scale factors with rioxarray, and visualizing Urban Heat Island intensity interactively with leafmap split maps and colorbars.
📝 WalkthroughWalkthroughAdded a Jupyter notebook that demonstrates MODIS MOD11A2 LST acquisition, preprocessing, GeoTIFF export, interactive mapping, split-map comparison, and scene-mean UHI index visualization for Lahore. ChangesUrban Heat Island Workflow
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Notebook
participant Earthaccess
participant MODIS
participant Leafmap
Notebook->>Earthaccess: Authenticate and search June 2023 Lahore granules
Earthaccess->>MODIS: Download first matching MOD11A2 granule
MODIS-->>Notebook: Provide HDF granule source
Notebook->>Notebook: Clip, mask, and convert LST to Celsius
Notebook->>Notebook: Compute scene-mean UHI index
Notebook->>Leafmap: Render LST and UHI GeoTIFF overlays
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
for more information, see https://pre-commit.ci
|
Tick the box to add this pull request to the merge queue (same as
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/notebooks/117_urban_heat_island.ipynb`:
- Around line 143-151: Update the notebook’s daytime LST processing around
lst_raw to load the QC_Day subdataset, decode bits 0–1, and mask lst_raw to
retain only good-quality daytime pixels before applying the 0.02 scale factor or
computing lst_celsius, scene means, and UHI. Preserve the existing fill-value
masking while ensuring all downstream calculations use the quality-filtered LST.
- Around line 143-151: Clip lst_raw to bbox immediately after loading and
squeezing it, before applying the scale factor; ensure the resulting
Lahore-specific lst_celsius is used for export and scene_mean aggregation.
- Around line 258-271: Update the split-map setup around m2.split_map to use
lst_output as one pane instead of adding it afterward with m2.add_raster. Keep
the satellite basemap as the opposite pane and preserve the existing LST
colormap, layer name, and opacity settings when configuring the raster side.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f1308c9a-3d3b-446f-9f68-4f07e1c03676
📒 Files selected for processing (1)
docs/notebooks/117_urban_heat_island.ipynb
|
/claude-review |
Code reviewThis PR adds a new documentation notebook ( Bugs
Security
Performance
Quality
CLAUDE.md
|
- add_colorbar: replace non-existent cmap/label/position kwargs with correct colors/vmin/vmax signature - split_map: remove leafmap.basemap_to_tiles() which does not exist; use string basemap names directly - Search/download: add guards against empty results and files lists
|
Fixed all issues from the automated code review:
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
docs/notebooks/117_urban_heat_island.ipynb (2)
40-40: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winInstall leafmap’s raster extra.
This notebook installs bare
leafmapbut later callsMap.add_rasteron local GeoTIFFs, so the raster dependencies (includinglocaltileserver) should be installed withleafmap[raster].Proposed fix
- "# %pip install leafmap earthaccess rioxarray matplotlib numpy" + "# %pip install \"leafmap[raster]\" earthaccess rioxarray matplotlib numpy"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/notebooks/117_urban_heat_island.ipynb` at line 40, Update the notebook’s dependency installation line to install the raster extra for leafmap, using `leafmap[raster]` instead of bare `leafmap`; preserve the other packages unchanged.Source: MCP tools
199-204: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep the raster colormap bounds aligned with the colorbar bounds.
add_rasterandadd_colorbareach acceptvmin/vmax, so the LST raster can render values outside the legend’s 20–50 range and the UHI raster outside –5–5. Pass the same bounds to both calls for each raster.Proposed fix
m.add_raster( lst_output, colormap="RdYlBu_r", + vmin=20, + vmax=50, layer_name="LST Day (°C)", opacity=0.75, ) m3.add_raster( uhi_output, colormap="seismic", + vmin=-5, + vmax=5, layer_name="UHI Index (°C above mean)", opacity=0.75, )Also applies to lines 251-256.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/notebooks/117_urban_heat_island.ipynb` around lines 199 - 204, Update the raster display calls in the notebook to pass explicit vmin/vmax bounds matching their corresponding colorbars: use 20–50 for the LST layer around add_raster and add_colorbar, and –5–5 for the UHI layer. Apply this consistently to the second raster section as well.Source: MCP tools
♻️ Duplicate comments (2)
docs/notebooks/117_urban_heat_island.ipynb (2)
126-140: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winClip the granule to Lahore before deriving outputs.
bounding_boxonly limits discovery;lst_rawremains the full MODIS tile, so the exported rasters andscene_meaninclude areas outside Lahore. Clip the LST and QC arrays before scaling, using the bbox CRS explicitly, e.g.rio.clip_box(*bbox, crs="EPSG:4326"). (corteva.github.io)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/notebooks/117_urban_heat_island.ipynb` around lines 126 - 140, Clip lst_raw and the corresponding QC array to the Lahore bounding box using rio.clip_box with crs="EPSG:4326" before scaling or deriving outputs. Ensure the clipped arrays, rather than the full MODIS tile, feed lst_celsius, exported rasters, and scene_mean.Source: MCP tools
126-134: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winMask
QC_Daybefore calculating LST and UHI.
where(lst_raw != 0)only removes fill values. MOD11A2 usesQC_Daybits 0–1 to distinguish good retrievals from other-quality, cloud-affected, and unavailable pixels; those unfiltered pixels currently feed every downstream calculation. Retain only(qc.astype("uint8") & 0b11) == 0before applying the scale factor. (ladsweb.modaps.eosdis.nasa.gov)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/notebooks/117_urban_heat_island.ipynb` around lines 126 - 134, Update the daytime LST preparation near lst_raw to load the QC_Day subdataset, retain only pixels where QC_Day bits 0–1 equal zero using the specified uint8 mask, and combine that mask with the existing fill-value filtering before applying the 0.02 scale factor and Celsius conversion. Ensure the filtered lst_celsius continues feeding downstream UHI calculations.Source: MCP tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@docs/notebooks/117_urban_heat_island.ipynb`:
- Line 40: Update the notebook’s dependency installation line to install the
raster extra for leafmap, using `leafmap[raster]` instead of bare `leafmap`;
preserve the other packages unchanged.
- Around line 199-204: Update the raster display calls in the notebook to pass
explicit vmin/vmax bounds matching their corresponding colorbars: use 20–50 for
the LST layer around add_raster and add_colorbar, and –5–5 for the UHI layer.
Apply this consistently to the second raster section as well.
---
Duplicate comments:
In `@docs/notebooks/117_urban_heat_island.ipynb`:
- Around line 126-140: Clip lst_raw and the corresponding QC array to the Lahore
bounding box using rio.clip_box with crs="EPSG:4326" before scaling or deriving
outputs. Ensure the clipped arrays, rather than the full MODIS tile, feed
lst_celsius, exported rasters, and scene_mean.
- Around line 126-134: Update the daytime LST preparation near lst_raw to load
the QC_Day subdataset, retain only pixels where QC_Day bits 0–1 equal zero using
the specified uint8 mask, and combine that mask with the existing fill-value
filtering before applying the 0.02 scale factor and Celsius conversion. Ensure
the filtered lst_celsius continues feeding downstream UHI calculations.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 50486b87-f4ed-4a6d-8e77-be2117ff7fe5
📒 Files selected for processing (1)
docs/notebooks/117_urban_heat_island.ipynb
|
/claude-review |
|
All four inline comments posted successfully. Now for the HDF4 GDAL dependency note — I'll fold that into the summary as a lower-confidence practical concern rather than an inline comment, since it's about environment/dependency fragility rather than a specific line-level defect. Code reviewThis PR adds a single new documentation notebook ( Bugs
Security
Performance
Quality
CLAUDE.md
|
- Clip the MOD11A2 granule to the Lahore bbox before scaling, exporting, and averaging. The granule covers a full ~1200 km MODIS tile, so the exported rasters and the UHI scene mean were not Lahore-specific. - Mask low-quality pixels using the QC_Day mandatory QA flags (bits 0-1), so cloud-contaminated retrievals no longer feed into the LST or the UHI index. - Split map now uses the LST raster as the left pane against the satellite basemap, matching the section's stated intent, instead of overlaying it on top of both panes. - Pass matching vmin/vmax to add_raster in the LST and UHI maps so the rendered stretch lines up with the hardcoded colorbar ranges. - Select the downloaded .hdf explicitly instead of files[0]; earthaccess can return sidecar files in unspecified order. - Drop the unused numpy import (and the matching pip install entry). - Register the notebook in the Zensical docs nav.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/notebooks/117_urban_heat_island.ipynb (1)
149-155: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winFail fast when the QA mask removes every pixel.
If no pixel passes
good_quality,lst_celsius.min(),lst_celsius.mean(), and the subsequentrio.to_raster()calls operate on an all-NaN array. Xarray reductions skip NaNs, so this case does not raise automatically. Count valid pixels after the mask and raise a descriptiveValueErrorbefore exporting.Proposed validation
lst_kelvin = lst_raw.where((lst_raw != 0) & good_quality) * 0.02 lst_celsius = lst_kelvin - 273.15 +valid_pixels = int(lst_celsius.count().item()) +if valid_pixels == 0: + raise ValueError("No valid LST pixels remain after applying the QC_Day mask.") print(🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/notebooks/117_urban_heat_island.ipynb` around lines 149 - 155, Add validation after computing lst_celsius and before the range output or any rio.to_raster calls: count non-NaN pixels remaining after the good_quality mask, and raise a descriptive ValueError when the count is zero. Preserve the existing reductions and raster export behavior when at least one valid pixel remains.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/notebooks/117_urban_heat_island.ipynb`:
- Line 40: Update the notebook’s commented dependency-install command to use the
raster-enabled leafmap extra (`leafmap[raster]`) instead of bare `leafmap`, and
ensure the setup documents or provisions GDAL with HDF4 support for
`rxr.open_rasterio` before the local raster cells use `m.add_raster` and
`m.split_map`.
---
Outside diff comments:
In `@docs/notebooks/117_urban_heat_island.ipynb`:
- Around line 149-155: Add validation after computing lst_celsius and before the
range output or any rio.to_raster calls: count non-NaN pixels remaining after
the good_quality mask, and raise a descriptive ValueError when the count is
zero. Preserve the existing reductions and raster export behavior when at least
one valid pixel remains.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: e9205e4e-8b31-43a8-a5bb-6a77b7f857fd
📒 Files selected for processing (2)
docs/notebooks/117_urban_heat_island.ipynbzensical.toml
| "metadata": {}, | ||
| "outputs": [], | ||
| "source": [ | ||
| "# %pip install leafmap earthaccess rioxarray matplotlib" |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf 'Repository files matching notebook:\n'
fd -a '117_urban_heat_island.ipynb' . || true
printf '\nOutline/stat for notebook:\n'
if [ -f docs/notebooks/117_urban_heat_island.ipynb ]; then
wc -l docs/notebooks/117_urban_heat_island.ipynb
python3 - <<'PY'
import json, pathlib
p=pathlib.Path('docs/notebooks/117_urban_heat_island.ipynb')
nb=json.loads(p.read_text())
for i, cell in enumerate(nb['cells'],1):
source=''.join(cell.get('source',[]))[:2000]
if any(k in source for k in ['%pip install','add_raster','split_map','open_rasterio','earthaccess','leafmap','leafmap']):
print(f'\nCell {i} type={cell["cell_type"]}')
lines=source.splitlines()
for j,l in enumerate(lines):
if any(s in l for s in ['%pip install','add_raster','split_map','open_rasterio','rxr.open_rasterio','earthaccess','leafmap']):
print(f'{j+1}: {l}')
PY
fi
printf '\nSearch relevant symbols in docs/notebooks:\n'
rg -n --glob '*.ipynb' 'add_raster|split_map|open_rasterio|rxr\.open_rasterio|%pip install|leafmap\[raster\]|localtileserver|earthaccess|rioxarray' docs/notebooks || true
printf '\nCheck installed package metadata availability if present:\n'
python3 - <<'PY'
import importlib.util, importlib.metadata, sys
for name in ['leafmap','localtileserver','rasterio','rioxarray']:
spec=importlib.util.find_spec(name)
print(f"{name}: spec={bool(spec)}", end='')
if spec:
print(f" origin={spec.origin}")
try:
md=importlib.metadata.metadata(name)
except importlib.metadata.PackageNotFoundError as e:
md=None
print(f" metadata_missing={e}")
else:
print(f" requires_dist={md.get('Requires-Dist','')}")
else:
print()
PYRepository: opengeos/leafmap
Length of output: 18671
🌐 Web query:
leafmap raster extra localtileserver rioxarray GDAL HDF4 driver pip wheels
💡 Result:
Leafmap, localtileserver, and rioxarray are common tools in the Python geospatial stack. Their relationship and requirements regarding raster data and GDAL drivers are as follows: Leafmap and Optional Dependencies: Leafmap provides extra install targets to manage its dependencies based on use case [1][2]. For raster data support, you can use: pip install "leafmap[raster]" This installs essential raster-handling libraries, including localtileserver, rioxarray, and relevant NetCDF/HDF5 support packages (e.g., netcdf4, h5netcdf, h5py) [2][3]. localtileserver and rioxarray: localtileserver uses rioxarray to enable visualization of multi-dimensional datasets like NetCDF, Zarr, and HDF5 [4][5]. You can explicitly install these dependencies for localtileserver via: pip install localtileserver[xarray] This ensures that rioxarray and xarray are available, allowing the server to handle data formats that rioxarray supports [4][5]. GDAL HDF4 Driver and Pip Wheels: The HDF4 format is a legacy data format [6][7]. The standard GDAL Python bindings available on PyPI (the gdal package) do not ship as pre-compiled "manylinux" or "macosx" wheels that bundle all native libraries; instead, they are source distributions that require a system-level installation of libgdal and its development headers (e.g., libgdal-dev) [8][9][10]. Because of this, whether the HDF4 driver is available depends on how your local system's GDAL library was compiled [6][10]. If you face difficulties with GDAL dependencies or specific driver support (like HDF4) via pip, the recommended approach is to use conda/mamba, which provides pre-built binaries with comprehensive driver support through the conda-forge channel [1][11]: mamba create -n geo leafmap geopandas localtileserver python -c conda-forge Alternatively, some newer community-driven packages (e.g., pyramids-gis) offer self-contained wheels that bundle GDAL/HDF4 native libraries on specific platforms to simplify installation, though these are independent of the standard GDAL pip package [7].
Citations:
- 1: https://leafmap.org/installation/
- 2: https://pypi.org/project/leafmap/
- 3: https://github.com/opengeos/leafmap/blob/master/pyproject.toml
- 4: https://localtileserver.banesullivan.com/user-guide/xarray.html
- 5: https://pypi.org/project/localtileserver/
- 6: https://gdal.org/en/latest/drivers/raster/hdf4.html
- 7: https://github.com/serapeum-org/pyramids/blob/main/docs/installation.md
- 8: https://gdal.org/en/latest/api/python/python_bindings.html
- 9: Additional python packaging documentation, version correction OSGeo/gdal#9405
- 10: https://pypi.org/project/GDAL/
- 11: https://context7.com/opengeos/leafmap/llms.txt
Install the raster dependencies before using local rasters.
Uncommenting this command installs bare leafmap, missing localtileserver; later cells call m.add_raster and m.split_map with local GeoTIFFs. Use leafmap[raster], e.g. "leafmap[raster]", since that extra includes the required raster backend dependencies. Also ensure GDAL has the HDF4 driver available for rxr.open_rasterio, because standard pip GDAL/Rasterio wheels do not guarantee all native drivers.
Proposed setup fix
- "# %pip install leafmap earthaccess rioxarray matplotlib"
+ "# %pip install \"leafmap[raster]\" earthaccess rioxarray matplotlib"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "# %pip install leafmap earthaccess rioxarray matplotlib" | |
| "# %pip install \"leafmap[raster]\" earthaccess rioxarray matplotlib" |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/notebooks/117_urban_heat_island.ipynb` at line 40, Update the notebook’s
commented dependency-install command to use the raster-enabled leafmap extra
(`leafmap[raster]`) instead of bare `leafmap`, and ensure the setup documents or
provisions GDAL with HDF4 support for `rxr.open_rasterio` before the local
raster cells use `m.add_raster` and `m.split_map`.
Description
Adds
docs/notebooks/117_urban_heat_island.ipynb— an end-to-end notebook for visualizing Urban Heat Island (UHI) effects using MODIS MOD11A2 Land Surface Temperature data and leafmap.What this notebook covers
earthaccessrioxarrayStudy area
Lahore, Pakistan — a South Asian megacity with well-documented UHI intensity, using a June 2023 summer granule to capture peak thermal contrast between built-up and vegetated land.
Motivation
No UHI or thermal LST example currently exists in the docs (notebooks 00–116). This fills that gap with a workflow directly applicable to climate research, urban planning, and heat risk assessment.
Related notebooks
116_hls_nasa_earthdata.ipynb— NASA Earthdata access (sameearthaccesspattern)05_load_raster.ipynb— raster loading12_split_map.ipynb— split map widgetSummary by CodeRabbit