MODIS fire

Day 15 of 30DayMapChallenge. Fire
R
30DayMapChallenge
spatial
datavisualization
Author

Michaël

Published

2025-11-15

Modified

2025-11-16

A photo of a forest fire

Eagle Creek Fire – CC-BY-NC-SA by Curtis Gregory Perry

Day 15 of 30DayMapChallenge: « Fire » (previously).

An animation of global fires in 2024 using MODIS data.

library(dggridR)
library(dplyr)
library(readr)
library(ggplot2)
library(purrr)
library(sf)
library(rnaturalearth)
library(glue)
library(classInt)

Data

See the docs. You can use a client like Filezilla to download the data.

  • SFTP: fuoco.geog.umd.edu
  • Login / Password (as of time of writing): fire / burnt

Get all year 2024 files available in /data/MODIS/C61/MCD14ML.

We’ll use binning on a 250 km discrete global grid.

# countries background
world <- ne_countries(scale = 10) |> 
  st_make_valid() |> 
  st_wrap_dateline()

# build the grid
dggs <- dgconstruct(spacing = 250) 

hex <- dggs |> 
  dgshptogrid(world, cellsize = 0.5) |> 
  st_make_valid() |>
  st_wrap_dateline() |> 
  st_filter(world) |> 
  select(seqnum)

# read all MODIS files and find their grid cell ID
modis <- dir("~/data/modis/", full.names = TRUE) |> 
  read_fwf(
    skip = 1,
    col_types = cols("YYYYMMDD" = col_date(format = "%Y%m%d")),
    fwf_positions(
      c(1, 10, 15, 17, 26, 36, 42, 48, 53, 61, 65, 68),
      c(9, 14, 16, 25, 35, 41, 47, 52, 60, 64, 67, 69),
      c("YYYYMMDD", "HHMM", "sat", "lat", "lon", "T21", "T31", "sample", "FRP", 
        "conf", "type", "dn")),
    num_threads = 10) |> 
  mutate(seqnum = dgGEO_to_SEQNUM(dggs, lon, lat)$seqnum)

Map

We generate one PNG file per day and create the video with a call to a system-installed ffmpeg.

# compute the number of fires for each cell and each day
modis_cells <- modis |> 
  count(seqnum, YYYYMMDD) |> 
  left_join(hex,
            join_by(seqnum)) |> 
  st_sf()

# prepare the breaks, to keep them identical on each image
breaks <- classIntervals(modis_cells$n, n = 4, style = "kmeans")

# create a PNG map for one day
create_map <- function(d) {
  p <- modis_cells |> 
    filter(YYYYMMDD == d) |> 
    ggplot() +
    geom_sf(data = world, fill = "#1a3853", color = "#002240") +
    geom_sf(aes(fill = n, color = n)) +
    scale_fill_viridis_c(aesthetics =  c("colour", "fill"),
                         breaks = round(breaks$brks),
                         transform = "log",
                         name = "Fires\nper cell\n(log\nscale)",
                         option = "B",
                         limits = c(1, max(modis_cells$seqnum))) +
    coord_sf(crs = "EPSG:8857") +
    labs(title = "MODIS fire detection",
         subtitle = d,
         caption = glue("MODIS - Global Monthly Fire Location \\
                        Product (MCD14ML)
                        https://r.iresmi.net/ - {Sys.Date()}")) +
    theme_void() +
    theme(text = element_text(family = "Ubuntu",
                              color = "white"),
          plot.margin = margin(2, 2, 2, 5, unit = "mm"),
          plot.caption = element_text(size = 7,
                                      color = "#777"))
  ggsave(glue("img/fire_{d}.png"), p, bg = "#002240", width = 9, height = 5)
}

# Iterate
modis |> 
  distinct(YYYYMMDD) |> 
  pull(YYYYMMDD) |> 
  walk(create_map, .progress = TRUE)

# generate the video
system(glue('ffmpeg -framerate 24 -pattern_type glob -i "img/fire*.png" \\
            -c:v libx264 -pix_fmt yuv420p modis.mp4'))
Figure 1: Animation of global fires in 2024 using MODIS data