Procházet zdrojové kódy

New dataset object factory. New temporal topology functions implemented.

git-svn-id: https://svn.osgeo.org/grass/grass/trunk@48796 15284696-431f-4ddb-bdfa-cd5b030d7da7
Soeren Gebbert před 13 roky
rodič
revize
35da455363

+ 39 - 18
lib/python/temporal/abstract_map_dataset.py

@@ -56,16 +56,19 @@ class abstract_map_dataset(abstract_dataset):
            @param timezone: Thee timezone of the map
            @param timezone: Thee timezone of the map
         
         
         """
         """
-        if start_time != None and not isinstance(start_time, datetime) :
-            core.fatal(_("Start time must be of type datetime"))
+        if start_time and not isinstance(start_time, datetime) :
+            core.fatal(_("Start time must be of type datetime for %s map <%s>") % (self.get_type(), self.get_id()))
 
 
-        if end_time != None and not isinstance(end_time, datetime) :
-            core.fatal(_("End time must be of type datetime"))
+        if end_time and not isinstance(end_time, datetime) :
+            core.fatal(_("End time must be of type datetime for %s map <%s>") % (self.get_type(), self.get_id()))
 
 
-        if start_time != None and end_time != None:
-            if start_time >= end_time:
-                core.error(_("End time must be later than start time"))
-                return False
+        if start_time and end_time:
+            if start_time > end_time:
+                core.fatal(_("End time must be greater than start time for %s map <%s>") % (self.get_type(), self.get_id()))
+            else:
+                # Do not create an interval in case start and end time are equal
+                if start_time == end_time:
+                    end_time = None
 
 
         self.base.set_ttype("absolute")
         self.base.set_ttype("absolute")
         
         
@@ -73,8 +76,6 @@ class abstract_map_dataset(abstract_dataset):
         self.absolute_time.set_end_time(end_time)
         self.absolute_time.set_end_time(end_time)
         self.absolute_time.set_timezone(timezone)
         self.absolute_time.set_timezone(timezone)
 
 
-        return True
-
     def update_absolute_time(self, start_time, end_time=None, timezone=None, dbif = None):
     def update_absolute_time(self, start_time, end_time=None, timezone=None, dbif = None):
         """Update the absolute time
         """Update the absolute time
 
 
@@ -103,10 +104,13 @@ class abstract_map_dataset(abstract_dataset):
            @param end_time: A double value in days
            @param end_time: A double value in days
 
 
         """
         """
-        if start_time != None and end_time != None:
-            if abs(float(start_time)) >= abs(float(end_time)):
-                core.error(_("End time must be greater than start time"))
-                return False
+        if start_time and end_time:
+            if abs(float(start_time)) > abs(float(end_time)):
+                core.fatal(_("End time must be greater than start time for %s map <%s>") % (self.get_type(), self.get_id()))
+            else:
+                # Do not create an interval in case start and end time are equal
+                if start_time == end_time:
+                    end_time = None
 
 
         self.base.set_ttype("relative")
         self.base.set_ttype("relative")
         
         
@@ -116,8 +120,6 @@ class abstract_map_dataset(abstract_dataset):
         else:
         else:
             self.relative_time.set_end_time(None)
             self.relative_time.set_end_time(None)
 
 
-        return True
-
     def update_relative_time(self, start_time, end_time=None, dbif = None):
     def update_relative_time(self, start_time, end_time=None, dbif = None):
         """Update the relative time interval
         """Update the relative time interval
 
 
@@ -152,6 +154,24 @@ class abstract_map_dataset(abstract_dataset):
         """
         """
         self.spatial_extent.set_spatial_extent(north, south, east, west, top, bottom)
         self.spatial_extent.set_spatial_extent(north, south, east, west, top, bottom)
         
         
+    def check_valid_time(self):
+        """Check for correct valid time"""
+        if self.is_time_absolute():
+            start, end, tz = self.get_absolute_time()
+        else:
+            start, end = self.get_relative_time()
+
+        if start:
+            if end:
+                if start >= end:
+                    core.error(_("Map <%s> has incorrect time interval, start time is greater than end time") % (self.get_id()))
+                    return False
+        else:
+            core.error(_("Map <%s> has incorrect start time") % (self.get_id()))
+            return False
+
+        return True
+
     def delete(self, dbif=None):
     def delete(self, dbif=None):
 	"""Delete a map entry from database if it exists
 	"""Delete a map entry from database if it exists
         
         
