Skip to content

Commit d4fde4c

Browse files
committedJan 18, 2022
first commit
1 parent 340b783 commit d4fde4c

8 files changed

+886
-0
lines changed
 

‎LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 James Calam Briggs
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎README.html

+467
Large diffs are not rendered by default.

‎README.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
### Description
2+
<p align="justify">
3+
This package provides functions to download annual fire frequencies from Google Earth Engine (i.e. number of months with active fires), based on monthly, global burned area maps derived with MODIS at a 500 m resolution. For details on the dataset, <a href="https://developers.google.com/earth-engine/datasets/catalog/MODIS_006_MCD64A1">refer to the GEE's data catalog</a>. Given a list of years, the algorithm will derive annual maps of fire occurrences, depicting per-pixel counts of months with active fires. These annual layers are then used to compute a series of metrics that characterize the temporal recurrence of fires:
4+
</p>
5+
<br>
6+
<li><i>Fire Return Interval (FRI) - </i>Mean number of years between firest</li>
7+
<li><i>Min. return time (MRI1) - </i>Minimum between-burn interval</li>
8+
<li><i>Mean return time (MRI2) - </i>Mean between-burn interval</li>
9+
<li><i>Maximum return time (MRI3) - </i>Maximum between-burn interval</li>
10+
<br>
11+
<p align="justify">
12+
The FRI is expressed as the quotient between the number of years with fires and the number of years in the time-series. Then, for each pixel, fire map uses Running Length Encoding (RLE) to break a time-series of fire occurrences (with 1 for "fire" and 0 for "unburnt") into segments of equal value. The length of segments corresponding to "unburnt" periods are used to calculate MRI1, MRI2, and MRI3.
13+
</p>
14+
15+
<figure style="text-align:center;">
16+
<img src="example.png" width=800px>
17+
<figcaption>Example output of `firemap` for the *IFRI* (a), *MRI1* (b), *MRI2* (c), and *MRI3* (d) indices, depicting the nuances of fire histories across the globe.</figcaption>
18+
</figure>
19+
20+
<br>
21+
22+
### How to use
23+
<p align="justify">
24+
`firemap` is not currently in PyPI, and therefore should be installed manually. To do so, please download the current repository as a zip file and use
25+
</p>
26+
<br>
27+
```ruby
28+
pip install firemap.zip
29+
```
30+
<br>
31+
<p align="justify">
32+
Once the package is installed, one can run `main.py` to run the algorithm. This is a command line script that demands a list of years for which to download and process data.
33+
</p>
34+
35+
<br>
36+
37+
### Requirements.
38+
<p align="justify">
39+
When installed, the package will also install needed dependencies. However, given the algorithm uses GEE, some configuration work is inposed on the user. Specifically, one should follow the <a href="https://www.earthdatascience.org/tutorials/intro-google-earth-engine-python-api/">GEE api configuration tutorial<a/> to setup access to data and computational resources.
40+
</p>
41+
42+
<br>
43+
44+
### Desclaimer
45+
<p align="justify">
46+
Note: the algorithm was designed to download data with a maximum resolution of 1-km, which is limited by the quota imposed by GEE on the size of files that can be directly downloaded after processing. Applying this algorithm at finer resolutions will require changes to the gee_download() function.
47+
</p>

‎example.png

199 KB
Loading

‎firemap/__init__.py

