Summer 2026 fires: interactive explorer

Every mapped fire of 2026, plus a drop-it-on-Paris size comparison

Author

Pierre Beaucoral

Published

October 2, 2026

This page is the hands-on companion to the 2026 season review. The review tells the story in charts; here you can pan, zoom and click through the fires yourself. It sits on its own page because the maps carry the outline of every fire, which is heavy, and the review should load fast.

Every fire of 2026

Zoomed out, each fire is a circle: the bigger the circle, the more ground burned. Zoom in a few steps and the circles give way to the real outlines. Click a fire for its date, size, country, and the vegetation that dominated the ground it burned. Blue is January to May, orange is the summer season.

Show code
# Same cache key and body as posts/2026.qmd (asof<date> is enough: the only
# input, tagged_2026_full, is built from a snapshot-aware key upstream).
leaflet_result <- cached(sprintf("leaflet_2026_asof%s", format(as_of, "%Y%m%d")), {
  x <- tagged_2026_full |>
    mutate(across(all_of(lc_cols), to_num))

  lc_mat <- as.matrix(st_drop_geometry(x)[, lc_cols])
  lc_mat[is.na(lc_mat)] <- 0
  dom <- lc_labels[lc_cols[max.col(lc_mat, ties.method = "first")]]
  dom[rowSums(lc_mat) <= 0] <- "n/a"
  x$dominant_lc <- unname(dom)

  total_ha_before <- sum(x$area_ha, na.rm = TRUE)

  x <- x |>
    st_simplify(dTolerance = 100) |>       # ~100 m in EPSG:3035 (metric)
    filter(!st_is_empty(geometry))

  types <- st_geometry_type(x)
  x_poly <- x[types %in% c("POLYGON", "MULTIPOLYGON"), ]
  x_gc   <- x[types == "GEOMETRYCOLLECTION", ]
  if (nrow(x_gc) > 0L) {
    x_poly <- rbind(x_poly, suppressWarnings(st_collection_extract(x_gc, "POLYGON")))
  }
  x_poly <- x_poly |>
    filter(!st_is_empty(geometry)) |>
    st_cast("MULTIPOLYGON") |>
    st_transform(4326)

  retained_ha <- sum(x_poly$area_ha, na.rm = TRUE)
  sliver_pct <- 100 * (total_ha_before - retained_ha) / total_ha_before

  list(sf = x_poly, sliver_pct = sliver_pct)
}, version = 3)

leaf_sf <- leaflet_result$sf
sliver_pct_lab <- sprintf("%.1f%%", leaflet_result$sliver_pct)

# One centroid per fire for the zoomed-out view (centroids are computed on the
# sphere here, which is fine for a marker position).
leaf_pts <- suppressWarnings(st_centroid(st_geometry(leaf_sf))) |>
  st_coordinates() |>
  as.data.frame() |>
  setNames(c("lng", "lat"))
leaf_df <- bind_cols(st_drop_geometry(leaf_sf), leaf_pts) |>
  mutate(
    col = if_else(period == "January–May", "#3E8EC4", "#D64A05"),
    # radius in pixels grows with sqrt(area): circle area ~ burned area
    radius = 2 + 0.07 * sqrt(area_ha),
    popup = sprintf(
      paste0("<b>%s</b><br/>EFFIS fire date: %s<br/>Mapped area: %s ha (%s km<sup>2</sup>)",
             "<br/>Dominant land cover: %s"),
      name_long, format(ba_date, "%Y-%m-%d"), comma(round(area_ha)),
      comma(round(area_ha / 100, 1)), dominant_lc
    )
  ) |>
  arrange(desc(area_ha))                    # big circles underneath small ones
leaf_sf$popup <- sprintf(
  paste0("<b>%s</b><br/>EFFIS fire date: %s<br/>Mapped area: %s ha (%s km<sup>2</sup>)",
         "<br/>Dominant land cover: %s"),
  leaf_sf$name_long, format(leaf_sf$ba_date, "%Y-%m-%d"), comma(round(leaf_sf$area_ha)),
  comma(round(leaf_sf$area_ha / 100, 1)), leaf_sf$dominant_lc
)
# Round vertices to 4 decimals (~8 m) to roughly halve the embedded JSON.
sf::st_geometry(leaf_sf) <- sf::st_sfc(
  lapply(sf::st_geometry(leaf_sf), function(g) {
    g[] <- lapply(g, function(poly) lapply(poly, round, 4))
    g
  }),
  crs = 4326
)
leaf_sf$col <- if_else(leaf_sf$period == "January–May", "#3E8EC4", "#D64A05")
Show code
# Esri World Light Gray tiles. CARTO's Positron (the previous basemap) now
# answers keyless requests with "API KEY REQUIRED" watermark tiles
# (checked 2026-10-02), so this page uses a light, keyless Esri canvas instead.
# Shared by both maps on this page.
add_basemap <- function(map) {
  addTiles(
    map,
    urlTemplate = paste0(
      "https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/",
      "World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}"
    ),
    attribution = "Tiles &copy; Esri; sources: Esri, HERE, Garmin, OpenStreetMap contributors",
    options = tileOptions(maxZoom = 16)
  )
}

pal_period <- c("#3E8EC4", "#D64A05")  # winter-spring blue vs summer orange-red
ZOOM_SWITCH <- 7L                      # outlines from this zoom level upward

