
Introduction
On January 7, 2025, two fires broke out nearly simultaneously in the City of Los Angeles. In this blog post, false-color images are made using satellite imagery from the time of the fire to analyze their impact on the areas. In addition, the disparity in age across the area affected by the fire is analyzed to see if there is a demographic disparity among those impacted.
Highlights
- Geospatial data wrangling
- False color image of fires with boundaries
- Difference in resident age between the fires
Datasets
A geodatabase created by Centers for Disease Control and Prevention and Agency for Toxic Substances Disease Registry concerning environmental justice at the county level in the United States. A subset of this data concerning counties in California in 2024 was used in this analysis.
GeoJSON files were downloaded containing the perimeter data for the Palisades and Eaton fires.
Landsat 8-9 data collected by NASA showing surface reflectance and temperature. The data used in this analysis is a NetCDF containing a subset of this data which centers on the area surrounding the two fires.
Load in Packages
import os
import pandas as pd
import numpy as np
import geopandas as gpd
import matplotlib.pyplot as plt
import xarray as xr
import rioxarray as rioxrFalse Color Image
False-color images use short-wave and infrared bands of light to penetrate smoke, enabling the analysis of wildfires using satellite imagery. Using this method, the damage done by the Eaton and Palisades fires can be visualized.
Read in Landsat Data
The NetCDF file containing satellite data is read in using xarray.open_dataset and the geometry data is recovered.
landsat = xr.open_dataset(os.path.join('data','landsat8-2025-02-23-palisades-eaton.nc'))
# Recover geometry information using CRS from the spatial_ref attribute
landsat.rio.write_crs(landsat.spatial_ref.crs_wkt, inplace=True)Generating False Color Image
This code builds a false-color composite by selecting the short-wave infrared, near-infrared, and red bands from the data. Those values are then scaled using robust scaling, which reduces the impact of outliers, and plotted to give the map below.
#False Color Image
# Plot with Short-wave Infrared, Near-Infrared, and Red
false = landsat[['swir22', 'nir08', 'red']].to_array(dim='band').fillna(0).plot.imshow(robust=True)
false_plot = rgb.plot.imshow(robust=True)
false_plot.axes.set_axis_off()
false_plot.axes.set_title("False Color Map of Fire Area")
plt.show()
Fire Perimeter Data
The fire perimeter is read in and their CRS are reprojected to match the Landsat data.
palisades, eaton = (
gpd.read_file(os.path.join("data", "Palisades_Perimeter_20250121.geojson")),
gpd.read_file(os.path.join("data", "Eaton_Perimeter_20250121.geojson"))
)
eaton = eaton.to_crs(landsat.rio.crs)
palisades = palisades.to_crs(landsat.rio.crs)Fire Perimeter Over False Color Image
The perimeters are mapped on top of the false-color images to better show the range of the fire. Through this we can see that both regions have significant scarring, as seen by the red and orange coloring. Additionally, the red coloring on the Palisades indicates that the fire in that area was burning hotter at the time the data was collected.
# Plotting Data Together
fig, ax = plt.subplots(1, 1, figsize=(14, 12))
# Plot False Color Map
landsat[['swir22', 'nir08', 'red']].to_array(dim='band').fillna(0).plot.imshow(ax=ax, robust=True)
# Plot Eaton And Palisades Perimeter
eaton.boundary.plot(ax=ax,
color='Black',
linewidth= 3.5,
label='Eaton Fire')
palisades.boundary.plot(ax=ax,
color='White',
linewidth = 3.5,
label='Palisades Fire')
# Add Title
ax.set_title("Wildfire Boundaries on False Color Composite", fontsize=18, fontweight='bold')
# Add Legend
ax.legend(loc='lower left', frameon=True, title="Fire Perimeters")
ax.axis('off')
plt.tight_layout()
plt.show()
Age Disparity Between Fire Areas
In this next section, the difference in the age of those directly affected by the two fires will be examined by displaying the percentile for people over 65 years old within each fire's perimeter.
Read In Data
The geodatabase is read in and the data is reprojected to match the Landsat data from earlier.
eji_data = gpd.read_file(os.path.join("data", "EJI_2024_California.gdb"))
if eji_data.crs != landsat.rio.crs:
eji_data = eji_data.to_crs(landsat.rio.crs)Create Census Raster from Data
Two rasters are created by spatially joining the perimeter data from the fires with the census data within them.
census_palisades, census_eaton = (
gpd.sjoin(eji_data, palisades, how = 'inner'),
gpd.sjoin(eji_data, eaton, how = 'inner')
)
census_palisades.head(5)Plot Environmental Justice Data
The two rasters are plotted next to each other, and it can be seen that the percentile of those over 65 was much higher within the region affected by the Palisades fire.
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))
# UPDATE WITH YOU EJI VARIABLE FROM STEP 1
eji_variable = 'EPL_AGE65'
# Find common min/max for legend range
vmin = min(census_palisades[eji_variable].min(), census_eaton[eji_variable].min())
vmax = max(census_palisades[eji_variable].max(), census_eaton[eji_variable].max())
# Plot census tracts within Palisades perimeter
census_palisades.plot(
column= eji_variable,
vmin=vmin, vmax=vmax,
legend=False,
ax=ax1,
)
ax1.set_title('Palisades Census Tracts')
ax1.axis('off')
# Plot census tracts within Eaton perimeter
census_eaton.plot(
column=eji_variable,
vmin=vmin, vmax=vmax,
legend=False,
ax=ax2,
)
ax2.set_title('Eaton Fire Census Tracts')
ax2.axis('off')
# Add overall title
fig.suptitle('Percentile of People over 65 Years Old in Eaton Fire')
# Add shared colorbar at the bottom
sm = plt.cm.ScalarMappable( norm=plt.Normalize(vmin=vmin, vmax=vmax))
cbar_ax = fig.add_axes([0.25, 0.08, 0.5, 0.02]) # [left, bottom, width, height]
cbar = fig.colorbar(sm, cax=cbar_ax, orientation='horizontal')
cbar.set_label('People Over 65 Years Old (Percentile)')
plt.show()
References
Centers for Disease Control and Prevention and Agency for Toxic Substances and Disease Registry. (2024). Environmental Justice Index for California.https://www.atsdr.cdc.gov/place-health/php/eji/eji-data-download.html
Earth Resources Observation and Science (EROS) Center. (2020). Landsat 8-9 Operational Land Imager / Thermal Infrared Sensor Level-2, Collection 2 [dataset]. U.S. Geological Survey.https://doi.org/10.5066/P9OGBGM6
Palisades and Eaton Dissolved Fire Perimeters as of 2025/01/21. (2025). ArcGIS REST Services Directory.https://services.arcgis.com/RmCCgQtiZLDCtblq/arcgis/rest/services/Palisades_and_Eaton_Dissolved_Fire_Perimeters_as_of_20250121/FeatureServer