# Accumulation of degree days
"t.rast.accumulate",
gs.run_command(input="lst_daily",
="mosq_daily_bedd",
output="mosq_daily_bedd",
basename="gran",
suffix="2014-01-01",
start="2019-01-01",
stop="12 months",
cycle="bedd",
method="11,30") limits
Time series accumulations
In this fourth tutorial on time series, we will go through data accumulation. We’ll mostly follow a modified version of the example presented in the t.rast.accumulate and t.rast.accdetect tools.
This tutorial can be run locally or in Google Colab. However, make sure you install GRASS 8.4+, download the LST sample data and set up your project as explained in the first time series tutorial.
Temporal data accumulation
t.rast.accumulate performs temporal accumulations of raster time series. Data accumulations are common in ecology or agriculture, especially temperature accumulation. Usually, to determine if insects or plants can survive in a certain place, a measure of accumulated temperature is used. For example, a certain plant species might need x growing degree days (GDD) to bloom, or a mosquito species might need x GDD to complete their development from egg to adult. Therefore, it is usual to accumulate temperature data, but other variables can be accumulated too, e.g. chlorophyll concentration to determine algal bloom occurrences in water bodies.
t.rast.accumulate expects a raster time series (STRDS) as input that will be sampled with a given granularity. All maps that have the start time during the actual granule will be accumulated with the predecessor granule accumulation result using the raster tool r.series.accumulate. The default granularity is one day, but any temporal granularity can be set. The start and end time of the accumulation process must be set. In addition, a cycle can be specified to defines after which interval of time the accumulation process restarts. The offset option specifies the time that should be skipped between two cycles.
The lower and upper limits of the accumulation process can be set either by using space time raster datasets or by using fixed values for all raster cells and time steps by means of the limits option is used, eg. limits=10,30
. The upper limit is only used in the Biologically Effective Degree Days (BEDD) calculation.
The output is a new space time raster dataset with the provided start time, end time and granularity containing the accumulated raster maps.
Accumulation using BEDD
Let’s consider the mosquito Aedes albopictus. Adults require a minimum average temperature of 11 °C to survive. We will compute the Biologically Effective Degree Days (BEDD) from 2014 until 2018 for each year with a granularity of one day. The base temperature will be 11°C, and the upper limit 30°C where adult mosquito survival is known to decrease. Hence the accumulation will start at 11°C and stop at 30°C.
# Get basic info
"t.info", input="mosq_daily_bedd") gs.run_command(
Suitable areas for mosquitos
According to Kobashayi et al (2002), populations of Aedes albopictus establish where at least 1350 DD are accumulated. These DD should be reached before October 1st to consider a place as suitable. Let’s find out when and where that condition is met.
We will first discard all cells with BEDD < 1350 from our accumulated time series, and then we will save the day of the year (DOY) where BEDD >= 1350.
="doy_bedd_higher_1350 = if(mosq_daily_bedd >= 1350, start_doy(mosq_daily_bedd, 0), null())"
exp
"t.rast.algebra",
gs.run_command(=exp,
expression="doy_bedd_higher_1350",
basename="gran",
suffix=4) nprocs
Then, we aggregate the doy_bedd_higher_1350
STRDS on an annual basis, as we want to see possible changes among years. We use method=minimum
to get the earliest day on which the condition is met each year.
"t.rast.aggregate",
gs.run_command(input="doy_bedd_higher_1350",
="minimum",
method="1 year",
granularity="annual_doy_bedd_higher_1350",
output="annual_doy_bedd_higher_1350",
basename="gran",
suffix=4) nprocs
"t.rast.list",
gs.run_command(input="annual_doy_bedd_higher_1350",
="id,min,max") columns
Following Neteler et al (2013), if the 1350 DD are achieved on or before August 1st, a place is considered highly suitable for Aedes albopictus, while if the condition is met after October 1st, the place is not suitable. Everything in between is defined as a linear function of DOY. Once again, we use the temporal algebra to create yearly suitability maps. Note we are using a nested if statement.
="suitability = if(annual_doy_bedd_higher_1350 <= 214, 1, if(annual_doy_bedd_higher_1350 > 214 && annual_doy_bedd_higher_1350 <= 274, (274 - annual_doy_bedd_higher_1350)/60.0, if(annual_doy_bedd_higher_1350 > 274, 0)))"
expression
"t.rast.algebra",
gs.run_command(=expression,
expression="suitability",
basename="gran",
suffix=4) nprocs
Let’s see how suitable area and suitability values change with time by means of an animation.
# Animation of annual anomalies
= gj.TimeSeriesMap()
suitability "suitability", fill_gaps=False)
suitability.add_raster_series(=(6, 10, 5, 45))
suitability.d_legend(at suitability.show()
Let’s do some basic math to quantify the suitable area increase from 2014 to 2018. We use r.univar
to get the number of non-null cells in each map.
= gs.parse_command("r.univar", map="lst_2014.001_avg", flags="g")['n']
land_cells = gs.parse_command("r.univar", map="suitability_2014", flags="g")['n']
suit_2014 = gs.parse_command("r.univar", map="suitability_2018", flags="g")['n']
suit_2018 = ((float(suit_2018) - float(suit_2014))/float(land_cells))*100.0
change print(f"The increase in suitable area was {change: .1f} %")
Detection of cycles
t.rast.accdetect is used to detect accumulation patterns in temporally accumulated STDRS created by t.rast.accumulate. The start and end time do not need to be the same but the cycle and offset options must be exactly the same that were used in the accumulation process that generated the input STRDS. Minimum and maximum values for pattern detection can be set either by using STRDS or fixed values for all raster cells and time steps (range
option).
Using a STRDS would allow specifying minimum and maximum values for each raster cell and each time step. For example, if you want to detect the germination (minimum value) and harvesting (maximum value) dates for different crops using the growing-degree-day (GDD) method for several years. Different crops may grow in different raster cells and change with time because of crop rotation. Hence we need to specify different GDD germination/harvesting (minimum/maximum) values for different raster cells and different years.
The t.rast.accdetect tool produces two output STRDS:
- occurrence: The occurrence STRDS stores the time in days from the beginning of a given cycle for each raster. These values can be used to compute the duration of the recognized accumulation pattern.
- indicator: The indicator STRDS uses three integer values to mark raster cells as beginning (1), intermediate state (2) or end (3) of an accumulation pattern. These values can be used to identify places with complete cycles.
Detection of mosquito generations
Following Kobashayi et al (2002), each mosquito generation might take around 365 DD. Let’s use this reference value to identify how many mosquito generations we could expect over our study area.
= list(range(1, 10))
cycle = list(range(1, 3286, 365))
cycle_beg = list(range(365, 3286, 365))
cycle_end
for i in range(len(cycle)):
print(f"cycle: {cycle[i]} - {cycle_beg[i]} {cycle_end[i]}")
# Identify generations
"t.rast.accdetect",
gs.run_command(input="mosq_daily_bedd",
=f"mosq_occurrence_gen_{cycle[i]}",
occurrence=f"mosq_indicator_gen_{cycle[i]}",
indicator=f"mosq_gen_{cycle[i]}",
basename="2014-01-01",
start="2019-01-01",
stop="12 months",
cyclerange=f"{cycle_beg[i]},{cycle_end[i]}")
"t.rast.aggregate",
gs.run_command(input=f"mosq_indicator_gen_{cycle[i]}",
=f"mosq_gen{cycle[i]}_yearly",
output=f"mosq_gen{cycle[i]}_yearly",
basename="1 year",
granularity="maximum",
method="gran")
suffix
# Keep only complete generations
= f"if(mosq_gen{cycle[i]}_yearly == 3, {cycle[i]}, null())"
exp "t.rast.mapcalc",
gs.run_command(input=f"mosq_gen{cycle[i]}_yearly",
=f"mosq_gen{cycle[i]}_yearly_clean",
output=f"mosq_clean_gen{cycle[i]}",
basename=exp)
expression
# Duration of each mosquito generation
# Beginning
"t.rast.aggregate",
gs.run_command(input=f"mosq_occurrence_gen_{cycle[i]}",
=f"mosq_min_day_gen{cycle[i]}",
output=f"occ_min_day_gen{cycle[i]}",
basename="minimum",
method="1 year",
granularity="gran")
suffix# End
"t.rast.aggregate",
gs.run_command(input=f"mosq_occurrence_gen_{cycle[i]}",
=f"mosq_max_day_gen{cycle[i]}",
output=f"occ_max_day_gen{cycle[i]}",
basename="maximum",
method="1 year",
granularity="gran")
suffix# Difference
= f"mosq_max_day_gen{cycle[i]} - mosq_min_day_gen{cycle[i]} + 1"
exp "t.rast.mapcalc",
gs.run_command(input=f"mosq_min_day_gen{cycle[i]},mosq_max_day_gen{cycle[i]}",
=f"mosq_duration_gen{cycle[i]}",
output=f"mosq_duration_gen{cycle[i]}",
basename=exp) expression
Maximum number of generations per year
Let’s now see which is the maximum number of generations in each cell.
for i in range(1, 6):
= gs.list_grouped(type="raster", pattern=f"mosq_clean_gen*_{i}")
maps "r.series",
gs.run_command(input=maps,
=f"mosq_max_n_generations_{i}",
output="maximum") method
Let’s see an animation:
# List of average maps
= gs.list_grouped(type="raster", pattern="mosq_max_n_generations_*")
map_list
# Animation with SeriesMap class
= gj.SeriesMap()
series
series.add_rasters(map_list)
series.d_barscale() series.show()
Median duration of mosquito generations per year
We can also estimate the median duration of the mosquito cycles per year and see the results with an animation.
for i in range(1, 6):
= gs.list_grouped(type="raster", pattern=f"mosq_duration_gen*_{i}")
maps "r.series",
gs.run_command(input=maps,
=f"mosq_med_duration_generations_{i}",
output="median") method
# List of average maps
= gs.list_grouped(type="raster", pattern="mosq_med_duration_generations_*")
map_list
# Animation with SeriesMap class
= gj.SeriesMap()
series
series.add_rasters(map_list)
series.d_barscale() series.show()
This process creates a large number of intermediate maps. When we have obtained the desired output, we can remove all intermediate series and maps with t.remove.
"t.list",
gs.run_command(type="strds",
="name LIKE '%gen%'",
where="to_remove.txt")
output"t.remove",
gs.run_command(="df",
flagsfile="to_remove.txt")
References
- Neteler, M., Metz, M., Rocchini, D., Rizzoli, A., Flacio, E., et al. 2013. Is Switzerland Suitable for the Invasion ofAedes albopictus? PLOS ONE 8(12). DOI.
- Kobayashi, M., Nihei, N., Kurihara, T. 2002. Analysis of Northern Distribution of Aedes albopictus (Diptera: Culicidae) in Japan by Geographical Information System. Journal of Medical Entomology 39(1), 4–11. DOI.
- Gebbert, S., Pebesma, E. 2014. TGRASS: A temporal GIS for field based environmental modeling. Environmental Modelling & Software 53, 1-12. DOI.
- Gebbert, S., Pebesma, E. 2017. The GRASS GIS temporal framework. International Journal of Geographical Information Science 31, 1273-1292. DOI.
- Temporal data processing wiki page.
The development of this tutorial was funded by the US National Science Foundation (NSF), award 2303651.