123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737 |
- #!/usr/bin/env python3
- ############################################################################
- #
- # MODULE: t.rast.accdetect
- # AUTHOR(S): Soeren Gebbert
- #
- # PURPOSE: Detect accumulation pattern in temporally accumulated space time raster datasets created by t.rast.accumulate.
- # 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: Detects accumulation patterns in temporally accumulated space time raster datasets created by t.rast.accumulate.
- # % keyword: temporal
- # % keyword: accumulation
- # % keyword: raster
- # % keyword: time
- # %end
- # %option G_OPT_STRDS_INPUT
- # %end
- # %option G_OPT_STRDS_INPUT
- # % key: minimum
- # % description: Input space time raster dataset that specifies the minimum values to detect the accumulation pattern
- # % required: no
- # %end
- # %option G_OPT_STRDS_INPUT
- # % key: maximum
- # % description: Input space time raster dataset that specifies the maximum values to detect the accumulation pattern
- # % required: no
- # %end
- # %option G_OPT_STRDS_OUTPUT
- # % key: occurrence
- # % description: The output space time raster dataset that stores the occurrence of the the accumulation pattern using the provided data range
- # % required: yes
- # %end
- # %option G_OPT_STRDS_OUTPUT
- # % key: indicator
- # % description: The output space time raster dataset that stores the indication of the start, intermediate and end of the specified data range
- # % 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 begin of the next cycle, eg '6 months'
- # % 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 at basename: set 'gran' for granularity, 'time' for the full time format, 'count' for numerical suffix with a specific number of digits (default %05)
- # % answer: gran
- # % required: no
- # % multiple: no
- # %end
- # %option
- # % key: range
- # % type: double
- # % key_desc: min,max
- # % description: The minimum and maximum value of the occurrence of accumulated values, these values will be used if the min/max space time raster datasets are not specified
- # % required: no
- # % multiple: no
- # %end
- # %option
- # % key: staend
- # % type: integer
- # % key_desc: start,intermediate,end
- # % description: The user defined values that indicate start, intermediate and end status in the indicator output space time raster dataset
- # % answer: 1,2,3
- # % 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
- import grass.script as grass
- # lazy imports at the end of the file
- ############################################################################
- range_relations = ["EQUALS", "DURING", "OVERLAPS", "OVERLAPPING", "CONTAINS"]
- def main():
- # Get the options
- input = options["input"]
- start = options["start"]
- stop = options["stop"]
- base = options["basename"]
- cycle = options["cycle"]
- offset = options["offset"]
- minimum = options["minimum"]
- maximum = options["maximum"]
- occurrence = options["occurrence"]
- range_ = options["range"]
- indicator = options["indicator"]
- staend = options["staend"]
- register_null = flags["n"]
- reverse = flags["r"]
- time_suffix = options["suffix"]
- grass.set_raise_on_error(True)
- # 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 %s dataset <%s> not found")
- % (input_strds.get_output_map_instance(None).get_type(), id)
- )
- input_strds.select(dbif)
- dummy = input_strds.get_new_map_instance(None)
- # The occurrence space time raster dataset
- if occurrence:
- if not minimum or not maximum:
- if not range_:
- dbif.close()
- grass.fatal(
- _(
- "You need to set the range to compute the occurrence"
- " space time raster dataset"
- )
- )
- if occurrence.find("@") >= 0:
- occurrence_id = occurrence
- else:
- occurrence_id = occurrence + "@" + mapset
- occurrence_strds = tgis.SpaceTimeRasterDataset(occurrence_id)
- if occurrence_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"
- )
- % occurrence_id
- )
- # The indicator space time raster dataset
- if indicator:
- if not occurrence:
- dbif.close()
- grass.fatal(
- _(
- "You need to set the occurrence to compute the indicator"
- " space time raster dataset"
- )
- )
- if not staend:
- dbif.close()
- grass.fatal(
- _(
- "You need to set the staend options to compute the indicator"
- " space time raster dataset"
- )
- )
- if indicator.find("@") >= 0:
- indicator = indicator
- else:
- indicator_id = indicator + "@" + mapset
- indicator_strds = tgis.SpaceTimeRasterDataset(indicator_id)
- if indicator_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"
- )
- % indicator_id
- )
- staend = staend.split(",")
- indicator_start = int(staend[0])
- indicator_mid = int(staend[1])
- indicator_end = int(staend[2])
- # The minimum threshold space time raster dataset
- minimum_strds = None
- if minimum:
- if minimum.find("@") >= 0:
- minimum_id = minimum
- else:
- minimum_id = minimum + "@" + mapset
- minimum_strds = tgis.SpaceTimeRasterDataset(minimum_id)
- if not minimum_strds.is_in_db():
- dbif.close()
- grass.fatal(
- _("Space time raster dataset <%s> not found") % (minimum_strds.get_id())
- )
- if minimum_strds.get_temporal_type() != input_strds.get_temporal_type():
- dbif.close()
- grass.fatal(
- _("Temporal type of input strds and minimum strds must be equal")
- )
- minimum_strds.select(dbif)
- # The maximum threshold space time raster dataset
- maximum_strds = None
- if maximum:
- if maximum.find("@") >= 0:
- maximum_id = maximum
- else:
- maximum_id = maximum + "@" + mapset
- maximum_strds = tgis.SpaceTimeRasterDataset(maximum_id)
- if not maximum_strds.is_in_db():
- dbif.close()
- grass.fatal(
- _("Space time raster dataset <%s> not found") % (maximum_strds.get_id())
- )
- if maximum_strds.get_temporal_type() != input_strds.get_temporal_type():
- dbif.close()
- grass.fatal(
- _("Temporal type of input strds and maximum strds must be equal")
- )
- maximum_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
- 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
- count = 1
- indi_count = 1
- occurrence_maps = {}
- indicator_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.debug(len(input_maps))
- input_topo = tgis.SpatioTemporalTopologyBuilder()
- input_topo.build(input_maps, input_maps)
- if len(input_maps) == 0:
- continue
- grass.message(_("Processing cycle %s - %s" % (str(start), str(end))))
- count = compute_occurrence(
- occurrence_maps,
- input_strds,
- input_maps,
- start,
- base,
- count,
- time_suffix,
- mapset,
- where,
- reverse,
- range_,
- minimum_strds,
- maximum_strds,
- dbif,
- )
- # Indicator computation is based on the occurrence so we need to start it after
- # the occurrence cycle
- if indicator:
- num_maps = len(input_maps)
- for i in range(num_maps):
- if reverse:
- map = input_maps[num_maps - i - 1]
- else:
- map = input_maps[i]
- 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(),
- )
- indicator_map_name = "{ba}_indicator_{su}".format(
- ba=base, su=suffix
- )
- elif (
- input_strds.get_temporal_type() == "absolute"
- and time_suffix == "time"
- ):
- suffix = tgis.create_time_suffix(map)
- indicator_map_name = "{ba}_indicator_{su}".format(
- ba=base, su=suffix
- )
- else:
- indicator_map_name = tgis.create_numeric_suffix(
- base + "_indicator", indi_count, time_suffix
- )
- indicator_map_id = dummy.build_id(indicator_map_name, mapset)
- indicator_map = input_strds.get_new_map_instance(indicator_map_id)
- # Check if new map is in the temporal database
- if indicator_map.is_in_db(dbif):
- if grass.overwrite():
- # Remove the existing temporal database entry
- indicator_map.delete(dbif)
- indicator_map = input_strds.get_new_map_instance(
- indicator_map_id
- )
- else:
- grass.fatal(
- _(
- "Map <%s> is already registered in the temporal"
- " database, use overwrite flag to overwrite."
- )
- % (indicator_map.get_map_id())
- )
- curr_map = occurrence_maps[map.get_id()].get_name()
- # Reverse time
- if reverse:
- if i == 0:
- prev_map = curr_map
- subexpr1 = "null()"
- subexpr3 = "%i" % (indicator_start)
- elif i > 0 and i < num_maps - 1:
- prev_map = occurrence_maps[map.next().get_id()].get_name()
- next_map = occurrence_maps[map.prev().get_id()].get_name()
- # In case the previous map is null() set null() or the start indicator
- subexpr1 = "if(isnull(%s), null(), %i)" % (
- curr_map,
- indicator_start,
- )
- # In case the previous map was not null() if the current map is null() set null()
- # if the current map is not null() and the next map is not null() set
- # intermediate indicator, if the next map is null set the end indicator
- subexpr2 = "if(isnull(%s), %i, %i)" % (
- next_map,
- indicator_end,
- indicator_mid,
- )
- subexpr3 = "if(isnull(%s), null(), %s)" % (curr_map, subexpr2)
- expression = "%s = if(isnull(%s), %s, %s)" % (
- indicator_map_name,
- prev_map,
- subexpr1,
- subexpr3,
- )
- else:
- prev_map = occurrence_maps[map.next().get_id()].get_name()
- subexpr1 = "if(isnull(%s), null(), %i)" % (
- curr_map,
- indicator_start,
- )
- subexpr3 = "if(isnull(%s), null(), %i)" % (
- curr_map,
- indicator_mid,
- )
- else:
- if i == 0:
- prev_map = curr_map
- subexpr1 = "null()"
- subexpr3 = "%i" % (indicator_start)
- elif i > 0 and i < num_maps - 1:
- prev_map = occurrence_maps[map.prev().get_id()].get_name()
- next_map = occurrence_maps[map.next().get_id()].get_name()
- # In case the previous map is null() set null() or the start indicator
- subexpr1 = "if(isnull(%s), null(), %i)" % (
- curr_map,
- indicator_start,
- )
- # In case the previous map was not null() if the current map is null() set null()
- # if the current map is not null() and the next map is not null() set
- # intermediate indicator, if the next map is null set the end indicator
- subexpr2 = "if(isnull(%s), %i, %i)" % (
- next_map,
- indicator_end,
- indicator_mid,
- )
- subexpr3 = "if(isnull(%s), null(), %s)" % (curr_map, subexpr2)
- expression = "%s = if(isnull(%s), %s, %s)" % (
- indicator_map_name,
- prev_map,
- subexpr1,
- subexpr3,
- )
- else:
- prev_map = occurrence_maps[map.prev().get_id()].get_name()
- subexpr1 = "if(isnull(%s), null(), %i)" % (
- curr_map,
- indicator_start,
- )
- subexpr3 = "if(isnull(%s), null(), %i)" % (
- curr_map,
- indicator_mid,
- )
- expression = "%s = if(isnull(%s), %s, %s)" % (
- indicator_map_name,
- prev_map,
- subexpr1,
- subexpr3,
- )
- grass.debug(expression)
- grass.mapcalc(expression, overwrite=True)
- map_start, map_end = map.get_temporal_extent_as_tuple()
- if map.is_time_absolute():
- indicator_map.set_absolute_time(map_start, map_end)
- else:
- indicator_map.set_relative_time(
- map_start, map_end, map.get_relative_time_unit()
- )
- indicator_maps[map.get_id()] = indicator_map
- indi_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
- empty_maps = []
- create_strds_register_maps(
- input_strds, occurrence_strds, occurrence_maps, register_null, empty_maps, dbif
- )
- if indicator:
- create_strds_register_maps(
- input_strds,
- indicator_strds,
- indicator_maps,
- register_null,
- empty_maps,
- dbif,
- )
- 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
- )
- ############################################################################
- def create_strds_register_maps(
- in_strds, out_strds, out_maps, register_null, empty_maps, dbif
- ):
- out_id = out_strds.get_id()
- if out_strds.is_in_db(dbif):
- if grass.overwrite():
- out_strds.delete(dbif)
- out_strds = in_strds.get_new_instance(out_id)
- temporal_type, semantic_type, title, description = in_strds.get_initial_values()
- out_strds.set_initial_values(temporal_type, semantic_type, title, description)
- out_strds.insert(dbif)
- # Register the maps in the database
- count = 0
- for map in out_maps.values():
- count += 1
- if count % 10 == 0:
- grass.percent(count, len(out_maps), 1)
- # Read the raster map data
- map.load()
- # In case of a empty map continue, do not register empty maps
- if not register_null:
- if map.metadata.get_min() is None and map.metadata.get_max() is None:
- empty_maps.append(map)
- continue
- # Insert map in temporal database
- map.insert(dbif)
- out_strds.register_map(map, dbif)
- out_strds.update_from_registered_maps(dbif)
- grass.percent(1, 1, 1)
- ############################################################################
- def compute_occurrence(
- occurrence_maps,
- input_strds,
- input_maps,
- start,
- base,
- count,
- tsuffix,
- mapset,
- where,
- reverse,
- range_,
- minimum_strds,
- maximum_strds,
- dbif,
- ):
- if minimum_strds:
- input_maps_minimum = input_strds.get_registered_maps_as_objects(
- where=where, dbif=dbif
- )
- minimum_maps = minimum_strds.get_registered_maps_as_objects(dbif=dbif)
- minimum_topo = tgis.SpatioTemporalTopologyBuilder()
- minimum_topo.build(input_maps_minimum, minimum_maps)
- if maximum_strds:
- input_maps_maximum = input_strds.get_registered_maps_as_objects(
- where=where, dbif=dbif
- )
- maximum_maps = maximum_strds.get_registered_maps_as_objects(dbif=dbif)
- maximum_topo = tgis.SpatioTemporalTopologyBuilder()
- maximum_topo.build(input_maps_maximum, maximum_maps)
- # Aggregate
- num_maps = len(input_maps)
- for i in range(num_maps):
- if reverse:
- map = input_maps[num_maps - i - 1]
- else:
- map = input_maps[i]
- # Compute the days since start
- input_start, input_end = map.get_temporal_extent_as_tuple()
- td = input_start - start
- if map.is_time_absolute():
- days = tgis.time_delta_to_relative_time(td)
- else:
- days = td
- if input_strds.get_temporal_type() == "absolute" and tsuffix == "gran":
- suffix = tgis.create_suffix_from_datetime(
- map.temporal_extent.get_start_time(), input_strds.get_granularity()
- )
- occurrence_map_name = "{ba}_{su}".format(ba=base, su=suffix)
- elif input_strds.get_temporal_type() == "absolute" and tsuffix == "time":
- suffix = tgis.create_time_suffix(map)
- occurrence_map_name = "{ba}_{su}".format(ba=base, su=suffix)
- else:
- occurrence_map_name = tgis.create_numeric_suffix(base, count, tsuffix)
- occurrence_map_id = map.build_id(occurrence_map_name, mapset)
- occurrence_map = input_strds.get_new_map_instance(occurrence_map_id)
- # Check if new map is in the temporal database
- if occurrence_map.is_in_db(dbif):
- if grass.overwrite():
- # Remove the existing temporal database entry
- occurrence_map.delete(dbif)
- occurrence_map = input_strds.get_new_map_instance(occurrence_map_id)
- else:
- grass.fatal(
- _(
- "Map <%s> is already registered in the temporal"
- " database, use overwrite flag to overwrite."
- )
- % (occurrence_map.get_map_id())
- )
- range_vals = range_.split(",")
- min = range_vals[0]
- max = range_vals[1]
- if minimum_strds:
- relations = input_maps_minimum[i].get_temporal_relations()
- for relation in range_relations:
- if relation in relations:
- min = str(relations[relation][0].get_id())
- break
- if maximum_strds:
- relations = input_maps_maximum[i].get_temporal_relations()
- for relation in range_relations:
- if relation in relations:
- max = str(relations[relation][0].get_id())
- break
- expression = "%s = if(%s > %s && %s < %s, %s, null())" % (
- occurrence_map_name,
- map.get_name(),
- min,
- map.get_name(),
- max,
- days,
- )
- grass.debug(expression)
- grass.mapcalc(expression, overwrite=True)
- map_start, map_end = map.get_temporal_extent_as_tuple()
- if map.is_time_absolute():
- occurrence_map.set_absolute_time(map_start, map_end)
- else:
- occurrence_map.set_relative_time(
- map_start, map_end, map.get_relative_time_unit()
- )
- # Store the new maps
- occurrence_maps[map.get_id()] = occurrence_map
- count += 1
- return count
- ############################################################################
- if __name__ == "__main__":
- options, flags = grass.parser()
- # lazy imports
- import grass.temporal as tgis
- main()
|