@@ -197,7 +217,7 @@ class abstract_map_dataset(abstract_dataset):
         if connect == True:
         if connect == True:
             dbif.close()
             dbif.close()
 
 
-    def unregister(self, dbif=None):
+    def unregister(self, dbif=None, update=True):
 	""" Remove the map entry in each space time dataset in which this map is registered
 	""" Remove the map entry in each space time dataset in which this map is registered
 
 
            @param dbif: The database interface to be used
            @param dbif: The database interface to be used
@@ -225,7 +245,8 @@ class abstract_map_dataset(abstract_dataset):
                 stds.unregister_map(self, dbif)
                 stds.unregister_map(self, dbif)
                 # Take care to update the space time dataset after
                 # Take care to update the space time dataset after
                 # the map has been unregistred
                 # the map has been unregistred
-                stds.update_from_registered_maps(dbif)
+                if update == True:
+                    stds.update_from_registered_maps(dbif)
 
 
         dbif.connection.commit()
         dbif.connection.commit()
 
 

+ 142 - 31
lib/python/temporal/abstract_space_time_dataset.py

@@ -107,6 +107,19 @@ class abstract_space_time_dataset(abstract_dataset):
 
 
         return granularity, temporal_type, semantic_type, title, description
         return granularity, temporal_type, semantic_type, title, description
 
 
+    def get_map_time(self):
+        """Return the type of the map time, interval, point, maixed or invalid"""
+        
+        temporal_type = self.get_temporal_type()
+
+        if temporal_type == "absolute":
+            map_time   = self.absolute_time.get_map_time()
+        elif temporal_type == "relative":
+            map_time   = self.relative_time.get_map_time()
+
+        return map_time
+
+
     def print_temporal_relation_matrix(self, maps):
     def print_temporal_relation_matrix(self, maps):
         """Print the temporal relation matrix of all registered maps to stdout
         """Print the temporal relation matrix of all registered maps to stdout
 
 
@@ -116,14 +129,28 @@ class abstract_space_time_dataset(abstract_dataset):
            @param dbif: The database interface to be used
            @param dbif: The database interface to be used
         """
         """
 
 
-        for map in maps:
-            print map.get_id(),
-        print " "    
+        for i in range(len(maps)):
+            reltations = ""
+            count = 0
+            for j in range(i + 1, len(maps)):
+                relation = maps[j].temporal_relation(maps[i])
 
 
-        for mapA in maps:
-            for mapB in maps:
-                print mapA.temporal_relation(mapB),
-            print " "
+                # print maps[j].base.get_name(), maps[i].base.get_name(), relation
+                if count == 0:
+                    relations = relation
+                else:
+                    relations += "," + relation
+                count += 1
+                # Break if the the next map follows
+                if relation == "follows":
+                    break
+                # Break if the the next map is after
+                if relation == "after":
+                    break
+            if i < len(maps) - 1:    
+                print maps[i].base.get_name(), relations    
+            else:
+                print maps[i].base.get_name()
 
 
     def get_temporal_relation_matrix(self, maps):
     def get_temporal_relation_matrix(self, maps):
         """Return the temporal relation matrix of all registered maps as listof lists
         """Return the temporal relation matrix of all registered maps as listof lists
@@ -133,7 +160,7 @@ class abstract_space_time_dataset(abstract_dataset):
            The temproal relation matrix includes the temporal relations between
            The temproal relation matrix includes the temporal relations between
            all registered maps. The relations are strings stored in a list of lists.
            all registered maps. The relations are strings stored in a list of lists.
            
            
-           @param dbif: The database interface to be used
+           @param maps: a ordered by start_time list of map objects
         """
         """
 
 
         matrix = []
         matrix = []
@@ -153,7 +180,7 @@ class abstract_space_time_dataset(abstract_dataset):
 
 
         return matrix
         return matrix
 
 
-    def get_temporal_map_type_count(self, maps):
+    def count_temporal_types(self, maps):
         """Return the temporal type of the registered maps as dictionary
         """Return the temporal type of the registered maps as dictionary
 
 
            The map list must be ordered by start time
            The map list must be ordered by start time
@@ -162,9 +189,8 @@ class abstract_space_time_dataset(abstract_dataset):
            * point    -> only the start time is present
            * point    -> only the start time is present
            * interval -> start and end time
            * interval -> start and end time
            * invalid  -> No valid time point or interval found
            * invalid  -> No valid time point or interval found