+285
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
# -*- coding: utf-8 -*-
2+
""" firemap
3+
=====
4+
This package provides functions to download annual fire frequencies from
5+
Google Earth Engine (i.e. number of months with active fires), based on
6+
monthly, global burned area maps derived with MODIS at a 500 m resolution.
7+
For details on the dataset, refer to the GEE's data catalog
8+
(https://developers.google.com/earth-engine/datasets/catalog/MODIS_006_MCD64A1).
9+
The download output is then used to derives spatial-temporal metrics that
10+
characterize pixel-level fire regimes:
11+
12+
The algorithm evaluates the fire regime of an area given GeoTiff images on
13+
annual fire occurrences. For every pixel, the algorithm will estimate the
14+
following metrics:
15+
16+
* Fire Return Interval (FRI) - Mean number of years between firest
17+
* Min. return time (MRI1) - Minimum between-burn interval
18+
* Mean return time (MRI2) - Mean between-burn interval
19+
* Maximum return time (MRI3) - Maximum between-burn interval
20+
21+
The FRI is expressed as the quotient between the number of years with fires
22+
and the number of years in the time-series. Then, for each pixel, fire map
23+
uses Running Length Encoding (RLE) to break a time-series of fire occurrences
24+
(with 1 for "fire" and 0 for "unburnt") into segments of equal value. The
25+
length of segments corresponding to "unburnt" periods are used to calculate
26+
MRI1, MRI2, and MRI3.
27+
Note: the algorithm was designed to download data with a maximum resolution
28+
of 1-km, which is limited by the quota imposed by GEE on the size of files
29+
that can be directly downloaded after processing. Applying this algorithm to
30+
larger files will require some modifications to the gee_download() function.
31+
"""
32+
33+
from progress.bar import Bar
34+
import urllib.request as ur
35+
from os.path import join
36+
from osgeo import gdal
37+
import rasterio as rt
38+
import numpy as np
39+
import glob2 as g
40+
import shutil
41+
import rle
42+
import os
43+
import ee
44+
45+
def gee_download(data_path, year):
46+
47+
""" Returns annaul
48+
49+
Sum and return the ages of your son and daughter
50+
51+
Parameters
52+
------------
53+
path: str
54+
Path to directory where to download the data
55+
path: str
56+
Year for which to download data
57+
Return
58+
-----------
59+
age : int
60+
The sum of your son and daughter ages.
61+
"""
62+
63+
# connect to GEE
64+
ee.Initialize()
65+
66+
#------------------------------------------------------------------------#
67+
bar = Bar('downloading data from GEE', max=162)
68+
#------------------------------------------------------------------------#
69+
70+
ydir = os.path.join(data_path, year)
71+
if os.path.isdir(ydir) == False:
72+
os.mkdir(ydir)
73+
74+
for x in range(0,18):
75+
76+
for y in range(0,9):
77+
78+
#================================================================#
79+
#
80+
#================================================================#
81+
82+
# build name of zip file to download
83+
zfile = ydir + "{:02d}".format(x) + '-' + \
84+
"{:02d}".format(y) + '_' + year + '.zip'
85+
86+
# build name of zip file to download
87+
zdir = ydir + "{:02d}".format(x) + '-' + \
88+
"{:02d}".format(y) + '_' + year
89+
90+
#================================================================#
91+
#
92+
#================================================================#
93+
94+
# output extent
95+
xmin = -180+(20*x)
96+
ymin = 90-(20*(y+1))
97+
xmax= -180+(20*(x+1))
98+
ymax = 90-(20*y)
99+
100+
# boulding box to write
101+
geometry = ('[[' + str(xmin) + ' ,' + str(ymax) +
102+
'], [' + str(xmax) + ', ' + str(ymax) +
103+
'], [' + str(xmax) + ', ' + str(ymin) +
104+
'], [' + str(xmin) + ', ' + str(ymin) + ']]')
105+
106+
# subset geometry
107+
geometry = ee.Geometry.Rectangle(xmin,ymin,xmax,ymax)
108+
109+
#================================================================#
110+
#
111+
#================================================================#
112+
113+
# read collection
114+
col = ee.ImageCollection('MODIS/006/MCD64A1').filterDate(
115+
year + '-01-01', year + '-12-31'
116+
).filterBounds(geometry).select('BurnDate')
117+
118+
#================================================================#
119+
#
120+
#================================================================#
121+
122+
# boulding box to write
123+
geometry = ('[[' + str(xmin) + ' ,' + str(ymax) +
124+
'], [' + str(xmax) + ', ' + str(ymax) +
125+
'], [' + str(xmax) + ', ' + str(ymin) +
126+
'], [' + str(xmin) + ', ' + str(ymin) + ']]')
127+
128+
def main(image):
129+
return image.expression('b("BurnDate") > 0').rename('burn')
130+
131+
image = col.map(main).sum()
132+
133+
# extract download link
134+
path = image.getDownloadUrl({'description':year, 'name':year,
135+
'reducer':'mean', 'scale':1000,
136+
'crs':'EPSG:4326', 'region':geometry})
137+
138+
#================================================================#
139+
#
140+
#================================================================#
141+
142+
# download file
143+
while os.path.isfile(zfile) == False:
144+
try:
145+
ur.urlretrieve(path, zfile)
146+
except:
147+
continue
148+
149+
# create unziped directory
150+
if os.path.isfile(zdir) == False:
151+
os.mkdir(zdir)
152+
153+
# unpack file into new directory
154+
shutil.unpack_archive(zfile, zdir)
155+
156+
bar.next()
157+
print(str(x) + '-' + str(y))
158+
159+
bar.next()
160+
161+
bar.finish()
162+
163+
#------------------------------------------------------------------------#
164+
bar = Bar('mosaic downloaded files', max=162)
165+
#------------------------------------------------------------------------#
166+
167+
# list files
168+
files = g.glob(join(data_path, '**', '*.tif'))
169+
bar.next()
170+
171+
iname = join(data_path, year, '.vrt')
172+
gdal.BuildVRT(iname, files)
173+
bar.next()
174+
175+
oname = join(data_path, year, '.tif')
176+
gdal.Translate(iname, oname)
177+
bar.next()
178+
bar.finish()
179+
180+
181+
182+
def fire_regime(input_path, output_path):
183+
184+
#------------------------------------------------------------------------#
185+
# list input images
186+
#------------------------------------------------------------------------#
187+
188+
files = sorted(g.glob(join(input_path, '*.tif')))
189+
190+
#------------------------------------------------------------------------#
191+
# use first image as a reference and extract metadata
192+
#------------------------------------------------------------------------#
193+
194+
p = rt.open(files[0]).meta.copy()
195+
p.update(dtype='float32', compress='deflate', predict=2, zlevel=9)
196+
197+
#------------------------------------------------------------------------#
198+
bar = Bar('estimate Fire Recurrence Interval (FRI)', max=len(files)+2)
199+
#------------------------------------------------------------------------#
200+
201+
# estimate sum of years with fires
202+
ia = np.zeros((p['height'],p['width']), dtype='uint8')
203+
for f in files:
204+
ia = ia + (rt.open(f).read(1) > 0).astype('uint8')
205+
bar.next()
206+
207+
# find non-zero pixels
208+
px = np.where(ia > 0)
209+
210+
# estimate average interval
211+
oa = np.zeros(ia.shape, dtype='float32')
212+
oa[px] = ia[px] / len(files)
213+
bar.next()
214+
215+
#------------------------------------------------------------------------#
216+
# write fire regime image
217+
#------------------------------------------------------------------------#
218+
219+
ods = rt.open(join(output_path, 'fire_recurrence_interval.tif'), 'w', **p)
220+
ods.write(oa, indexes=1)
221+
ods.close()
222+
bar.next()
223+
bar.finish()
224+
225+
#------------------------------------------------------------------------#
226+
bar = Bar('evaluate intervals between fires', max=len(px[0])+len(files)+3)
227+
#------------------------------------------------------------------------#
228+
229+
# use non-zero pixel indices to derive a new matrix with all years
230+
ia = np.zeros((len(px[0]),len(files)))
231+
for i in range(0,len(files)):
232+
ia[:,i] = rt.open(files[i]).read(1)[px]
233+
bar.next()
234+
235+
# create empty ouput array
236+
oa = np.zeros((len(px[0]),3))
237+
238+
# derive metrics on the longest time without fires
239+
for i in range(0,len(px[0])):
240+
unique_id, id_length = rle.encode(ia[i,:]) # find sequences
241+
ind = np.where(np.array(unique_id) == 0)
242+
if len(ind[0]) > 0:
243+
oa[i,0] = np.min(np.array(id_length)[ind]) # min. interval
244+
oa[i,1] = np.max(np.array(id_length)[ind]) # mean interval
245+
oa[i,2] = np.mean(np.array(id_length)[ind]) # max. interval
246+
247+
bar.next()
248+
249+
#------------------------------------------------------------------------#
250+
# write metrics
251+
#------------------------------------------------------------------------#
252+
253+
# update metadata to reduce file size
254+
p.update(dtype='uint8')
255+
256+
# assign estimate metric to output array
257+
fr = np.zeros((p['height'],p['width']), dtype='uint8')
258+
fr[px] = oa[:,0]
259+
260+
# write output
261+
ods = rt.open(join(input_path, 'mininum_return.tif'), 'w', **p)
262+
ods.write(fr, indexes=1)
263+
ods.close()
264+
bar.next()
265+
266+
# assign estimate metric to output array
267+
fr = np.zeros((p['height'],p['width']), dtype='uint8')
268+
fr[px] = oa[:,1]
269+
270+
# write output
271+
ods = rt.open(join(input_path, 'maximum_return.tif'), 'w', **p)
272+
ods.write(fr, indexes=1)
273+
ods.close()
274+
bar.next()
275+
276+
# assign estimate metric to output array
277+
fr = np.zeros((p['height'],p['width']), dtype='uint8')
278+
fr[px] = oa[:,2]
279+
280+
# write output
281+
ods = rt.open(join(input_path, 'mean_return.tif'), 'w', **p)
282+
ods.write(fr, indexes=1)
283+
ods.close()
284+
bar.next()
285+
bar.finish()

