"""!@package grass.temporal @brief GRASS Python scripting module (temporal GIS functions) Temporal GIS related functions to be used in temporal GIS Python library package. Usage: @code import grass.temporal as tgis tgis.print_temporal_relations(maps) ... @endcode (C) 2008-2011 by the GRASS Development Team This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. @author Soeren Gebbert """ from abstract_map_dataset import * from datetime_math import * import grass.lib.vector as vector import grass.lib.gis as gis from ctypes import * ############################################################################### class TemporalTopologyBuilder(object): """!This class is designed to build the temporal topology of temporally related abstract dataset objects. The abstract dataset objects must be provided as a single list, or in two lists. Example: @code # We have a space time raster dataset and build a map list # from all registered maps ordered by start time maps = strds.get_registered_maps_as_objects() # Now lets build the temporal topology of the maps in the list identical = False tb = TemporalTopologyBuilder() tb.build(maps) dbif, connected = init_dbif(None) for _map in tb: _map.select(dbif) _map.print_info() # Using the next and previous methods, we can iterate over the # topological related maps in this way _first = tb.get_first() while _first: _first.print_topology_info() _first = _first.next() # Dictionary like accessed _map = tb["name@mapset"] @endcode """ def __init__(self): self._reset() # 0001-01-01 00:00:00 self._timeref = datetime(1,1,1) def _reset(self): self._store = {} self._first = None self._iteratable = False def _set_first(self, first): self._first = first self._insert(first) def _detect_first(self): if len(self) > 0: prev_ = self._store.values()[0] while prev_ is not None: self._first = prev_ prev_ = prev_.prev() def _insert(self, t): self._store[t.get_id()] = t def get_first(self): """!Return the first map with the earliest start time @return The map with the earliest start time """ return self._first def _build_internal_iteratable(self, maps): """!Build an iteratable temporal topology structure for all maps in the list and store the maps internally Basically the "next" and "prev" relations will be set in the temporal topology structure of each map The maps will be added to the object, so they can be accessed using the iterator of this class @param maps: A sorted (by start_time)list of abstract_dataset objects with initiated temporal extent """ self._build_iteratable(maps) for _map in maps: self._insert(_map) # Detect the first map self._detect_first() def _build_iteratable(self, maps): """!Build an iteratable temporal topology structure for all maps in the list Basically the "next" and "prev" relations will be set in the temporal topology structure of each map. @param maps: A sorted (by start_time)list of abstract_dataset objects with initiated temporal extent """ # for i in xrange(len(maps)): # offset = i + 1 # for j in xrange(offset, len(maps)): # # Get the temporal relationship # relation = maps[j].temporal_relation(maps[i]) # # # Build the next reference # if relation != "equivalent" and relation != "started": # maps[i].set_next(maps[j]) # break # First we need to order the map list chronologically sorted_maps = sorted( maps, key=AbstractDatasetComparisonKeyStartTime) for i in xrange(len(sorted_maps) - 1): sorted_maps[i].set_next(sorted_maps[i + 1]) for map_ in sorted_maps: next_ = map_.next() if next_: next_.set_prev(map_) map_.set_topology_build_true() def _map_to_rect(self, tree, map_): """Use the temporal extent of a map to create and return a RTree rectange""" rect = vector.RTreeAllocRect(tree) start, end = map_.get_valid_time() if not end: end = start if map_.is_time_absolute(): start = time_delta_to_relative_time(start - self._timeref) end = time_delta_to_relative_time(end - self._timeref) vector.RTreeSetRect1D(rect, tree, float(start), float(end)) return rect def _build_1d_rtree(self, maps): """Build and return the one dimensional R*-Tree""" tree = vector.RTreeCreateTree(-1, 0, 4) for i in xrange(len(maps)): rect = self._map_to_rect(tree, maps[i]) vector.RTreeInsertRect(rect, i + 1, tree) return tree def build(self, mapsA, mapsB=None): """!Build the temporal topology structure between one or two unordered lists of abstract dataset objects This method builds the temporal topology from mapsA to mapsB and vice verse. The temporal topology structure of each map, defined in class temporal_map_relations, will be reseted and rebuild for mapsA and mapsB. After building the temporal topology the modified map objects of mapsA can be accessed in the same way as a dictionary using there id. The implemented iterator assures the chronological iteration over the mapsA. @param mapsA: A list of abstract_dataset objects with initiated temporal extent @param mapsB: An optional list of abstract_dataset objects with initiated temporal extent """ identical = False if mapsA == mapsB: identical = True if mapsB == None: mapsB = mapsA idetnical = True for map_ in mapsA: map_.reset_topology() if not identical: for map_ in mapsB: map_.reset_topology() tree = self. _build_1d_rtree(mapsA) for j in xrange(len(mapsB)): list_ = gis.ilist() rect = self._map_to_rect(tree, mapsB[j]) num = vector.RTreeSearch2(tree, rect, byref(list_)) vector.RTreeFreeRect(rect) for k in xrange(list_.n_values): i = list_.value[k] - 1 # Get the temporal relationship relation = mapsB[j].temporal_relation(mapsA[i]) if relation == "equivalent": if mapsB[j].get_id() != mapsA[i].get_id(): mapsB[j].append_equivalent(mapsA[i]) mapsA[i].append_equivalent(mapsB[j]) elif relation == "follows": if not mapsB[j].get_follows() or \ (mapsB[j].get_follows() and \ mapsA[i] not in mapsB[j].get_follows()): mapsB[j].append_follows(mapsA[i]) if not mapsA[i].get_precedes() or \ (mapsA[i].get_precedes() and mapsB[j] not in mapsA[i].get_precedes()): mapsA[i].append_precedes(mapsB[j]) elif relation == "precedes": if not mapsB[j].get_precedes() or \ (mapsB[j].get_precedes() and \ mapsA[i] not in mapsB[j].get_precedes()): mapsB[j].append_precedes(mapsA[i]) if not mapsA[i].get_follows() or \ (mapsA[i].get_follows() and \ mapsB[j] not in mapsA[i].get_follows()): mapsA[i].append_follows(mapsB[j]) elif relation == "during" or relation == "starts" or \ relation == "finishes": if not mapsB[j].get_during() or \ (mapsB[j].get_during() and \ mapsA[i] not in mapsB[j].get_during()): mapsB[j].append_during(mapsA[i]) if not mapsA[i].get_contains() or \ (mapsA[i].get_contains() and \ mapsB[j] not in mapsA[i].get_contains()): mapsA[i].append_contains(mapsB[j]) if relation == "starts": if not mapsB[j].get_starts() or \ (mapsB[j].get_starts() and \ mapsA[i] not in mapsB[j].get_starts()): mapsB[j].append_starts(mapsA[i]) if not mapsA[i].get_started() or \ (mapsA[i].get_started() and \ mapsB[j] not in mapsA[i].get_started()): mapsA[i].append_started(mapsB[j]) if relation == "finishes": if not mapsB[j].get_finishes() or \ (mapsB[j].get_finishes() and \ mapsA[i] not in mapsB[j].get_finishes()): mapsB[j].append_finishes(mapsA[i]) if not mapsA[i].get_finished() or \ (mapsA[i].get_finished() and \ mapsB[j] not in mapsA[i].get_finished()): mapsA[i].append_finished(mapsB[j]) elif relation == "contains" or relation == "started" or \ relation == "finished": if not mapsB[j].get_contains() or \ (mapsB[j].get_contains() and \ mapsA[i] not in mapsB[j].get_contains()): mapsB[j].append_contains(mapsA[i]) if not mapsA[i].get_during() or \ (mapsA[i].get_during() and \ mapsB[j] not in mapsA[i].get_during()): mapsA[i].append_during(mapsB[j]) if relation == "started": if not mapsB[j].get_started() or \ (mapsB[j].get_started() and \ mapsA[i] not in mapsB[j].get_started()): mapsB[j].append_started(mapsA[i]) if not mapsA[i].get_starts() or \ (mapsA[i].get_starts() and \ mapsB[j] not in mapsA[i].get_starts()): mapsA[i].append_starts(mapsB[j]) if relation == "finished": if not mapsB[j].get_finished() or \ (mapsB[j].get_finished() and \ mapsA[i] not in mapsB[j].get_finished()): mapsB[j].append_finished(mapsA[i]) if not mapsA[i].get_finishes() or \ (mapsA[i].get_finishes() and \ mapsB[j] not in mapsA[i].get_finishes()): mapsA[i].append_finishes(mapsB[j]) elif relation == "overlaps": if not mapsB[j].get_overlaps() or \ (mapsB[j].get_overlaps() and \ mapsA[i] not in mapsB[j].get_overlaps()): mapsB[j].append_overlaps(mapsA[i]) if not mapsA[i].get_overlapped() or \ (mapsA[i].get_overlapped() and \ mapsB[j] not in mapsA[i].get_overlapped()): mapsA[i].append_overlapped(mapsB[j]) elif relation == "overlapped": if not mapsB[j].get_overlapped() or \ (mapsB[j].get_overlapped() and \ mapsA[i] not in mapsB[j].get_overlapped()): mapsB[j].append_overlapped(mapsA[i]) if not mapsA[i].get_overlaps() or \ (mapsA[i].get_overlaps() and \ mapsB[j] not in mapsA[i].get_overlaps()): mapsA[i].append_overlaps(mapsB[j]) self._build_internal_iteratable(mapsA) if not identical and mapsB != None: self._build_iteratable(mapsB) vector.RTreeDestroyTree(tree) def __iter__(self): start_ = self._first while start_ is not None: yield start_ start_ = start_.next() def __getitem__(self, index): return self._store[index.get_id()] def __len__(self): return len(self._store) def __contains__(self, _map): return _map in self._store.values() ############################################################################### def print_temporal_topology_relationships(maps1, maps2=None, dbif=None): """!Print the temporal relationships of the map lists maps1 and maps2 to stdout. @param maps1: A list of abstract_dataset objects with initiated temporal extent @param maps2: An optional list of abstract_dataset objects with initiated temporal extent @param dbif: The database interface to be used """ tb = TemporalTopologyBuilder() tb.build(maps1, maps2) dbif, connected = init_dbif(dbif) for _map in tb: _map.select(dbif) _map.print_info() if connected: dbif.close() return ############################################################################### def count_temporal_topology_relationships(maps1, maps2=None, dbif=None): """!Count the temporal relations of a single list of maps or between two lists of maps @param maps1: A list of abstract_dataset objects with initiated temporal extent @param maps2: A list of abstract_dataset objects with initiated temporal extent @param dbif: The database interface to be used @return A dictionary with counted temporal relationships """ tb = TemporalTopologyBuilder() tb.build(maps1, maps2) dbif, connected = init_dbif(dbif) relations = None for _map in tb: if relations != None: r = _map.get_number_of_relations() for k in r.keys(): relations[k] += r[k] else: relations = _map.get_number_of_relations() if connected: dbif.close() return relations ############################################################################### def create_temporal_relation_sql_where_statement( start, end, use_start=True, use_during=False, use_overlap=False, use_contain=False, use_equal=False, use_follows=False, use_precedes=False): """!Create a SQL WHERE statement for temporal relation selection of maps in space time datasets @param start: The start time @param end: The end time @param use_start: Select maps of which the start time is located in the selection granule @verbatim map : s granule: s-----------------e map : s--------------------e granule: s-----------------e map : s--------e granule: s-----------------e @endverbatim @param use_during: during: Select maps which are temporal during the selection granule @verbatim map : s-----------e granule: s-----------------e @endverbatim @param use_overlap: Select maps which temporal overlap the selection granule @verbatim map : s-----------e granule: s-----------------e map : s-----------e granule: s----------e @endverbatim @param use_contain: Select maps which temporally contain the selection granule @verbatim map : s-----------------e granule: s-----------e @endverbatim @param use_equal: Select maps which temporally equal to the selection granule @verbatim map : s-----------e granule: s-----------e @endverbatim @param use_follows: Select maps which temporally follow the selection granule @verbatim map : s-----------e granule: s-----------e @endverbatim @param use_precedes: Select maps which temporally precedes the selection granule @verbatim map : s-----------e granule: s-----------e @endverbatim Usage: @code >>> # Relative time >>> start = 1 >>> end = 2 >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=False) >>> create_temporal_relation_sql_where_statement(start, end) '((start_time >= 1 and start_time < 2) )' >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=True) '((start_time >= 1 and start_time < 2) )' >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=False, use_during=True) '(((start_time > 1 and end_time < 2) OR (start_time >= 1 and end_time < 2) OR (start_time > 1 and end_time <= 2)))' >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=False, use_overlap=True) '(((start_time < 1 and end_time > 1 and end_time < 2) OR (start_time < 2 and start_time > 1 and end_time > 2)))' >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=False, use_contain=True) '(((start_time < 1 and end_time > 2) OR (start_time <= 1 and end_time > 2) OR (start_time < 1 and end_time >= 2)))' >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=False, use_equal=True) '((start_time = 1 and end_time = 2))' >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=False, use_follows=True) '((start_time = 2))' >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=False, use_precedes=True) '((end_time = 1))' >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=True, use_during=True, use_overlap=True, use_contain=True, ... use_equal=True, use_follows=True, use_precedes=True) '((start_time >= 1 and start_time < 2) OR ((start_time > 1 and end_time < 2) OR (start_time >= 1 and end_time < 2) OR (start_time > 1 and end_time <= 2)) OR ((start_time < 1 and end_time > 1 and end_time < 2) OR (start_time < 2 and start_time > 1 and end_time > 2)) OR ((start_time < 1 and end_time > 2) OR (start_time <= 1 and end_time > 2) OR (start_time < 1 and end_time >= 2)) OR (start_time = 1 and end_time = 2) OR (start_time = 2) OR (end_time = 1))' >>> # Absolute time >>> start = datetime(2001, 1, 1, 12, 30) >>> end = datetime(2001, 3, 31, 14, 30) >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=False) >>> create_temporal_relation_sql_where_statement(start, end) "((start_time >= '2001-01-01 12:30:00' and start_time < '2001-03-31 14:30:00') )" >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=True) "((start_time >= '2001-01-01 12:30:00' and start_time < '2001-03-31 14:30:00') )" >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=False, use_during=True) "(((start_time > '2001-01-01 12:30:00' and end_time < '2001-03-31 14:30:00') OR (start_time >= '2001-01-01 12:30:00' and end_time < '2001-03-31 14:30:00') OR (start_time > '2001-01-01 12:30:00' and end_time <= '2001-03-31 14:30:00')))" >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=False, use_overlap=True) "(((start_time < '2001-01-01 12:30:00' and end_time > '2001-01-01 12:30:00' and end_time < '2001-03-31 14:30:00') OR (start_time < '2001-03-31 14:30:00' and start_time > '2001-01-01 12:30:00' and end_time > '2001-03-31 14:30:00')))" >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=False, use_contain=True) "(((start_time < '2001-01-01 12:30:00' and end_time > '2001-03-31 14:30:00') OR (start_time <= '2001-01-01 12:30:00' and end_time > '2001-03-31 14:30:00') OR (start_time < '2001-01-01 12:30:00' and end_time >= '2001-03-31 14:30:00')))" >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=False, use_equal=True) "((start_time = '2001-01-01 12:30:00' and end_time = '2001-03-31 14:30:00'))" >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=False, use_follows=True) "((start_time = '2001-03-31 14:30:00'))" >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=False, use_precedes=True) "((end_time = '2001-01-01 12:30:00'))" >>> create_temporal_relation_sql_where_statement(start, end, ... use_start=True, use_during=True, use_overlap=True, use_contain=True, ... use_equal=True, use_follows=True, use_precedes=True) "((start_time >= '2001-01-01 12:30:00' and start_time < '2001-03-31 14:30:00') OR ((start_time > '2001-01-01 12:30:00' and end_time < '2001-03-31 14:30:00') OR (start_time >= '2001-01-01 12:30:00' and end_time < '2001-03-31 14:30:00') OR (start_time > '2001-01-01 12:30:00' and end_time <= '2001-03-31 14:30:00')) OR ((start_time < '2001-01-01 12:30:00' and end_time > '2001-01-01 12:30:00' and end_time < '2001-03-31 14:30:00') OR (start_time < '2001-03-31 14:30:00' and start_time > '2001-01-01 12:30:00' and end_time > '2001-03-31 14:30:00')) OR ((start_time < '2001-01-01 12:30:00' and end_time > '2001-03-31 14:30:00') OR (start_time <= '2001-01-01 12:30:00' and end_time > '2001-03-31 14:30:00') OR (start_time < '2001-01-01 12:30:00' and end_time >= '2001-03-31 14:30:00')) OR (start_time = '2001-01-01 12:30:00' and end_time = '2001-03-31 14:30:00') OR (start_time = '2001-03-31 14:30:00') OR (end_time = '2001-01-01 12:30:00'))" @endcode """ where = "(" if use_start: if isinstance(start, datetime): where += "(start_time >= '%s' and start_time < '%s') " % (start, end) else: where += "(start_time >= %i and start_time < %i) " % (start, end) if use_during: if use_start: where += " OR " if isinstance(start, datetime): where += "((start_time > '%s' and end_time < '%s') OR " % (start, end) where += "(start_time >= '%s' and end_time < '%s') OR " % (start, end) where += "(start_time > '%s' and end_time <= '%s'))" % (start, end) else: where += "((start_time > %i and end_time < %i) OR " % (start, end) where += "(start_time >= %i and end_time < %i) OR " % (start, end) where += "(start_time > %i and end_time <= %i))" % (start, end) if use_overlap: if use_start or use_during: where += " OR " if isinstance(start, datetime): where += "((start_time < '%s' and end_time > '%s' and end_time < '%s') OR " % (start, start, end) where += "(start_time < '%s' and start_time > '%s' and end_time > '%s'))" % (end, start, end) else: where += "((start_time < %i and end_time > %i and end_time < %i) OR " % (start, start, end) where += "(start_time < %i and start_time > %i and end_time > %i))" % (end, start, end) if use_contain: if use_start or use_during or use_overlap: where += " OR " if isinstance(start, datetime): where += "((start_time < '%s' and end_time > '%s') OR " % (start, end) where += "(start_time <= '%s' and end_time > '%s') OR " % (start, end) where += "(start_time < '%s' and end_time >= '%s'))" % (start, end) else: where += "((start_time < %i and end_time > %i) OR " % (start, end) where += "(start_time <= %i and end_time > %i) OR " % (start, end) where += "(start_time < %i and end_time >= %i))" % (start, end) if use_equal: if use_start or use_during or use_overlap or use_contain: where += " OR " if isinstance(start, datetime): where += "(start_time = '%s' and end_time = '%s')" % (start, end) else: where += "(start_time = %i and end_time = %i)" % (start, end) if use_follows: if use_start or use_during or use_overlap or use_contain or use_equal: where += " OR " if isinstance(start, datetime): where += "(start_time = '%s')" % (end) else: where += "(start_time = %i)" % (end) if use_precedes: if use_start or use_during or use_overlap or use_contain or use_equal \ or use_follows: where += " OR " if isinstance(start, datetime): where += "(end_time = '%s')" % (start) else: where += "(end_time = %i)" % (start) where += ")" # Catch empty where statement if where == "()": where = None return where