MODIS MOD021KM and FIRMS¶
Wildfires Sensors
Context¶
Purpose¶
Explore MODIS satellite imagery and wildfire data that is open and free for scientific use.
Sensor description¶
The MOD021KM product contains calibrated and geolocated at-aperture radiances for 36 discrete bands located in the 0.4 to 14.4 micron region of the electromagnetic spectrum.
Highlights¶
Use
satpy
to load, visualise, and regrid MODIS level 1B data.Fetch a fire database containing some 497364 fires from 2020.
Visualisation of fire pixels from the database.
Visualisation of the fire pixels alongside bands from the MODIS satellite data.
Contributions¶
Notebook¶
Samuel Jackson (author), Science & Technology Facilities Council, @samueljackson92 Alejandro Coca-Castro (reviewer), The Alan Turing Institute, @acocac, 06/09/21 (latest revision)
Dataset originator/creator¶
MOD021KM¶
MODIS Characterization Support Team (MCST) MODIS Adaptive Processing System (MODAPS)
Firms¶
University of Maryland
Dataset authors¶
MOD021KM¶
MODIS Science Data Support Team (SDST)
Firms¶
NASA’s Applied Sciences Program
Dataset documentation¶
Louis Giglio, Wilfrid Schroeder, Joanne V. Hall, and Christopher O. Justice. MODIS Collection 6 Active Fire Product User’s Guide Revision B. Technical Report, NASA, 2018. URL: https://modis-fire.umd.edu/files/MODIS_C6_Fire_User_Guide_B.pdf.
Note
The author acknowledges MODIS Science Team and the use of data and/or imagery from NASA’s Fire Information for Resource Management System (FIRMS) (https://earthdata.nasa.gov/firms), part of NASA’s Earth Observing System Data and Information System (EOSDIS).
Install and load libraries¶
! pip -q install pyspectral
! pip -q install 'satpy[modis_l1b]==0.26.0'
import pandas as pd
import numpy as np
import geopandas
import intake
import fsspec, aiohttp
import hvplot.xarray
import hvplot.pandas
import holoviews as hv
import panel as pn
import satpy
import xarray as xr
import tempfile
from pathlib import Path
from scipy.spatial import cKDTree
from satpy.writers import get_enhanced_image
from getpass import getpass
from pathlib import Path
from pyresample import geometry
import datetime
import urllib.request
import os.path
Fetch Data¶
Note
To download data from the NASA’s Earth Data site you must have a valid Earth Data account. Please register with Earth Data is you do not already have an account and then provide your username and password when prompted in the cell below.
Important
In order to download MODIS level1 data you must have authorized access to LAADS Web
on your account. To do this navigate to your earthdata profile page
Click “Applications”
Click “Authorized Apps”
Click “Autorize More Applications”
Search for “LAADS Web”
Click “Authorize”
Now you should successfully be able to download MODIS data.
fname = 'MOD021KM.A2020245.0840.061.2020245193036.hdf'
if not os.path.isfile(fname) or os.path.getsize(fname) == 0:
username = input('Username: ')
password = getpass('Password: ')
fsspec.config.conf['https'] = dict(client_kwargs={'auth': aiohttp.BasicAuth(username, password)})
url = f'https://ladsweb.modaps.eosdis.nasa.gov/archive/allData/61/MOD021KM/2020/245/{fname}'
filename = url.split('/')[-1]
with fsspec.open(url) as f:
with Path(filename).open('wb') as handle:
data = f.read()
try:
data.decode('utf-8')
raise RuntimeError('Could not download MODIS data! Have you authorized LAADS Web in your Eathdata account above?')
except UnicodeDecodeError:
handle.write(data)
Download the database of MODIS wildfires from a publically accessible location.
filepath = 'https://firms2.modaps.eosdis.nasa.gov/data/country/zips/'
filename = 'modis_2020_all_countries.zip'
if not os.path.isfile(filename) or os.path.getsize(filename) == 0:
urllib.request.urlretrieve(filepath+filename, '../sensors/' + filename)
!unzip modis_2020_all_countries.zip
Archive: modis_2020_all_countries.zip
creating: modis/2020/
inflating: modis/2020/modis_2020_Afghanistan.csv
inflating: modis/2020/modis_2020_Albania.csv
inflating: modis/2020/modis_2020_Algeria.csv
inflating: modis/2020/modis_2020_Andorra.csv
inflating: modis/2020/modis_2020_Angola.csv
inflating: modis/2020/modis_2020_Antigua_and_Barbuda.csv
inflating: modis/2020/modis_2020_Argentina.csv
inflating: modis/2020/modis_2020_Armenia.csv
inflating: modis/2020/modis_2020_Australia.csv
inflating: modis/2020/modis_2020_Austria.csv
inflating: modis/2020/modis_2020_Azerbaijan.csv
inflating: modis/2020/modis_2020_Bahamas.csv
inflating: modis/2020/modis_2020_Bahrain.csv
inflating: modis/2020/modis_2020_Bangladesh.csv
inflating: modis/2020/modis_2020_Barbados.csv
inflating: modis/2020/modis_2020_Belarus.csv
inflating: modis/2020/modis_2020_Belgium.csv
inflating: modis/2020/modis_2020_Belize.csv
inflating: modis/2020/modis_2020_Benin.csv
inflating: modis/2020/modis_2020_Bermuda.csv
inflating: modis/2020/modis_2020_Bhutan.csv
inflating: modis/2020/modis_2020_Bolivia.csv
inflating: modis/2020/modis_2020_Bosnia_and_Herzegovina.csv
inflating: modis/2020/modis_2020_Botswana.csv
inflating: modis/2020/modis_2020_Brazil.csv
inflating: modis/2020/modis_2020_British_Virgin_Islands.csv
inflating: modis/2020/modis_2020_Brunei_Darussalam.csv
inflating: modis/2020/modis_2020_Bulgaria.csv
inflating: modis/2020/modis_2020_Burkina_Faso.csv
inflating: modis/2020/modis_2020_Burundi.csv
inflating: modis/2020/modis_2020_Cambodia.csv
inflating: modis/2020/modis_2020_Cameroon.csv
inflating: modis/2020/modis_2020_Canada.csv
inflating: modis/2020/modis_2020_Cape_Verde.csv
inflating: modis/2020/modis_2020_Cayman_Islands.csv
inflating: modis/2020/modis_2020_Central_African_Republic.csv
inflating: modis/2020/modis_2020_Chad.csv
inflating: modis/2020/modis_2020_Chile.csv
inflating: modis/2020/modis_2020_China.csv
inflating: modis/2020/modis_2020_Colombia.csv
inflating: modis/2020/modis_2020_Comoros.csv
inflating: modis/2020/modis_2020_Costa_Rica.csv
inflating: modis/2020/modis_2020_Cote_d_Ivoire.csv
inflating: modis/2020/modis_2020_Croatia.csv
inflating: modis/2020/modis_2020_Cuba.csv
inflating: modis/2020/modis_2020_Curacao.csv
inflating: modis/2020/modis_2020_Cyprus.csv
inflating: modis/2020/modis_2020_Czech_Republic.csv
inflating: modis/2020/modis_2020_Dem_Rep_Korea.csv
inflating: modis/2020/modis_2020_Democratic_Republic_of_the_Congo.csv
inflating: modis/2020/modis_2020_Denmark.csv
inflating: modis/2020/modis_2020_Djibouti.csv
inflating: modis/2020/modis_2020_Dominica.csv
inflating: modis/2020/modis_2020_Dominican_Republic.csv
inflating: modis/2020/modis_2020_Ecuador.csv
inflating: modis/2020/modis_2020_Egypt.csv
inflating: modis/2020/modis_2020_El_Salvador.csv
inflating: modis/2020/modis_2020_Equatorial_Guinea.csv
inflating: modis/2020/modis_2020_Eritrea.csv
inflating: modis/2020/modis_2020_Estonia.csv
inflating: modis/2020/modis_2020_Ethiopia.csv
inflating: modis/2020/modis_2020_Falkland_Islands.csv
inflating: modis/2020/modis_2020_Fiji.csv
inflating: modis/2020/modis_2020_Finland.csv
inflating: modis/2020/modis_2020_France.csv
inflating: modis/2020/modis_2020_French_Guiana.csv
inflating: modis/2020/modis_2020_French_Polynesia.csv
inflating: modis/2020/modis_2020_French_Southern_and_Antarctic_Lands.csv
inflating: modis/2020/modis_2020_Gabon.csv
inflating: modis/2020/modis_2020_Georgia.csv
inflating: modis/2020/modis_2020_Germany.csv
inflating: modis/2020/modis_2020_Ghana.csv
inflating: modis/2020/modis_2020_Greece.csv
inflating: modis/2020/modis_2020_Greenland.csv
inflating: modis/2020/modis_2020_Guadeloupe.csv
inflating: modis/2020/modis_2020_Guam.csv
inflating: modis/2020/modis_2020_Guatemala.csv
inflating: modis/2020/modis_2020_Guinea-Bissau.csv
inflating: modis/2020/modis_2020_Guinea.csv
inflating: modis/2020/modis_2020_Guyana.csv
inflating: modis/2020/modis_2020_Haiti.csv
inflating: modis/2020/modis_2020_Heard_I_and_McDonald_Islands.csv
inflating: modis/2020/modis_2020_Honduras.csv
inflating: modis/2020/modis_2020_Hong_Kong.csv
inflating: modis/2020/modis_2020_Hungary.csv
inflating: modis/2020/modis_2020_India.csv
inflating: modis/2020/modis_2020_Indonesia.csv
inflating: modis/2020/modis_2020_Iran.csv
inflating: modis/2020/modis_2020_Iraq.csv
inflating: modis/2020/modis_2020_Ireland.csv
inflating: modis/2020/modis_2020_Israel.csv
inflating: modis/2020/modis_2020_Italy.csv
inflating: modis/2020/modis_2020_Jamaica.csv
inflating: modis/2020/modis_2020_Japan.csv
inflating: modis/2020/modis_2020_Jordan.csv
inflating: modis/2020/modis_2020_Kazakhstan.csv
inflating: modis/2020/modis_2020_Kenya.csv
inflating: modis/2020/modis_2020_Kiribati.csv
inflating: modis/2020/modis_2020_Kosovo.csv
inflating: modis/2020/modis_2020_Kuwait.csv
inflating: modis/2020/modis_2020_Kyrgyzstan.csv
inflating: modis/2020/modis_2020_Lao_PDR.csv
inflating: modis/2020/modis_2020_Latvia.csv
inflating: modis/2020/modis_2020_Lebanon.csv
inflating: modis/2020/modis_2020_Lesotho.csv
inflating: modis/2020/modis_2020_Liberia.csv
inflating: modis/2020/modis_2020_Libya.csv
inflating: modis/2020/modis_2020_Lithuania.csv
inflating: modis/2020/modis_2020_Luxembourg.csv
inflating: modis/2020/modis_2020_Macedonia_Former_Yugoslav_Republic_of.csv
inflating: modis/2020/modis_2020_Madagascar.csv
inflating: modis/2020/modis_2020_Malawi.csv
inflating: modis/2020/modis_2020_Malaysia.csv
inflating: modis/2020/modis_2020_Maldives.csv
inflating: modis/2020/modis_2020_Mali.csv
inflating: modis/2020/modis_2020_Malta.csv
inflating: modis/2020/modis_2020_Martinique.csv
inflating: modis/2020/modis_2020_Mauritania.csv
inflating: modis/2020/modis_2020_Mauritius.csv
inflating: modis/2020/modis_2020_Mexico.csv
inflating: modis/2020/modis_2020_Moldova.csv
inflating: modis/2020/modis_2020_Mongolia.csv
inflating: modis/2020/modis_2020_Montenegro.csv
inflating: modis/2020/modis_2020_Montserrat.csv
inflating: modis/2020/modis_2020_Morocco.csv
inflating: modis/2020/modis_2020_Mozambique.csv
inflating: modis/2020/modis_2020_Myanmar.csv
inflating: modis/2020/modis_2020_Namibia.csv
inflating: modis/2020/modis_2020_Nepal.csv
inflating: modis/2020/modis_2020_Netherlands.csv
inflating: modis/2020/modis_2020_New_Caledonia.csv
inflating: modis/2020/modis_2020_New_Zealand.csv
inflating: modis/2020/modis_2020_Nicaragua.csv
inflating: modis/2020/modis_2020_Niger.csv
inflating: modis/2020/modis_2020_Nigeria.csv
inflating: modis/2020/modis_2020_Northern_Mariana_Islands.csv
inflating: modis/2020/modis_2020_Norway.csv
inflating: modis/2020/modis_2020_Oman.csv
inflating: modis/2020/modis_2020_Pakistan.csv
inflating: modis/2020/modis_2020_Palau.csv
inflating: modis/2020/modis_2020_Palestine.csv
inflating: modis/2020/modis_2020_Panama.csv
inflating: modis/2020/modis_2020_Papua_New_Guinea.csv
inflating: modis/2020/modis_2020_Paraguay.csv
inflating: modis/2020/modis_2020_Peru.csv
inflating: modis/2020/modis_2020_Philippines.csv
inflating: modis/2020/modis_2020_Poland.csv
inflating: modis/2020/modis_2020_Portugal.csv
inflating: modis/2020/modis_2020_Puerto_Rico.csv
inflating: modis/2020/modis_2020_Qatar.csv
inflating: modis/2020/modis_2020_Republic_of_Congo.csv
inflating: modis/2020/modis_2020_Republic_of_Korea.csv
inflating: modis/2020/modis_2020_Reunion.csv
inflating: modis/2020/modis_2020_Romania.csv
inflating: modis/2020/modis_2020_Russian_Federation.csv
inflating: modis/2020/modis_2020_Rwanda.csv
inflating: modis/2020/modis_2020_Saint_Helena.csv
inflating: modis/2020/modis_2020_Saint_Kitts_and_Nevis.csv
inflating: modis/2020/modis_2020_Saint_Lucia.csv
inflating: modis/2020/modis_2020_Saint_Vincent_and_the_Grenadines.csv
inflating: modis/2020/modis_2020_Samoa.csv
inflating: modis/2020/modis_2020_Sao_Tome_and_Principe.csv
inflating: modis/2020/modis_2020_Saudi_Arabia.csv
inflating: modis/2020/modis_2020_Senegal.csv
inflating: modis/2020/modis_2020_Serbia.csv
inflating: modis/2020/modis_2020_Seychelles.csv
inflating: modis/2020/modis_2020_Sierra_Leone.csv
inflating: modis/2020/modis_2020_Singapore.csv
inflating: modis/2020/modis_2020_Slovakia.csv
inflating: modis/2020/modis_2020_Slovenia.csv
inflating: modis/2020/modis_2020_Solomon_Islands.csv
inflating: modis/2020/modis_2020_Somalia.csv
inflating: modis/2020/modis_2020_South_Africa.csv
inflating: modis/2020/modis_2020_South_Sudan.csv
inflating: modis/2020/modis_2020_Spain.csv
inflating: modis/2020/modis_2020_Sri_Lanka.csv
inflating: modis/2020/modis_2020_Sudan.csv
inflating: modis/2020/modis_2020_Suriname.csv
inflating: modis/2020/modis_2020_Svalbard_and_Jan_Mayen.csv
inflating: modis/2020/modis_2020_Swaziland.csv
inflating: modis/2020/modis_2020_Sweden.csv
inflating: modis/2020/modis_2020_Switzerland.csv
inflating: modis/2020/modis_2020_Syria.csv
inflating: modis/2020/modis_2020_Taiwan.csv
inflating: modis/2020/modis_2020_Tajikistan.csv
inflating: modis/2020/modis_2020_Tanzania.csv
inflating: modis/2020/modis_2020_Thailand.csv
inflating: modis/2020/modis_2020_The_Gambia.csv
inflating: modis/2020/modis_2020_Timor-Leste.csv
inflating: modis/2020/modis_2020_Togo.csv
inflating: modis/2020/modis_2020_Tonga.csv
inflating: modis/2020/modis_2020_Trinidad_and_Tobago.csv
inflating: modis/2020/modis_2020_Tunisia.csv
inflating: modis/2020/modis_2020_Turkey.csv
inflating: modis/2020/modis_2020_Turkmenistan.csv
inflating: modis/2020/modis_2020_Turks_and_Caicos_Islands.csv
inflating: modis/2020/modis_2020_Uganda.csv
inflating: modis/2020/modis_2020_Ukraine.csv
inflating: modis/2020/modis_2020_United_Arab_Emirates.csv
inflating: modis/2020/modis_2020_United_Kingdom.csv
inflating: modis/2020/modis_2020_United_States.csv
inflating: modis/2020/modis_2020_United_States_Minor_Outlying_Islands.csv
inflating: modis/2020/modis_2020_United_States_Virgin_Islands.csv
inflating: modis/2020/modis_2020_Uruguay.csv
inflating: modis/2020/modis_2020_Uzbekistan.csv
inflating: modis/2020/modis_2020_Vanuatu.csv
inflating: modis/2020/modis_2020_Venezuela.csv
inflating: modis/2020/modis_2020_Vietnam.csv
inflating: modis/2020/modis_2020_Yemen.csv
inflating: modis/2020/modis_2020_Zambia.csv
inflating: modis/2020/modis_2020_Zimbabwe.csv
Load an intake catalog for the downloaded data
path = Path(tempfile.mkdtemp())
catalog_file = path / 'catalog.yaml'
with catalog_file.open('w') as f:
f.write('''
sources:
modis_l1b:
args:
urlpath: 'MOD021KM.A2020245.0840.061.2020245193036.hdf'
reader: 'modis_l1b'
description: "MODIS Level 1B Products"
driver: SatpySource
metadata: {}
modis_fires:
args:
urlpath: 'modis/2020/modis_*.csv'
description: "MODIS Level 2 Fires"
driver: csv
metadata: {}
''')
from intake.source.base import PatternMixin
from intake.source.base import DataSource, Schema
import glob
class SatpySource(DataSource, PatternMixin):
"""Intake driver for data supported by satpy.Scene"""
container = 'python'
name = 'satpy'
version = '0.0.1'
partition_access = False
def __init__(self, urlpath, reader=None, metadata=None, path_as_pattern=True):
"""
Parameters
----------
path: str, location of model pkl file
Either the absolute or relative path to the file
opened. Some examples:
- ``{{ CATALOG_DIR }}/file.nat``
- ``{{ CATALOG_DIR }}/*.nc``
reader: str, optional
Name of the satpy reader to use when loading data (ie. ``modis_l1b``)
metadata: dict, optional
Additional metadata to pass to the intake catalog
path_as_pattern: bool or str, optional
Whether to treat the path as a pattern (ie. ``data_{field}.nc``)
and create new coodinates in the output corresponding to pattern
fields. If str, is treated as pattern to match on. Default is True.
"""
self.path_as_pattern = path_as_pattern
self.urlpath = urlpath
self._reader = reader
super(SatpySource, self).__init__(metadata=metadata)
def _load(self):
files = self.urlpath
files = list(glob.glob(files))
return satpy.Scene(files, reader=self._reader)
def _get_schema(self):
self._schema = Schema(
npartitions=1,
extra_metadata={}
)
return self._schema
def read(self):
self._load_metadata()
return self._load()
intake.register_driver('SatpySource', SatpySource, overwrite=True)
cat = intake.open_catalog(catalog_file)
Load MODIS Satellite Data¶
Here we use satpy
to load the MODIS level 1b data into memory. As well as loading the image data, satpy
provides a easy way to regrid the data to different coordinate systems.
scn = cat['modis_l1b'].read()
scn
<satpy.scene.Scene at 0x7ff611fa13a0>
scn.load(['true_color', '20'], resolution=1000)
scn.show('true_color')
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
Resample to a different projection¶
In the plot above you can see that the raw MODIS data has distortion towards edge of the image. By regridding the data we can avoid some of this distortion.
area_id = "Southern Africa"
description = "Southern Africa in Mercator-Projection"
proj_id = "Southern Africa"
proj_dict = {"proj": "eqc"}
width = 1000 # width of the result domain in pixels
height = 1000 # height of the result domain in pixels
llx = 23E5 # projection x coordinate of lower left corner of lower left pixel
lly = -22.9E5 # projection y coordinate of lower left corner of lower left pixel
urx = 48E5 # projection x coordinate of upper right corner of upper right pixel
ury = -1.9E5 # projection y coordinate of upper right corner of upper right pixel
area_extent = (llx,lly,urx,ury)
from pyresample import create_area_def
resolution=1000
center =(0,0)
area_def = create_area_def(area_id, proj_dict, center=center, resolution=resolution)
modis_scn = scn.resample(area_def, radius_of_influence=10000)
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/pyproj/crs/crs.py:1216: UserWarning: You will likely lose important projection information when converting to a PROJ string from another format. See: https://proj.org/faq.html#what-is-the-best-format-for-describing-coordinate-reference-systems
return self._crs.to_proj4(version=version)
modis_scn.show('true_color')
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
Load MODIS Fire Database¶
Now we’re going to load the modis fire database from CSV files. This contains the longitude, latitude, and time of where fires have been detected to occur. It also includes an estimate of the Fire Radiative Power (FRP), a measure of the intensity of the fire, for each fire detected.
fires = cat['modis_fires'].read()
time = fires.acq_date.astype(str) + ' ' + fires.acq_time.astype(str).str.zfill(4)
fires['timestamp'] = pd.to_datetime(time, format='%Y-%m-%d %H%M')
fires = fires.loc[fires.satellite == 'Terra']
fires = geopandas.GeoDataFrame(
fires, geometry=geopandas.points_from_xy(fires.longitude, fires.latitude))
# We're only interested in data from Southern Africa for now
llx = 0 # projection x coordinate of lower left corner of lower left pixel
lly = -30 # projection y coordinate of lower left corner of lower left pixel
urx = 55 # projection x coordinate of upper right corner of upper right pixel
ury = 10 # projection y coordinate of upper right corner of upper right pixel
fires = fires.cx[llx:urx, lly:ury]
fires.set_index('timestamp', drop=False, inplace=True)
fires = fires.sort_index()
fires = fires.loc['2020-09-01 00:00:00':'2020-10-01 00:00:00']
fires
latitude | longitude | brightness | scan | track | acq_date | acq_time | satellite | instrument | confidence | version | bright_t31 | frp | daynight | type | timestamp | geometry | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
timestamp | |||||||||||||||||
2020-09-01 07:02:00 | -15.8201 | 46.6282 | 320.8 | 1.2 | 1.1 | 2020-09-01 | 702 | Terra | MODIS | 59 | 6.03 | 306.2 | 8.7 | D | 0 | 2020-09-01 07:02:00 | POINT (46.62820 -15.82010) |
2020-09-01 07:02:00 | -15.8183 | 46.6171 | 323.7 | 1.2 | 1.1 | 2020-09-01 | 702 | Terra | MODIS | 67 | 6.03 | 307.9 | 12.7 | D | 0 | 2020-09-01 07:02:00 | POINT (46.61710 -15.81830) |
2020-09-01 07:02:00 | -15.7672 | 47.2667 | 324.8 | 1.1 | 1.1 | 2020-09-01 | 702 | Terra | MODIS | 72 | 6.03 | 307.6 | 9.9 | D | 0 | 2020-09-01 07:02:00 | POINT (47.26670 -15.76720) |
2020-09-01 07:02:00 | -15.7055 | 47.7069 | 328.2 | 1.1 | 1.0 | 2020-09-01 | 702 | Terra | MODIS | 75 | 6.03 | 306.8 | 15.4 | D | 0 | 2020-09-01 07:02:00 | POINT (47.70690 -15.70550) |
2020-09-01 07:02:00 | -15.0942 | 47.2146 | 320.4 | 1.2 | 1.1 | 2020-09-01 | 702 | Terra | MODIS | 65 | 6.03 | 302.7 | 9.6 | D | 0 | 2020-09-01 07:02:00 | POINT (47.21460 -15.09420) |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2020-09-30 22:05:00 | -4.5185 | 12.0523 | 306.7 | 1.8 | 1.3 | 2020-09-30 | 2205 | Terra | MODIS | 69 | 6.03 | 288.2 | 17.6 | N | 2 | 2020-09-30 22:05:00 | POINT (12.05230 -4.51850) |
2020-09-30 22:06:00 | -2.7462 | 10.7768 | 302.2 | 1.5 | 1.2 | 2020-09-30 | 2206 | Terra | MODIS | 47 | 6.03 | 287.3 | 8.6 | N | 0 | 2020-09-30 22:06:00 | POINT (10.77680 -2.74620) |
2020-09-30 22:06:00 | -0.8865 | 8.8320 | 302.2 | 1.2 | 1.1 | 2020-09-30 | 2206 | Terra | MODIS | 48 | 6.03 | 289.0 | 4.9 | N | 0 | 2020-09-30 22:06:00 | POINT (8.83200 -0.88650) |
2020-09-30 22:06:00 | -2.8421 | 10.8256 | 301.0 | 1.5 | 1.2 | 2020-09-30 | 2206 | Terra | MODIS | 37 | 6.03 | 287.2 | 7.8 | N | 0 | 2020-09-30 22:06:00 | POINT (10.82560 -2.84210) |
2020-09-30 22:08:00 | 4.4222 | 7.1571 | 302.5 | 1.1 | 1.1 | 2020-09-30 | 2208 | Terra | MODIS | 49 | 6.03 | 291.9 | 4.5 | N | 2 | 2020-09-30 22:08:00 | POINT (7.15710 4.42220) |
65459 rows × 17 columns
Daily Fire Radiative Power¶
Here we plot the Fire Radiative Power (FRP) for each day of the month of September. FRP is a measure of the intensity of a fire in units of MegaWatts.
from bokeh.models.formatters import DatetimeTickFormatter
def plot_timeseries(data, y_variable):
"""Timeseries plot showing the mean fire radiative power at each day as well as the 5% and 95%"""
def percentile(n):
def percentile_(x):
return np.percentile(x, n)
percentile_.__name__ = 'percentile_%s' % n
return percentile_
formatter = DatetimeTickFormatter(months='%b')
bounds = data.groupby([data.index.day_of_year])[y_variable].agg([percentile(5), percentile(95)])
avg = data.groupby([data.index.day_of_year])[y_variable].mean()
bounds.index = data.groupby([data.index.day_of_year])['timestamp'].first()
avg.index = bounds.index
tseries = hv.Overlay([
(bounds.hvplot.area('timestamp', 'percentile_5', 'percentile_95',
alpha=0.2, color='red', xformatter=formatter)),
avg.hvplot.line(x='timestamp', label=f'Average Daily FRP', color='red', xformatter=formatter)])
return tseries.options(width=800, height=400, xlabel='Day',ylabel=f'FRP (MW)',legend_position='top_left')
plot_timeseries(fires, 'frp')
Geographical distribution of Fires¶
Here we plot the geographical distribution of fires detected by MODIS over Southern Africa.
fires.hvplot.points('longitude', 'latitude', groupby='index.day_of_year', c='frp', geo=True, alpha=0.2,
tiles='ESRI', xlim=(llx, urx), ylim=(lly, ury), cmap='autumn', logz=True,
dynamic=False)
WARNING:param.GeoPointPlot11511: Log color mapper lower bound <= 0 and will not render correctly. Ensure you set a positive lower bound on the color dimension or using the `clim` option.
WARNING:param.GeoPointPlot11511: Log color mapper lower bound <= 0 and will not render correctly. Ensure you set a positive lower bound on the color dimension or using the `clim` option.
Visualising Fire Pixels with Satellite Imagery¶
Visualise a color image of the MODIS region using hvplot
.
modis_dataset = modis_scn.to_xarray_dataset()
img = get_enhanced_image(modis_scn['true_color'].squeeze())
img = img.data
img = img.clip(0, 1)
img = (img * 255).astype(np.uint8)
modis_dataset['true_color'] = img
grid = modis_scn.min_area().get_lonlats()
lons, lats = grid[0][0], grid[1][:, 0]
modis_dataset = modis_dataset.assign_coords(dict(x=lons, y=lats))
modis_dataset = modis_dataset.rename(dict(x='longitude', y='latitude'))
rgb = modis_dataset['true_color'].hvplot.rgb(x='longitude', y='latitude', bands='bands', rasterize=True)
rgb
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/pyproj/crs/crs.py:1216: UserWarning: You will likely lose important projection information when converting to a PROJ string from another format. See: https://proj.org/faq.html#what-is-the-best-format-for-describing-coordinate-reference-systems
return self._crs.to_proj4(version=version)
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
OMP: Info #271: omp_set_nested routine deprecated, please use omp_set_max_active_levels instead.
Get the fire pixels that are visible in the the MODIS scene by filtering the time, longitude & latitude. We can also compute which row and column the fire pixel is in for this projection.
grid = modis_scn.min_area().get_lonlats()
# Mask any fire pixels not in this scene
time_mask = np.logical_and(fires.timestamp >= scn.attrs['start_time'],
fires.timestamp <= scn.attrs['end_time'])
fire_points = fires.loc[time_mask]
fire_points = fire_points.cx[grid[0].min():grid[0].max(), grid[1].min():grid[1].max()]
# extract the x and y coordinates as flat arrays
x = np.ravel(grid[0])
y = np.ravel(grid[1])
points = np.dstack([x, y]).squeeze()
# Find locations of fire pixels within satellite image
tree = cKDTree(points)
distance, idx = tree.query(fire_points[['longitude', 'latitude']], k=1)
index = np.unravel_index(idx, grid[0].shape)
index = np.vstack(index).T
index
# fire_points[['y', 'x']] = index
fire_points = fire_points[img.values[0, index[:, 0], index[:, 1]] != 0]
fire_points
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
latitude | longitude | brightness | scan | track | acq_date | acq_time | satellite | instrument | confidence | version | bright_t31 | frp | daynight | type | timestamp | geometry | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
timestamp | |||||||||||||||||
2020-09-01 08:40:00 | -12.0362 | 30.9757 | 321.5 | 1.8 | 1.3 | 2020-09-01 | 840 | Terra | MODIS | 67 | 6.03 | 305.2 | 20.0 | D | 0 | 2020-09-01 08:40:00 | POINT (30.97570 -12.03620) |
2020-09-01 08:40:00 | -11.6813 | 29.0762 | 328.7 | 1.3 | 1.1 | 2020-09-01 | 840 | Terra | MODIS | 79 | 6.03 | 307.0 | 20.8 | D | 0 | 2020-09-01 08:40:00 | POINT (29.07620 -11.68130) |
2020-09-01 08:40:00 | -11.9208 | 31.1634 | 323.1 | 1.8 | 1.3 | 2020-09-01 | 840 | Terra | MODIS | 65 | 6.03 | 306.3 | 24.3 | D | 0 | 2020-09-01 08:40:00 | POINT (31.16340 -11.92080) |
2020-09-01 08:40:00 | -12.0026 | 30.6289 | 326.0 | 1.7 | 1.3 | 2020-09-01 | 840 | Terra | MODIS | 75 | 6.03 | 304.3 | 31.9 | D | 0 | 2020-09-01 08:40:00 | POINT (30.62890 -12.00260) |
2020-09-01 08:40:00 | -11.7261 | 29.1128 | 325.1 | 1.3 | 1.1 | 2020-09-01 | 840 | Terra | MODIS | 71 | 6.03 | 304.9 | 16.8 | D | 0 | 2020-09-01 08:40:00 | POINT (29.11280 -11.72610) |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2020-09-01 08:42:00 | -17.1728 | 20.0847 | 329.4 | 1.5 | 1.2 | 2020-09-01 | 842 | Terra | MODIS | 79 | 6.03 | 304.9 | 30.4 | D | 0 | 2020-09-01 08:42:00 | POINT (20.08470 -17.17280) |
2020-09-01 08:42:00 | -18.4965 | 19.8103 | 324.7 | 1.5 | 1.2 | 2020-09-01 | 842 | Terra | MODIS | 72 | 6.03 | 306.0 | 20.2 | D | 0 | 2020-09-01 08:42:00 | POINT (19.81030 -18.49650) |
2020-09-01 08:44:00 | -27.9381 | 24.6669 | 328.1 | 1.2 | 1.1 | 2020-09-01 | 844 | Terra | MODIS | 83 | 6.03 | 297.1 | 27.9 | D | 0 | 2020-09-01 08:44:00 | POINT (24.66690 -27.93810) |
2020-09-01 08:44:00 | -27.9366 | 24.6552 | 311.5 | 1.2 | 1.1 | 2020-09-01 | 844 | Terra | MODIS | 62 | 6.03 | 296.1 | 7.7 | D | 0 | 2020-09-01 08:44:00 | POINT (24.65520 -27.93660) |
2020-09-01 08:44:00 | -27.4436 | 24.7104 | 320.0 | 1.1 | 1.1 | 2020-09-01 | 844 | Terra | MODIS | 70 | 6.03 | 299.1 | 15.0 | D | 0 | 2020-09-01 08:44:00 | POINT (24.71040 -27.44360) |
898 rows × 17 columns
Now overlay the fire pixels ontop of the MODIS image, along with the FRP for each fire pixel. Try zooming in, you should be able to see clear smoke trails at the locations of some of the fires!
rgb = modis_dataset['true_color'].hvplot.rgb(x='longitude', y='latitude', bands='bands', data_aspect=1, hover=False, title='True Colour', rasterize=True)
points = fire_points.hvplot.points('longitude', 'latitude', c='frp', cmap='autumn', logz=True, alpha=0.4)
rgb*points
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
We can also overlay the fire pixels ontop of the MODIS 3.7 micron wavelength band. The 3.7 band is a thermal band. Fires will appear as very bright pixels in image. Try zooming in, you should be able to clearly see bright spots at the location of each fire pixel.
rgb = modis_dataset['20'].hvplot.image(x='longitude', y='latitude', cmap='viridis', data_aspect=1, hover=False, title='Band 20: 3.7 micron', rasterize=True)
points = fire_points.hvplot.points('longitude', 'latitude', c='frp', cmap='autumn', logz=True, alpha=0.4)
rgb*points
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
/Users/acoca/anaconda3/envs/envai-book/lib/python3.8/site-packages/dask/core.py:121: RuntimeWarning: invalid value encountered in log
return func(*(_execute_task(a, cache) for a in args))
Summary¶
This notebook has demonstrated the use of:
The
satpy
package to easily load, plot and regrid satellite data from the MODIS sensor.hvplot
to interatively visualise wildfire pixels detected from the MODIS sensor.geopandas
to load, filter, and manipulate historical wildfire pixel data.