‎pyproject.toml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[build-system]
2+
requires = [
3+
"setuptools>=54",
4+
"wheel"
5+
]
6+
build-backend = "setuptools.build_meta"

‎setup.cfg

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[metadata]
2+
name = firemap
3+
version = 0.0.1
4+
author = Ruben Remelgado
5+
author_email = ruben.remelgado@gmail.com
6+
description = Mapping of fire regimes using Google Earth Engine
7+
long_description = file: README.md
8+
long_description_content_type = text/markdown
9+
url = https://github.com/RRemelgado/firemap
10+
project_urls =
11+
Bug Tracker = https://github.com/RRemelgado/firemap/issues
12+
Zenod Repository =
13+
classifiers =
14+
Programming Language :: Python :: 3.6
15+
License :: OSI Approved :: MIT License
16+
Operating System :: OS Independent
17+
18+
[options]
19+
packages = find:
20+
python_requires = >=3.7

‎setup.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import setuptools
2+
from os.path import join, dirname
3+
4+
def read(fname):
5+
with open(join(dirname(__file__), fname)) as f:
6+
return f.read()
7+
8+
setuptools.setup(
9+
name="firemap",
10+
version="0.0.1",
11+
author="Ruben Remelgado",
12+
author_email="ruben.remelgado@gmail.com",
13+
description="Mapping of fire regimes using Google Earth Engine",
14+
long_description=read('README.md'),
15+
long_description_content_type="text/markdown",
16+
url="https://github.com/RRemelgado/firemap",
17+
project_urls={
18+
"Bug Tracker": "https://github.com/RRemelgado/firemap/issues",
19+
},
20+
classifiers=[
21+
'Development Status :: 5 - Production/Stable',
22+
'Programming Language :: Python',
23+
'Programming Language :: Python :: 3.6',
24+
'Programming Language :: Python :: 3.7',
25+
'Programming Language :: Python :: 3.8',
26+
'Programming Language :: Python :: 3.9',
27+
'Programming Language :: Python :: 3 :: Only',
28+
'License :: OSI Approved :: MIT License'
29+
],
30+
packages=("firemap",),
31+
install_requires=[
32+
"progress",
33+
"earthengine-api",
34+
"google-api-python-client",
35+
"pyCrypto",
36+
"rasterio",
37+
"numpy",
38+
"glob2",
39+
"RLE"]
40+
)

0 commit comments

Comments
 (0)
Please sign in to comment.