Browse Source

TGIS: new module t.copy (#1930)

* new module t.copy

Co-authored-by: Veronica Andreo <veroandreo@gmail.com>
Co-authored-by: Guido Riembauer <62383722+griembauer@users.noreply.github.com>
Markus Metz 2 years ago
parent
commit
0e34c62103

+ 1 - 0
temporal/Makefile

@@ -2,6 +2,7 @@ MODULE_TOPDIR = ..
 
 SUBDIRS = \
 	t.connect \
+	t.copy \
 	t.create \
 	t.support \
 	t.topology \

+ 7 - 0
temporal/t.copy/Makefile

@@ -0,0 +1,7 @@
+MODULE_TOPDIR = ../../
+
+PGM = t.copy
+
+include $(MODULE_TOPDIR)/include/Make/Script.make
+
+default: script $(TEST_DST)

+ 46 - 0
temporal/t.copy/t.copy.html

@@ -0,0 +1,46 @@
+<h2>DESCRIPTION</h2>
+
+The purpose of <b>t.copy</b> is to create a copy of a space
+time dataset. The new space time dataset will be located in the
+current mapset, whereas the old space time dataset can be located in a
+different mapset as long as this mapset is in the search path.
+
+<p>
+If the <em>-c</em> flag is given, the maps of the old space time dataset
+will also be copied to the current mapset, otherwise the original maps
+will be simply registered in the temporal database. The new copies will
+have the same name as the old maps.
+
+<h2>NOTES</h2>
+A fully qualified name for the input space-time dataset is only required
+if space-time datasets with the same name exist in different mapsets.
+
+<h2>EXAMPLE</h2>
+
+In the North Carolina sample dataset with the separately available
+mapset <em>modis_lst</em> included, copy the space-time raster dataset
+<em>LST_Day_monthly@modis_lst</em> to the current mapset <em>user1</em>:
+
+<div class="code"><pre>
+g.mapsets mapset=modis_lst operation=add
+t.copy input=LST_Day_monthly@modis_lst type=strds output=LST_Day_monthly
+</pre></div>
+
+<h2>SEE ALSO</h2>
+
+<em>
+<a href="t.rast.extract.html">t.rast.extract</a>,
+<a href="t.rast3d.extract.html">t.rast3d.extract</a>,
+<a href="t.vect.extract.html">t.vect.extract</a>,
+<a href="t.info.html">t.info</a>
+</em>
+
+<h2>AUTHOR</h2>
+
+Markus Metz, mundialis
+
+<!--
+<p>
+<i>Last changed: $Date$</i>
+-->
+

+ 214 - 0
temporal/t.copy/t.copy.py

@@ -0,0 +1,214 @@
+#!/usr/bin/env python3
+
+############################################################################
+#
+# MODULE:	    t.copy
+# AUTHOR(S):	Markus Metz
+#
+# PURPOSE:	    Create a copy of a space time dataset
+# COPYRIGHT:	(C) 2021 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: Creates a copy of a space time raster dataset.
+# % keyword: temporal
+# % keyword: copy
+# % keyword: time
+# %end
+
+# %option G_OPT_STDS_INPUT
+# %end
+
+# %option G_OPT_STDS_TYPE
+# % guidependency: input
+# % guisection: Required
+# % options: strds, str3ds, stvds
+# %end
+
+# %option G_OPT_STDS_OUTPUT
+# %end
+
+# %flag
+# % key: c
+# % label: Also copy maps of the space-time dataset
+# % description: By default the old maps are only registered in the new dataset.
+# %end
+
+
+import grass.script as gscript
+
+
+############################################################################
+
+
+def main():
+    # lazy imports
+    import grass.temporal as tgis
+
+    # Get the options
+    input = options["input"]
+    output = options["output"]
+    stdstype = options["type"]
+    copy_maps = flags["c"]
+
+    maptype = "raster"
+    element = "cell"
+    if stdstype == "str3ds":
+        maptype = "raster_3d"
+        element = "g3dcell"
+    elif stdstype == "stvds":
+        maptype = "vector"
+        element = "vector"
+
+    # Make sure the temporal database exists
+    tgis.init()
+
+    # Get the current mapset to create the id of the space time dataset
+    mapset = gscript.gisenv()["MAPSET"]
+
+    inname = input
+    inmapset = mapset
+    if "@" in input:
+        inname, inmapset = input.split("@")
+
+    outname = output
+    outmapset = mapset
+    if "@" in output:
+        outname, outmapset = output.split("@")
+        if outmapset != mapset:
+            gscript.fatal(
+                _("The output dataset <%s> must be in the current mapset<%s>.")
+                % (input, mapset)
+            )
+
+    msgr = tgis.get_tgis_message_interface()
+
+    dbif = tgis.SQLDatabaseInterfaceConnection()
+    dbif.connect()
+
+    old_sp = tgis.open_old_stds(input, stdstype, dbif)
+    old_maps = old_sp.get_registered_maps_as_objects(dbif=dbif)
+
+    if not old_maps:
+        dbif.close()
+        gscript.warning(
+            _("Empty space-time %s dataset <%s>, nothing to copy") % (maptype, input)
+        )
+        return
+
+    overwrite = gscript.overwrite()
+
+    # Check the new stds
+    new_sp = tgis.check_new_stds(output, stdstype, dbif, overwrite)
+
+    new_maps = None
+    if copy_maps:
+        gscript.message(_("Copying %s maps to the current mapset...") % maptype)
+        new_maps = []
+        num_maps = len(old_maps)
+        count = 0
+
+        for map in old_maps:
+            count += 1
+            map_id = map.get_id()
+            map_name = map_id
+            map_mapset = mapset
+            if "@" in map_id:
+                map_name, map_mapset = map_id.split("@")
+
+            if map_mapset != mapset:
+                found = gscript.find_file(name=map_name, element=element, mapset=mapset)
+                if found["name"] is not None and len(found["name"]) > 0:
+                    gscript.fatal(
+                        _("A %s map <%s> exists already in the current mapset <%s>.")
+                        % (maptype, map_name, mapset)
+                    )
+
+                kwargs = {maptype: "%s,%s" % (map_id, map_name)}
+                gscript.run_command("g.copy", **kwargs)
+            else:
+                # the map is already in the current mapset
+                gscript.message(
+                    _("The %s map <%s> is already in the current mapset, not copying")
+                    % (maptype, map_name)
+                )
+
+            if count % 10 == 0:
+                msgr.percent(count, num_maps, 1)
+
+            # We need to build the id
+            if maptype != "vector":
+                map_id = tgis.AbstractMapDataset.build_id(map_name, mapset)
+            else:
+                map_id = tgis.AbstractMapDataset.build_id(
+                    map_name, mapset, map.get_layer()
+                )
+
+            new_map = tgis.open_new_map_dataset(
+                map_name,
+                None,
+                type="raster",
+                temporal_extent=map.get_temporal_extent(),
+                overwrite=overwrite,
+                dbif=dbif,
+            )
+            # semantic label
+            semantic_label = map.metadata.get_semantic_label()
+            if semantic_label is not None and semantic_label != "None":
+                new_map.metadata.set_semantic_label(semantic_label)
+
+            new_maps.append(new_map)
+    else:
+        # don't copy maps, use old maps
+        new_maps = old_maps
+
+    temporal_type, semantic_type, title, description = old_sp.get_initial_values()
+    new_sp = tgis.open_new_stds(
+        output,
+        stdstype,
+        old_sp.get_temporal_type(),
+        title,
+        description,
+        semantic_type,
+        dbif,
+        gscript.overwrite(),
+    )
+
+    # Register the maps in the database
+    num_maps = len(new_maps)
+    count = 0
+    for map in new_maps:
+        count += 1
+
+        # Insert map in temporal database
+        if not map.is_in_db(dbif, mapset):
+            semantic_label = map.metadata.get_semantic_label()
+            map.load()
+            # semantic labels are not yet properly implemented in TGIS
+            if semantic_label is not None and semantic_label != "None":
+                map.metadata.set_semantic_label(semantic_label)
+            map.insert(dbif)
+        new_sp.register_map(map, dbif)
+
+    # Update the spatio-temporal extent and the metadata table entries
+    new_sp.update_from_registered_maps(dbif)
+
+    dbif.close()
+
+
+###############################################################################
+
+if __name__ == "__main__":
+    options, flags = gscript.parser()
+    main()

+ 167 - 0
temporal/t.copy/testsuite/test_t_copy.py

@@ -0,0 +1,167 @@
+"""Test t.copy
+
+(C) 2014 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 Markus Metz
+"""
+
+from grass.gunittest.case import TestCase
+from grass.gunittest.gmodules import SimpleModule
+
+
+class TestSTDSCopy(TestCase):
+    @classmethod
+    def setUpClass(cls):
+        """Initiate the temporal GIS and set the region"""
+        cls.use_temp_region()
+        cls.runModule("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1")
+        cls.runModule("g.region", s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10)
+
+    @classmethod
+    def tearDownClass(cls):
+        """Remove the temporary region"""
+        cls.del_temp_region()
+        cls.runModule("g.remove", flags="f", type="raster", pattern="prec_*")
+
+    def setUp(self):
+        """Create input data for t.copy"""
+        # Use always the current mapset as temporal database
+        self.runModule("r.mapcalc", expression="prec_1 = 100", overwrite=True)
+        self.runModule("r.mapcalc", expression="prec_2 = 200", overwrite=True)
+        self.runModule("r.mapcalc", expression="prec_3 = 300", overwrite=True)
+        self.runModule("r.mapcalc", expression="prec_4 = 400", overwrite=True)
+        self.runModule("r.mapcalc", expression="prec_5 = 500", overwrite=True)
+        self.runModule("r.mapcalc", expression="prec_6 = 600", overwrite=True)
+
+        self.runModule(
+            "t.create",
+            type="strds",
+            temporaltype="absolute",
+            output="precip_abs1",
+            title="A test",
+            description="A test",
+            overwrite=True,
+        )
+        self.runModule(
+            "t.register",
+            flags="i",
+            type="raster",
+            input="precip_abs1",
+            maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6",
+            start="2001-01-01",
+            increment="3 months",
+            overwrite=True,
+        )
+
+    def tearDown(self):
+        """Remove generated data"""
+        self.runModule("t.remove", flags="df", type="strds", inputs="precip_abs1")
+
+    def test_copy(self):
+        """Copy a strds"""
+        self.assertModule(
+            "t.copy",
+            input="precip_abs1",
+            output="precip_abs2",
+            type="strds",
+        )
+
+        # self.assertModule("t.info",  flags="g",  input="precip_abs2")
+
+        tinfo_string = """start_time='2001-01-01 00:00:00'
+        end_time='2002-07-01 00:00:00'
+        granularity='3 months'
+        map_time=interval
+        north=80.0
+        south=0.0
+        east=120.0
+        west=0.0
+        top=0.0
+        bottom=0.0
+        aggregation_type=None
+        number_of_maps=6
+        nsres_min=10.0
+        nsres_max=10.0
+        ewres_min=10.0
+        ewres_max=10.0
+        min_min=100.0
+        min_max=600.0
+        max_min=100.0
+        max_max=600.0"""
+
+        info = SimpleModule("t.info", flags="g", input="precip_abs2")
+        self.assertModuleKeyValue(
+            module=info, reference=tinfo_string, precision=2, sep="="
+        )
+        self.runModule("t.remove", flags="df", type="strds", inputs="precip_abs2")
+
+
+class TestSTDSCopyFails(TestCase):
+    @classmethod
+    def setUpClass(cls):
+        """Initiate the temporal GIS and set the region"""
+        cls.use_temp_region()
+        cls.runModule("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1")
+        cls.runModule("g.region", s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10)
+        cls.runModule("r.mapcalc", expression="prec_1 = 100", overwrite=True)
+        cls.runModule("r.mapcalc", expression="prec_2 = 200", overwrite=True)
+        cls.runModule("r.mapcalc", expression="prec_3 = 300", overwrite=True)
+        cls.runModule("r.mapcalc", expression="prec_4 = 400", overwrite=True)
+        cls.runModule("r.mapcalc", expression="prec_5 = 500", overwrite=True)
+        cls.runModule("r.mapcalc", expression="prec_6 = 600", overwrite=True)
+
+        cls.runModule(
+            "t.create",
+            type="strds",
+            temporaltype="absolute",
+            output="precip_abs1",
+            title="A test",
+            description="A test",
+            overwrite=True,
+        )
+        cls.runModule(
+            "t.register",
+            flags="i",
+            type="raster",
+            input="precip_abs1",
+            maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6",
+            start="2001-01-01",
+            increment="3 months",
+            overwrite=True,
+        )
+
+    @classmethod
+    def tearDownClass(cls):
+        """Remove the temporary region"""
+        cls.del_temp_region()
+        cls.runModule("t.remove", flags="df", type="strds", inputs="precip_abs1")
+        cls.runModule("g.remove", flags="f", type="raster", pattern="prec_*")
+
+    def test_error_handling(self):
+        """Test arguments for t.copy"""
+        # No input
+        self.assertModuleFail("t.copy", output="precip_abs2")
+        # No output
+        self.assertModuleFail("t.copy", input="precip_abs1")
+        # Wrong type
+        self.assertModuleFail(
+            "t.copy",
+            input="precip_abs1",
+            output="precip_abs2",
+            type="stvds",
+        )
+        # Input does not exist
+        self.assertModuleFail(
+            "t.copy",
+            input="precip_abs1@this_mapset_does_not_exist",
+            output="precip_abs2",
+        )
+
+
+if __name__ == "__main__":
+    from grass.gunittest.main import test
+
+    test()