123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570 |
- #!/usr/bin/env python3
- ############################################################################
- #
- # MODULE: t.rast.accumulate
- # AUTHOR(S): Soeren Gebbert
- #
- # PURPOSE: Compute cyclic accumulations of a space time raster dataset
- # COPYRIGHT: (C) 2013-2017 by the GRASS Development Team
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- #############################################################################
- # %module
- # % description: Computes cyclic accumulations of a space time raster dataset.
- # % keyword: temporal
- # % keyword: accumulation
- # % keyword: raster
- # % keyword: time
- # %end
- # %option G_OPT_STRDS_INPUT
- # %end
- # %option G_OPT_STRDS_OUTPUT
- # %end
- # %option G_OPT_STRDS_INPUT
- # % key: lower
- # % description: Input space time raster dataset that defines the lower threshold, values lower than this threshold are excluded from accumulation
- # % required: no
- # %end
- # %option G_OPT_STRDS_INPUT
- # % key: upper
- # % description: Input space time raster dataset that defines the upper threshold, values higher than this threshold are excluded from accumulation
- # % required: no
- # %end
- # %option
- # % key: start
- # % type: string
- # % description: The temporal starting point to begin the accumulation, eg '2001-01-01'
- # % required: yes
- # % multiple: no
- # %end
- # %option
- # % key: stop
- # % type: string
- # % description: The temporal date to stop the accumulation, eg '2009-01-01'
- # % required: no
- # % multiple: no
- # %end
- # %option
- # % key: cycle
- # % type: string
- # % description: The temporal cycle to restart the accumulation, eg '12 months'
- # % required: yes
- # % multiple: no
- # %end
- # %option
- # % key: offset
- # % type: string
- # % description: The temporal offset to the beginning of the next cycle, eg '6 months'
- # % required: no
- # % multiple: no
- # %end
- # %option
- # % key: granularity
- # % type: string
- # % description: The granularity for accumulation '1 day'
- # % answer: 1 day
- # % required: no
- # % multiple: no
- # %end
- # %option
- # % key: basename
- # % type: string
- # % label: Basename of the new generated output maps
- # % description: A numerical suffix separated by an underscore will be attached to create a unique identifier
- # % required: yes
- # % multiple: no
- # % gisprompt:
- # %end
- # %option
- # % key: suffix
- # % type: string
- # % description: Suffix to add to the basename. Set 'gran' for granularity, 'time' for the full time format, 'num' for numerical suffix with a specific number of digits (default %05)
- # % answer: gran
- # % required: no
- # % multiple: no
- # %end
- # %option
- # % key: limits
- # % type: double
- # % key_desc: lower,upper
- # % description: Use these limits in case lower and/or upper input space time raster datasets are not defined or contain NULL values
- # % required: yes
- # % multiple: no
- # %end
- # %option
- # % key: scale
- # % type: double
- # % description: Scale factor for input space time raster dataset
- # % required: no
- # % multiple: no
- # %end
- # %option
- # % key: shift
- # % type: double
- # % description: Shift factor for input space time raster dataset
- # % required: no
- # % multiple: no
- # %end
- # %option
- # % key: method
- # % type: string
- # % label: This method will be applied to compute the accumulative values from the input maps in a single granule
- # % description: Growing Degree Days or Winkler indices; Mean: sum(input maps)/(number of input maps); Biologically Effective Degree Days; Huglin Heliothermal index
- # % options: mean,gdd,bedd,huglin
- # % answer: mean
- # % required: no
- # % multiple: no
- # %end
- # %flag
- # % key: n
- # % description: Register empty maps in the output space time raster dataset, otherwise they will be deleted
- # %end
- # %flag
- # % key: r
- # % description: Reverse time direction in cyclic accumulation
- # %end
- from __future__ import print_function
- import grass.script as grass
- from copy import copy
- ############################################################################
- def main():
- # lazy imports
- import grass.temporal as tgis
- from grass.pygrass.modules import Module
- # Get the options
- input = options["input"]
- output = options["output"]
- start = options["start"]
- stop = options["stop"]
- base = options["basename"]
- cycle = options["cycle"]
- lower = options["lower"]
- upper = options["upper"]
- offset = options["offset"]
- limits = options["limits"]
- shift = options["shift"]
- scale = options["scale"]
- method = options["method"]
- granularity = options["granularity"]
- register_null = flags["n"]
- reverse = flags["r"]
- time_suffix = options["suffix"]
- # Make sure the temporal database exists
- tgis.init()
- # We need a database interface
- dbif = tgis.SQLDatabaseInterfaceConnection()
- dbif.connect()
- mapset = tgis.get_current_mapset()
- if input.find("@") >= 0:
- id = input
- else:
- id = input + "@" + mapset
- input_strds = tgis.SpaceTimeRasterDataset(id)
- if not input_strds.is_in_db():
- dbif.close()
- grass.fatal(_("Space time raster dataset <%s> not found") % (id))
- input_strds.select(dbif)
- if output.find("@") >= 0:
- out_id = output
- else:
- out_id = output + "@" + mapset
- # The output space time raster dataset
- output_strds = tgis.SpaceTimeRasterDataset(out_id)
- if output_strds.is_in_db(dbif):
- if not grass.overwrite():
- dbif.close()
- grass.fatal(
- _(
- "Space time raster dataset <%s> is already in the "
- "database, use overwrite flag to overwrite"
- )
- % out_id
- )
- if (
- tgis.check_granularity_string(granularity, input_strds.get_temporal_type())
- is False
- ):
- dbif.close()
- grass.fatal(_("Invalid granularity"))
- if tgis.check_granularity_string(cycle, input_strds.get_temporal_type()) is False:
- dbif.close()
- grass.fatal(_("Invalid cycle"))
- if offset:
- if (
- tgis.check_granularity_string(offset, input_strds.get_temporal_type())
- is False
- ):
- dbif.close()
- grass.fatal(_("Invalid offset"))
- # The lower threshold space time raster dataset
- if lower:
- if not range:
- dbif.close()
- grass.fatal(
- _(
- "You need to set the range to compute the occurrence"
- " space time raster dataset"
- )
- )
- if lower.find("@") >= 0:
- lower_id = lower
- else:
- lower_id = lower + "@" + mapset
- lower_strds = tgis.SpaceTimeRasterDataset(lower_id)
- if not lower_strds.is_in_db():
- dbif.close()
- grass.fatal(
- _("Space time raster dataset <%s> not found") % (lower_strds.get_id())
- )
- if lower_strds.get_temporal_type() != input_strds.get_temporal_type():
- dbif.close()
- grass.fatal(_("Temporal type of input strds and lower strds must be equal"))
- lower_strds.select(dbif)
- # The upper threshold space time raster dataset
- if upper:
- if not lower:
- dbif.close()
- grass.fatal(
- _("The upper option works only in conjunction with the lower option")
- )
- if upper.find("@") >= 0:
- upper = upper
- else:
- upper_id = upper + "@" + mapset
- upper_strds = tgis.SpaceTimeRasterDataset(upper_id)
- if not upper_strds.is_in_db():
- dbif.close()
- grass.fatal(
- _("Space time raster dataset <%s> not found") % (upper_strds.get_id())
- )
- if upper_strds.get_temporal_type() != input_strds.get_temporal_type():
- dbif.close()
- grass.fatal(_("Temporal type of input strds and upper strds must be equal"))
- upper_strds.select(dbif)
- input_strds_start, input_strds_end = input_strds.get_temporal_extent_as_tuple()
- if input_strds.is_time_absolute():
- start = tgis.string_to_datetime(start)
- if stop:
- stop = tgis.string_to_datetime(stop)
- else:
- stop = input_strds_end
- start = tgis.adjust_datetime_to_granularity(start, granularity)
- else:
- start = int(start)
- if stop:
- stop = int(stop)
- else:
- stop = input_strds_end
- if input_strds.is_time_absolute():
- end = tgis.increment_datetime_by_string(start, cycle)
- else:
- end = start + cycle
- limit_relations = ["EQUALS", "DURING", "OVERLAPS", "OVERLAPPING", "CONTAINS"]
- count = 1
- output_maps = []
- while input_strds_end > start and stop > start:
- # Make sure that the cyclic computation will stop at the correct time
- if stop and end > stop:
- end = stop
- where = "start_time >= '%s' AND start_time < '%s'" % (str(start), str(end))
- input_maps = input_strds.get_registered_maps_as_objects(where=where, dbif=dbif)
- grass.message(_("Processing cycle %s - %s" % (str(start), str(end))))
- if len(input_maps) == 0:
- continue
- # Lets create a dummy list of maps with granularity conform intervals
- gran_list = []
- gran_list_low = []
- gran_list_up = []
- gran_start = start
- while gran_start < end:
- map = input_strds.get_new_map_instance("%i@%i" % (count, count))
- if input_strds.is_time_absolute():
- gran_end = tgis.increment_datetime_by_string(gran_start, granularity)
- map.set_absolute_time(gran_start, gran_end)
- gran_start = tgis.increment_datetime_by_string(gran_start, granularity)
- else:
- gran_end = gran_start + granularity
- map.set_relative_time(
- gran_start, gran_end, input_strds.get_relative_time_unit()
- )
- gran_start = gran_start + granularity
- gran_list.append(copy(map))
- gran_list_low.append(copy(map))
- gran_list_up.append(copy(map))
- # Lists to compute the topology with upper and lower datasets
- # Create the topology between the granularity conform list and all maps
- # of the current cycle
- gran_topo = tgis.SpatioTemporalTopologyBuilder()
- gran_topo.build(gran_list, input_maps)
- if lower:
- lower_maps = lower_strds.get_registered_maps_as_objects(dbif=dbif)
- gran_lower_topo = tgis.SpatioTemporalTopologyBuilder()
- gran_lower_topo.build(gran_list_low, lower_maps)
- if upper:
- upper_maps = upper_strds.get_registered_maps_as_objects(dbif=dbif)
- gran_upper_topo = tgis.SpatioTemporalTopologyBuilder()
- gran_upper_topo.build(gran_list_up, upper_maps)
- old_map_name = None
- # Aggregate
- num_maps = len(gran_list)
- for i in range(num_maps):
- if reverse:
- map = gran_list[num_maps - i - 1]
- else:
- map = gran_list[i]
- # Select input maps based on temporal topology relations
- input_maps = []
- if map.get_equal():
- input_maps += map.get_equal()
- elif map.get_contains():
- input_maps += map.get_contains()
- elif map.get_overlaps():
- input_maps += map.get_overlaps()
- elif map.get_overlapped():
- input_maps += map.get_overlapped()
- elif map.get_during():
- input_maps += map.get_during()
- # Check input maps
- if len(input_maps) == 0:
- continue
- # New output map
- if input_strds.get_temporal_type() == "absolute" and time_suffix == "gran":
- suffix = tgis.create_suffix_from_datetime(
- map.temporal_extent.get_start_time(), input_strds.get_granularity()
- )
- output_map_name = "{ba}_{su}".format(ba=base, su=suffix)
- elif (
- input_strds.get_temporal_type() == "absolute" and time_suffix == "time"
- ):
- suffix = tgis.create_time_suffix(map)
- output_map_name = "{ba}_{su}".format(ba=base, su=suffix)
- else:
- output_map_name = tgis.create_numeric_suffix(base, count, time_suffix)
- output_map_id = map.build_id(output_map_name, mapset)
- output_map = input_strds.get_new_map_instance(output_map_id)
- # Check if new map is in the temporal database
- if output_map.is_in_db(dbif):
- if grass.overwrite():
- # Remove the existing temporal database entry
- output_map.delete(dbif)
- output_map = input_strds.get_new_map_instance(output_map_id)
- else:
- grass.fatal(
- _(
- "Map <%s> is already registered in the temporal"
- " database, use overwrite flag to overwrite."
- )
- % (output_map.get_map_id())
- )
- map_start, map_end = map.get_temporal_extent_as_tuple()
- if map.is_time_absolute():
- output_map.set_absolute_time(map_start, map_end)
- else:
- output_map.set_relative_time(
- map_start, map_end, map.get_relative_time_unit()
- )
- limits_vals = limits.split(",")
- limits_lower = float(limits_vals[0])
- limits_upper = float(limits_vals[1])
- lower_map_name = None
- if lower:
- relations = gran_list_low[i].get_temporal_relations()
- for relation in limit_relations:
- if relation in relations:
- lower_map_name = str(relations[relation][0].get_id())
- break
- upper_map_name = None
- if upper:
- relations = gran_list_up[i].get_temporal_relations()
- for relation in limit_relations:
- if relation in relations:
- upper_map_name = str(relations[relation][0].get_id())
- break
- input_map_names = []
- for input_map in input_maps:
- input_map_names.append(input_map.get_id())
- # Set up the module
- accmod = Module(
- "r.series.accumulate",
- input=input_map_names,
- output=output_map_name,
- run_=False,
- )
- if old_map_name:
- accmod.inputs["basemap"].value = old_map_name
- if lower_map_name:
- accmod.inputs["lower"].value = lower_map_name
- if upper_map_name:
- accmod.inputs["upper"].value = upper_map_name
- accmod.inputs["limits"].value = (limits_lower, limits_upper)
- if shift:
- accmod.inputs["shift"].value = float(shift)
- if scale:
- accmod.inputs["scale"].value = float(scale)
- if method:
- accmod.inputs["method"].value = method
- print(accmod)
- accmod.run()
- if accmod.returncode != 0:
- dbif.close()
- grass.fatal(_("Error running r.series.accumulate"))
- output_maps.append(output_map)
- old_map_name = output_map_name
- count += 1
- # Increment the cycle
- start = end
- if input_strds.is_time_absolute():
- start = end
- if offset:
- start = tgis.increment_datetime_by_string(end, offset)
- end = tgis.increment_datetime_by_string(start, cycle)
- else:
- if offset:
- start = end + offset
- end = start + cycle
- # Insert the maps into the output space time dataset
- if output_strds.is_in_db(dbif):
- if grass.overwrite():
- output_strds.delete(dbif)
- output_strds = input_strds.get_new_instance(out_id)
- temporal_type, semantic_type, title, description = input_strds.get_initial_values()
- output_strds.set_initial_values(temporal_type, semantic_type, title, description)
- output_strds.insert(dbif)
- empty_maps = []
- # Register the maps in the database
- count = 0
- for output_map in output_maps:
- count += 1
- if count % 10 == 0:
- grass.percent(count, len(output_maps), 1)
- # Read the raster map data
- output_map.load()
- # In case of a empty map continue, do not register empty maps
- if not register_null:
- if (
- output_map.metadata.get_min() is None
- and output_map.metadata.get_max() is None
- ):
- empty_maps.append(output_map)
- continue
- # Insert map in temporal database
- output_map.insert(dbif)
- output_strds.register_map(output_map, dbif)
- # Update the spatio-temporal extent and the metadata table entries
- output_strds.update_from_registered_maps(dbif)
- grass.percent(1, 1, 1)
- dbif.close()
- # Remove empty maps
- if len(empty_maps) > 0:
- for map in empty_maps:
- grass.run_command(
- "g.remove", flags="f", type="raster", name=map.get_name(), quiet=True
- )
- if __name__ == "__main__":
- options, flags = grass.parser()
- main()
|