diff --git a/python/grass/temporal/c_libraries_interface.py b/python/grass/temporal/c_libraries_interface.py index 77f2e17245a..1cd2334af3e 100644 --- a/python/grass/temporal/c_libraries_interface.py +++ b/python/grass/temporal/c_libraries_interface.py @@ -51,6 +51,7 @@ class RPCDefs(object): WRITE_SEMANTIC_LABEL = 15 READ_SEMANTIC_LABEL = 16 REMOVE_SEMANTIC_LABEL = 17 + READ_MAP_HISTORY = 18 G_FATAL_ERROR = 49 TYPE_RASTER = 0 @@ -981,6 +982,142 @@ def _read_vector_info(name, mapset): ############################################################################### +def _read_map_history(lock, conn, data): + """Read map history from the spatial database using C-library functions + + :param lock: A multiprocessing.Lock instance + :param conn: A multiprocessing.Pipe instance used to send True or False + :param data: The list of data entries [function_id, maptype, name, mapset] + """ + kvp = None + try: + maptype = data[1] + name = data[2] + mapset = data[3] + if maptype == RPCDefs.TYPE_RASTER: + kvp = _read_raster_history(name, mapset) + elif maptype == RPCDefs.TYPE_VECTOR: + kvp = _read_vector_history(name, mapset) + elif maptype == RPCDefs.TYPE_RASTER3D: + kvp = _read_raster3d_history(name, mapset) + except: + raise + finally: + conn.send(kvp) + + +############################################################################### + + +def _read_raster_history(name, mapset): + """Read the raster history from the file system and store the content + into a dictionary + + This method uses the ctypes interface to the gis and raster libraries + to read the map history + + :param name: The name of the map + :param mapset: The mapset of the map + :returns: The key value pairs of the map specific metadata, or None in + case of an error + """ + + kvp = {} + + if not libgis.G_find_raster(name, mapset): + return None + + # Read the raster history + hist = libraster.History() + ret = libraster.Rast_read_history(name, mapset, byref(hist)) + if ret < 0: + logging.warning(_("Unable to read history file")) + return None + else: + kvp["creation_time"] = decode( + libraster.Rast_get_history(byref(hist), libraster.HIST_MAPID) + ) + kvp["creator"] = decode( + libraster.Rast_get_history(byref(hist), libraster.HIST_CREATOR) + ) + + return kvp + + +############################################################################### + + +def _read_raster3d_history(name, mapset): + """Read the 3D raster map info from the file system and store the content + into a dictionary + + This method uses the ctypes interface to the gis and raster3d libraries + to read the map metadata information + + :param name: The name of the map + :param mapset: The mapset of the map + :returns: The key value pairs of the map specific metadata, or None in + case of an error + """ + + kvp = {} + + if not libgis.G_find_raster3d(name, mapset): + return None + + # Read the region information + hist = libraster.History() + ret = libraster3d.Rast3d_read_history(name, mapset, byref(hist)) + if ret < 0: + logging.warning(_("Unable to read history file")) + return None + else: + kvp["creation_time"] = decode( + libraster.Rast_get_history(byref(hist), libraster3d.HIST_MAPID) + ) + kvp["creator"] = decode( + libraster.Rast_get_history(byref(hist), libraster3d.HIST_CREATOR) + ) + + return kvp + + +############################################################################### + + +def _read_vector_history(name, mapset): + """Read the vector history from the file system and store the content + into a dictionary + + This method uses the ctypes interface to the gis and raster libraries + to read the map history + + :param name: The name of the map + :param mapset: The mapset of the map + :returns: The key value pairs of the map specific metadata, or None in + case of an error + """ + + kvp = {} + + if not libgis.G_find_vector(name, mapset): + return None + + # Read the vector history + Map = libvector.Map_info() + if libvector.Vect_open_old(byref(Map), name, mapset, "1") > 0: + kvp["creation_time"] = decode(libvector.Vect_get_map_date(byref(Map))) + kvp["creator"] = decode(libvector.Vect_get_person(byref(Map))) + else: + None + libvector.Vect_close(byref(Map)) + + return kvp + + +############################################################################### + + def _convert_timestamp_from_grass(ts): """Convert a GRASS file based timestamp into the temporal framework format datetime or integer. @@ -1124,6 +1261,7 @@ def error_handler(data): functions[RPCDefs.WRITE_SEMANTIC_LABEL] = _write_semantic_label functions[RPCDefs.READ_SEMANTIC_LABEL] = _read_semantic_label functions[RPCDefs.REMOVE_SEMANTIC_LABEL] = _remove_semantic_label + functions[RPCDefs.READ_MAP_HISTORY] = _read_map_history functions[RPCDefs.G_FATAL_ERROR] = _fatal_error libgis.G_gisinit("c_library_server") @@ -1374,6 +1512,21 @@ def read_raster_full_info(self, name, mapset): ) return self.safe_receive("read_raster_full_info") + def read_raster_history(self, name, mapset): + """Read the raster map history from the file system and store the content + into a dictionary + + :param name: The name of the map + :param mapset: The mapset of the map + :returns: The key value pairs of the map history (creation, creation_time), + or None in case of an error + """ + self.check_server() + self.client_conn.send( + [RPCDefs.READ_MAP_HISTORY, RPCDefs.TYPE_RASTER, name, mapset, None] + ) + return self.safe_receive("read_raster_history") + def has_raster_timestamp(self, name, mapset): """Check if a file based raster timestamp exists @@ -1533,6 +1686,21 @@ def read_raster3d_info(self, name, mapset): ) return self.safe_receive("read_raster3d_info") + def read_raster3d_history(self, name, mapset): + """Read the 3D raster map history from the file system and store the content + into a dictionary + + :param name: The name of the map + :param mapset: The mapset of the map + :returns: The key value pairs of the map history (creation, creation_time), + or None in case of an error + """ + self.check_server() + self.client_conn.send( + [RPCDefs.READ_MAP_HISTORY, RPCDefs.TYPE_RASTER3D, name, mapset, None] + ) + return self.safe_receive("read_raster3d_history") + def has_raster3d_timestamp(self, name, mapset): """Check if a file based 3D raster timestamp exists @@ -1655,6 +1823,21 @@ def read_vector_full_info(self, name, mapset): ) return self.safe_receive("read_vector_full_info") + def read_vector_history(self, name, mapset): + """Read the vector map history from the file system and store the content + into a dictionary + + :param name: The name of the map + :param mapset: The mapset of the map + :returns: The key value pairs of the map history (creation, creation_time), + or None in case of an error + """ + self.check_server() + self.client_conn.send( + [RPCDefs.READ_MAP_HISTORY, RPCDefs.TYPE_VECTOR, name, mapset, None] + ) + return self.safe_receive("read_vector_history") + def has_vector_timestamp(self, name, mapset, layer=None): """Check if a file based vector timestamp exists diff --git a/python/grass/temporal/register.py b/python/grass/temporal/register.py index 7514cb33d03..71a87f53253 100644 --- a/python/grass/temporal/register.py +++ b/python/grass/temporal/register.py @@ -17,7 +17,7 @@ :authors: Soeren Gebbert """ from datetime import datetime -import grass.script as gscript +import grass.script as gs from .core import get_tgis_message_interface, init_dbif, get_current_mapset from .open_stds import open_old_stds from .abstract_map_dataset import AbstractMapDataset @@ -81,6 +81,7 @@ def register_maps_in_space_time_dataset( start_time_in_file = False end_time_in_file = False semantic_label_in_file = False + overwrite = gs.overwrite() msgr = get_tgis_message_interface() @@ -95,15 +96,13 @@ def register_maps_in_space_time_dataset( increment = str(increment) if maps and file: - msgr.fatal(_("%s= and %s= are mutually exclusive") % ("maps", "file")) + msgr.fatal(_("maps and file are mutually exclusive")) if end and increment: - msgr.fatal(_("%s= and %s= are mutually exclusive") % ("end", "increment")) + msgr.fatal(_("end and increment are mutually exclusive")) if end and interval: - msgr.fatal( - _("%s= and the %s flag are mutually exclusive") % ("end", "interval") - ) + msgr.fatal(_("end and the interval flag are mutually exclusive")) if increment and not start: msgr.fatal(_("The increment option requires the start option")) @@ -112,13 +111,14 @@ def register_maps_in_space_time_dataset( msgr.fatal(_("The interval flag requires the start option")) if end and not start: - msgr.fatal(_("Please specify %s= and %s=") % ("start_time", "end_time")) + msgr.fatal(_("Please specify start_time and end_time")) if not maps and not file: - msgr.fatal(_("Please specify %s= or %s=") % ("maps", "file")) + msgr.fatal(_("Please specify maps or file")) + # We may need the mapset mapset = get_current_mapset() - dbif, connection_state_changed = init_dbif(None) + dbif, connection_state_changed = init_dbif(dbif) # create new stds only in the current mapset # remove all connections to any other mapsets @@ -135,23 +135,17 @@ def register_maps_in_space_time_dataset( dbif.close() msgr.fatal( _( - "Space time %(sp)s dataset <%(name)s> with relative" - " time found, but no relative unit set for %(sp)s " + "Space time {sp} dataset <{name}> with relative" + " time found, but no relative unit set for {sp} " "maps" - ) - % {"name": name, "sp": sp.get_new_map_instance(None).get_type()} + ).format(name=name, sp=sp.get_new_map_instance(None).get_type()) ) maplist = [] # Map names as comma separated string if maps: - if maps.find(",") < 0: - maplist = [ - maps, - ] - else: - maplist = maps.split(",") + maplist = maps.split(",") # Build the map list again with the ids for idx, maplist_item in enumerate(maplist): @@ -241,155 +235,147 @@ def register_maps_in_space_time_dataset( msgr.debug(2, "Gathering map information...") - for count in range(len(maplist)): + for count, row in enumerate(maplist): if count % 50 == 0: msgr.percent(count, num_maps, 1) # Get a new instance of the map type - map = dataset_factory(type, maplist[count]["id"]) + map_object = dataset_factory(type, row["id"]) - if map.map_exists() is not True: + map_object_id = map_object.get_map_id() + map_object_layer = map_object.get_layer() + map_object_type = map_object.get_type() + if not map_object.map_exists(): msgr.fatal( - _("Unable to update %(t)s map <%(id)s>. " "The map does not exist.") - % {"t": map.get_type(), "id": map.get_map_id()} + _("Unable to update {t} map <{mid}>. The map does not exist.").format( + t=map_object_type, mid=map_object_id + ) ) # Use the time data from file - if "start" in maplist[count]: - start = maplist[count]["start"] - if "end" in maplist[count]: - end = maplist[count]["end"] + if "start" in row: + start = row["start"] + if "end" in row: + end = row["end"] # Use the semantic label from file - if "semantic_label" in maplist[count]: - semantic_label = maplist[count]["semantic_label"] + if "semantic_label" in row: + semantic_label = row["semantic_label"] else: semantic_label = None - is_in_db = False + is_in_db = map_object.is_in_db(dbif, mapset) # Put the map into the database of the current mapset - if not map.is_in_db(dbif, mapset): + if not is_in_db: # Break in case no valid time is provided - if (start == "" or start is None) and not map.has_grass_timestamp(): + if (start == "" or start is None) and not map_object.has_grass_timestamp(): dbif.close() - if map.get_layer(): + if map_object_layer: msgr.fatal( _( - "Unable to register %(t)s map <%(id)s> with " - "layer %(l)s. The map has timestamp and " + "Unable to register {t} map <{mid}> with " + "layer {l}. The map has timestamp and " "the start time is not set." + ).format( + t=map_object_type, + mid=map_object_id, + l=map_object_layer, ) - % { - "t": map.get_type(), - "id": map.get_map_id(), - "l": map.get_layer(), - } ) else: msgr.fatal( _( - "Unable to register %(t)s map <%(id)s>. The" + "Unable to register {t} map <{mid}>. The" " map has no timestamp and the start time " "is not set." - ) - % {"t": map.get_type(), "id": map.get_map_id()} + ).format(t=map_object_type, mid=map_object_id) ) if start != "" and start is not None: # We need to check if the time is absolute and the unit was specified time_object = check_datetime_string(start) if isinstance(time_object, datetime) and unit: - msgr.fatal( - _("%(u)s= can only be set for relative time") % {"u": "unit"} - ) + msgr.fatal(_("unit can only be set for relative time")) if not isinstance(time_object, datetime) and not unit: - msgr.fatal( - _("%(u)s= must be set in case of relative time" " stamps") - % {"u": "unit"} - ) + msgr.fatal(_("unit must be set in case of relative time stamps")) if unit: - map.set_time_to_relative() + map_object.set_time_to_relative() else: - map.set_time_to_absolute() + map_object.set_time_to_absolute() else: - is_in_db = True # Check the overwrite flag - if not gscript.overwrite(): - if map.get_layer(): + if not overwrite: + if map_object_layer: msgr.warning( _( "Map is already registered in temporal " - "database. Unable to update %(t)s map " - "<%(id)s> with layer %(l)s. Overwrite flag" + "database. Unable to update {t} map " + "<{mid}> with layer {l}. Overwrite flag" " is not set." + ).format( + t=map_object_type, + mid=map_object_id, + l=str(map_object_layer), ) - % { - "t": map.get_type(), - "id": map.get_map_id(), - "l": str(map.get_layer()), - } ) else: msgr.warning( _( "Map is already registered in temporal " - "database. Unable to update %(t)s map " - "<%(id)s>. Overwrite flag is not set." - ) - % {"t": map.get_type(), "id": map.get_map_id()} + "database. Unable to update {t} map " + "<{mid}>. Overwrite flag is not set." + ).format(t=map_object_type, mid=map_object_id) ) # Simple registration is allowed if name: - map_object_list.append(map) + map_object_list.append(map_object) # Jump to next map continue - # Select information from temporal database - map.select(dbif) + # Reload properties from database + map_object.select(dbif) # Save the datasets that must be updated - datasets = map.get_registered_stds(dbif) + datasets = map_object.get_registered_stds(dbif) if datasets is not None: for dataset in datasets: if dataset != "": datatsets_to_modify[dataset] = dataset - if name and map.get_temporal_type() != sp.get_temporal_type(): + if name and map_object.get_temporal_type() != sp.get_temporal_type(): dbif.close() - if map.get_layer(): + if map_object_layer: msgr.fatal( _( - "Unable to update %(t)s map <%(id)s> " - "with layer %(l)s. The temporal types " + "Unable to update {t} map <{id}> " + "with layer {l}. The temporal types " "are different." + ).format( + t=map_object_type, + mid=map_object_id, + l=map_object_layer, ) - % { - "t": map.get_type(), - "id": map.get_map_id(), - "l": map.get_layer(), - } ) else: msgr.fatal( _( - "Unable to update %(t)s map <%(id)s>. " + "Unable to update {t} map <{mid}>. " "The temporal types are different." - ) - % {"t": map.get_type(), "id": map.get_map_id()} + ).format(t=map_object_type, mid=map_object_id) ) # Load the data from the grass file database - map.load() + map_object.load() # Try to read an existing time stamp from the grass spatial database # in case this map wasn't already registered in the temporal database # Read the spatial database time stamp only, if no time stamp was provided for this map # as method argument or in the input file if not is_in_db and not start: - map.read_timestamp_from_grass() + map_object.read_timestamp_from_grass() # Set the valid time if start: @@ -398,8 +384,8 @@ def register_maps_in_space_time_dataset( if start_time_in_file: count = 1 assign_valid_time_to_map( - ttype=map.get_temporal_type(), - map=map, + ttype=map_object.get_temporal_type(), + map_object=map_object, start=start, end=end, unit=unit, @@ -413,17 +399,17 @@ def register_maps_in_space_time_dataset( # semantic label defined in input file # -> update raster metadata # -> write band identifier to GRASS data base - map.set_semantic_label(semantic_label) + map_object.set_semantic_label(semantic_label) else: # Try to read semantic label from GRASS data base if defined - map.read_semantic_label_from_grass() + map_object.read_semantic_label_from_grass() if is_in_db: # Gather the SQL update statement - statement += map.update_all(dbif=dbif, execute=False) + statement += map_object.update_all(dbif=dbif, execute=False) else: # Gather the SQL insert statement - statement += map.insert(dbif=dbif, execute=False) + statement += map_object.insert(dbif=dbif, execute=False) # Sqlite3 performance is better for huge datasets when committing in # small chunks @@ -435,7 +421,7 @@ def register_maps_in_space_time_dataset( # Store the maps in a list to register in a space time dataset if name: - map_object_list.append(map) + map_object_list.append(map_object) msgr.percent(num_maps, num_maps, 1) @@ -444,13 +430,11 @@ def register_maps_in_space_time_dataset( # Finally Register the maps in the space time dataset if name and map_object_list: - count = 0 num_maps = len(map_object_list) - for map in map_object_list: + for count, map_object in enumerate(map_object_list): if count % 50 == 0: msgr.percent(count, num_maps, 1) - sp.register_map(map=map, dbif=dbif) - count += 1 + sp.register_map(map=map_object, dbif=dbif) # Update the space time tables if name and map_object_list: @@ -461,11 +445,11 @@ def register_maps_in_space_time_dataset( # Update affected datasets if datatsets_to_modify: for dataset in datatsets_to_modify: - if type == "rast" or type == "raster": + if type in ["rast", "raster"]: ds = dataset_factory("strds", dataset) - elif type == "raster_3d" or type == "rast3d" or type == "raster3d": + elif type in ["raster_3d", "rast3d", "raster3d"]: ds = dataset_factory("str3ds", dataset) - elif type == "vect" or type == "vector": + elif type in ["vect", "vector"]: ds = dataset_factory("stvds", dataset) ds.select(dbif) ds.update_from_registered_maps(dbif) @@ -480,7 +464,7 @@ def register_maps_in_space_time_dataset( def assign_valid_time_to_map( - ttype, map, start, end, unit, increment=None, mult=1, interval=False + ttype, map_object, start, end, unit, increment=None, mult=1, interval=False ): """Assign the valid time to a map dataset @@ -509,7 +493,7 @@ def assign_valid_time_to_map( start_time = string_to_datetime(start) if start_time is None: msgr.fatal( - _('Unable to convert string "%s"into a ' "datetime object") % (start) + _('Unable to convert string "{}" into a datetime object').format(start) ) end_time = None @@ -517,7 +501,9 @@ def assign_valid_time_to_map( end_time = string_to_datetime(end) if end_time is None: msgr.fatal( - _('Unable to convert string "%s"into a ' "datetime object") % (end) + _('Unable to convert string "{}" into a datetime object').format( + end + ) ) # Add the increment @@ -530,28 +516,32 @@ def assign_valid_time_to_map( if end_time is None: msgr.fatal(_("Error occurred in increment computation")) - if map.get_layer(): + if map_object.get_layer(): msgr.debug( 1, _( - "Set absolute valid time for map <%(id)s> with " - "layer %(layer)s to %(start)s - %(end)s" - ) - % { - "id": map.get_map_id(), - "layer": map.get_layer(), - "start": str(start_time), - "end": str(end_time), - }, + "Set absolute valid time for map <{id}> with " + "layer {layer} to {start} - {end}" + ).format( + id=map_object.get_map_id(), + layer=map_object.get_layer(), + start=str(start_time), + end=str(end_time), + ), ) else: msgr.debug( 1, - _("Set absolute valid time for map <%s> to %s - %s") - % (map.get_map_id(), str(start_time), str(end_time)), + _( + "Set absolute valid time for map <{mid}> to {start_time} - {end_time}" + ).format( + mid=map_object.get_map_id(), + start_time=str(start_time), + end_time=str(end_time), + ), ) - map.set_absolute_time(start_time, end_time) + map_object.set_absolute_time(start_time, end_time) else: start_time = int(start) end_time = None @@ -564,23 +554,34 @@ def assign_valid_time_to_map( if interval: end_time = start_time + int(increment) - if map.get_layer(): + if map_object.get_layer(): msgr.debug( 1, _( - "Set relative valid time for map <%s> with layer" - " %s to %i - %s with unit %s" - ) - % (map.get_map_id(), map.get_layer(), start_time, str(end_time), unit), + "Set relative valid time for map <{mid}> with layer" + " {layer} to {start} - {end} with unit {unit}" + ).format( + mid=map_object.get_map_id(), + layer=map_object.get_layer(), + start=start_time, + end=str(end_time), + unit=unit, + ), ) else: msgr.debug( 1, - _("Set relative valid time for map <%s> to %i - %s " "with unit %s") - % (map.get_map_id(), start_time, str(end_time), unit), + _( + "Set relative valid time for map <{mid}> to {start} - {end} with unit {unit}" + ).format( + mid=map_object.get_map_id(), + start=start_time, + end=str(end_time), + unit=unit, + ), ) - map.set_relative_time(start_time, end_time, unit) + map_object.set_relative_time(start_time, end_time, unit) ############################################################################## @@ -603,37 +604,34 @@ def register_map_object_list( import grass.pygrass.modules as pymod import copy - dbif, connection_state_changed = init_dbif(dbif) + dbif, connection_state_changed = init_dbif(None) - filename = gscript.tempfile(True) - file = open(filename, "w") - - empty_maps = [] - for map_layer in map_list: - # Read the map data - map_layer.load() - # In case of a empty map continue, do not register empty maps - - if delete_empty: - if type in ["raster", "raster_3d", "rast", "rast3d"]: - if ( - map_layer.metadata.get_min() is None - and map_layer.metadata.get_max() is None - ): - empty_maps.append(map_layer) - continue - if type == "vector": - if map_layer.metadata.get_number_of_primitives() == 0: - empty_maps.append(map_layer) - continue - - start, end = map_layer.get_temporal_extent_as_tuple() - id = map_layer.get_id() - if not end: - end = start - string = "%s|%s|%s\n" % (id, str(start), str(end)) - file.write(string) - file.close() + filename = gs.tempfile(True) + with open(filename, "w") as register_file: + empty_maps = [] + for map_layer in map_list: + # Read the map data + map_layer.load() + # In case of a empty map continue, do not register empty maps + if delete_empty: + if type in ["raster", "raster_3d", "rast", "rast3d"]: + if ( + map_layer.metadata.get_min() is None + and map_layer.metadata.get_max() is None + ): + empty_maps.append(map_layer) + continue + if type == "vector": + if map_layer.metadata.get_number_of_primitives() == 0: + empty_maps.append(map_layer) + continue + + start, end = map_layer.get_temporal_extent_as_tuple() + id = map_layer.get_id() + if not end: + end = start + string = f"{id}|{start}|{end}\n" + register_file.write(string) if output_stds: output_stds_id = output_stds.get_id() @@ -644,22 +642,21 @@ def register_map_object_list( type, output_stds_id, unit=unit, file=filename, dbif=dbif ) - g_remove = pymod.Module("g.remove", flags="f", quiet=True, run_=False, finish_=True) - # Remove empty maps and unregister them from the temporal database + g_remove = pymod.Module("g.remove", flags="f", quiet=True, run_=False, finish_=True) if len(empty_maps) > 0: - for map in empty_maps: + for map_object in empty_maps: mod = copy.deepcopy(g_remove) - if map.get_name(): - if map.get_type() == "raster": - mod(type="raster", name=map.get_name()) - if map.get_type() == "raster3d": - mod(type="raster_3d", name=map.get_name()) - if map.get_type() == "vector": - mod(type="vector", name=map.get_name()) + if map_object.get_name(): + if map_object.get_type() == "raster": + mod(type="raster", name=map_object.get_name()) + if map_object.get_type() == "raster3d": + mod(type="raster_3d", name=map_object.get_name()) + if map_object.get_type() == "vector": + mod(type="vector", name=map_object.get_name()) mod.run() - if map.is_in_db(dbif): - map.delete(dbif) + if map_object.is_in_db(dbif): + map_object.delete(dbif) if connection_state_changed: dbif.close() diff --git a/python/grass/temporal/space_time_datasets.py b/python/grass/temporal/space_time_datasets.py index df1da106e0c..9ec80a29068 100644 --- a/python/grass/temporal/space_time_datasets.py +++ b/python/grass/temporal/space_time_datasets.py @@ -9,6 +9,7 @@ :authors: Soeren Gebbert """ import getpass +from datetime import datetime from .abstract_map_dataset import AbstractMapDataset from .abstract_space_time_dataset import AbstractSpaceTimeDataset from .base import ( @@ -55,6 +56,8 @@ import grass.script.array as garray +GRASS_TIMESTAMP_FMT = "%a %b %d %H:%M:%S %Y" + ############################################################################### @@ -408,7 +411,16 @@ def load(self): return False # Fill base information - self.base.set_creator(str(getpass.getuser())) + kvp = self.ciface.read_raster_history(self.get_name(), self.get_mapset()) + + if kvp: + self.base.set_creator(kvp["creator"]) + self.base.set_ctime( + datetime.strptime(kvp["creation_time"], GRASS_TIMESTAMP_FMT) + ) + else: + self.base.set_creator(str(getpass.getuser())) + self.base.set_ctime() kvp = self.ciface.read_raster_info(self.get_name(), self.get_mapset()) @@ -790,7 +802,16 @@ def load(self): return False # Fill base information - self.base.set_creator(str(getpass.getuser())) + kvp = self.ciface.read_raster3d_history(self.get_name(), self.get_mapset()) + + if kvp: + self.base.set_creator(kvp["creator"]) + self.base.set_ctime( + datetime.strptime(kvp["creation_time"], GRASS_TIMESTAMP_FMT) + ) + else: + self.base.set_creator(str(getpass.getuser())) + self.base.set_ctime() # Fill spatial extent kvp = self.ciface.read_raster3d_info(self.get_name(), self.get_mapset()) @@ -1114,10 +1135,18 @@ def load(self): return False # Fill base information - self.base.set_creator(str(getpass.getuser())) + kvp = self.ciface.read_vector_history(self.get_name(), self.get_mapset()) - # Get the data from an existing vector map + if kvp: + self.base.set_creator(kvp["creator"]) + self.base.set_ctime( + datetime.strptime(kvp["creation_time"], GRASS_TIMESTAMP_FMT) + ) + else: + self.base.set_creator(str(getpass.getuser())) + self.base.set_ctime() + # Get the data from an existing vector map kvp = self.ciface.read_vector_info(self.get_name(), self.get_mapset()) if kvp: diff --git a/python/grass/temporal/testsuite/test_register_function.py b/python/grass/temporal/testsuite/test_register_function.py index fcf1ad756bf..923e4034027 100644 --- a/python/grass/temporal/testsuite/test_register_function.py +++ b/python/grass/temporal/testsuite/test_register_function.py @@ -24,7 +24,7 @@ def setUpClass(cls): os.putenv("GRASS_OVERWRITE", "1") # Use always the current mapset as temporal database cls.runModule("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1") - tgis.init() + cls.dbif = tgis.init() cls.use_temp_region() cls.runModule("g.region", n=80.0, s=0.0, e=120.0, w=0.0, t=1.0, b=0.0, res=10.0) @@ -72,7 +72,7 @@ def tearDown(self): self.runModule( "t.unregister", type="raster", - maps="register_map_1,register_map_2", + maps="register_map_1,register_map_2,elevation", quiet=True, ) self.runModule( @@ -118,7 +118,8 @@ def test_absolute_time_strds_1(self): def test_absolute_time_strds_2(self): """Test the registration of maps with absolute time in a space time raster dataset. - The timestamps are set using the C-Interface beforehand, so that the register function needs + The timestamps are set using the C-Interface beforehand, + so that the register function needs to read the timetsamp from the map metadata. """ @@ -155,9 +156,10 @@ def test_absolute_time_strds_2(self): def test_absolute_time_strds_3(self): """Test the registration of maps with absolute time in a - space time raster dataset. The timestamps are set via method arguments and with the - c-interface. The timestamps of the method arguments should overwrite the - time stamps set via the C-interface. + space time raster dataset. The timestamps are set via method + arguments and with the c-interface. The timestamps of the + method arguments should overwrite the time stamps set via the + C-interface. """ ciface = tgis.get_tgis_c_library_interface() @@ -187,9 +189,10 @@ def test_absolute_time_strds_3(self): def test_absolute_time_strds_4(self): """Test the registration of maps with absolute time in a - space time raster dataset. The timestamps are set via method arguments and with the - c-interface. The timestamps of the method arguments should overwrite the - time stamps set via the C-interface. The C-interface sets relative time stamps. + space time raster dataset. The timestamps are set via method + arguments and with the c-interface. The timestamps of the method + arguments should overwrite the time stamps set via the C-interface. + The C-interface sets relative time stamps. """ ciface = tgis.get_tgis_c_library_interface() @@ -258,7 +261,8 @@ def test_absolute_time_1(self): def test_absolute_time_2(self): """Test the registration of maps with absolute time - using register_maps_in_space_time_dataset() and register_map_object_list() with empty map deletion + using register_maps_in_space_time_dataset() and + register_map_object_list() with empty map deletion """ tgis.register_maps_in_space_time_dataset( type="raster", @@ -300,9 +304,58 @@ def test_absolute_time_2(self): map_3 = tgis.VectorDataset("register_map_null@" + tgis.get_current_mapset()) self.assertEqual(map_3.map_exists(), False) + def test_history_raster(self): + """Test that raster maps are registered with the history + (creator and creation time) of the raster map itself (and from a + different mapset (PERMANENT) + """ + tgis.register_maps_in_space_time_dataset( + type="raster", + name=None, + maps="elevation@PERMANENT", + start="2001-01-01 10:30:01", + increment="1 year", + interval=True, + dbif=self.dbif, + ) + + map_1 = tgis.RasterDataset("elevation@PERMANENT") + map_1.select(self.dbif, tgis.get_current_mapset()) + # Test that creation time of the map is used + self.assertEqual( + map_1.base.get_ctime(), datetime.datetime(2006, 11, 7, 1, 9, 51) + ) + # Test that registered creator of the map is not the current user + self.assertEqual(map_1.base.get_creator(), "helena") + + def test_history_vector(self): + """Test that vector maps are registered with the history (creator + and creation time) of the vector map itself (and from a + different mapset (PERMANENT) + """ + tgis.register_maps_in_space_time_dataset( + type="vector", + name=None, + maps="lakes@PERMANENT", + start="2001-01-01 10:30:01", + increment="1 year", + interval=True, + dbif=self.dbif, + ) + + map_1 = tgis.VectorDataset("lakes@PERMANENT") + map_1.select(self.dbif, tgis.get_current_mapset()) + # Test that creation time of the map is used + self.assertEqual( + map_1.base.get_ctime(), datetime.datetime(2006, 11, 7, 19, 48, 8) + ) + # Test that registered creator of the map is not the current user + self.assertTrue(map_1.base.get_creator(), "helena") + def test_absolute_time_3(self): """Test the registration of maps with absolute time. - The timestamps are set using the C-Interface beforehand, so that the register function needs + The timestamps are set using the C-Interface beforehand, + so that the register function needs to read the timetsamp from the map metadata. """ @@ -365,8 +418,8 @@ def test_relative_time_strds_1(self): def test_relative_time_strds_2(self): """Test the registration of maps with relative time in a - space time raster dataset. The timetsamps are set for the maps using the - C-interface before registration. + space time raster dataset. The timestamps are set for the maps + using the C-interface before registration. """ ciface = tgis.get_tgis_c_library_interface() ciface.write_raster_timestamp( @@ -459,8 +512,8 @@ def test_relative_time_2(self): self.assertEqual(unit, "seconds") def test_relative_time_3(self): - """Test the registration of maps with relative time. The timetsamps are set beforehand using - the C-interface. + """Test the registration of maps with relative time. The + timestamps are set beforehand using the C-interface. """ ciface = tgis.get_tgis_c_library_interface() ciface.write_raster_timestamp( @@ -619,7 +672,8 @@ def test_absolute_time_stvds_1(self): def test_absolute_time_stvds_2(self): """Test the registration of maps with absolute time in a space time raster dataset. - The timestamps are set using the C-Interface beforehand, so that the register function needs + The timestamps are set using the C-Interface beforehand, + so that the register function needs to read the timetsamp from the map metadata. """ @@ -656,9 +710,9 @@ def test_absolute_time_stvds_2(self): def test_absolute_time_stvds_3(self): """Test the registration of maps with absolute time in a - space time raster dataset. The timestamps are set via method arguments and with the - c-interface. The timestamps of the method arguments should overwrite the - time stamps set via the C-interface. + space time raster dataset. The timestamps are set via method + arguments and with the C-interface. The timestamps of the method + arguments should overwrite the time stamps set via the C-interface. """ ciface = tgis.get_tgis_c_library_interface() @@ -687,7 +741,8 @@ def test_absolute_time_stvds_3(self): self.assertEqual(end, datetime.datetime(2001, 2, 2)) def test_absolute_time_1(self): - """Register vector maps in the temporal database and in addition in a stvds using the object method + """Register vector maps in the temporal database and in addition + in a stvds using the object method :return: """