Переглянути джерело

i.* + r.*: minor backports; testsuites backported

git-svn-id: https://svn.osgeo.org/grass/grass/branches/releasebranch_7_0@63919 15284696-431f-4ddb-bdfa-cd5b030d7da7
Markus Neteler 10 роки тому
батько
коміт
37dbda417d
28 змінених файлів з 1718 додано та 94 видалено
  1. 1 1
      imagery/i.atcorr/aerosolmodel.cpp
  2. 2 0
      imagery/i.landsat.toar/main.c
  3. 3 0
      imagery/i.segment/write_output.c
  4. 1 1
      imagery/imageryintro.html
  5. 2 2
      raster/r.contour/cont.c
  6. 89 0
      raster/r.gwflow/testsuite/validation_7x7_grid.py
  7. 87 0
      raster/r.gwflow/testsuite/validation_excavation.py
  8. 0 43
      raster/r.gwflow/valid_calc_7x7.py
  9. 0 38
      raster/r.gwflow/valid_calc_excavation.py
  10. 111 0
      raster/r.in.poly/testsuite/test_rinpoly.py
  11. 177 0
      raster/r.mapcalc/testsuite/const_map_test.sh
  12. 56 0
      raster/r.mapcalc/testsuite/test_r3_mapcalc.py
  13. 223 0
      raster/r.mapcalc/testsuite/test_r_mapcalc.py
  14. 190 0
      raster/r.profile/testsuite/test_profile_ncspm.py
  15. 95 0
      raster/r.recode/testsuite/test_rrecode_ncspm.py
  16. 1 1
      raster/r.series.accumulate/test_suite/test.r.series.accumulate.sh
  17. 2 1
      raster/r.series.interp/main.c
  18. 16 0
      raster/r.slope.aspect/testsuite/data/fractal_surf.ascii
  19. 25 0
      raster/r.slope.aspect/testsuite/data/gdal_aspect.grd
  20. 5 0
      raster/r.slope.aspect/testsuite/data/gdal_aspect.grd.aux.xml
  21. 25 0
      raster/r.slope.aspect/testsuite/data/gdal_slope.grd
  22. 5 0
      raster/r.slope.aspect/testsuite/data/gdal_slope.grd.aux.xml
  23. 163 0
      raster/r.slope.aspect/testsuite/test_r_slope_aspect.py
  24. 4 4
      raster/r.sunmask/main.c
  25. 6 3
      raster/r.viewshed/main.cpp
  26. 153 0
      raster/r.viewshed/testsuite/data/elevation.ascii
  27. 153 0
      raster/r.viewshed/testsuite/data/lake_viewshed.ascii
  28. 123 0
      raster/r.viewshed/testsuite/test_r_viewshed.py

+ 1 - 1
imagery/i.atcorr/aerosolmodel.cpp

@@ -385,7 +385,7 @@ void AerosolModel::exscphase(const double X, const double nr,
 
 	for(int k = 1; k <= mu; k++)
 	{
-	    double co_n = (2 * k + 1) / k / (k + 1);
+	    double co_n = (2.0 * k + 1) / k / (k + 1);
 	    RS1 += co_n * (RAn[k] * PIn[k] + RBn[k] * TAUn[k]);
 	    RS2 += co_n * (RAn[k] * TAUn[k] + RBn[k] * PIn[k]);
 	    IS1 += co_n * (IAn[k] * PIn[k] + IBn[k] * TAUn[k]);

+ 2 - 0
imagery/i.landsat.toar/main.c

@@ -721,6 +721,8 @@ int main(int argc, char *argv[])
 	    Rast_write_units(band_out, "W/(m^2 sr um)");
 	else
 	    Rast_write_units(band_out, "unitless");
+
+	/*  set raster timestamp from acq date? (see r.timestamp module)  */
     }
     Rast_set_window(&orig_cellhd);
 

+ 3 - 0
imagery/i.segment/write_output.c

@@ -209,6 +209,9 @@ int write_output(struct globals *globals)
 	}
 	G_free(inbuf);
 	G_free(in_fd);
+	G_free(fp_range);
+	G_free(min);
+	G_free(max);
     }
 
     /* free memory */

+ 1 - 1
imagery/imageryintro.html

@@ -1,6 +1,6 @@
 <!-- meta page description: Image processing in GRASS GIS -->
 <!-- meta page index: imagery -->
-<h3>General introduction</h3>
+<h3>Image processing in general</h3>
 
 <b>Digital numbers and physical values (reflection/radiance-at-sensor):</b>
 <p>

+ 2 - 2
raster/r.contour/cont.c

