import os
import re
from datetime import datetime, timedelta
import requests
import zipfile
import numpy as np
import pandas as pd
import geopandas as gpd
import xarray as xr
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import osmnx as ox
from rasterio.transform import from_origin
from rasterstats import zonal_stats
import earthaccess
import cftime
from matplotlib.colors import LogNorm
Flood Watch Report – Maui Heavy Rainfall Analysis
Overview
In this lesson, you will analyze the Flash Flood Watch for Maui issued in January 15, 2024 due to prolonged, heavy rainfall. You will simulate rainfall data to evaluate flood risks across different regions of the island.
This lesson demonstrates how to integrate U.S. Census population data with rainfall data for Maui. By combining demographic and environmental datasets, you can visualize which communities experienced the most rainfall during a flood event, supporting disaster impact analysis and response planning.
This type of hazard analysis helps develop early-warning systems and improve response strategies for climate-related emergencies.
Learning Objectives
By the end of this lesson, you should be able to:
- Understand the meteorological cause and risk factors of flash floods in Maui.
- Access and work with NASA rainfall data using Python.
- Compare NASA data and rain gauge data to see similarities.
- Access and process U.S. Census tract population data using Python and the Census API.
- Overlay population and rainfall data to identify high-risk communities.
- Create a reproducible, automated workflow for spatial disaster analysis.
Introduction
Flash floods are sudden, powerful events that can occur when intense rain overwhelms drainage systems. On the early hours of January 16, 2024, the National Weather Service issued a Flood Watch for Maui, warning residents of potentially hazardous rainfall due to a slow-moving weather system (Maui Now 2024).
Heavy rains, particularly over windward and wildfire-scarred slopes, threatened roads, homes, and infrastructure. This report explores the event using simulated rainfall data and visual tools to assess possible flood impact zones.
NASA Global Precipitation Measurement (GPM)
The Global Precipitation Measurement (GPM) IMERG Final Precipitation L3 Half Hourly 0.1 degree x 0.1 degree V07 (GPM_3IMERGHH) provides high-resolution multi-satellite precipitation estimates (Huffman et al. 2023). This dataset is produced by the Precipitation Processing System (PPS) at NASA’s Goddard Space Flight Center (GSFC) and is distributed through the Goddard Earth Sciences Data and Information Services Center (GES DISC).
NASA Earthaccess
NASA’s earthaccess
is a Python library designed to simplify the process of searching, accessing, and downloading Earth science data from NASA’s repositories. It integrates seamlessly with NASA’s Earthdata Login system, allowing users to authenticate and interact with various datasets programmatically.
A NASA Earthdata Login profile is required to access Earthdata datasets for this lesson.
Open Street Map
OpenStreetMap (OSM) is a collaborative project that creates a free, editable map of the world, built entirely by a community of mappers. In Python, the osmnx library provides a powerful interface for querying and analyzing OSM data, including administrative boundaries, road networks, and building footprints.
In this lesson, we use osmnx
to retrieve the geographic boundary of Hawaii from OSM and extract its bounding box to spatially filter satellite precipitation data.
Data Analysis
Before beginning, please note that this lesson uses the Python programming language and the following Python packages:
os
: Provides a portable way to interact with the operating system, including file system operations and environment variables.re
: Enables powerful string pattern matching and text processing using regular expressions.datetime
: Used to manipulate dates and times, including timedelta arithmetic for time-based analysis.numpy
: Foundational package for scientific computing in Python, supporting array operations and numerical computation.pandas
: Offers data structures and functions for handling, analyzing, and visualizing structured data.xarray
: Enables working with labeled multi-dimensional arrays, ideal for processing NetCDF and other gridded scientific data.matplotlib.pyplot
: Core plotting library for generating static, interactive, and animated visualizations in Python.matplotlib.animation
: Provides tools for creating animated plots and time series visualizations.osmnx
: Enables downloading, visualizing, and analyzing street networks and other OpenStreetMap data.rasterio.transform
: Supports creation and manipulation of geospatial raster transforms for coordinate referencing.rasterstats
: Computes summary statistics of raster datasets over vector geometries for spatial analysis.earthaccess
: Simplifies NASA Earthdata access by managing authentication and dataset queries in a user-friendly way.
The earthaccess library simplifies the process of accessing NASA Earthdata by handling authentication and data discovery. Before you can search or download data using earthaccess, you need to authenticate using your Earthdata Login credentials. The earthaccess.login()
function is the starting point for this process.
# Authenticate with Earthdata using your Earthdata Login credentials
# This will prompt for username/password or use existing credentials
earthaccess.login()
To retrieve NASA precipitation data for Hawaii using earthaccess, we begin by querying geographic boundary data from OpenStreetMap using osmnx, then extract the bounding box for the region. This bounding box is used to search for GPM IMERG half-hourly precipitation data for a specific date range.
# Query Hawaii boundary from OSM
= ox.geocode_to_gdf("Hawaii, USA")
hawaii
# Extract bounding box as (lon_min, lat_min, lon_max, lat_max)
= hawaii.total_bounds # [minx, miny, maxx, maxy]
bbox = (bbox[0], bbox[1], bbox[2], bbox[3]) bounding_box
Search for GPM IMERG Half-Hourly Precipitation Data:
# Search for GPM IMERG Half-Hourly Level 3 data using earthaccess
= earthaccess.search_data(
results ="GPM_3IMERGHH", # Dataset short name
short_name="07", # Dataset version
version=("2024-01-16", "2024-01-17"), # Example date range
temporal=bounding_box # Geographic bounding box for Hawaii
bounding_box
)
# Extract data download links from search results
= [granule.data_links()[0] for granule in results]
all_urls
# Print number of URLs found
print(len(all_urls), "URLs found.")
96 URLs found.
Finally, this code will download and create a list of the file paths of the downloaded data from earthaccess
:
= earthaccess.download(all_urls) file_path
Visualizing Precipitation Data
Once we’ve downloaded the GPM IMERG precipitation data, we can use xarray
to open the NetCDF file and subset it to the region of interest (in this case, Hawaii). This subset is then visualized using matplotlib
and xarray’s built-in plotting utilities to generate a precipitation map for the first available half-hourly time step.
# Display the number of observations (files) found
print(len(file_path), "observations found.")
for file in file_path:
# Open the selected GPM IMERG dataset (e.g., 16th file in list)
with xr.open_dataset(file_path[15], engine="h5netcdf", group="Grid") as ds:
# Subset the precipitation variable to the Hawaii bounding box
= ds["precipitation"].sel(
precip_subset =slice(bounding_box[1], bounding_box[3]),
lat=slice(bounding_box[0], bounding_box[2])
lon
)
# Select the first time step and reorient data for plotting
= precip_subset.isel(time=0)
ras_data = ras_data.transpose("lat", "lon")
ras_data
# Create a figure and axis
= plt.subplots( )
fig, ax
# Plot the precipitation data
=ax, cmap="Blues", cbar_kwargs={"label": "Precipitation (mm/hr)"})
ras_data.plot(ax
# Overlay the Hawaii boundary geometry from OSM
=ax, edgecolor="black", linewidth=1.5)
hawaii.boundary.plot(ax
# Add plot labels and formatting
"GPM IMERG Precipitation over Hawaii with OSM Boundary")
plt.title("Longitude")
plt.xlabel("Latitude")
plt.ylabel(
plt.tight_layout() plt.show()
96 observations found.
Animation: Visualizing Half-Hourly Rainfall Over Time
To understand how precipitation evolves over time, we can animate the sequence of GPM IMERG half-hourly rainfall images. This animation cycles through each file, extracts the timestamp, overlays the precipitation data on the Hawaii boundary, and renders a smooth temporal visualization using matplotlib.animation
. This is especially helpful for spotting storm patterns and tracking rainfall intensity across the region.
# Compute global min/max across all rasters
= np.inf
global_min = -np.inf
global_max
for file in file_path:
with xr.open_dataset(file, engine="h5netcdf", group="Grid") as ds:
= ds["precipitation"].sel(
precip =slice(bounding_box[1], bounding_box[3]),
lat=slice(bounding_box[0], bounding_box[2])
lon=0).transpose("lat", "lon")
).isel(time
= precip.min().item()
current_min = precip.max().item()
current_max
if np.isfinite(current_min):
= min(global_min, current_min)
global_min if np.isfinite(current_max):
= max(global_max, current_max)
global_max
# --- Start Plotting ---
= plt.subplots( ) # Moved this up
fig, ax
# Load first frame just for layout
with xr.open_dataset(file_path[0], engine="h5netcdf", group="Grid") as ds:
= ds["precipitation"].sel(
data0 =slice(bounding_box[1], bounding_box[3]),
lat=slice(bounding_box[0], bounding_box[2])
lon=0).transpose("lat", "lon")
).isel(time
= data0.lon.values
lon = data0.lat.values
lat
= np.meshgrid(lon, lat)
lon2d, lat2d
# Plot the first frame using global vmin/vmax
= ax.pcolormesh(
mesh
lon2d, lat2d, data0.values,="Blues", shading="auto",
cmap=global_min, vmax=global_max
vmin
)
# Add a consistent colorbar
= fig.colorbar(mesh, ax=ax, label="Precipitation (mm/hr)")
cbar
# Overlay the Hawaii boundary outline
=ax, edgecolor="black", linewidth=1.5, zorder=2)
hawaii.boundary.plot(ax
# Set up the title and axes
= ax.set_title(f"GPM IMERG Precipitation over Hawaii - Frame 1/{len(file_path)}")
title_text "Longitude")
ax.set_xlabel("Latitude")
ax.set_ylabel(
plt.tight_layout()
# --- Define update function for animation ---
def update(frame_index):
file = file_path[frame_index]
with xr.open_dataset(file, engine="h5netcdf", group="Grid") as ds:
= ds["precipitation"].sel(
data =slice(bounding_box[1], bounding_box[3]),
lat=slice(bounding_box[0], bounding_box[2])
lon=0).transpose("lat", "lon")
).isel(time
# Parse timestamp
= os.path.basename(file)
filename = re.search(r"3IMERG\.(\d{8})", filename)
match = match.group(1)
date_str = re.search(r"(\d{4}).V07B", filename)
match = match.group(1)
time_str = int(time_str)
time_str
try:
= datetime.strptime(date_str, "%Y%m%d") + timedelta(minutes=time_str)
dt -= timedelta(hours=10)
dt = dt.strftime("%Y-%m-%d %H:%M:%S HST")
timestamp_str except ValueError:
= f"Invalid time in filename: {time_str}"
timestamp_str
# Update the mesh
mesh.set_array(data.values.ravel())f"GPM IMERG Precipitation over Hawaii — {timestamp_str}")
title_text.set_text(return mesh, title_text
# --- Animate and Save ---
print("Creating animation...")
= animation.FuncAnimation(
ani =len(file_path),
fig, update, frames=200, blit=True, repeat=False
interval
)
"data/images/hawaii_precip.gif", writer="pillow", fps=3) ani.save(
Focusing on Maui
To focus analysis on our location of analysis, we use osmnx
to retrieve the geographic boundary for Maui County, Hawaii from OpenStreetMap. We then extract its bounding box and apply a small buffer (0.1 degrees) to ensure that nearby data just outside the strict boundary is included. This padded extent will be used to spatially filter satellite precipitation data or other geospatial layers relevant to the region.
# Get the geometry for Maui County, Hawaii from OpenStreetMap
= ox.geocode_to_gdf("Maui County, Hawaii, USA").to_crs("EPSG:4326")
maui
# Extract the bounding box of Maui County as (minx, miny, maxx, maxy)
= maui.total_bounds
bounding_box
# Define a small padding buffer (in degrees) around the bounding box
= 0.1 # ~0.1 degrees ≈ 5–6 km buffer
pad
# Compute padded latitude and longitude boundaries
= bounding_box[1] - pad # Southern boundary
lat_min = bounding_box[3] + pad # Northern boundary
lat_max = bounding_box[0] - pad # Western boundary
lon_min = bounding_box[2] + pad # Eastern boundary lon_max
After defining the padded bounding box for Maui, we can visualize GPM IMERG half-hourly precipitation data for that area. This plot overlays satellite-derived rainfall intensity on the island’s geographic outline using xarray
and matplotlib
, providing spatial context for localized precipitation events.
# Initialize figure and axis for the plot
= plt.subplots()
fig, ax
with xr.open_dataset(file_path[15], engine="h5netcdf", group="Grid") as ds:
# Subset the precipitation variable to the Hawaii bounding box
= ds["precipitation"].sel(
precip_subset =slice(lat_min, lat_max),
lat=slice(lon_min, lon_max)
lon
)
# Select the first time step and reorient data for plotting
= precip_subset.isel(time=0)
ras_data = ras_data.transpose("lat", "lon")
ras_data
# Plot the precipitation data on the map
=ax, cmap="Blues", cbar_kwargs={"label": "Precipitation (mm/hr)"})
ras_data.plot(ax
# Overlay the Maui County boundary using OSM data
=ax, edgecolor="black", linewidth=1.5)
maui.boundary.plot(ax
# Add plot title and axis labels
"GPM IMERG Precipitation over Hawaii with OSM Boundary")
plt.title("Longitude")
plt.xlabel("Latitude")
plt.ylabel(
# Adjust layout to prevent overlap
plt.tight_layout()
# Display the plot
plt.show()
To visualize how rainfall evolves across Maui County, this animation cycles through a sequence of GPM IMERG half-hourly datasets. For each frame, it subsets the data to the Maui region, overlays the county boundary, and updates the timestamp extracted from the filename. The animation is then saved as a .gif
for easy sharing and visual analysis.
# Compute global min/max across all rasters
= np.inf
global_min = -np.inf
global_max
for file in file_path:
with xr.open_dataset(file, engine="h5netcdf", group="Grid") as ds:
= ds["precipitation"].sel(
precip =slice(lat_min, lat_max),
lat=slice(lon_min, lon_max)
lon=0).transpose("lat", "lon")
).isel(time
= precip.min().item()
current_min = precip.max().item()
current_max
if np.isfinite(current_min):
= min(global_min, current_min)
global_min if np.isfinite(current_max):
= max(global_max, current_max)
global_max
# --- Start Plotting ---
= plt.subplots( ) # Moved this up
fig, ax
# Load first frame just for layout
with xr.open_dataset(file_path[0], engine="h5netcdf", group="Grid") as ds:
= ds["precipitation"].sel(
data0 =slice(lat_min, lat_max),
lat=slice(lon_min, lon_max)
lon=0).transpose("lat", "lon")
).isel(time
= data0.lon.values
lon = data0.lat.values
lat
= np.meshgrid(lon, lat)
lon2d, lat2d
= LogNorm(vmin=max(global_min, 0.01), vmax=global_max)
norm
= ax.pcolormesh(
mesh
lon2d, lat2d, data0.values,="Blues", shading="auto",
cmap=norm
norm
)
# Add a consistent colorbar
= fig.colorbar(mesh, ax=ax, label="Precipitation (mm/hr)")
cbar
# Overlay the Hawaii boundary outline
=ax, edgecolor="black", linewidth=1.5, zorder=2)
maui.boundary.plot(ax
# Set up the title and axes
= ax.set_title(f"GPM IMERG Precipitation over Hawaii - Frame 1/{len(file_path)}")
title_text "Longitude")
ax.set_xlabel("Latitude")
ax.set_ylabel(
plt.tight_layout()
# --- Define update function for animation ---
def update(frame_index):
file = file_path[frame_index]
with xr.open_dataset(file, engine="h5netcdf", group="Grid") as ds:
= ds["precipitation"].sel(
data =slice(lat_min, lat_max),
lat=slice(lon_min, lon_max)
lon=0).transpose("lat", "lon")
).isel(time
# Parse timestamp
= os.path.basename(file)
filename = re.search(r"3IMERG\.(\d{8})", filename)
match = match.group(1)
date_str = re.search(r"(\d{4}).V07B", filename)
match = match.group(1)
time_str = int(time_str)
time_str
try:
= datetime.strptime(date_str, "%Y%m%d") + timedelta(minutes=time_str)
dt -= timedelta(hours=10)
dt = dt.strftime("%Y-%m-%d %H:%M:%S HST")
timestamp_str except ValueError:
= f"Invalid time in filename: {time_str}"
timestamp_str
# Update the mesh
mesh.set_array(data.values.ravel())f"GPM IMERG Precipitation over Maui, Hawaii — {timestamp_str}")
title_text.set_text(return mesh, title_text
# --- Animate and Save ---
print("Creating animation...")
= animation.FuncAnimation(
ani =len(file_path),
fig, update, frames=200, blit=True, repeat=False
interval
)
"data/images/maui_precip.gif", writer="pillow", fps=3) # Save to file at 3 frames per second ani.save(
Extracting Time Series of Mean Precipitation Over Maui
We can use the GPM IMERG half-hourly precipitation files to calculates the mean rainfall over Maui County for each timestep. The data is spatially subset to the OSM Maui bounding box, rasterized using a geographic transform, and then aggregated over the island polygon using the function zonal_stats
. Results are compiled into a DataFrame for further analysis or plotting.
# Prepare an empty list to store the results for each file
= []
results
# Loop through each file in the downloaded GPM IMERG dataset
for file in file_path:
try:
# --- Open and subset precipitation data ---
with xr.open_dataset(file, engine="h5netcdf", group="Grid") as ds:
# Select and subset precipitation for Maui bounding box
= ds["precipitation"].sel(
data =slice(lat_min, lat_max), # Latitude bounds
lat=slice(lon_min, lon_max) # Longitude bounds
lon=0).transpose("lat", "lon") # First timestep and axis order for raster analysis
).isel(time
# --- Build affine transform for georeferencing raster ---
= data["lat"].values # Latitude array
lats = data["lon"].values # Longitude array
lons = lons[1] - lons[0] # Pixel width
res_x = lats[1] - lats[0] # Pixel height (note: no flip needed if ordered from top to bottom)
res_y = from_origin(
transform =lons.min(), # Western edge
west=lats.max(), # Northern edge
north=res_x, # Horizontal resolution
xsize=res_y # Vertical resolution
ysize
)
# --- Extract raw array of precipitation values ---
= data.values # 2D array (lat × lon)
arr
# --- Parse datetime from filename ---
= os.path.basename(file) # Extract base filename
filename = re.search(r"3IMERG\.(\d{8})", filename) # Extract date string (YYYYMMDD)
match_date = re.search(r"\.(\d{4,5})\.V", filename) # Extract time in minutes
match_min
if match_date and match_min:
= match_date.group(1)
date_str = match_min.group(1)
mins_str = datetime.strptime(date_str, "%Y%m%d") + timedelta(minutes=int(mins_str)) # Combine date and time
dt = dt - timedelta(hours=10) # Convert from UTC to Hawaii Standard Time
dt else:
= None # Fallback if parsing fails
dt
# --- Compute zonal mean over the Maui polygon ---
= zonal_stats(
stats # Polygon geometry
maui, # Raster array
arr, =transform, # Spatial transform
affine="mean", # Compute mean value
stats=np.nan # Handle missing values
nodata0]
)[
# Extract mean precipitation value from the stats result
= stats["mean"]
mean_precip
# Append timestamp and mean to results list
results.append({"datetime": dt,
"mean_precip": mean_precip
})
except Exception as e:
# Catch and report any errors (e.g., malformed file or data)
print(f"Skipping file {file} due to error: {e}")
# --- Convert results list to a clean DataFrame ---
= pd.DataFrame(results) # Each row: [datetime, mean_precip] results_df
Now that we’ve extracted mean precipitation values for each GPM IMERG file, we can visualize how rainfall changes over time across Maui County. This line plot presents a temporal snapshot of precipitation intensity, helping to identify storm events, rainfall variability, and dry periods.
# Create the time series line plot
# Set figure size for clarity and legibility
plt.figure( )
# Plot datetime vs. mean precipitation with markers and connecting lines
'datetime'], results_df['mean_precip'], marker='o', linestyle='-')
plt.plot(results_df[
# Add a title and axis labels to describe the plot
'Mean Precipitation Over Maui County Time Series') # Plot title
plt.title('Date and Time (HST)') # X-axis label
plt.xlabel('Mean Precipitation') # Y-axis label (units assumed mm/hr)
plt.ylabel(
# Automatically format x-axis to prevent overlapping date labels
# Rotate and align date labels on the x-axis
plt.gcf().autofmt_xdate()
# Add a dashed grid to the background for easier reading
True, linestyle='--', alpha=0.7) # Enable grid with light dashed lines
plt.grid(
# Adjust spacing to prevent overlapping elements
plt.tight_layout()
# Display the plot in the output cell
plt.show()
Comparing IMERG Sattelite data and Oberved Precipitation data
The data for this report comes from the Kahului Airport and downloaded from WeatherSpark.
# Load your CSV (update with your actual file path)
= pd.read_csv("data/maui_rain/maui_rain_gauge_Jan2024.csv")
obs_df
# Parse datetime
"datetime"] = pd.to_datetime(obs_df["Date"] + " " + obs_df["Time"])
obs_df[
# Convert precipitation to float
"obs_precip"] = obs_df["Precipitation"].str.replace(" in", "", regex=False).astype(float) obs_df[
Plot the data with dual y-axis without proportional scaling to visually compare the two datasets.
# Make sure datetime columns are parsed
"datetime"] = pd.to_datetime(obs_df["Date"] + " " + obs_df["Time"])
obs_df["obs_precip"] = obs_df["Precipitation"].str.replace(" in", "", regex=False).astype(float)
obs_df[
"datetime"] = pd.to_datetime(results_df["datetime"])
results_df[
# Create the plot
= plt.subplots(figsize=(12, 6))
fig, ax1
# Plot observed precipitation on left y-axis
= ax1.plot(
line1, "datetime"], obs_df["obs_precip"],
obs_df[="Observed (inches)", color="tab:blue", marker="o"
label
)"Precipitation (inches)", color="tab:blue")
ax1.set_ylabel(='y', labelcolor="tab:blue")
ax1.tick_params(axis
# Create right y-axis
= ax1.twinx()
ax2
# Plot IMERG precipitation on right y-axis (assumed in mm)
= ax2.plot(
line2, "datetime"], results_df["mean_precip"],
results_df[="IMERG (mm)", color="tab:green", marker="x", linestyle="--"
label
)"Precipitation (mm)", color="tab:green")
ax2.set_ylabel(='y', labelcolor="tab:green")
ax2.tick_params(axis
# Combine legends
= [line1, line2]
lines = [line.get_label() for line in lines]
labels ="upper left")
ax1.legend(lines, labels, loc
# Formatting
"Observed vs IMERG Precipitation")
plt.title("Datetime")
ax1.set_xlabel(
fig.autofmt_xdate()True)
plt.grid(
plt.tight_layout() plt.show()
Integrating Maui Census Tract Data
This section downloads the latest census tract shapefile for Hawaii and loads it as a GeoDataFrame.
= "data/maui_rain/tl_2023_15_tract.zip"
shapefile_zip = "data/maui_rain/tl_2023_15_tract" shapefile_dir
# Set URLs and file paths
= "https://www2.census.gov/geo/tiger/TIGER2023/TRACT/tl_2023_15_tract.zip"
tiger_url
# Ensure the directory exists
=True)
os.makedirs(os.path.dirname(shapefile_zip), exist_ok
# Download and extract if not already present
if not os.path.exists(shapefile_dir):
= requests.get(tiger_url, verify=False)
r with open(shapefile_zip, 'wb') as f:
f.write(r.content)with zipfile.ZipFile(shapefile_zip, 'r') as zip_ref:
zip_ref.extractall(shapefile_dir)
# Load shapefile
= gpd.read_file(os.path.join(shapefile_dir, 'tl_2023_15_tract.shp')) tracts
Fetch ACS 2023 Population Data for Maui Tracts
This section retrieves the latest census population data for Maui tracts and merges it with the shapefile.
# Provide your Census API key
= "API_KEY"
API_KEY = (
acs_url "https://api.census.gov/data/2023/acs/acs5"
"?get=NAME,B01003_001E&for=tract:*&in=state:15+county:009"
f"&key={API_KEY}"
)
= requests.get(acs_url) response
Combining Census data with TIGER shapes
= response.json()
census_data = pd.DataFrame(census_data[1:], columns=census_data[0])
census_df 'GEOID'] = census_df['state'] + census_df['county'] + census_df['tract']
census_df[
# Merge population data with tracts
= tracts.merge(
tracts 'GEOID', 'B01003_001E']],
census_df[[='GEOID',
on='left'
how
)'B01003_001E'] = pd.to_numeric(tracts['B01003_001E']) tracts[
= np.inf, -np.inf
global_min, global_max for file in file_path:
with xr.open_dataset(file, engine="h5netcdf", group="Grid") as ds:
= ds["precipitation"].sel(
precip =slice(lat_min, lat_max),
lat=slice(lon_min, lon_max)
lon=0).transpose("lat", "lon")
).isel(time= precip.min().item()
current_min = precip.max().item()
current_max if np.isfinite(current_min): global_min = min(global_min, current_min)
if np.isfinite(current_max): global_max = max(global_max, current_max)
# Optional: Use a LogNorm to enhance contrast
from matplotlib.colors import LogNorm
= LogNorm(vmin=0.05, vmax=global_max)
norm
= plt.subplots( )
fig, ax
# Load first raster
with xr.open_dataset(file_path[0], engine="h5netcdf", group="Grid") as ds:
= ds["precipitation"].sel(
data0 =slice(lat_min, lat_max),
lat=slice(lon_min, lon_max)
lon=0).transpose("lat", "lon")
).isel(time= ds["lon"].sel(lon=slice(lon_min, lon_max)).values
lon = ds["lat"].sel(lat=slice(lat_min, lat_max)).values
lat
= np.meshgrid(lon, lat)
lon2d, lat2d
# Plot base precipitation frame
= ax.pcolormesh(
mesh
lon2d, lat2d, data0.values,="Blues", shading="auto", norm=norm, zorder=1
cmap
)= fig.colorbar(mesh, ax=ax, label="Precipitation (mm/hr)")
cbar
# Plot census tracts colored by population
tracts.plot(=ax,
ax='B01003_001E',
column='Reds',
cmap='black',
edgecolor=0.5,
linewidth=0.6,
alpha=2,
zorder=True,
legend={'label': "Population (ACS B01003_001E)"}
legend_kwds
)
# Title and axes
= ax.set_title("Frame 1")
title_text "Longitude")
ax.set_xlabel("Latitude")
ax.set_ylabel(
plt.tight_layout()
# -----------------------------
# Step 4: Define Animation
# -----------------------------
def update(frame_index):
file = file_path[frame_index]
with xr.open_dataset(file, engine="h5netcdf", group="Grid") as ds:
= ds["precipitation"].sel(
data =slice(lat_min, lat_max),
lat=slice(lon_min, lon_max)
lon=0).transpose("lat", "lon")
).isel(time
# Update raster data
= data.values
data_vals <= 0] = np.nan # Avoid log(0)
data_vals[data_vals
mesh.set_array(data_vals.ravel())
# Timestamp label
= os.path.basename(file)
filename = re.search(r"3IMERG\.(\d{8})", filename)
date_match = re.search(r"(\d{4}).V07B", filename)
time_match if date_match and time_match:
= date_match.group(1)
date_str = int(time_match.group(1))
minutes = datetime.strptime(date_str, "%Y%m%d") + timedelta(minutes=minutes) - timedelta(hours=10)
dt = dt.strftime("%Y-%m-%d %H:%M:%S HST")
label else:
= "Unknown timestamp"
label
f"Maui 2023 Population & Precipitation — {label}")
title_text.set_text(return mesh, title_text
# -----------------------------
# Step 5: Run and Save Animation
# -----------------------------
= animation.FuncAnimation(
ani =len(file_path),
fig, update, frames=200, blit=True, repeat=False
interval
)
"data/images/maui_precip_population.gif", writer="pillow", fps=3) ani.save(
Summary
This workflow demonstrates how to:
- Download and merge census tract boundaries and population data for Maui.
- Process and map local rainfall station observations.
- Overlay demographic and environmental data for actionable disaster analysis.
You can expand this analysis by incorporating additional census variables, different time periods, or more advanced spatial statistics as needed for your project.