WFIGS

Wildland Fire Interagency Geospatial Services

Overview

The WFIGS module provides access to wildland fire data from the NIFC Open Data portal. This includes current and historical fire perimeters and incident locations.

Data Source: WFIGS Fire History

Available Datasets

Dataset Description Category
:current_perimeters Current fire perimeters (updated every 5 min) perimeters
:current_locations Current fire locations (updated every 5 min) locations
:daily_perimeters Every perimeter geometry change, 2023-present (sparse 2021-2022) history
:historic_geomac Historic GeoMAC perimeters (2000-2018) history
:perimeters_all_years All historical perimeters history
:historic_geomac_YYYY Year-specific GeoMAC (2000-2018) history

Basic Usage

using WildfireData.WFIGS

# List all datasets
WFIGS.datasets()

# Filter by category
WFIGS.datasets(category=:perimeters)

# Get dataset info
WFIGS.info(:current_perimeters)

Downloading Data

# Download current perimeters (GeoJSON)
data = WFIGS.download(:current_perimeters, limit=10)

# Download with filter
data = WFIGS.download(:current_perimeters, where="GISAcres > 1000", limit=10)

# Download specific fields only
data = WFIGS.download(:current_perimeters, fields="IncidentName,GISAcres", limit=10)

Querying

# Count records
n = WFIGS.count(:current_perimeters)
n = WFIGS.count(:current_perimeters, where="GISAcres > 1000")

# Get field information
fields = WFIGS.fields(:current_perimeters)

Saving and Loading Files

# Download and save to local file
path = WFIGS.download_file(:current_perimeters, limit=100)

# Load previously downloaded file
data = WFIGS.load_file(:current_perimeters)

# Custom filename
path = WFIGS.download_file(:current_perimeters, filename="my_perimeters.geojson")

Working with Results

The download functions return a GeoJSON.FeatureCollection:

data = WFIGS.download(:current_locations, limit=5)

# Access features (iterate directly over the collection)
for feature in data
    println("$(feature.IncidentName): $(feature.DailyAcres) acres")
end

# Access geometry
feature = data[1]
geom = GeoJSON.geometry(feature)
println("Type: $(typeof(geom))")

Fire Progression

WFIGS keeps only the latest perimeter per incident in :current_perimeters and :perimeters_all_years. Two archives keep every perimeter:

Years Dataset Ordering field Timestamp field
2000-2018 :historic_geomac_YYYY perimeterdatetime perimeterdatetime
2019-2022 none (final perimeter only)
2023-present :daily_perimeters BurnPeriod poly_DateCurrent

:daily_perimeters writes a new feature each time an incident’s perimeter geometry changes (attribute-only edits update the latest feature in place). BurnPeriod is a sequential index per incident, not a fixed interval, and AcreageChange is the difference from the previous feature. NIFC labels it an automated product, not an official progression.

incidents finds the identifiers for a fire by name. It returns one row per incident with the IRWIN ID, UniqueFireIdentifier, number of archived perimeters, maximum acreage, and first/last perimeter dates.