-           * holes    -> In case the maps are interval
 
 
-           @param dbif: The database interface to be used
+           @param maps: A sorted (start_time) list of abstract_dataset objects
         """
         """
 
 
         time_invalid = 0
         time_invalid = 0
@@ -181,11 +207,6 @@ class abstract_space_time_dataset(abstract_dataset):
 
 
             if start and end:
             if start and end:
                 time_interval += 1
                 time_interval += 1
-                # Check for holes
-                if i < len(maps) - 1:
-                    relation = maps[i + 1].temporal_relation(maps[i])
-                    if relation != "follows":
-                        holes += 1
             elif start and not end:
             elif start and not end:
                 time_point += 1
                 time_point += 1
             else:
             else:
@@ -195,26 +216,37 @@ class abstract_space_time_dataset(abstract_dataset):
         tcount["interval"] = time_interval
         tcount["interval"] = time_interval
         tcount["invalid"] = time_invalid
         tcount["invalid"] = time_invalid
 
 
-        holes = 0
+        return tcount
+
+    def count_gaps(self, maps, is_interval = False):
+        """Count the number of gaps between temporal neighbours. The maps must have intervals as valid time
+        
+           @param maps: A sorted (start_time) list of abstract_dataset objects
+           @param is_interval: Set true if the maps have vaild interval time (relative or absolute)
+           @return The numbers of gaps between temporal neighbours
+        """
 
 
-        # Check for holes
-        if time_interval > 0 and time_point == 0 and time_invalid == 0:
+        gaps = 0
+
+        # Check for gaps
+        if is_interval:    
             for i in range(len(maps)):
             for i in range(len(maps)):
                 if i < len(maps) - 1:
                 if i < len(maps) - 1:
                     relation = maps[i + 1].temporal_relation(maps[i])
                     relation = maps[i + 1].temporal_relation(maps[i])
-                    if relation != "follows":
-                        holes += 1
-
-        tcount["holes"] = holes
+                    if relation == "after":
+                        gaps += 1
+        else:
+            gaps = None # Gaps only possible in temporal consistent datasets (only intervals)
 
 
-        return tcount
+        return gaps
 
 
-    def get_temporal_relations_count(self, maps):
+    def count_temporal_relations(self, maps):
         """Count the temporal relations between the registered maps.
         """Count the temporal relations between the registered maps.
 
 
            The map list must be ordered by start time
            The map list must be ordered by start time
 
 
-           @param dbif: The database interface to be used
+           @param maps: A sorted (start_time) list of abstract_dataset objects
+           @return A dictionary with counted temporal relationships
         """
         """
 
 
         tcount = {}
         tcount = {}
@@ -228,8 +260,77 @@ class abstract_space_time_dataset(abstract_dataset):
                 else:
                 else:
                     tcount[relation] = 1
                     tcount[relation] = 1
 
 
+                # Break if the the next map follows
+                if relation == "follows":
+                    break
+                # Break if the the next map is after
+                if relation == "after":
+                    break
+
         return tcount
         return tcount
 
 
+    def check_temporal_topology(self, maps=None, dbif=None):
+        """Check the temporal topology
+
+           Correct topology means, that time intervals are not overlap or
+           that intervals does not contain other intervals. Equal time intervals or
+           points of time are not allowed.
+
+           The map list must be ordered by start time
+
+           Allowed and not allowed temporal relationships for correct topology
+           after      -> allowed
+           before     -> allowed
+           follows    -> allowed
+           precedes   -> allowed
+
+           equivalent -> not allowed
+           during     -> not allowed
+           contains   -> not allowed
+           overlaps   -> not allowed
+           overlapped -> not allowed
+           starts     -> not allowed
+           finishes   -> not allowed
+           started    -> not allowed
+           finished   -> not allowed
+
+           @param maps: A sorted (start_time) list of abstract_dataset objects
+           @return True if topology is correct
+        """
+        if maps == None:
+            maps = get_registered_maps_as_objects(where=None, order="start_time", dbif=dbif)
+
+        relations = self.count_temporal_relations(maps)
+
+        map_time = self.get_map_time()
+
+        if map_time == "interval" or map_time == "mixed":
+            if relations.has_key("equivalent"):
+                return False
+            if relations.has_key("during"):
+                return False
+            if relations.has_key("contains"):
+                return False
+            if relations.has_key("overlaps"):
+                return False
+            if relations.has_key("overlapped"):
+                return False
+            if relations.has_key("starts"):
+                return False
+            if relations.has_key("finishes"):
+                return False
+            if relations.has_key("started"):
+                return False
+            if relations.has_key("finished"):
+                return False
+        elif map_time == "point":
+            if relations.has_key("equivalent"):
+                return False
+        else:
+            return False
+
+        return True
+
     def get_registered_maps_as_objects(self, where=None, order="start_time", dbif=None):
     def get_registered_maps_as_objects(self, where=None, order="start_time", dbif=None):
         """Return all registered maps as ordered object list
         """Return all registered maps as ordered object list
 
 