leaflet(options = leafletOptions(minZoom = 3)) |>
  add_basemap() |>
  addCircleMarkers(
    data = leaf_df, lng = ~lng, lat = ~lat, group = "circles",
    radius = ~radius, weight = 0.6, color = ~col, fillColor = ~col,
    fillOpacity = 0.4, popup = ~popup
  ) |>
  addPolygons(
    data = leaf_sf, group = "outlines",
    weight = 0.7, color = ~col, fillColor = ~col, fillOpacity = 0.35,
    popup = ~popup
  ) |>
  groupOptions("circles", zoomLevels = 3:(ZOOM_SWITCH - 1L)) |>
  groupOptions("outlines", zoomLevels = ZOOM_SWITCH:18) |>
  setView(lng = 12, lat = 47, zoom = 4) |>
  addLegend(
    position = "bottomleft", colors = pal_period,
    labels = c("January–May", paste("1 June –", as_of_lab)),
    title = "2026 fires", opacity = 0.7
  )

Two things to know. Circle size is proportional to burned area, so a few giants stand out against thousands of small fires. And the dates are the EFFIS fire date, the day the satellite service attributes to the fire, not an official start or end date. I simplified the outlines slightly (about 100 m of tolerance) to keep the page responsive; the tiniest border slivers, about 0.4% of the total area, drop out in the process. For survey-grade outlines, use the official EFFIS viewer.

How big is a fire, really? Drop it on Paris

Numbers like “forty thousand hectares” are hard to picture. So here is a comparison with a city you may know: the map below takes the true shape of each of the ten largest 2026 fires and slides it onto the real municipal boundary of Paris, one fire at a time.

Show code
gallery <- build_gallery_scars(
  tagged_2026_full, n = 10L, lc_cols = lc_cols, lc_labels = lc_labels
)

# version 2: gallery place names now fall back to province/country when EFFIS
# has no commune ("N.A."), so v1 caches carry stale labels.
paris_cmp <- cached(
  sprintf("paris_comparison_asof%s", format(as_of, "%Y%m%d")),
  build_paris_comparison(gallery),
  version = 2
)

paris_fires <- paris_cmp$fires_wgs84 |>
  mutate(
    fill_col = unname(pal_lc[dominant_lc]),
    times_paris = area_ha / paris_cmp$paris_ha,
    group_label = sprintf(
      "%s. %s (%s), %s ha", rank, commune, iso_a2, comma(round(area_ha))
    ),
    popup_html = sprintf(
      paste(
        "<b>%02d. %s</b><br/>Country: %s<br/>EFFIS fire date: %s<br/>",
        "Mapped area: %s ha (%s km<sup>2</sup>)<br/>",
        "<b>%.1f×</b> the area of Paris (%s ha, %s km<sup>2</sup>)"
      ),
      as.integer(rank), commune, name_long, format(ba_date, "%Y-%m-%d"),
      comma(round(area_ha)), comma(round(area_ha / 100)), times_paris,
      comma(round(paris_cmp$paris_ha)), comma(round(paris_cmp$paris_ha / 100))
    )
  )
NoteRead this map honestly
  • These fires never happened anywhere near Paris. Their real locations are on the maps in the season review; here, each outline is moved so its size can be compared directly to a familiar shape.
  • Paris’s own outline is its real municipal boundary (INSEE code 75056), about 10,535 hectares (105 km2) by this geometry. Boundary data: france-geojson (Etalab, Licence Ouverte).
  • Pick a fire from the list on the right; only one outline is shown at a time, so sizes are never stacked on top of each other.
Show code
# One dark, permanent outline for Paris, plus one leaflet GROUP per fire;
# baseGroups makes the selection mutually exclusive, and hideGroup() on every
# label but the first leaves fire 01 selected by default.
combined_geom <- c(sf::st_geometry(paris_cmp$paris_wgs84), sf::st_geometry(paris_fires))
bbox_all <- sf::st_bbox(combined_geom)

paris_map <- leaflet(width = "100%", height = 520) |>
  add_basemap() |>
  addPolygons(
    data = paris_cmp$paris_wgs84,
    color = "#1A1A1A", weight = 2.4, fill = FALSE, opacity = 0.9
  ) |>
  fitBounds(
    lng1 = unname(bbox_all["xmin"]), lat1 = unname(bbox_all["ymin"]),
    lng2 = unname(bbox_all["xmax"]), lat2 = unname(bbox_all["ymax"])
  )

paris_map <- purrr::reduce(seq_len(nrow(paris_fires)), function(map_acc, i) {
  row_i <- paris_fires[i, ]
  addPolygons(
    map_acc,
    data = row_i, group = row_i$group_label,
    color = "grey20", weight = 1,
    fillColor = row_i$fill_col, fillOpacity = 0.55,
    popup = row_i$popup_html
  )
}, .init = paris_map)

paris_map <- paris_map |>
  addLayersControl(
    baseGroups = paris_fires$group_label,
    options = layersControlOptions(collapsed = FALSE),
    position = "topright"
  )

paris_map <- purrr::reduce(
  paris_fires$group_label[-1],
  function(map_acc, g) hideGroup(map_acc, g),
  .init = paris_map
)

paris_map

Source: EFFIS rapid perimeters, snapshot 2026-10-02; areas from geometry in EPSG:3035. The outlines are rapid estimates and are revised as better imagery arrives.