using WildfireData.WFIGS
WFIGS.incidents("park", year=2024)
31×7 DataFrame
6 rows omitted
Row IncidentName IrwinID UniqueFireIdentifier Perimeters MaxAcres FirstDate LastDate
String String String Int64 Real? Date Date
1 PARK {22CAD814-48B1-4460-A368-F50F493F3740} 2024-CABTU-013761 70 4.29603e5 2024-07-25 2024-08-24
2 Parks {8CD96B08-32E3-496F-B440-AF274BF4D1A2} 2024-IDPAF-005597 8 5997.45 2024-08-29 2024-10-17
3 Park {D323367A-2F9E-4EBD-A1CA-4E1D048A83B1} 2024-MIHMF-240369 4 0.1 2024-09-26 2024-09-27
4 Park {62AFFA16-D08C-4F74-AA71-EE9E6C404C25} 2024-WYHPD-240617 2 12.1 2024-11-01 2025-01-15
5 Deer Park {1BFBD573-A50A-43DA-9FAB-8A9D5E9A709C} 2024-WYJOX-240038 2 10.3 2024-04-15 2024-12-02
6 Parks {40554294-C94F-42D7-B45C-3A26A7827015} 2024-AKMSS-401059 2 0.170366 2024-05-13 2024-05-14
7 Prevedel Park {E09700C5-C08F-419F-B6EA-7D5281E01079} 2024-UTBRS-200222 2 1.11907 2024-06-16 2024-06-26
8 RIDGE PARK {D4520105-FA2E-46F2-900D-70244FEE6A4F} 2024-WANES-001242 1 0.34 2024-10-28 2024-10-28
9 Enneking Parkway {A51F363E-81FB-46EA-B8E4-B91988FFC180} 2024-MAMAS-FD040043 1 0.2 2024-09-15 2024-09-15
10 Roadside Park {78167340-798F-4987-97A7-121820D94E0E} 2024-TXTXS-241491 1 33.3917 2024-05-21 2024-05-21
11 Redgranite Park {AE384FE9-679D-4B2C-B55A-D0AFDD007AB4} 2024-WIWIC-FIRE122319 1 0.02 2024-05-17 2024-05-17
12 rx-Parks Farm {BBF18CAF-D5D7-4FD3-A7F3-63BBEC65171B} 2024-WIWIS-RX2064 1 84.8 2024-03-21 2024-03-21
13 Park {6E76B242-8120-4E2B-A5C0-9F90A059565F} 2024-WANES-001728 1 0.6 2024-08-09 2024-08-09
20 Park Road Fire {F06757AE-232B-48D4-A7CB-871704ADEEF2} 2024-DCRCP-240004 1 0.1 2024-04-01 2024-04-01
21 Park Road Meadow Fire {2B3129AA-DF3D-46B9-AB25-4D545FAC794D} 2024-DCRCP-240042 1 0.65 2024-12-20 2024-12-20
22 Canyon Park {BA2B7F72-A8A7-48FD-BD9B-FEE8CD5DDED6} 2024-IDEIS-000158 1 0.4 2024-12-02 2024-12-02
23 Sparks 2 {81333F6D-88FE-4B0C-AEDF-0200943DC6AA} 2024-MTLG17-000194 1 41.55 2024-10-31 2024-10-31
24 Balllpark {7446F1C5-3946-4B8E-8C10-8CEF9A23789A} 2024-NVCCD-030976 1 0.3 2024-09-03 2024-09-03
25 VANCE PARK {CB95779D-F8C8-426F-A2A5-F5651F5941E3} 2024-WAOLS-000051 1 2.9 2024-07-13 2024-07-13
26 Cross Park Rd {7935848A-D75C-420F-8651-F4C3FCB862F5} 2024-MN3SS-000258 1 20 2024-02-28 2024-02-28
27 Astor Park {741E6B59-6794-4141-ABB6-B751583FF52C} 2024-FLFNF-001488 1 missing 2024-06-25 2024-06-25
28 Park Entrance {4B7F92ED-EFC5-4F49-88DB-5DB83C7230BD} 2024-WIWIC-FIRE122089 1 0.58 2024-05-16 2024-05-16
29 BROWN'S PARK COMPREHENSIVE RX {F5616B87-C81E-4B4A-B9F0-222F754F8DE0} 2024-COBPR-000018 1 1 2024-09-04 2024-09-04
30 Parker {0DB0CB5B-C10F-4E89-98BB-8EFF40C207AF} 2024-MOMTF-000355 1 4 2024-04-12 2024-04-12
31 Parkers {76CECBE9-FD09-4236-B557-5FECDE93BFDE} 2024-MN1BS-001222 1 3 2024-04-13 2024-04-13

progression downloads all perimeters for one fire, sorted in time. Each feature is the cumulative footprint at that time. daily=true keeps the last perimeter of each UTC day.

Perimeters are fetched one per request, several at a time. The NIFC server returns large polygons at tens of KB/s, so a large fire at full resolution (tens of MB) takes minutes. tolerance simplifies perimeters on the server (in degrees; 1e-4 is about 11 m) and cuts the download size roughly tenfold.

# By UniqueFireIdentifier, IRWIN ID, or incident name
fc = WFIGS.progression("2024-CABTU-013761")
fc = WFIGS.progression("{22CAD814-48B1-4460-A368-F50F493F3740}")
fc = WFIGS.progression("Park")   # warns if several incidents share the name

fd = WFIGS.progression("2024-CABTU-013761", daily=true, tolerance=1e-4)
for f in fd
    println(Dates.unix2datetime(f.poly_DateCurrent / 1000), "  ", round(f.poly_GISAcres), " acres")
end

# Pre-2019 fires come from the GeoMAC archive
fg = WFIGS.progression("2018-CABTU-016737", dataset=:historic_geomac_2018)

Newly burned area per step is the set difference of consecutive polygons. That needs a geometry library (LibGEOS.jl, GeometryOps.jl); this package returns the raw perimeters.

using WildfireData.WFIGS
using GeoJSON, CairoMakie, GeoMakie, Tyler
using Extents, TileProviders
using Dates
import JSON3
CairoMakie.activate!(visible=false)  # don't open an image viewer when Tyler calls display
# OpenStreetMap's tile policy requires an identifying User-Agent and attribution:
# https://operations.osmfoundation.org/policies/tiles/
Tyler.USER_AGENT[] = "WildfireData.jl-docs (+https://github.com/RallypointOne/WildfireData.jl)"

fd = WFIGS.progression("2024-CABTU-013761", daily=true, tolerance=1e-4, verbose=false)
fc = GeoJSON.read(JSON3.write(fd))
dates = Date.(unix2datetime.([f.poly_DateCurrent / 1000 for f in fc]))