@@ -390,6 +491,10 @@ class abstract_space_time_dataset(abstract_dataset):
 
 
         # First select all data from the database
         # First select all data from the database
         map.select(dbif)
         map.select(dbif)
+
+        if not map.check_valid_time():
+            core.fatal(_("Map <%s> has invalid time") % map.get_id())
+
         map_id = map.base.get_id()
         map_id = map.base.get_id()
         map_name = map.base.get_name()
         map_name = map.base.get_name()
         map_mapset = map.base.get_mapset()
         map_mapset = map.base.get_mapset()
@@ -702,10 +807,7 @@ class abstract_space_time_dataset(abstract_dataset):
 		    max_start_time = row[0]
 		    max_start_time = row[0]
 
 
 		if end_time < max_start_time:
 		if end_time < max_start_time:
-                    map_time = "mixed"
 		    use_start_time = True
 		    use_start_time = True
-                else:
-                    map_time = "interval"
 		    
 		    
         # Set the maximum start time as end time
         # Set the maximum start time as end time
         if use_start_time:
         if use_start_time:
@@ -733,8 +835,17 @@ class abstract_space_time_dataset(abstract_dataset):
 	    else:
 	    else:
 		dbif.cursor.execute(sql)
 		dbif.cursor.execute(sql)
 
 
-            if end_time == None:
-                map_time = "point"
+        # Count the temporal map types
+        tlist = self.count_temporal_types(self.get_registered_maps_as_objects(dbif=dbif))
+
+        if tlist["interval"] > 0 and tlist["point"] == 0 and tlist["invalid"] == 0:
+            map_time = "interval"
+        elif tlist["interval"] == 0 and tlist["point"] > 0 and tlist["invalid"] == 0:
+            map_time = "point"
+        elif tlist["interval"] > 0 and tlist["point"] > 0 and tlist["invalid"] == 0:
+            map_time = "mixed"
+        else:
+            map_time = "invalid"
 
 
         # Set the map time type
         # Set the map time type
         if self.is_time_absolute():
         if self.is_time_absolute():

+ 11 - 0
lib/python/temporal/datetime_math.py

@@ -26,6 +26,17 @@ import copy
 
 
 ###############################################################################
 ###############################################################################
 
 
+def datetime_delta_to_double(dt1, dt2):
+    """Compute the the dfference dt2 - dt1 and convert the time delta into a 
+       double value, representing days.
+    """
+
+    delta = dt2 - dt1
+
+    return float(delta.days) + float(delta.seconds/86400.0)
+
+###############################################################################
+
 def increment_datetime_by_string(mydate, increment, mult = 1):
 def increment_datetime_by_string(mydate, increment, mult = 1):
     """Return a new datetime object incremented with the provided relative dates specified as string.
     """Return a new datetime object incremented with the provided relative dates specified as string.
        Additional a multiplier can be specified to multiply the increment bevor adding to the provided datetime object.
        Additional a multiplier can be specified to multiply the increment bevor adding to the provided datetime object.

+ 31 - 18
lib/python/temporal/space_time_datasets_tools.py

