!pip install OWSLib==0.28.1 --quiet
Get Fire Perimeters from an OGC API
Run this notebook
You can launch this notbook using mybinder, by clicking the button below.
Approach
- Use
OWSLib
to determine what data is available and inspect the metadata - Use
OWSLib
to filter and read the data - Use
geopandas
andfolium
to analyze and plot the data
Note that the default examples environment is missing one requirement: oswlib
. We can pip install
that before we move on.
from owslib.ogcapi.features import Features
import geopandas as gpd
import datetime as dt
from datetime import datetime, timedelta
About the Data
The fire data shown is generated by the FEDs algorithm. The FEDs algorithm tracks fire movement and severity by ingesting observations from the VIIRS thermal sensors on the Suomi NPP and NOAA-20 satellites. This algorithm uses raw VIIRS observations to generate a polygon of the fire, locations of the active fire line, and estimates of fire mean Fire Radiative Power (FRP). The VIIRS sensors overpass at ~1:30 AM and PM local time, and provide estimates of fire evolution ~ every 12 hours. The data produced by this algorithm describe where fires are in space and how fires evolve through time. This CONUS-wide implementation of the FEDs algorithm is based on Chen et al 2020’s algorithm for California.
The data produced by this algorithm is considered experimental.
Look at the data that is availible through the OGC API
The datasets that are distributed throught the OGC API are organized into collections. We can display the collections with the command:
= "https://firenrt.delta-backend.com"
OGC_URL
= Features(url=OGC_URL)
w w.feature_collections()
['public.eis_fire_snapshot_fireline_nrt',
'public.eis_fire_lf_fireline_archive',
'public.eis_fire_lf_perimeter_archive',
'public.eis_fire_snapshot_perimeter_nrt',
'public.eis_fire_lf_newfirepix_nrt',
'public.eis_fire_lf_nfplist_nrt',
'public.eis_fire_lf_perimeter_nrt',
'public.eis_fire_lf_nfplist_archive',
'public.eis_fire_lf_newfirepix_archive',
'public.eis_fire_snapshot_newfirepix_nrt',
'public.eis_fire_lf_fireline_nrt',
'public.st_squaregrid',
'public.st_hexagongrid',
'public.st_subdivide']
We will focus on the public.eis_fire_snapshot_fireline_nrt
collection, the public.eis_fire_snapshot_perimeter_nrt
collection, and the public.eis_fire_lf_perimeter_archive
collection here.
Inspect the metatdata for public.eis_fire_snapshot_perimeter_nrt collection
We can access information that describes the public.eis_fire_snapshot_perimeter_nrt
table.
= w.collection("public.eis_fire_snapshot_perimeter_nrt") perm
We are particularly interested in the spatial and temporal extents of the data.
"extent"] perm[
{'spatial': {'bbox': [[-124.63103485107422,
24.069578170776367,
-62.97413635253906,
49.40156555175781]],
'crs': 'http://www.opengis.net/def/crs/OGC/1.3/CRS84'},
'temporal': {'interval': [['2023-06-07T00:00:00+00:00',
'2023-06-27T00:00:00+00:00']],
'trs': 'http://www.opengis.net/def/uom/ISO-8601/0/Gregorian'}}
In addition to getting metadata about the data we can access the queryable fields. Each of these fields will represent a column in our dataframe.
= w.collection_queryables("public.eis_fire_snapshot_perimeter_nrt")
perm_q "properties"] perm_q[
{'wkb_geometry': {'$ref': 'https://geojson.org/schema/Geometry.json'},
'duration': {'name': 'duration', 'type': 'number'},
'farea': {'name': 'farea', 'type': 'number'},
'fireid': {'name': 'fireid', 'type': 'number'},
'flinelen': {'name': 'flinelen', 'type': 'number'},
'fperim': {'name': 'fperim', 'type': 'number'},
'isactive': {'name': 'isactive', 'type': 'number'},
'meanfrp': {'name': 'meanfrp', 'type': 'number'},
'n_newpixels': {'name': 'n_newpixels', 'type': 'number'},
'n_pixels': {'name': 'n_pixels', 'type': 'number'},
'ogc_fid': {'name': 'ogc_fid', 'type': 'number'},
'pixden': {'name': 'pixden', 'type': 'number'},
't': {'name': 't', 'type': 'string'}}
Filter the data
It is always a good idea to do any data filtering as early as possible. In this example we know that we want the data for particular spatial and temporal extents. We can apply those and other filters using the OWSLib
package.
In the below example we are:
- choosing the
public.eis_fire_snapshot_perimeter_nrt
collection - subsetting it by space using the
bbox
parameter - subsetting it by time using the
datetime
parameter - filtering for fires over 5km^2 and over 2 days long using the
filter
parameter. Thefilter
parameter lets us filter by the columns in ‘public.eis_fire_snapshot_perimeter_nrt’ using SQL-style queries.
NOTE: The limit
parameter desginates the maximum number of objects the query will return. The default limit is 10, so if we want to all of the fire perimeters within certain conditions, we need to make sure that the limit is large.
## Get the most recent fire perimeters, and 7 days before most recent fire perimeter
= max(*perm["extent"]["temporal"]["interval"])
most_recent_time = dt.datetime.strptime(most_recent_time, "%Y-%m-%dT%H:%M:%S+00:00")
now = now - dt.timedelta(weeks=1)
last_week = dt.datetime.strftime(last_week, "%Y-%m-%dT%H:%M:%S+00:00")
last_week print("Most Recent Time =", most_recent_time)
print("Last week =", last_week)
Most Recent Time = 2023-06-27T00:00:00+00:00
Last week = 2023-06-20T00:00:00+00:00
= w.collection_items(
perm_results "public.eis_fire_snapshot_perimeter_nrt", # name of the dataset we want
=["-106.8", "24.5", "-72.9", "37.3"], # coodrinates of bounding box,
bbox=[last_week + "/" + most_recent_time], # date range
datetime=1000, # max number of items returned
limitfilter="farea>5 AND duration>2", # additional filters based on queryable fields
)
The result is a dictionary containing all of the data and some summary fields. We can look at the keys to see what all is in there.
perm_results.keys()
dict_keys(['type', 'id', 'title', 'description', 'numberMatched', 'numberReturned', 'links', 'features'])
For instance you can check the total number of matched items and make sure that it is equal to the number of returned items. This is how you know that the limit
you defined above is high enough.
"numberMatched"] == perm_results["numberReturned"] perm_results[
True
You can also access the data directly in the browser or in an HTTP GET call using the constructed link.
"links"][1]["href"] perm_results[
'https://firenrt.delta-backend.com/collections/public.eis_fire_snapshot_perimeter_nrt/items?bbox=-106.8%2C24.5%2C-72.9%2C37.3&datetime=2023-06-20T00%3A00%3A00%2B00%3A00%2F2023-06-27T00%3A00%3A00%2B00%3A00&limit=1000&filter=farea%3E5+AND+duration%3E2'
Read data
In addition to all the summary fields, the perm_results
dict contains all the data. We can pass the data into geopandas to make it easier to interact with.
= gpd.GeoDataFrame.from_features(perm_results["features"])
df df
geometry | duration | farea | fireid | flinelen | fperim | isactive | meanfrp | n_newpixels | n_pixels | ogc_fid | pixden | t | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | POLYGON ((-95.88101 30.22697, -95.88102 30.227... | 45.0 | 9.811672 | 63986 | 0.0 | 12.124017 | 1 | 0.0 | 0 | 104 | 430 | 10.599620 | 2023-06-26T12:00:00 |
1 | POLYGON ((-92.83274 29.81987, -92.83264 29.820... | 4.0 | 8.958987 | 80714 | 0.0 | 12.583987 | 0 | 0.0 | 0 | 63 | 538 | 7.032045 | 2023-06-20T12:00:00 |
2 | POLYGON ((-89.29801 36.86458, -89.29801 36.864... | 5.0 | 7.138301 | 80749 | 0.0 | 13.474343 | 0 | 0.0 | 0 | 25 | 1902 | 3.502234 | 2023-06-21T12:00:00 |
3 | POLYGON ((-106.33318 36.38887, -106.32771 36.3... | 5.5 | 11.325657 | 82393 | 0.0 | 18.949202 | 1 | 0.0 | 0 | 157 | 5271 | 13.862330 | 2023-06-26T00:00:00 |
4 | POLYGON ((-106.00861 36.60666, -106.00859 36.6... | 11.0 | 12.858160 | 79918 | 0.0 | 20.962730 | 1 | 0.0 | 0 | 85 | 5273 | 6.610588 | 2023-06-25T12:00:00 |
5 | MULTIPOLYGON (((-101.28638 32.47797, -101.2863... | 14.0 | 9.415797 | 78723 | 0.0 | 36.674171 | 1 | 0.0 | 0 | 114 | 5647 | 12.107313 | 2023-06-26T00:00:00 |
6 | MULTIPOLYGON (((-102.69888 31.72183, -102.6987... | 27.0 | 10.484613 | 71936 | 0.0 | 17.485984 | 1 | 0.0 | 0 | 123 | 5736 | 11.731477 | 2023-06-25T12:00:00 |
7 | POLYGON ((-103.44609 31.79658, -103.44608 31.7... | 17.0 | 23.515158 | 76919 | 0.0 | 22.167102 | 1 | 0.0 | 0 | 10 | 5774 | 0.425258 | 2023-06-25T00:00:00 |
8 | POLYGON ((-103.89600 32.08170, -103.89603 32.0... | 7.0 | 11.917955 | 79765 | 0.0 | 21.145696 | 0 | 0.0 | 0 | 7 | 5806 | 0.587349 | 2023-06-21T00:00:00 |
9 | POLYGON ((-106.63614 30.07301, -106.63617 30.0... | 3.5 | 76.317924 | 83254 | 0.0 | 51.647117 | 1 | 0.0 | 0 | 341 | 5881 | 4.468151 | 2023-06-26T00:00:00 |
10 | POLYGON ((-106.78326 30.05115, -106.78330 30.0... | 2.5 | 10.102581 | 83084 | 0.0 | 13.674702 | 1 | 0.0 | 0 | 72 | 5901 | 7.126891 | 2023-06-24T12:00:00 |
11 | POLYGON ((-102.85583 27.81858, -102.85601 27.8... | 8.0 | 33.159692 | 81431 | 0.0 | 25.731754 | 1 | 0.0 | 0 | 340 | 5969 | 10.253412 | 2023-06-26T00:00:00 |
12 | POLYGON ((-97.43908 28.43692, -97.43900 28.436... | 19.0 | 5.312484 | 73340 | 0.0 | 10.166578 | 0 | 0.0 | 0 | 40 | 6187 | 7.529434 | 2023-06-20T12:00:00 |
13 | POLYGON ((-100.71656 25.53519, -100.71659 25.5... | 6.5 | 5.131956 | 79873 | 0.0 | 8.995199 | 0 | 0.0 | 0 | 98 | 6423 | 19.096034 | 2023-06-21T00:00:00 |
14 | POLYGON ((-102.25288 26.97686, -102.25289 26.9... | 6.0 | 10.666481 | 82098 | 0.0 | 13.479402 | 1 | 0.0 | 0 | 73 | 6470 | 6.843869 | 2023-06-26T00:00:00 |
15 | POLYGON ((-106.64036 25.02528, -106.64036 25.0... | 14.5 | 9.843044 | 78547 | 0.0 | 18.376243 | 1 | 0.0 | 0 | 62 | 7118 | 6.298864 | 2023-06-26T00:00:00 |
16 | POLYGON ((-106.52000 24.91752, -106.52000 24.9... | 12.5 | 16.358760 | 79217 | 0.0 | 19.469750 | 1 | 0.0 | 0 | 138 | 7129 | 8.435847 | 2023-06-25T12:00:00 |
17 | POLYGON ((-106.47703 24.93926, -106.47705 24.9... | 7.0 | 5.051376 | 82013 | 0.0 | 10.648427 | 1 | 0.0 | 0 | 47 | 7143 | 9.304395 | 2023-06-26T12:00:00 |
Explore data
We can quickly explore the data by setting the coordinate reference system (crs
) and using .explore()
= df.set_crs("EPSG:4326")
df df.explore()
Visualize Most Recent Fire Perimeters with Firelines
If we wanted to combine collections to make more informative analyses, we can use some of the same principles.
First we’ll get the queryable fields, and the extents:
= w.collection_queryables("public.eis_fire_snapshot_fireline_nrt")
fline_q = w.collection("public.eis_fire_snapshot_fireline_nrt")
fline_collection "properties"] fline_q[
{'wkb_geometry': {'$ref': 'https://geojson.org/schema/Geometry.json'},
'fireid': {'name': 'fireid', 'type': 'number'},
'mergeid': {'name': 'mergeid', 'type': 'number'},
'ogc_fid': {'name': 'ogc_fid', 'type': 'number'},
't': {'name': 't', 'type': 'string'}}
Read
Then we’ll use those fields to get most recent fire perimeters and fire lines.
= w.collection_items(
perm_results "public.eis_fire_snapshot_perimeter_nrt",
=most_recent_time,
datetime=1000,
limit
)= gpd.GeoDataFrame.from_features(perm_results["features"])
perimeters
## Get the most recent fire lines
= perimeters.fireid.unique()
perimeter_ids = ",".join(map(str, perimeter_ids))
perimeter_ids
= w.collection_items(
fline_results "public.eis_fire_snapshot_fireline_nrt",
=1000,
limitfilter="fireid IN ("
+ perimeter_ids
+ ")", # only the fires from the fire perimeter query above
)= gpd.GeoDataFrame.from_features(fline_results["features"]) fline
Visualize
= perimeters.set_crs("epsg:4326")
perimeters = fline.set_crs("epsg:4326")
fline
= perimeters.explore()
m = fline.explore(m=m, color="orange")
m m
Visualize the Growth of the Camp Fire
We may be interested in understanding how a fire evolved through time. To do this, we can work with the “Large fire” or “lf” perimeter collections. The public.eis_fire_lf_perimeter_nrt
collection has the full spread history of fires from this year. public.eis_fire_lf_perimeter_archive
has the full spread history of fires from 2018-2021 that were in the Western United States. The Camp Fire was in 2018, so we will work with the public.eis_fire_lf_perimeter_archive
collection.
We can start by querying with information specific to the Camp Fire, like it’s genreal region (Northern California), and when it was active (November 2018). With that information, we can get the fireID associated with the Camp Fire.
= w.collection_items(
perimeters_archive_results "public.eis_fire_lf_perimeter_archive",
=["-124.52", "39.2", "-120", "42"], # North California bounding box,
bbox=["2018-11-01T00:00:00+00:00/2018-11-30T12:00:00+00:00"],
datetime=3000,
limit
)
perimeters_archive_results
= gpd.GeoDataFrame.from_features(perimeters_archive_results["features"])
perimeters = perimeters.sort_values(by = "t", ascending = False)
perimeters = perimeters.set_crs("epsg:4326")
perimeters
print(perimeters.fireid.unique())
= perimeters.explore(style_kwds = {'fillOpacity':0})
m m
['F17028' 'F18493']
Based on the map, we know that the fireID for the Camp Fire is “F17028”. We can use that to directly query for that particular fire.
= w.collection_items(
perimeters_archive_results "public.eis_fire_lf_perimeter_archive",
filter="fireid = 'F17028'",
=["2018-01-01T00:00:00+00:00/2018-12-31T12:00:00+00:00"],
datetime=3000,
limit
)
perimeters_archive_results
= gpd.GeoDataFrame.from_features(perimeters_archive_results["features"])
perimeters = perimeters.sort_values(by = "t", ascending = False)
perimeters = perimeters.set_crs("epsg:4326")
perimeters
= perimeters.explore(style_kwds = {'fillOpacity':0})
m m
Download Data
Downloading pre-filtered data may be useful for working locally, or for working with the data in GIS software.
We can download the dataframe we made by writing it out into a shapefile or into a GeoJSON file.
perimeters.to_file('perimeters.shp')
perimeters.to_file('perimeters.geojson', driver='GeoJSON')
Collection Information
The API hosts 9 different collections. There are four different types of data, and three different time-scales availible for querying through the API. “*snapshot*” collections are useful for visualizing the most recent data. It contains the most recent fires perimeters, active firelines, or VIIRS observations within the last 20 days. “*lf*” collections (short for Large Fire), show every fire perimeter, active fire line, or VIIRS observations for fires over 5 km^2. Collections that end in *archive are for year 2018 - 2021 across the Western United States. Collections with the *nrt ending are for CONUS from this most recent year. FireIDs are consistent only between layers with the same timescale (snapshot, lf_*nrt, and lf_archive*).
public.eis_fire_snapshot_perimeter_nrt
Perimeter of cumulative fire-area. Most recent perimeter from the last 20 days.
public.eis_fire_lf_perimeter_nrt
Perimeter of cumulative fire-area, from fires over 5 km^2. Every fire perimeter from current year to date.
public.eis_fire_lf_perimeter_archive
Perimeter of cumulative fire-area, from fires over 5 km^2 in the Western United States. Every fire perimeter from 2018-2021.
Column | Description | Unit |
---|---|---|
meanfrp | Mean fire radiative power. The weighted sum of the fire radiative power detected at each new pixel, divided by the number of pixels. If no new pixels are detected, meanfrp is set to zero. | MW/(pixel area) |
t | Time of VIIRS detection, corrected to noon and midnight. | Datetime. yyyy-mm-ddThh:mm:ss. Local time. |
fireid | Fire ID. Unique for each fire. Matches fireid. | Numeric ID |
pixden | Number of pixels divided by area of perimeter. | pixels/Km^2 |
duration | Number of days since first observation of fire. Fires with a single observation have a duration of zero. | Days |
flinelen | Length of active fire line, based on new pixels. If no new pixels are detected, flinelen is set to zero. | Km |
fperim | Length of fire perimeter. | Km |
farea | Area within fire perimeter. | Km^2 |
n_newpixels | Number of pixels newly detected since last overpass. | pixels |
n_pixels | Number of pixel-detections in history of fire. | pixels |
isactive | Have new fire pixels been detected in the last 5 days? | Boolean |
ogc_fid | The ID used by the OGC API to sort perimeters. | Numeric ID |
geometry | The shape of the perimeter. | Geometry |
public.eis_fire_snapshot_fireline_nrt
Active fire line as estimated by new VIIRS detections. Most fire line from the last 20 days.
public.eis_fire_lf_fireline_nrt
Active fire line as estimated by new VIIRS detections, from fires over 5 km^2. Every fire line from current year to date.
public.eis_fire_lf_fireline_nrt
Active fire line as estimated by new VIIRS detections, from fires over 5 km^2 in the Western United States. Every fire line from 2018-2021.
Column | Description | Unit |
---|---|---|
fireid | ID of fire pixel associated with. | Numeric ID |
t | Time of VIIRS detection, corrected to noon and midnight. | Datetime. yyyy-mm-ddThh:mm:ss. Local time. |
mergeid | ID used to connect pixels to perimeters. Matches fireid | Numeric ID |
ogc_fid | The ID used by the OGC API to sort pixels. | Numeric ID |
public.eis_fire_snapshot_newfirepix_nrt
New pixel detections that inform the most recent time-step’s perimeter and fireline calculation from the last 20 days.
public.eis_fire_lf_newfirepix_nrt
New pixel detections that inform a given time-step’s perimeter and fireline calculation. Availible from start of current year to date.
public.eis_fire_lf_newfirepix_archive
New pixel detections that inform a given time-step’s perimeter and fireline calculation. Availible for Western United States from 2018-2021.
Column | Description | Unit |
---|---|---|
fireid | ID of fire pixel associated with. | Numeric ID |
t | Time of VIIRS detection, corrected to noon and midnight. | Datetime. yyyy-mm-ddThh:mm:ss. Local time. |
mergeid | ID used to connect pixels to perimeters. Matches fireid | Numeric ID |
ogc_fid | The ID used by the OGC API to sort pixels. | Numeric ID |