ext = Extent(; X=(-122.2, -121.2), Y=(39.6, 40.5))
m = Tyler.Map(ext;
    size = (900, 700),
    axis = (; type = GeoMakie.GeoAxis, dest = "+proj=merc",
        xticks = -122.2:0.2:-121.2, yticks = 39.6:0.2:40.4,
        xticklabelsvisible = false, yticklabelsvisible = false),
    provider = TileProviders.OpenStreetMap(:Mapnik),
    fetching_scheme = Tyler.SimpleTiling(),  # only the tiles in view
)
text!(m.axis, 1, 0; text="© OpenStreetMap contributors", space=:relative, align=(:right, :bottom), offset=(-4, 4), fontsize=10)

# GeoMakie rounds tick labels to 3 significant digits (-121.8 prints as -122°),
# so label the grid lines inside the map instead
for lon in -122.0:0.2:-121.4
    text!(m.axis, lon, ext.Y[2]; text="$(abs(lon))°W", align=(:center, :top), offset=(0, -4), fontsize=12)
end
for lat in 39.8:0.2:40.4
    text!(m.axis, ext.X[1], lat; text="$(lat)°N", align=(:left, :bottom), offset=(4, 2), fontsize=12)
end

# Day number of each perimeter (1 = first day). A log color scale spreads the
# early days, when most of the area burned, across more of the colormap.
day = Dates.value.(dates .- dates[1]) .+ 1

# Draw latest first so earlier (smaller) perimeters stay visible on top
n = length(fc)
perims = Dict{Int, Any}()
for i in n:-1:1
    geom = GeoJSON.geometry(fc[i])
    isnothing(geom) && continue
    perims[i] = poly!(m.axis, geom, color=day[i], colormap=:viridis, colorscale=log10, colorrange=extrema(day),
        strokecolor=:black, strokewidth=0.3)
end
ticks = [1, 2, 4, 8, 16, maximum(day)]
Colorbar(m.figure[1, 2], colormap=:viridis, limits=extrema(day), scale=log10,
    ticks=(ticks, Dates.format.(dates[1] .+ Day.(ticks .- 1), "u d")),
    label="Perimeter date ($(year(dates[1])))")
m  # show the Map, not m.figure, so rendering waits for tiles

The same perimeters as an animation, one frame per day:

acres = [f.poly_GISAcres for f in fc]

# Hold the final day for two seconds before looping
frames = [1:n; fill(n, 6)]

# Animate the map above, whose tiles are already downloaded
Record(m.figure, frames; framerate=3) do i
    for (j, p) in pairs(perims)
        p.visible = j <= i
    end
    size_label = ismissing(acres[i]) ? "" : ", $(round(Int, acres[i])) acres"
    m.axis.title = "Park Fire: $(dates[i])$size_label"
end

Plot Example

using WildfireData.WFIGS
using GeoJSON, CairoMakie, GeoMakie, Tyler
using Extents, TileProviders
import JSON3
CairoMakie.activate!(visible=false)  # don't open an image viewer when Tyler calls display
# OpenStreetMap's tile policy requires an identifying User-Agent and attribution:
# https://operations.osmfoundation.org/policies/tiles/
Tyler.USER_AGENT[] = "WildfireData.jl-docs (+https://github.com/RallypointOne/WildfireData.jl)"

data = WFIGS.download(:historic_geomac_2018, limit=100, verbose=false)
fc = GeoJSON.read(JSON3.write(data))

ext = Extent(; X=(-130.0, -65.0), Y=(24.0, 50.0))
m = Tyler.Map(ext;
    size = (900, 600),
    axis = (; type = GeoMakie.GeoAxis, dest = "+proj=merc"),
    provider = TileProviders.OpenStreetMap(:Mapnik),
    fetching_scheme = Tyler.SimpleTiling(),  # only the tiles in view
)
text!(m.axis, 1, 0; text="© OpenStreetMap contributors", space=:relative, align=(:right, :bottom), offset=(-4, 4), fontsize=10)

for feature in fc
    geom = GeoJSON.geometry(feature)
    isnothing(geom) && continue
    poly!(m.axis, geom, color=(:orange, 0.4), strokecolor=:red, strokewidth=0.5)
end

m  # show the Map, not m.figure, so rendering waits for tiles

API Reference

Functions

  • datasets(; category=nothing) - List available datasets
  • info(dataset) - Print dataset information
  • download(dataset; where, fields, limit, verbose) - Download data
  • download_file(dataset; filename, force, verbose, ...) - Download and save to file
  • load_file(dataset; filename) - Load previously downloaded file
  • count(dataset; where) - Get record count
  • fields(dataset) - Get field names and types
  • incidents(name; dataset, year) - Look up IRWIN ID and UniqueFireIdentifier by incident name
  • progression(fire; dataset, daily, tolerance, ntasks, verbose) - All archived perimeters of one fire, in time order
  • query_url(dataset; where, outfields, limit, format) - Build query URL
  • dir() - Get local data directory path