@@ -77,12 +77,7 @@ def register_maps_in_space_time_dataset(type, name, maps=None, file=None, start=
     else:
     else:
         id = name
         id = name
 
 
-    if type == "rast":
-        sp = space_time_raster_dataset(id)
-    if type == "rast3d":
-        sp = space_time_raster3d_dataset(id)
-    if type == "vect":
-        sp = space_time_vector_dataset(id)
+    sp = dataset_factory(type, id)
 
 
     connect = False
     connect = False
 
 
@@ -232,12 +227,7 @@ def unregister_maps_from_space_time_datasets(type, name, maps, file=None, dbif =
         else:
         else:
             id = name
             id = name
 
 
-        if type == "rast":
-            sp = space_time_raster_dataset(id)
-        if type == "rast3d":
-            sp = space_time_raster3d_dataset(id)
-        if type == "vect":
-            sp = space_time_vector_dataset(id)
+        sp = dataset_factory(type, id)
 
 
         if sp.is_in_db(dbif) == False:
         if sp.is_in_db(dbif) == False:
             dbif.close()
             dbif.close()
@@ -418,12 +408,7 @@ def assign_valid_time_to_maps(type, maps, ttype, start, end=None, file=file, inc
             else:
             else:
                 mapid = entry
                 mapid = entry
 
 
-        if type == "rast":
-            map = raster_dataset(mapid)
-        if type == "rast3d":
-            map = raster3d_dataset(mapid)
-        if type == "vect":
-            map = vector_dataset(mapid)
+        sp = dataset_factory(type, id)
 
 
         # Use the time data from file
         # Use the time data from file
         if start_time_in_file:
         if start_time_in_file:
@@ -530,3 +515,31 @@ def assign_valid_time_to_map(ttype, map, start, end, increment=None, mult=1, dbi
 
 
     if connect == True:
     if connect == True:
         dbif.close()
         dbif.close()
+
+###############################################################################
+
+def dataset_factory(type, id):
+    """A factory functions to create space time or map datasets
+    
+       @param type: the dataset type: rast, rast3d, vect, strds, str3ds, stvds
+       @param id: The id of the dataset ("name@mapset")
+    """
+    print type, id
+    if type == "strds":
+        sp = space_time_raster_dataset(id)
+    elif type == "str3ds":
+        sp = space_time_raster3d_dataset(id)
+    elif type == "stvds":
+        sp = space_time_vector_dataset(id)
+    elif type == "rast":
+        sp = raster_dataset(id)
+    elif type == "rast3d":
+        sp = raster3d_dataset(id)
+    elif type == "vect":
+        sp = vector_dataset(id)
+    else:
+        core.error(_("Unknown dataset type: %s") % type)
+        return None
+
+    return sp
+

+ 20 - 4
lib/python/temporal/temporal_extent.py

@@ -164,8 +164,9 @@ class abstract_temporal_extent(sql_database_interface):
 	   A   |-------|
 	   A   |-------|
 	   B  |---------|
 	   B  |---------|
 	"""
 	"""
-        if  self.D["end_time"] == None and map.D["end_time"] == None :
-            return False
+        # Check single point of time in interval
+        if  map.D["end_time"] == None:
+                return False
 
 
         # Check single point of time in interval
         # Check single point of time in interval
         if  self.D["end_time"] == None:
         if  self.D["end_time"] == None:
@@ -184,8 +185,9 @@ class abstract_temporal_extent(sql_database_interface):
 	   A  |---------|
 	   A  |---------|
 	   B   |-------|
 	   B   |-------|
 	"""
 	"""
-        if  self.D["end_time"] == None and map.D["end_time"] == None :
-            return False
+        # Check single point of time in interval
+        if  self.D["end_time"] == None:
+                return False
 
 
         # Check single point of time in interval
         # Check single point of time in interval
         if  map.D["end_time"] == None:
         if  map.D["end_time"] == None:
@@ -250,6 +252,20 @@ class abstract_temporal_extent(sql_database_interface):
 	"""Returns the temporal relation between temporal objects
 	"""Returns the temporal relation between temporal objects
 	   Temporal relationsships are implemented after [Allen and Ferguson 1994 Actions and Events in Interval Temporal Logic]
 	   Temporal relationsships are implemented after [Allen and Ferguson 1994 Actions and Events in Interval Temporal Logic]
 	"""
 	"""
+        
+        # First check for correct time
+        if not self.D.has_key("start_time"):
+            return None
+        if not self.D.has_key("end_time"):
+            return None
+        if not map.D.has_key("start_time"):
+            return None
+        if not map.D.has_key("end_time"):
+            return None
+
+        if self.D["start_time"] == None or map.D["start_time"] == None:
+            return None
+
 	if self.equivalent(map):
 	if self.equivalent(map):
 	    return "equivalent"
 	    return "equivalent"
 	if self.during(map):
 	if self.during(map):