Header image

Eaton and Palisades Fires Analysis

Author: William Mullins ・ Date: December 3, 2025

GitHub
Back to Blog
Remote SensingWildfire
December 3, 2025Python · Geospatial

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

CDC and ATSDR Environmental Justice Index For California in 2024

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.

Palisades and Eaton Dissolved Fire Perimeters as of 2025/01/21

GeoJSON files were downloaded containing the perimeter data for the Palisades and Eaton fires.

Landsat Collection 2 Level-2 Data

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 rioxr

False 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()
False Color Map of Fire Area

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()
False Color Map With Census

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()
Percentile of People over 65 Years Old in Eaton Fire

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