@@ -346,9 +346,9 @@ static void getpoint(struct cell *curr, double level,
     if (Rast_raster_cmp(&curr->z[p1], &curr->z[p2], DCELL_TYPE) == 0)
 	ratio = 1;
     else if (Rast_is_d_null_value(&curr->z[p1]))
-	ratio = 1 / 2;
+	ratio = 0.5;
     else if (Rast_is_d_null_value(&curr->z[p2]))
-	ratio = 1 / 2;
+	ratio = 0.5;
     else
 	ratio = (level - curr->z[p1]) / (curr->z[p2] - curr->z[p1]);
 

+ 89 - 0
raster/r.gwflow/testsuite/validation_7x7_grid.py

@@ -0,0 +1,89 @@
+"""Test to verify r.gwflow calculation, this calculation is based on
+the example at page 133 of the following book:
+author = "Kinzelbach, W. and Rausch, R.",
+title = "Grundwassermodellierung",
+publisher = "Gebr{\"u}der Borntraeger (Berlin, Stuttgart)",
+year = "1995"
+
+@author Soeren Gebbert
+"""
+from grass.gunittest.case import TestCase
+
+class Validation7x7Grid(TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        """Use temporary region settings"""
+        cls.use_temp_region()
+        cls.runModule("g.region", res=100, n=700, s=0, w=0, e=700)
+
+    @classmethod
+    def tearDownClass(cls):
+        """!Remove the temporary region
+        """
+        cls.del_temp_region()
+
+    def setUp(self):
+        """Create input data for transient groundwater flow computation
+        """
+        self.runModule("r.mapcalc", expression="phead=50")
+        self.runModule("r.mapcalc", expression="status=if(col() == 1 || col() == 7 , 2, 1)")
+        self.runModule("r.mapcalc", expression="well=if((row() == 4 && col() == 4), -0.1, 0)")
+        self.runModule("r.mapcalc", expression="hydcond=0.0005")
+        self.runModule("r.mapcalc", expression="recharge=0")
+        self.runModule("r.mapcalc", expression="top_conf=20")
+        self.runModule("r.mapcalc", expression="bottom=0")
+        self.runModule("r.mapcalc", expression="s=0.0001")
+        self.runModule("r.mapcalc", expression="null=0.0")
+
+    def test_transient(self):
+        #First compute the groundwater flow after 500 seconds to have initial conditions
+        self.assertModule("r.gwflow", flags="f", solver="cholesky", top="top_conf", bottom="bottom", phead="phead",\
+         status="status", hc_x="hydcond", hc_y="hydcond", q="well", s="s",\
+         recharge="recharge", output="gwresult_conf", dtime=500, type="confined", budget="water_budget",  overwrite=True)
+
+        # loop over the timesteps each 500 seconds
+        for i in range(20):
+            self.assertModule("r.gwflow",  flags="f", solver="cholesky", top="top_conf", bottom="bottom", phead="gwresult_conf",\
+             status="status", hc_x="hydcond", hc_y="hydcond", q="well", s="s",\
+             recharge="recharge", output="gwresult_conf", dtime=500, type="confined", budget="water_budget",  overwrite=True)
+
+        # Output of r.univar
+        univar_string="""n=49
+        null_cells=0
+        cells=49
+        min=45.1219899394172
+        max=50
+        range=4.8780100605828
+        mean=49.081632669812
+        mean_of_abs=49.081632669812
+        stddev=0.908558909200636
+        variance=0.825479291487849
+        coeff_var=1.85111794326975
+        sum=2405.00000082079"""
+        
+        # Output of r.info, only a subset of the output is needed
+        info_string="""north=700
+        south=0
+        east=700
+        west=0
+        nsres=100
+        ewres=100
+        rows=7
+        cols=7
+        cells=49
+        datatype=DCELL
+        ncats=0
+        min=45.1219899394172
+        max=50
+        map=gwresult_conf"""
+        
+        self.assertRasterFitsUnivar(raster="gwresult_conf",  reference=univar_string,  precision=3)
+        self.assertRasterFitsInfo(raster="gwresult_conf",  reference=info_string,  precision=3)
+
+
+if __name__ == '__main__':
+    from grass.gunittest.main import test
+    test()
+
+

+ 87 - 0
raster/r.gwflow/testsuite/validation_excavation.py

@@ -0,0 +1,87 @@
+"""Test to verify r.gwflow calculation, this calculation is based on
+the example at page 167 of the following book:
+author = "Kinzelbach, W. and Rausch, R.",
+title = "Grundwassermodellierung",
+publisher = "Gebr{\"u}der Borntraeger (Berlin, Stuttgart)",
+year = "1995"
+
+@author Soeren Gebbert
+"""
+
+from grass.gunittest.case import TestCase
+
+class ValidationExcavation(TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        """Use temporary region settings"""
+        cls.use_temp_region()
+        cls.runModule("g.region", flags="p",  res=50, n=950, s=0, w=0, e=2000)
+
+    @classmethod
+    def tearDownClass(cls):
+        """!Remove the temporary region
+        """
+        cls.del_temp_region()
+
+    def setUp(self):
+        """Create input data for steady state groundwater flow computation
+        """
+        self.runModule("r.mapcalc", expression="phead= if(row() == 19, 5, 3)")
+        self.runModule("r.mapcalc", expression="status=if((col() == 1 && row() == 13) ||\
+                                      (col() == 1 && row() == 14) ||\
+                                      (col() == 2 && row() == 13) ||\
+                                      (col() == 2 && row() == 14) ||\
+                                      (row() == 19), 2, 1)")
+
+        self.runModule("r.mapcalc", expression="hydcond=0.001")
+        self.runModule("r.mapcalc", expression="recharge=0.000000006")
+        self.runModule("r.mapcalc", expression="top=20")
+        self.runModule("r.mapcalc", expression="bottom=0")
+        self.runModule("r.mapcalc", expression="poros=0.1")
+        self.runModule("r.mapcalc", expression="null=0.0")
+
+    def test_steady_state(self):
+        #compute a steady state groundwater flow
+        self.assertModule("r.gwflow", flags="f", solver="cholesky", top="top", bottom="bottom", phead="phead", \
+            status="status", hc_x="hydcond", hc_y="hydcond", s="poros", \
+            recharge="recharge", output="gwresult", dtime=864000000000, type="unconfined", budget="water_budget")
+        
+        # Output of r.univar -g
+        univar_string="""n=760
+        null_cells=0
+        cells=760
+        min=3
+        max=5.39762629189687
+        range=2.39762629189687
+        mean=5.02846950820457
+        mean_of_abs=5.02846950820457
+        stddev=0.333565013446849
+        variance=0.111265618195797
+        coeff_var=6.63352960384062
+        sum=3821.63682623547"""
+
+        # Output of r.info -gre, only a subset of the output is needed
+        info_string="""north=950
+        south=0
+        east=2000
+        west=0
+        nsres=50
+        ewres=50
+        rows=19
+        cols=40
+        cells=760
+        datatype=DCELL
+        ncats=0
+        min=3
+        max=5.3976262918968
+        map=gwresult"""
+        
+        self.assertRasterFitsUnivar(raster="gwresult",  reference=univar_string,  precision=3)
+        self.assertRasterFitsInfo(raster="gwresult",  reference=info_string,  precision=3)
+
+if __name__ == '__main__':
+    from grass.gunittest.main import test
+    test()
+
+

+ 0 - 43
raster/r.gwflow/valid_calc_7x7.py

@@ -1,43 +0,0 @@
-#!/usr/bin/env python
-# Python script to verify r.gwflow calculation, this calculation is based on
-# the example at page 133 of the following book:
-#	author = "Kinzelbach, W. and Rausch, R.",
-#	title = "Grundwassermodellierung",
-#	publisher = "Gebr{\"u}der Borntraeger (Berlin, Stuttgart)",
-#	year = "1995"
-#
-import sys
-import os
-import grass.script as grass
-
-# Overwrite existing maps
-grass.run_command("g.gisenv", set="OVERWRITE=1")
-
-grass.message(_("Set the region"))
-
-# The area is 2000m x 1000m with a cell size of 25m x 25m
-grass.run_command("g.region", res=100, n=700, s=0, w=0, e=700)
-
-grass.run_command("r.mapcalc", expression="phead=50")
-grass.run_command("r.mapcalc", expression="status=if(col() == 1 || col() == 7 , 2, 1)")
-grass.run_command("r.mapcalc", expression="well=if((row() == 4 && col() == 4), -0.1, 0)")
-grass.run_command("r.mapcalc", expression="hydcond=0.0005")
-grass.run_command("r.mapcalc", expression="recharge=0")
-grass.run_command("r.mapcalc", expression="top_conf=20")
-grass.run_command("r.mapcalc", expression="bottom=0")
-grass.run_command("r.mapcalc", expression="s=0.0001")
-grass.run_command("r.mapcalc", expression="null=0.0")
-
-#First compute the groundwater flow
-grass.run_command("r.gwflow", "f", solver="cholesky", top="top_conf", bottom="bottom", phead="phead",\
- status="status", hc_x="hydcond", hc_y="hydcond", q="well", s="s",\
- recharge="recharge", output="gwresult_conf", dt=500, type="confined", budget="water_budget")
-
-count=500
-# loop over the timesteps
-for i in range(20):
-    grass.run_command("r.gwflow", "f", solver="cholesky", top="top_conf", bottom="bottom", phead="gwresult_conf",\
-     status="status", hc_x="hydcond", hc_y="hydcond", q="well", s="s",\
-     recharge="recharge", output="gwresult_conf", dt=500, type="confined", budget="water_budget")
-    count += 500
-

+ 0 - 38
raster/r.gwflow/valid_calc_excavation.py

@@ -1,38 +0,0 @@
-#!/usr/bin/env python
-# Shellscript to verify r.gwflow calculation, this calculation is based on
-# the example at page 167 of the following book:
-#	author = "Kinzelbach, W. and Rausch, R.",
-#	title = "Grundwassermodellierung",
-#	publisher = "Gebr{\"u}der Borntraeger (Berlin, Stuttgart)",
-#	year = "1995"
-#
-import sys
-import os
-import grass.script as grass
-
-# Overwrite existing maps
-grass.run_command("g.gisenv", set="OVERWRITE=1")
-
-grass.message(_("Set the region"))
-
-# The area is 2000m x 1000m with a cell size of 25m x 25m
-grass.run_command("g.region", res=50, n=950, s=0, w=0, e=2000)
-
-grass.run_command("r.mapcalc", expression="phead= if(row() == 19, 5, 3)")
-grass.run_command("r.mapcalc", expression="status=if((col() == 1 && row() == 13) ||\
-                     (col() == 1 && row() == 14) ||\
-		     (col() == 2 && row() == 13) ||\
-		     (col() == 2 && row() == 14) ||\
-		     (row() == 19), 2, 1)")
-
-grass.run_command("r.mapcalc", expression="hydcond=0.001")
-grass.run_command("r.mapcalc", expression="recharge=0.000000006")
-grass.run_command("r.mapcalc", expression="top=20")
-grass.run_command("r.mapcalc", expression="bottom=0")
-grass.run_command("r.mapcalc", expression="poros=0.1")
-grass.run_command("r.mapcalc", expression="null=0.0")
-
-#compute a steady state groundwater flow
-grass.run_command("r.gwflow", "f", solver="cholesky", top="top", bottom="bottom", phead="phead", \
- status="status", hc_x="hydcond", hc_y="hydcond", s="poros", \
- recharge="recharge", output="gwresult", dt=864000000000, type="unconfined", budget="water_budget")

+ 111 - 0
raster/r.in.poly/testsuite/test_rinpoly.py

@@ -0,0 +1,111 @@
+import os
+import tempfile
+import grass.gunittest
+from grass.script.core import read_command
+
+
+input1 = \
+"""
+A
+634308.630394 223320.356473
+640640.712946 223092.401501
+641248.592871 217748.123827
+= 10.01 label1
+A
+639576.923077 222256.566604
+639045.028143 216329.737336
+637702.626642 224662.757974
+= -8
+L
+633523.452158 222231.238274
+642565.666041 221218.105066
+641957.786116 222585.834897
+= 3 label2
+"""
+
+
+class TestRInPoly(grass.gunittest.TestCase):
+
+    rinpoly = 'test_rinpoly'
+
+    @classmethod
+    def setUpClass(cls):
+        cls.use_temp_region()
+        cls.runModule('g.region', raster='elevation')
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.del_temp_region()
+
+    def setUp(self):
+        self.tmpFile = tempfile.NamedTemporaryFile(delete=False)
+
+    def tearDown(self):
+        """Remove rinpoly map after each test method"""
+        self.runModule('g.remove', flags='f', type='raster',
+                       name=self.rinpoly)
+        os.unlink(self.tmpFile.name)
+
+    def testTypeCell(self):
+        """Test type of resulting map"""
+        self.tmpFile.write(input1)
+        self.tmpFile.close()
+        self.assertModule('r.in.poly', input=self.tmpFile.name, output=self.rinpoly, type='CELL')
+        minmax = 'min=-8\nmax=10\ndatatype=CELL'
+        self.assertRasterFitsInfo(raster=self.rinpoly, reference=minmax)
+
+    def testTypeFCell(self):
+        """Test type of resulting map"""
+        self.tmpFile.write(input1)
+        self.tmpFile.close()
+        self.assertModule('r.in.poly', input=self.tmpFile.name, output=self.rinpoly, type='FCELL')
+        minmax = 'min=-8\nmax=10.01\ndatatype=FCELL'
+        self.assertRasterFitsInfo(raster=self.rinpoly, reference=minmax, precision=1e-8)
+
+    def testTypeDCell(self):
+        """Test type of resulting map"""
+        self.tmpFile.write(input1)
+        self.tmpFile.close()
+        self.assertModule('r.in.poly', input=self.tmpFile.name, output=self.rinpoly, type='DCELL')
+        minmax = 'min=-8\nmax=10.01\ndatatype=DCELL'
+        self.assertRasterFitsInfo(raster=self.rinpoly, reference=minmax, precision=1e-8)
+
+    def testTypeCellNull(self):
+        """Test type of resulting map"""
+        self.tmpFile.write(input1)
+        self.tmpFile.close()
+        self.assertModule('r.in.poly', input=self.tmpFile.name, output=self.rinpoly, type='CELL',
+                          null=-8)
+        minmax = 'min=3\nmax=10\ndatatype=CELL'
+        self.assertRasterFitsInfo(raster=self.rinpoly, reference=minmax, precision=1e-8)
+
+    def testTypeDCellNull(self):
+        """Test type of resulting map"""
+        self.tmpFile.write(input1)
+        self.tmpFile.close()
+        self.assertModule('r.in.poly', input=self.tmpFile.name, output=self.rinpoly, type='DCELL',
+                          null=-8)
+        minmax = 'min=3\nmax=10.01\ndatatype=DCELL'
+        self.assertRasterFitsInfo(raster=self.rinpoly, reference=minmax, precision=1e-8)
+
+    def testTypeDCellNull2(self):
+        """Test type of resulting map"""
+        self.tmpFile.write(input1)
+        self.tmpFile.close()
+        self.assertModule('r.in.poly', input=self.tmpFile.name, output=self.rinpoly, type='DCELL',
+                          null=0)
+        minmax = 'min=-8\nmax=10.01\ndatatype=DCELL'
+        self.assertRasterFitsInfo(raster=self.rinpoly, reference=minmax, precision=1e-8)
+
+    def testLabels(self):
+        """Test type of resulting map"""
+        self.tmpFile.write(input1)
+        self.tmpFile.close()
+        self.assertModule('r.in.poly', input=self.tmpFile.name, output=self.rinpoly, type='DCELL')
+        category = read_command('r.category', map=self.rinpoly, values=[-8, 3, 10.01]).strip()
+        self.assertEqual(first="-8\t\n3\tlabel2\n10.01\tlabel1", second=category,
+                         msg="Labels do not match")
+
+
+if __name__ == '__main__':
+    grass.gunittest.test()

+ 177 - 0
raster/r.mapcalc/testsuite/const_map_test.sh

@@ -0,0 +1,177 @@
+#!/bin/sh
+
+# Markus Neteler
+# Test cases for 2D raster data
+
+# Tests:
+#   - generate 3x3 map, value 1/1.1
+#   - calculate statistics
+#   - compare with known results
+
+#
+# TODO
+#   - how big EPSILON?
+
+if [ -z "$GISBASE" ] ; then
+    echo "You must be in GRASS GIS to run this program."
+    exit 1
+fi
+
+#### check if we have awk
+if [ ! -x "`which awk`" ] ; then
+    echo "$PROG: awk required, please install first" 1>&2
+    exit 1
+fi
+
+# setting environment, so that awk works properly in all languages
+unset LC_ALL
+export LC_NUMERIC=C
+
+eval `g.gisenv`
+: ${GISBASE?} ${GISDBASE?} ${LOCATION_NAME?} ${MAPSET?}
+LOCATION=$GISDBASE/$LOCATION_NAME/$MAPSET
+
+# some definitions
+PIXEL=3
+# how big EPSILON?
+#    epsilon for doubles in IEEE is 2.220446e-16
+EPSILON=22204460000000000
+PID=$$
+TMPNAME="`echo ${PID}_tmp_testmap | sed 's+\.+_+g'`"
+
+# some functions - keep order here
+cleanup()
+{
+ echo "Removing temporary map"
+ g.remove -f type=raster name=$TMPNAME > /dev/null
+}
+
+# check if a MASK is already present:
+MASKTMP=mask.$TMPNAME
+USERMASK=usermask_${MASKTMP}
+if test -f $LOCATION/cell/MASK
+then
+ echo "A user raster mask (MASK) is present. Saving it..."
+ g.rename raster=MASK,$USERMASK > /dev/null
+fi
+
+finalcleanup()
+{
+ echo "Restoring user region"
+ g.region region=$TMPNAME
+ g.remove -f type=region name=$TMPNAME > /dev/null
+ #restore user mask if present:
+ if test -f $LOCATION/cell/$USERMASK ; then
+  echo "Restoring user MASK"
+  g.remove -f type=raster name=MASK > /dev/null
+  g.rename raster=$USERMASK,MASK > /dev/null
+ fi
+}
+
+check_exit_status()
+{
+ if [ $1 -ne 0 ] ; then
+  echo "An error occurred."
+  cleanup ; finalcleanup
+  exit 1
+ fi
+}
+
+########## test function goes here
+compare_result()
+{
+ EXPECTED=$1
+ FOUND=$2
+ VALUENAME=$3
+
+ # test for NAN
+ if [ "$FOUND" = "nan" ] ; then
+  echo "ERROR. $VALUENAME: Expected=$EXPECTED | FOUND=$FOUND"
+  cleanup ; finalcleanup
+  exit 1
+ fi
+
+ # check for difference + 1
+ DIFF=`echo $EXPECTED $FOUND $EPSILON | awk '{printf "%16f", ($1 - $2) * $3 }'`
+ #make absolute value
+ DIFF=`echo $DIFF | awk '{printf("%f", sqrt($1 * $1))}'`
+ #round to integer
+ DIFF=`echo $DIFF | awk '{printf("%20d", int($1+0.5))}'`
+
+ # check if difference > 0
+ if [ $DIFF -gt 0 ] ; then
+  echo "ERROR. $VALUENAME: Expected=$EXPECTED | FOUND=$FOUND"
+  cleanup ; finalcleanup
+  exit 1
+ fi
+}
+
+#check if a MASK is already present:
+MASKTMP=mask.$TMPNAME
+USERMASK=usermask_${MASKTMP}
+if test -f $LOCATION/cell/MASK
+then
+ echo "A user raster mask (MASK) is present. Saving it..."
+ g.rename raster=MASK,$USERMASK > /dev/null
+ check_exit_status $?
+fi
+
+echo "Saving current & setting test region."
+g.region save=$TMPNAME
+check_exit_status $?
+g.region s=0 n=$PIXEL w=0 e=$PIXEL res=1 tbres=1
+check_exit_status $?
+
+########### 2D raster INT tests ###########
+VALUE=1
+echo "INT/CELL test."
+r.mapcalc "$TMPNAME = 1"
+check_exit_status $?
+
+echo "Univariate statistics of INT/CELL test."
+eval `r.univar -g $TMPNAME`
+check_exit_status $?
+compare_result 9 $n n
+compare_result $VALUE $min min
+compare_result $VALUE $max max
+compare_result 0 $range range
+compare_result $VALUE $mean mean
+compare_result 0 $stddev stddev
+compare_result 0 $variance variance
+compare_result 0 $coeff_var coeff_var
+compare_result 9 $sum sum
+
+cleanup
+echo "INT/CELL univariate statistics test successful"
+echo "##################################"
+
+########### 2D raster FCELL tests ###########
+VALUE=1.1
+echo "FLOAT/FCELL test."
+r.mapcalc "$TMPNAME = $VALUE"
+check_exit_status $?
+
+echo "Univariate statistics of FLOAT/FCELL test."
+eval `r.univar -g $TMPNAME`
+check_exit_status $?
+compare_result 9 $n n
+compare_result $VALUE $min min
+compare_result $VALUE $max max
+compare_result 0 $range range
+compare_result $VALUE $mean mean
+compare_result 0 $stddev stddev
+compare_result 0 $variance variance
+compare_result 0 $coeff_var coeff_var
+compare_result 9.9 $sum sum
+
+cleanup
+echo "FLOAT/FCELL univariate statistics test successful"
+echo "##################################"
+
+###########
+# if we arrive here...
+
+finalcleanup
+echo "All tests successful. Congrats."
+exit 0
+

+ 56 - 0
raster/r.mapcalc/testsuite/test_r3_mapcalc.py

@@ -0,0 +1,56 @@
+import grass.gunittest
+
+
+# TODO: add more expressions
+# TODO: add tests with prepared data
+
+class TestBasicOperations(grass.gunittest.TestCase):
+
+    # TODO: replace by unified handing of maps
+    to_remove = []
+
+    @classmethod
+    def setUpClass(cls):
+        cls.use_temp_region()
+        cls.runModule('g.region',
+            n=85, s=5, e=85, w=5,
+            b=0, t=2000,
+            res=1e-07)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.del_temp_region()
+        if cls.to_remove:
+            cls.runModule('g.remove', flags='f', type='raster_3d',
+                name=','.join(cls.to_remove), verbose=True)
+
+    def test_difference_of_the_same_map_double(self):
+        """Test zero difference of map with itself"""
+        self.runModule('r3.mapcalc', flags='s',
+                       expression='a = rand(1.0, 200)')
+        self.to_remove.append('a')
+        self.assertModule('r3.mapcalc',
+            expression='diff_a_a = a - a')
+        self.to_remove.append('diff_a_a')
+        self.assertRaster3dMinMax('diff_a_a', refmin=0, refmax=0)
+
+    def test_difference_of_the_same_map_float(self):
+        """Test zero difference of map with itself"""
+        self.runModule('r3.mapcalc', flags='s',
+                       expression='af = rand(float(1), 200)')
+        self.to_remove.append('af')
+        self.assertModule('r3.mapcalc',
+            expression='diff_af_af = af - af')
+        self.to_remove.append('diff_af_af')
+        self.assertRaster3dMinMax('diff_af_af', refmin=0, refmax=0)
+
+    def test_difference_of_the_same_expression(self):
+        """Test zero difference of two same expressions"""
+        self.assertModule('r3.mapcalc',
+            expression='diff_e_e = 3 * x() * y() * z() - 3 * x() * y() * z()')
+        self.to_remove.append('diff_e_e')
+        self.assertRaster3dMinMax('diff_e_e', refmin=0, refmax=0)
+
+
+if __name__ == '__main__':
+    grass.gunittest.test()

+ 223 - 0
raster/r.mapcalc/testsuite/test_r_mapcalc.py

@@ -0,0 +1,223 @@
+import grass.gunittest
+from grass.gunittest.gmodules import SimpleModule
+
+cell_seed_500 = """\
+north: 20
+south: 10
+east: 25
+west: 15
+rows: 10
+cols: 10
+2 12 183 135 117 176 138 117 182 40
+157 70 195 1 30 125 122 74 108 104
+163 146 82 164 67 63 60 3 147 193
+151 173 144 173 128 77 141 6 144 193
+180 56 14 121 124 107 46 107 90 60
+177 57 12 104 178 13 167 104 163 187
+55 133 27 114 195 60 78 35 49 11
+55 138 25 105 32 58 47 18 139 32
+24 74 36 71 131 124 87 161 160 154
+136 45 48 146 9 182 69 12 35 19
+"""
+
+dcell_seed_600 = """\
+north: 20
+south: 10
+east: 25
+west: 15
+rows: 10
+cols: 10
+130.790433856418332 101.3319248101491041 33.5781271447787759 37.4064724824657944 98.2794723130458152 73.9118866262841863 185.9530433718733775 74.5210037729812882 166.1178416001017695 90.9915650902159996
+109.2478664232956334 25.6499350759712215 150.9024447059825036 125.7544119036241312 66.7235333366722614 167.9375729129454271 123.1009291055983965 12.0922254083554606 59.389026967287819 113.2843489528100349
+40.0044184023145277 135.8273774212801186 71.6737798852435049 191.6223505280372876 4.1546013811569615 143.3082794522489962 177.043829294835632 115.0300571162354402 141.8985452774071234 127.8949967123638061
+93.2842559637482793 9.7471880052423856 118.1216452002055632 158.1474162140586088 67.2957262519499437 3.6524546350146849 147.0965842667525862 37.060628529871579 47.3408278816968959 66.2219633495724054
+175.5638637866295539 67.1399023507611901 162.2058392782793703 198.1586789345953719 36.474049475167746 49.2589028048889617 112.1169663235969836 22.0227597984432535 95.9169228571662131 86.7470895014531322
+93.5401613888204935 193.7821104138942587 193.8286564351004699 3.2623643889134684 94.6955247357847725 25.7099391122614307 155.592251526775442 25.3392337002970294 48.3979699868663005 99.6836079482556272
+104.16296861365457 190.7865884377180805 6.2841805474238619 49.3731395705159528 100.1903962703459285 116.927654961282343 19.8626348109264264 40.9693022766258466 81.6500759554420057 169.2220572316770131
+118.8112518721558217 55.8955021401724039 112.9150308215961331 62.6399760484719081 85.400498505854145 191.0144187084912062 124.2128169358724534 167.9341741649760706 170.6149695243870781 158.3034517206661462
+130.0453795775294736 64.1996403829061535 62.9317494959142465 175.1909990236256931 122.9624852869890361 79.9546265736285733 9.6594013716963367 114.0611338072915544 11.9371167643030809 186.9121199748369122
+3.2891990250261536 30.9245408751958379 46.4021422454598991 104.2378950097200203 47.424093232347019 73.4801303522840499 22.4778583078695213 132.870185207462697 48.1666164169167388 100.5504714442693057
+"""
+
+fcell_seed_700 = """\
+north: 20
+south: 10
+east: 25
+west: 15
+rows: 10
+cols: 10
+146.756378 192.682159 2.644822 147.270462 62.178818 192.668198 94.320778 107.710426 98.319664 114.444504
+12.995321 18.026272 151.590958 5.249451 197.266708 103.663635 115.424088 28.01062 78.555168 62.912098
+164.053619 154.652039 98.536011 44.601639 85.322289 168.383957 44.93845 128.62262 89.910591 107.242188
+111.182487 63.080284 177.791473 47.439354 42.451859 72.396568 170.597778 170.622742 141.88858 105.126854
+120.76828 148.581085 42.124866 56.432236 164.652176 98.094009 60.741329 66.286987 187.847427 160.120056
+50.530689 179.090652 138.114014 138.629211 193.147903 172.861481 133.72728 108.720459 103.508438 28.81559
+39.653179 101.948265 35.744762 25.570076 78.767021 154.600616 144.907684 82.370148 116.378654 18.218494
+35.587288 66.534409 65.744408 186.476959 137.081116 151.379272 48.261463 8.323328 130.432739 53.346546
+152.67189 15.512391 146.049072 185.276245 34.417141 127.522453 124.54998 52.08218 167.141342 87.771118
+69.0522 43.57811 63.15279 68.677063 74.202805 97.429077 167.123199 19.892767 120.593437 190.960815
+"""
+
+
+class TestRandFunction(grass.gunittest.TestCase):
+
+    # TODO: replace by unified handing of maps
+    to_remove = []
+
+    @classmethod
+    def setUpClass(cls):
+        cls.use_temp_region()
+        cls.runModule('g.region', n=20, s=10, e=25, w=15, res=1)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.del_temp_region()
+        if cls.to_remove:
+            cls.runModule('g.remove', flags='f', type='raster',
+                name=','.join(cls.to_remove))
+
+    def rinfo_contains_number(self, raster, number):
+        """Test that r.info standard output for raster contains a given number
+
+        To be used in test methods for testing presence of a given number.
+        """
+        rinfo = SimpleModule('r.info', map=raster)
+        self.runModule(rinfo)
+        self.assertIn(str(number), rinfo.outputs.stdout)
+
+    def test_seed_not_required(self):
+        """Test that seed is not required when rand() is not used"""
+        self.assertModule('r.mapcalc', expression='nonrand_cell = 200')
+        self.to_remove.append('nonrand_cell')
+
+    def test_seed_required(self):
+        """Test that seed is required when rand() is used
+        
+        This test can, and probably should, generate an error message.
+        """
+        self.assertModuleFail('r.mapcalc', expression='rand_x = rand(1, 200)')
+        # TODO: assert map not exists but it would be handy here
+        # TODO: test that error message was generated
+
+    def test_seed_cell(self):
+        """Test given seed with CELL against reference map"""
+        seed = 500
+        self.runModule('r.in.ascii', input='-', stdin=cell_seed_500,
+                       output='rand_cell_ref')
+        self.to_remove.append('rand_cell_ref')
+        self.assertModule('r.mapcalc', seed=seed,
+                          expression='rand_cell = rand(1, 200)')
+        self.to_remove.append('rand_cell')
+        # this assert is using r.mapcalc but we are testing different
+        # functionality than used by assert
+        self.assertRastersNoDifference(actual='rand_cell',
+                                       reference='rand_cell_ref',
+                                       precision=0)
+        self.rinfo_contains_number('rand_cell', seed)
+
+    def test_seed_dcell(self):
+        """Test given seed with DCELL against reference map"""
+        seed = 600
+        self.runModule('r.in.ascii', input='-', stdin=dcell_seed_600,
+                       output='rand_dcell_ref')
+        self.to_remove.append('rand_dcell_ref')
+        self.assertModule('r.mapcalc', seed=seed,
+                          expression='rand_dcell = rand(1.0, 200.0)')
+        self.to_remove.append('rand_dcell')
+        # this assert is using r.mapcalc but we are testing different
+        # functionality than used by assert
+        self.assertRastersNoDifference(actual='rand_dcell',
+                                       reference='rand_dcell_ref',
+                                       precision=0.00000000000001)
+        self.rinfo_contains_number('rand_dcell', seed)
+
+    def test_seed_fcell(self):
+        """Test given seed with FCELL against reference map"""
+        seed = 700
+        self.runModule('r.in.ascii', input='-', stdin=fcell_seed_700,
+                       output='rand_fcell_ref')
+        self.to_remove.append('rand_fcell_ref')
+        self.assertModule('r.mapcalc', seed=seed,
+                          expression='rand_fcell = rand(float(1), 200)')
+        self.to_remove.append('rand_fcell')
+        # this assert is using r.mapcalc but we are testing different
+        # functionality than used by assert
+        self.assertRastersNoDifference(actual='rand_fcell',
+                                       reference='rand_fcell_ref',
+                                       precision=0.000001)
+        self.rinfo_contains_number('rand_fcell', seed)
+
+    def test_auto_seed(self):
+        """Test that two runs with -s does not give same maps"""
+        self.assertModule('r.mapcalc', flags='s',
+                          expression='rand_auto_1 = rand(1., 2)')
+        self.to_remove.append('rand_auto_1')
+        self.assertModule('r.mapcalc', flags='s',
+                          expression='rand_auto_2 = rand(1., 2)')
+        self.to_remove.append('rand_auto_2')
+        self.assertRastersDifference(
+            'rand_auto_1', 'rand_auto_2',
+            statistics=dict(min=-1, max=1, mean=0),
+            precision=0.5)  # low precision, we have few cells
+
+
+# TODO: add more expressions
+# TODO: add tests with prepared data
+
+class TestBasicOperations(grass.gunittest.TestCase):
+
+    # TODO: replace by unified handing of maps
+    to_remove = []
+
+    @classmethod
+    def setUpClass(cls):
+        cls.use_temp_region()
+        cls.runModule('g.region', n=20, s=10, e=25, w=15, res=1)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.del_temp_region()
+        if cls.to_remove:
+            cls.runModule('g.remove', flags='f', type='raster',
+                name=','.join(cls.to_remove), verbose=True)
+
+    def test_difference_of_the_same_map_double(self):
+        """Test zero difference of map with itself"""
+        self.runModule('r.mapcalc', flags='s',
+                       expression='a = rand(1.0, 200)')
+        self.to_remove.append('a')
+        self.assertModule('r.mapcalc',
+            expression='diff_a_a = a - a')
+        self.to_remove.append('diff_a_a')
+        self.assertRasterMinMax('diff_a_a', refmin=0, refmax=0)
+
+    def test_difference_of_the_same_map_float(self):
+        """Test zero difference of map with itself"""
+        self.runModule('r.mapcalc', flags='s',
+                       expression='af = rand(float(1), 200)')
+        self.to_remove.append('af')
+        self.assertModule('r.mapcalc',
+            expression='diff_af_af = af - af')
+        self.to_remove.append('diff_af_af')
+        self.assertRasterMinMax('diff_af_af', refmin=0, refmax=0)
+
+    def test_difference_of_the_same_map_int(self):
+        """Test zero difference of map with itself"""
+        self.runModule('r.mapcalc', flags='s',
+                       expression='ai = rand(1, 200)')
+        self.to_remove.append('ai')
+        self.assertModule('r.mapcalc',
+            expression='diff_ai_ai = ai - ai')
+        self.to_remove.append('diff_ai_ai')
+        self.assertRasterMinMax('diff_ai_ai', refmin=0, refmax=0)
+
+    def test_difference_of_the_same_expression(self):
+        """Test zero difference of two same expressions"""
+        self.assertModule('r.mapcalc',
+            expression='diff_e_e = 3 * x() * y() - 3 * x() * y()')
+        self.to_remove.append('diff_e_e')
+        self.assertRasterMinMax('diff_e_e', refmin=0, refmax=0)
+
+
+if __name__ == '__main__':
+    grass.gunittest.test()

+ 190 - 0
raster/r.profile/testsuite/test_profile_ncspm.py

@@ -0,0 +1,190 @@
+from grass.gunittest import TestCase, test
+from grass.gunittest.gmodules import SimpleModule
+import grass.script.core as gcore
+
+# not used yet
+LOCATION = 'nc_spm'
+
+output1 = """
+ 0.000000 88.370453
+ 10.000000 88.397057
+ 20.000000 89.526253
+ 30.000000 89.677551
+ 40.000000 91.297195
+ 50.000000 91.297195
+ 60.000000 92.330658
+ 70.000000 93.069199
+ 80.000000 94.768280
+ 90.000000 95.524551
+ 100.000000 96.770805
+ 110.000000 96.770805
+ 120.000000 97.418869
+"""
+
+output2 = """
+637656.000000 224222.000000 0.000000 88.370453
+637664.540486 224227.201932 10.000000 88.397057
+637673.080972 224232.403865 20.000000 89.526253
+637681.621458 224237.605797 30.000000 89.677551
+637690.161944 224242.807729 40.000000 91.297195
+637698.702430 224248.009662 50.000000 91.297195
+637707.242916 224253.211594 60.000000 92.330658
+637715.783402 224258.413526 70.000000 93.069199
+637724.323887 224263.615459 80.000000 94.768280
+637732.864373 224268.817391 90.000000 95.524551
+637741.404859 224274.019323 100.000000 96.770805
+637749.945345 224279.221256 110.000000 96.770805
+637758.485831 224284.423188 120.000000 97.418869
+"""
+
+output3 = """
+ 0.000000 91.071831
+ 10.000000 91.431198
+ 20.000000 91.746628
+ 30.000000 91.746628
+ 40.000000 91.748047
+ 50.000000 91.872192
+ 60.000000 91.730049
+ 70.000000 91.690292
+ 80.000000 91.341331
+ 86.533231 91.341331
+ 96.533231 91.639000
+ 106.533231 nodata
+ 116.533231 nodata
+ 126.533231 nodata
+ 136.533231 nodata
+ 146.533231 nodata
+ 156.533231 nodata
+ 166.533231 nodata
+ 176.533231 nodata
+ 186.533231 nodata
+ 196.533231 nodata
+ 206.533231 nodata
+ 216.533231 nodata
+"""
+
+output4 = """
+ 0.000000 88.370453
+ 25.000000 89.526253
+ 50.000000 91.297195
+ 75.000000 94.768280
+ 100.000000 96.770805
+ 125.000000 97.646629
+"""
+
+output5 = """
+635747.000000 222664.000000 0.000000 117.672462
+635738.870095 222669.822770 10.000000 116.417213
+635730.740190 222675.645539 20.000000 115.639481
+635722.610285 222681.468309 30.000000 112.835342
+635714.480381 222687.291079 40.000000 111.324890
+635706.350476 222693.113848 50.000000 108.612282
+635698.220571 222698.936618 60.000000 106.313347
+635690.090666 222704.759388 70.000000 104.915665
+635681.960761 222710.582158 80.000000 102.878601
+635673.830856 222716.404927 90.000000 102.935074
+635673.000000 222717.000000 91.021975 102.935074
+635665.017450 222710.976803 101.021975 102.932213
+635657.034900 222704.953607 111.021975 102.931152
+635649.052351 222698.930410 121.021975 102.932213
+635641.069801 222692.907213 131.021975 102.932213
+635633.087251 222686.884017 141.021975 102.931648
+635625.104701 222680.860820 151.021975 102.936768
+635617.122151 222674.837623 161.021975 102.903358
+635609.139601 222668.814427 171.021975 105.447823
+635601.157052 222662.791230 181.021975 105.447823
+635593.174502 222656.768033 191.021975 108.423523
+635585.191952 222650.744836 201.021975 109.192360
+635577.209402 222644.721640 211.021975 112.042763
+635569.226852 222638.698443 221.021975 114.321136
+635563.000000 222634.000000 228.822556 114.321136
+635569.507914 222626.407434 238.822556 115.357292
+635576.015827 222618.814868 248.822556 114.609100
+635582.523741 222611.222302 258.822556 111.636292
+635589.031655 222603.629736 268.822556 112.355431
+635595.539569 222596.037170 278.822556 110.162842
+635602.047482 222588.444604 288.822556 109.172668
+635608.555396 222580.852038 298.822556 109.172668
+635615.063310 222573.259472 308.822556 108.030540
+635621.571224 222565.666906 318.822556 105.670113
+635628.079137 222558.074340 328.822556 105.770287
+635634.587051 222550.481774 338.822556 105.169937
+635641.000000 222543.000000 348.676634 105.416862
+635647.589446 222550.521915 358.676634 105.011185
+635654.178892 222558.043830 368.676634 104.854263
+635660.768338 222565.565744 378.676634 104.573921
+635667.357784 222573.087659 388.676634 103.413361
+635673.947230 222580.609574 398.676634 105.485588
+635680.536676 222588.131489 408.676634 109.791016
+635687.126122 222595.653403 418.676634 109.701485
+635693.715568 222603.175318 428.676634 112.104370
+635700.305014 222610.697233 438.676634 113.684036
+635706.894460 222618.219148 448.676634 113.684036
+635713.483906 222625.741062 458.676634 114.252579
+635720.073352 222633.262977 468.676634 114.115379
+635726.662798 222640.784892 478.676634 114.123955
+635733.252244 222648.306807 488.676634 115.766998
+635739.841690 222655.828721 498.676634 116.547440
+635746.431136 222663.350636 508.676634 117.672462
+"""
+
+
+class TestProfileNCSPM(TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        gcore.use_temp_region()
+        gcore.run_command('g.region', raster='elevation')
+
+    @classmethod
+    def tearDownClass(cls):
+        gcore.del_temp_region()
+
+    def test_profile_default(self):
+        rprofile = SimpleModule('r.profile', input='elevation',
+                                coordinates=[637656, 224222, 637766, 224289])
+        self.assertModule(rprofile)
+        self.assertMultiLineEqual(rprofile.outputs.stdout.strip(), output1.strip())
+        self.assertIn('128.798292 [meters]', rprofile.outputs.stderr)  # distance
+        self.assertIn('10 [meters]', rprofile.outputs.stderr)  # resolution
+
+    def test_profile_m(self):
+        rprofile = SimpleModule('r.profile', input='elevation', units='meters',
+                                coordinates=[637656, 224222, 637766, 224289])
+        self.assertModule(rprofile)
+        self.assertIn('128.798292 [meters]', rprofile.outputs.stderr)  # distance
+        self.assertIn('10 [meters]', rprofile.outputs.stderr)  # resolution
+
+    def test_profile_resolution(self):
+        rprofile = SimpleModule('r.profile', input='elevation', resolution=25,
+                                coordinates=[637656, 224222, 637766, 224289])
+        self.assertModule(rprofile)
+        self.assertMultiLineEqual(rprofile.outputs.stdout.strip(), output4.strip())
+        self.assertIn('128.798292 [meters]', rprofile.outputs.stderr)  # distance
+        self.assertIn('25 [meters]', rprofile.outputs.stderr)  # resolution
+
+    def test_profile_ne(self):
+        rprofile = SimpleModule('r.profile', input='elevation', flags='g',
+                                coordinates=[637656, 224222, 637766, 224289])
+        self.assertModule(rprofile)
+        self.assertMultiLineEqual(rprofile.outputs.stdout.strip(), output2.strip())
+
+    def test_profile_region(self):
+        rprofile = SimpleModule('r.profile', input='elevation', null_value='nodata',
+                                coordinates=[644914, 224579, 644986,
+                                             224627, 645091, 224549])
+        self.assertModule(rprofile)
+        self.assertMultiLineEqual(rprofile.outputs.stdout.strip(), output3.strip())
+        self.assertIn("WARNING: Endpoint coordinates are outside of current region settings",
+                      rprofile.outputs.stderr)
+
+    def test_profile_directions(self):
+        rprofile = SimpleModule('r.profile', input='elevation', flags='g',
+                                coordinates=[635747, 222664, 635673, 222717, 635563,
+                                             222634, 635641, 222543, 635747, 222664])
+        self.assertModule(rprofile)
+        self.assertMultiLineEqual(rprofile.outputs.stdout.strip(), output5.strip())
+
+
+if __name__ == '__main__':
+    test()

+ 95 - 0
raster/r.recode/testsuite/test_rrecode_ncspm.py

@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+from grass.gunittest import TestCase, test
+from grass.gunittest.gmodules import SimpleModule
+from grass.script.core import read_command
+
+
+rules1 = """
+55:65:1
+65:75:2
+75:85:3
+85:95:4
+95:105:5
+105:115:6
+115:125:7
+125:135:8
+135:145:9
+145:155:10
+155:165:11
+"""
+
+
+rules2 = """
+55.6:156:-0.5:0.5
+"""
+
+rules3 = """
+0:1:0:255
+"""
+
+rules4 = """
+0:5:1
+6:10:2
+11:*:3
+"""
+
+
+class TestNCMaps(TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.use_temp_region()
+        cls.runModule('g.region', raster='elevation@PERMANENT')
+        cls.runModule('r.mapcalc', expression="random01 = rand(0, 1.)", seed=1, overwrite=True)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.del_temp_region()
+        cls.runModule('g.remove', type='raster', name=['random01', 'recoded'], flags='f')
+
+    def test_formats_elevation(self):
+        recode = SimpleModule('r.recode', input='elevation@PERMANENT', output='recoded',
+                              rules='-', overwrite=True)
+        recode.inputs.stdin = rules1
+        self.assertModule(recode)
+        info = 'min=1\nmax=11\ndatatype=CELL'
+        self.assertRasterFitsInfo(raster='recoded', reference=info)
+
+        recode.flags['d'].value = True
+        self.assertModule(recode)
+        info = 'min=1\nmax=11\ndatatype=DCELL'
+        self.assertRasterFitsInfo(raster='recoded', reference=info)
+        recode.flags['d'].value = False
+
+        recode.inputs.stdin = rules2
+        self.assertModule(recode)
+        info = 'min=-0.5\nmax=0.5\ndatatype=FCELL'
+        self.assertRasterFitsInfo(raster='recoded', reference=info, precision=1e-3)
+
+        recode.flags['d'].value = True
+        self.assertModule(recode)
+        info = 'min=-0.5\nmax=0.5\ndatatype=DCELL'
+        self.assertRasterFitsInfo(raster='recoded', reference=info, precision=1e-3)
+        recode.flags['d'].value = False
+
+    def test_formats_random(self):
+        recode = SimpleModule('r.recode', input='random01', output='recoded',
+                              rules='-', overwrite=True)
+        recode.inputs.stdin = rules3
+        self.assertModule(recode)
+        category = read_command('r.category', map='recoded')
+        n_cats = len(category.strip().split('\n'))
+        if n_cats <= 2:
+            self.fail(msg="Number of categories is <= 2 "
+                          "which suggests input map values were read as integers.")
+
+    def test_formats_landcover(self):
+        recode = SimpleModule('r.recode', input='landuse96_28m@PERMANENT',
+                              output='recoded', rules='-', overwrite=True)
+        recode.inputs.stdin = rules4
+        self.assertModule(recode)
+        info = 'min=1\nmax=3\ndatatype=CELL'
+        self.assertRasterFitsInfo(raster='recoded', reference=info)
+
+if __name__ == '__main__':
+    test()

+ 1 - 1
raster/r.series.accumulate/test_suite/test.r.series.accumulate.sh

@@ -51,7 +51,7 @@ r.series.accumulate basemap=basemap input=map_a, \
                     output=test_accu_9 range=6,9 method=mean --verbose
 
 # Test for correct results
-for map in `g.list type=rast pattern=test_accu_*` ; do
+for map in `g.list type=raster pattern=test_accu_*` ; do
     r.out.ascii input=${map} output=${map}.ref precision=2
 done
 

+ 2 - 1
raster/r.series.interp/main.c

@@ -64,8 +64,9 @@ int main(int argc, char *argv[])
     G_add_keyword(_("raster"));
     G_add_keyword(_("series"));
     G_add_keyword(_("interpolation"));
+    /* TODO: re-phrase the description */
     module->description =
-	_("Interpolate raster maps located (temporal or spatial) "
+	_("Interpolates raster maps located (temporal or spatial) "
           "in between input raster maps at specific sampling positions.");
 
     parm.input = G_define_standard_option(G_OPT_R_INPUTS);

+ 16 - 0
raster/r.slope.aspect/testsuite/data/fractal_surf.ascii

@@ -0,0 +1,16 @@
+north: 20
+south: 10
+east: 25
+west: 15
+rows: 10
+cols: 10
+18042.0449657581812062 25383.8864299957058392 14054.8876733628094371 9718.6028777712708688 6811.412182415643656 2930.9162473829337614 -2117.0534472693707357 -3110.9860095434078175 4306.5996776199253873 987.4507122526649709 
+10168.7073320247400261 9977.3079539500449755 5446.1555224152061783 4481.6517453232163462 1848.8039189165833704 -1700.797216407640235 -1544.4356998912949166 5619.2167452912372028 166.5044979636321045 2281.682802785037893 
+3177.1414551946154461 -1491.910636019688809 -522.2980383497083494 -7733.1639587946547181 -9407.8445629291018122 -5840.1778570036331075 736.7154505421329986 6800.3416513099646181 -931.3345534823686194 -5463.0700545775525825 
+10109.0777441430691397 913.7987579666439615 -2463.2252033934960309 -14508.6051479571506206 -20888.966440910462552 -14497.8998969001877413 -10522.8316460882324463 -816.1074612102775063 3359.0009318826023446 1090.6260443768023833 
+8603.3056905043868028 4831.7340326208714032 -3556.3140177522709564 -8175.5341846188184718 -18357.2730383907437499 -18661.3149188292372855 -14409.728317302373398 -7102.8287566742747003 1779.425264984356545 3837.2560983032271906 
+3627.8840285699184278 -133.8571474710850566 -3452.0622719314178539 -8000.7487283949603807 -16672.8438014870589541 -18038.9599507521306805 -17202.0380336391281162 -8919.2834336959240318 -5346.6450827091866813 -1954.214336749309723 
+873.3209737757239282 -3346.9982385845969475 -11470.5463756023364112 -13199.9832339778495225 -14721.2637378373001411 -16224.3487699516826979 -19522.6366976581812196 -13069.7222692795330659 -9759.9528482313853601 -9054.4029620675919432 
+292.2682278056205973 -3794.3364980614819615 -11426.0054321001316566 -16664.9545810654999514 -23409.7229947478008398 -27173.6223750138269679 -21000.9338307948601141 -8467.5305864940492029 -3463.9662640234532773 -4937.8907319630416168 
+-5159.7969122775912183 -5883.0143716484344623 -4182.0455386342391648 -9149.4519877815255313 -28860.9848558809571841 -24850.2964268596515467 -18894.4634812169642828 -7910.7308709206999993 -2709.5147474691702882 1055.4081442089825487 
+-1721.3017714211916882 -3815.394704173491391 721.3503318535069866 -4283.5534905015056211 -19226.051844524248736 -18363.9268685237584577 -12999.7725237885060778 -12001.7549241148735746 -4098.3666298110829302 5166.4987688895371321

+ 25 - 0
raster/r.slope.aspect/testsuite/data/gdal_aspect.grd

@@ -0,0 +1,25 @@
+DSAA
+10 10
+15.5 24.5
+10.5 19.5
+1.3795901536942 359.78637695312
+1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 
+
+1.70141E+38 54.044281005859 61.536731719971 26.817855834961 27.830316543579 133.78686523438 160.15232849121 179.70936584473 172.18589782715 1.70141E+38 
+
+1.70141E+38 353.74163818359 22.493587493896 1.3795901536942 320.41970825195 252.02178955078 182.20143127441 163.56805419922 124.30812072754 1.70141E+38 
+
+1.70141E+38 336.80844116211 325.04400634766 314.70040893555 310.37930297852 285.00897216797 205.49880981445 181.18598937988 176.19082641602 1.70141E+38 
+
+1.70141E+38 320.38543701172 323.04837036133 342.1510925293 7.9401998519897 60.624557495117 202.65769958496 209.92497253418 235.44174194336 1.70141E+38 
+
+1.70141E+38 347.72735595703 4.0403199195862 14.852857589722 20.189586639404 208.29371643066 208.56834411621 208.52819824219 222.8828125 1.70141E+38 
+
+1.70141E+38 20.142166137695 359.78637695312 347.99960327148 285.37921142578 235.00204467773 227.87588500977 223.58207702637 161.80085754395 1.70141E+38 
+
+1.70141E+38 313.41848754883 307.28234863281 300.10757446289 271.76864624023 244.55999755859 218.78259277344 236.69653320312 357.2619934082 1.70141E+38 
+
+1.70141E+38 281.6360168457 294.11804199219 289.54504394531 286.39514160156 280.21655273438 162.12962341309 115.12343597412 334.69934082031 1.70141E+38 
+
+1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 
+

+ 5 - 0
raster/r.slope.aspect/testsuite/data/gdal_aspect.grd.aux.xml

@@ -0,0 +1,5 @@
+<PAMDataset>
+  <PAMRasterBand band="1">
+    <NoDataValue>-9.99900000000000E+03</NoDataValue>
+  </PAMRasterBand>
+</PAMDataset>

+ 25 - 0
raster/r.slope.aspect/testsuite/data/gdal_slope.grd

@@ -0,0 +1,25 @@
+DSAA
+10 10
+15.5 24.5
+10.5 19.5
+89.825134277344 89.994964599609
+1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 
+
+1.70141E+38 89.963233947754 89.98900604248 89.994964599609 89.99275970459 89.988899230957 89.992683410645 89.992202758789 89.988243103027 1.70141E+38 
+
+1.70141E+38 89.986907958984 89.989105224609 89.991172790527 89.991111755371 89.985824584961 89.992034912109 89.992797851562 89.987106323242 1.70141E+38 
+
+1.70141E+38 89.990310668945 89.990707397461 89.98983001709 89.988838195801 89.98462677002 89.98787689209 89.990631103516 89.975311279297 1.70141E+38 
+
+1.70141E+38 89.990867614746 89.990509033203 89.990196228027 89.986480712891 89.882415771484 89.987167358398 89.992004394531 89.990997314453 1.70141E+38 
+
+1.70141E+38 89.989814758301 89.99072265625 89.992767333984 89.986122131348 89.977241516113 89.991233825684 89.993072509766 89.989074707031 1.70141E+38 
+
+1.70141E+38 89.989501953125 89.990852355957 89.99259185791 89.985816955566 89.992454528809 89.994033813477 89.992149353027 89.825134277344 1.70141E+38 
+
+1.70141E+38 89.987243652344 89.991683959961 89.99422454834 89.994071960449 89.992774963379 89.992279052734 89.979469299316 89.982360839844 1.70141E+38 
+
+1.70141E+38 89.994606018066 89.99430847168 89.993423461914 89.992500305176 89.985404968262 89.979461669922 89.976203918457 89.972076416016 1.70141E+38 
+
+1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 1.70141E+38 
+

+ 5 - 0
raster/r.slope.aspect/testsuite/data/gdal_slope.grd.aux.xml

@@ -0,0 +1,5 @@
+<PAMDataset>
+  <PAMRasterBand band="1">
+    <NoDataValue>-9.99900000000000E+03</NoDataValue>
+  </PAMRasterBand>
+</PAMDataset>

+ 163 - 0
raster/r.slope.aspect/testsuite/test_r_slope_aspect.py

@@ -0,0 +1,163 @@
+import grass.gunittest
+from grass.gunittest.gmodules import call_module
+
+SMALL_MAP = """\
+north:   15
+south:   10
+east:    25
+west:    20
+rows:    5
+cols:    5
+
+100.0 150.0 150.0 100.0 100.0
+100.0 150.0 150.0 100.0 100.0
+100.0 150.0 150.0 150.0 150.0
+100.0 150.0 150.0 100.0 100.0
+100.0 150.0 150.0 100.0 100.0
+"""
+
+class TestSlopeAspect(grass.gunittest.TestCase):
+
+    def test_limits(self):
+        slope = 'limits_slope'
+        aspect = 'limits_aspect'
+        self.assertModule('r.slope.aspect', elevation='elevation',
+                          slope=slope, aspect=aspect)
+        self.assertRasterMinMax(map=slope, refmin=0, refmax=90,
+                                msg="Slope in degrees must be between 0 and 90")
+        self.assertRasterMinMax(map=aspect, refmin=0, refmax=360,
+                                msg="Aspect in degrees must be between 0 and 360")
+
+    def test_limits_precent(self):
+        slope = 'limits_percent_slope'
+        aspect = 'limits_percent_aspect'
+        self.assertModule('r.slope.aspect', elevation='elevation',
+                          slope=slope, aspect=aspect, format='percent')
+        self.assertRasterMinMax(map=slope, refmin=0, refmax=100,
+                                msg="Slope in percent must be between 0 and 100")
+        self.assertRasterMinMax(map=aspect, refmin=0, refmax=360,
+                                msg="Aspect in degrees must be between 0 and 360")
+
+
+class TestSlopeAspectAgainstReference(grass.gunittest.TestCase):
+    """
+
+    Data created using::
+
+        g.region n=20 s=10 e=25 w=15 res=1
+        r.surf.fractal output=fractal_surf
+        r.out.ascii input=fractal_surf output=data/fractal_surf.ascii
+        gdaldem slope .../fractal_surf.ascii .../gdal_slope.grd -of GSAG
+        gdaldem aspect .../fractal_surf.ascii .../gdal_aspect.grd -of GSAG -trigonometric
+
+    GDAL version 1.11.0 was used. Note: GDAL-slope/aspect implementation is originally based on
+    GRASS GIS 4.1.
+    """
+
+    # precision for comparisons
+    precision = 0.0001
+
+    @classmethod
+    def setUpClass(cls):
+        cls.use_temp_region()
+        call_module('g.region', n=20, s=10, e=25, w=15, res=1)
+        cls.elevation = 'fractal_surf'
+        cls.runModule('r.in.ascii', input='data/fractal_surf.ascii',
+                       output=cls.elevation)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.del_temp_region()
+        cls.runModule('g.remove', flags='f', type='raster', name=cls.elevation)
+
+    def test_slope(self):
+        ref_slope = 'reference_slope'
+        slope = 'fractal_slope'
+
+        # TODO: using gdal instead of ascii because of cannot seek error
+        self.runModule('r.in.gdal', flags='o',
+                       input='data/gdal_slope.grd', output=ref_slope)
+        self.assertModule('r.slope.aspect', elevation=self.elevation,
+                          slope=slope)
+        # check we have expected values
+        self.assertRasterMinMax(map=slope, refmin=0, refmax=90,
+                                msg="Slope in degrees must be between 0 and 90")
+        # check against reference data
+        self.assertRastersNoDifference(actual=slope, reference=ref_slope,
+                                       precision=self.precision)
+
+    def test_aspect(self):
+        ref_aspect = 'reference_aspect'
+        aspect = 'fractal_aspect'
+        # TODO: using gdal instead of ascii because of cannot seek error
+        self.runModule('r.in.gdal', flags='o',
+                       input='data/gdal_aspect.grd', output=ref_aspect)
+        self.assertModule('r.slope.aspect', elevation=self.elevation,
+                          aspect=aspect)
+        # check we have expected values
+        self.assertRasterMinMax(map=aspect, refmin=0, refmax=360,
+                                msg="Aspect in degrees must be between 0 and 360")
+        # check against reference data
+        self.assertRastersNoDifference(actual=aspect, reference=ref_aspect,
+                                       precision=self.precision)
+
+
+class TestSlopeAspectAgainstItself(grass.gunittest.TestCase):
+
+    precision = 0.0000001
+
+    @classmethod
+    def setUpClass(cls):
+        cls.use_temp_region()
+        call_module('g.region', raster='elevation')
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.del_temp_region()
+
+    def test_slope_aspect_together(self):
+        """Slope and aspect computed separately and together should be the same
+        """
+        elevation = 'elevation'
+        t_aspect = 'sa_together_aspect'
+        t_slope = 'sa_together_slope'
+        s_aspect = 'sa_separately_aspect'
+        s_slope = 'sa_separately_slope'
+        self.assertModule('r.slope.aspect', elevation=elevation,
+                          aspect=s_aspect)
+        self.assertModule('r.slope.aspect', elevation=elevation,
+                          slope=s_slope)
+        self.assertModule('r.slope.aspect', elevation=elevation,
+                          slope=t_slope, aspect=t_aspect)
+        self.assertRastersNoDifference(actual=t_aspect, reference=s_aspect,
+                                       precision=self.precision)
+        self.assertRastersNoDifference(actual=t_slope, reference=s_slope,
+                                       precision=self.precision)
+
+
+# TODO: implement this class
+class TestExtremes(grass.gunittest.TestCase):
+
+    def setUp(self):
+        self.use_temp_region()
+
+    def tearDown(self):
+        self.del_temp_region()
+
+    def test_small(self):
+        elevation = 'small_elevation'
+        slope = 'small_slope'
+        aspect = 'small_aspect'
+        self.runModule('r.in.ascii', input='-', output=elevation,
+                       stdin_=SMALL_MAP)
+        call_module('g.region', raster=elevation)
+        self.assertModule('r.slope.aspect', elevation=elevation,
+                          slope=slope, aspect=aspect)
+        self.assertRasterMinMax(map=slope, refmin=0, refmax=90,
+                                msg="Slope in degrees must be between 0 and 90")
+        self.assertRasterMinMax(map=aspect, refmin=0, refmax=360,
+                                msg="Aspect in degrees must be between 0 and 360")
+
+
+if __name__ == '__main__':
+    grass.gunittest.test()

+ 4 - 4
raster/r.sunmask/main.c

@@ -361,7 +361,7 @@ int main(int argc, char *argv[])
 		    fprintf(stdout, "sunangleabovehorizon=%f\n",
 			    pdat->elevref);
 
-		    if (sretr / 60 <= 24.0) {
+		    if (sretr / 60 <= 24) {
 			fprintf(stdout, "sunrise=%02d:%02d:%02d\n",
 				sretr / 60, sretr % 60, sretr_sec);
 			fprintf(stdout, "sunset=%02d:%02d:%02d\n", ssetr / 60,
@@ -382,7 +382,7 @@ int main(int argc, char *argv[])
 		    fprintf(stdout, "Solar position: sun azimuth: %f, sun angle above horz. (refraction corrected): %f\n",
 			    pdat->azim, pdat->elevref);
 		    
-		    if (sretr / 60 <= 24.0) {
+		    if (sretr / 60 <= 24) {
 			fprintf(stdout, "Sunrise time (without refraction): %02d:%02d:%02d\n",
 				sretr / 60, sretr % 60, sretr_sec);
 			fprintf(stdout, "Sunset time  (without refraction): %02d:%02d:%02d\n",
@@ -409,7 +409,7 @@ int main(int argc, char *argv[])
     if (use_solpos) {
 	G_debug(3, "current_time:%f sunrise:%f", current_time, sunrise);
 	if ((current_time < sunrise)) {
-	    if (sretr / 60 <= 24.0)
+	    if (sretr / 60 <= 24)
 		G_message(_("Time (%02i:%02i:%02i) is before sunrise (%02d:%02d:%02d)"),
 			  pdat->hour, pdat->minute, pdat->second, sretr / 60,
 			  sretr % 60, sretr_sec);
@@ -420,7 +420,7 @@ int main(int argc, char *argv[])
 	    G_warning(_("Nothing to calculate. Please verify settings."));
 	}
 	if ((current_time > sunset)) {
-	    if (sretr / 60 <= 24.0)
+	    if (sretr / 60 <= 24)
 		G_message(_("Time (%02i:%02i:%02i) is after sunset (%02d:%02d:%02d)"),
 			  pdat->hour, pdat->minute, pdat->second, ssetr / 60,
 			  ssetr % 60, ssetr_sec);

+ 6 - 3
raster/r.viewshed/main.cpp

@@ -175,9 +175,12 @@ int main(int argc, char *argv[])
        the algorithm will work correctly in theory. But this
        requires some changes. To do. */
     if (!(vp.row < hd->nrows && vp.col < hd->ncols)) {
-	G_warning(_("Viewpoint outside grid"));
-	G_warning(_("viewpont: (row=%d, col=%d)"), vp.row, vp.col);
-	G_fatal_error(_("grid: (rows=%d, cols=%d)"), hd->nrows, hd->ncols);
+	/* unfortunately, we don't know the point coordinates now */
+	G_warning(_("Region extent: north=%d, south=%d, east=%d, west=%d"),
+	    hd->window.north, hd->window.south, hd->window.east, hd->window.west);
+	G_warning(_("Region extent: rows=%d, cols=%d"), hd->nrows, hd->ncols);
+	G_warning(_("Viewpoint: row=%d, col=%d"), vp.row, vp.col);
+	G_fatal_error(_("Viewpoint outside of computational region"));
     }
 
 

Різницю між файлами не показано, бо вона завелика
+ 153 - 0
raster/r.viewshed/testsuite/data/elevation.ascii


Різницю між файлами не показано, бо вона завелика
+ 153 - 0
raster/r.viewshed/testsuite/data/lake_viewshed.ascii


+ 123 - 0
raster/r.viewshed/testsuite/test_r_viewshed.py

@@ -0,0 +1,123 @@
+import grass.gunittest
+from grass.gunittest.gmodules import call_module
+
+
+class TestViewshed(grass.gunittest.TestCase):
+
+    viewshed = 'test_viewshed_from_elevation'
+
+    @classmethod
+    def setUpClass(cls):
+        cls.use_temp_region()
+        cls.runModule('g.region', raster='elevation')
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.del_temp_region()
+
+    def tearDown(cls):
+        """Remove viewshed map after each test method"""
+        # TODO: eventually, removing maps should be handled through testing framework fucntions
+        cls.runModule('g.remove', flags='f', type='raster',
+                      name=cls.viewshed)
+
+    def test_limits(self):
+        """Test if results is in expected limits"""
+        obs_elev = '1.72'
+        self.assertModule('r.viewshed', input='elevation',
+            coordinates=(634720,216180), output=self.viewshed,
+            observer_elevation=obs_elev)
+        self.assertRasterMinMax(map=self.viewshed, refmin=0, refmax=180,
+                                msg="Viewing angle above the ground must be between 0 and 180 deg")
+
+    def test_limits_flags(self):
+        obs_elev = '1.72'
+        # test e flag
+        self.assertModule('r.viewshed', input='elevation', flags='e',
+                          coordinates=(634720, 216180), output=self.viewshed,
+                          observer_elevation=obs_elev)
+        minmax = 'null_cells=1963758\nmin=-24.98421\nmax=43.15356'
+        self.assertRasterFitsUnivar(raster=self.viewshed, reference=minmax, precision=1e-5)
+        # test b flag (#1788)
+        self.assertModule('r.viewshed', input='elevation', flags='b',
+                          coordinates=(634720, 216180), output=self.viewshed,
+                          observer_elevation=obs_elev, overwrite=True)
+        minmax = 'min=0\nmax=1\ndatatype=CELL'
+        self.assertRasterFitsInfo(raster=self.viewshed, reference=minmax,
+                                  msg="Values of binary output must be 0 or 1")
+
+    def test_limits_extreme_value(self):
+        """Test extremely high observer elevation
+
+        Unfortunatelly, this takes very long time to compute
+        (something like ten times more).
+        """
+        obs_elev = '500000'
+        self.assertModule('r.viewshed', input='elevation',
+            coordinates=(634720,216180), output=self.viewshed,
+            observer_elevation=obs_elev)
+        self.assertRasterMinMax(map=self.viewshed, refmin=0, refmax=180,
+            msg="Viewing angle above the ground must be between 0 and 180 deg")
+
+
+class TestViewshedAgainstReference(grass.gunittest.TestCase):
+    """
+
+    Data created using NC data set::
+
+        g.region n=216990 s=215520 w=633730 e=635950 res=10 -p
+        r.out.ascii elevation out=data/elevation.ascii
+        r.viewshed input=elevation output=lake_viewshed coordinates=634720,216180 observer_elevation=1.72
+        r.out.ascii lake_viewshed out=data/lake_viewshed.ascii
+
+    """
+
+    # precision for comparisons
+    precision = 0.0001
+
+    # list to hold rasters to be removed
+    # here we accumulate all rasters created during tests
+    # then we remove them at the end in class tear down
+    to_remove = []
+
+    @classmethod
+    def setUpClass(cls):
+        cls.use_temp_region()
+        cls.runModule('g.region', n=216990, s=215520, e=635950, w=633730, res=10)
+        cls.elevation = 'ref_elevation'
+        cls.runModule('r.in.ascii', input='data/elevation.ascii',
+                       output=cls.elevation)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.del_temp_region()
+        cls.runModule('g.remove', flags='f', type='raster',
+                      name=cls.elevation)
+        if cls.to_remove:
+            cls.runModule('g.remove', flags='f', type='raster',
+                          name=','.join(cls.to_remove))
+
+    def test_viewshed(self):
+        ref_viewshed = 'reference_viewshed'
+        viewshed = 'actual_viewshed'
+        obs_elev = '1.72'
+
+        self.runModule('r.in.ascii',
+                       input='data/lake_viewshed.ascii', output=ref_viewshed)
+        self.to_remove.append(ref_viewshed)
+
+        self.assertModule('r.viewshed', input=self.elevation,
+                          coordinates=(634720,216180), output=viewshed, observer_elevation=obs_elev)
+        self.to_remove.append(viewshed)
+
+        # check we have expected values
+        self.assertRasterMinMax(map=viewshed, refmin=0, refmax=180,
+                                msg="Viewshed in degrees must be between 0 and 180")
+        # check against reference data
+        self.assertRastersNoDifference(actual=viewshed, reference=ref_viewshed,
+                                       precision=self.precision)
+        # TODO: add self.assertRasterFitsUnivar()
+
+
+if __name__ == '__main__':
+    grass.gunittest.test()