From daa80467a2f8fb053eb3339aace59b99c4996e41 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Fri, 30 Jan 2026 12:47:06 +0100 Subject: [PATCH 01/17] first improved version --- scripts/r.reclass.area/r.reclass.area.py | 404 +++++++++++++++-------- 1 file changed, 265 insertions(+), 139 deletions(-) diff --git a/scripts/r.reclass.area/r.reclass.area.py b/scripts/r.reclass.area/r.reclass.area.py index ff43f26e454..30aeb849631 100755 --- a/scripts/r.reclass.area/r.reclass.area.py +++ b/scripts/r.reclass.area/r.reclass.area.py @@ -14,6 +14,7 @@ # for details. # ############################################################################# +# 02/2026: rewrite with Tools API and new options (Eric Goddard) # 10/2013: added option to use a pre-clumped input map (Eric Goddard) # 8/2012: added fp maps support, cleanup, removed tabs AK # 3/2007: added label support MN @@ -39,24 +40,40 @@ # %option # % key: value # % type: double -# % description: Value option that sets the area size limit (in hectares) -# % required: yes +# % description: Value option that sets the area size limit (in hectares) (deprecated) +# % required: no # % guisection: Area # %end # %option # % key: mode # % type: string -# % description: Lesser or greater than specified value +# % description: Keep only areas lesser or greater than specified value (deprecated) # % options: lesser,greater -# % required: yes +# % required: no +# % guisection: Area +# %end + +# %option +# % key: lower +# % type: double +# % description: Value for the lower area size limit (in hectares). Pixel clumps with areas below this value are removed. +# % required: no +# % guisection: Area +# %end + +# %option +# % key: upper +# % type: double +# % description: Value for the upper area size limit (in hectares). Pixel clumps with areas above this value are removed. +# % required: no # % guisection: Area # %end # %option # % key: method # % type: string -# % description: Method used for reclassification +# % description: Method used for area size filtering # % options: reclass,rmarea # % answer: reclass # % guisection: Area @@ -69,111 +86,184 @@ # %flag # % key: d -# % description: Clumps including diagonal neighbors +# % description: Generate clumps including diagonal neighbors # %end -import sys -import os +# %flag +# % key: i +# % description: Invert filter (remove clumps larger than lower and smaller than upper) +# %end + +# %flag +# % key: m +# % description: Apply lower area threshold as "minsize" during clumping +# %end + +# %flag +# % key: v +# % description: Output vector map, do not convert back to raster (only for method 'rmarea') +# %end + +# %rules +# % required: lower, upper, mode +# % requires: mode, value +# % requires: -m, lower +# % excludes: -c, -d, -m +# %end + + import atexit -import grass.script as gs -from grass.script.utils import decode, encode +import io +import sys + +from functools import partial +from math import ceil, inf, prod +from typing import Literal -TMPRAST = [] +import grass.script as gs +from grass.tools import Tools +TEMP_PREFIX = gs.tempname(12) -def reclass(inf, outf, lim, clump, diag, les): - infile = inf - outfile = outf - lesser = les - limit = lim - clumped = clump - diagonal = diag - s = gs.read_command("g.region", flags="p") - s = decode(s) - kv = gs.parse_key_val(s, sep=":") - s = kv["projection"].strip().split() - if s == "0": - gs.fatal(_("xy-locations are not supported")) - gs.fatal(_("Need projected data with grids in meters")) +def cleanup() -> None: + """Delete temporary maps.""" + tools = Tools(capture_output=False) + tools.g_remove( + flags="fb", + type=["raster", "vector"] if options["method"] == "rmarea" else "raster", + pattern=f"{TEMP_PREFIX}_*", + superquiet=True, + ) - if not gs.find_file(infile)["name"]: - gs.fatal(_("Raster map <%s> not found") % infile) - if clumped and diagonal: - gs.fatal(_("flags c and d are mutually exclusive")) +def check_projection() -> tuple[float, float]: + """Check if current projection is supported. - if clumped: - clumpfile = infile - else: - clumpfile = "%s.clump.%s" % (infile.split("@")[0], outfile) - TMPRAST.append(clumpfile) - - if not gs.overwrite(): - if gs.find_file(clumpfile)["name"]: - gs.fatal(_("Temporary raster map <%s> exists") % clumpfile) - if diagonal: - gs.message( - _("Generating a clumped raster file including diagonal neighbors...") - ) - gs.run_command("r.clump", flags="d", input=infile, output=clumpfile) - else: - gs.message(_("Generating a clumped raster file ...")) - gs.run_command("r.clump", input=infile, output=clumpfile) - - if lesser: - gs.message( + Issues a fatal message if the tool does not reliably + work in the current type of projection (XY, latlon) + """ + tools = Tools(capture_output=True) + proj = tools.g_region(flags="p", format="json").json + if proj["crs"]["type_code"] == "0": + gs.fatal( _( - "Generating a reclass map with area size less than " - "or equal to %f hectares..." - ) - % limit + "xy-locations are not supported\n" + "Need projected data with grids in meters", + ), ) + in_proj = gs.parse_key_val(tools.g_proj(flags="p", format="shell").text) + if in_proj["unit"].lower() == "degree": + gs.fatal(_("Latitude-longitude locations are not supported")) + if in_proj["name"].lower() == "xy_location_unprojected": + gs.fatal(_("xy-locations are not supported")) + return proj["ewres"], proj["nsres"] + + +def get_clumpfile( + input_map: str, + resolution: tuple[float, float], + minsize: float | None = None, + *, + clump_flags: Literal["d"] | None = None, +) -> str: + """Generate a clump-raster map.""" + tools = Tools(capture_output=False) + clump_map = f"{TEMP_PREFIX}_clump" + + if minsize: + minsize = ceil((minsize * 10000.0) / prod(resolution)) + + msg = _("Generating a clumped raster file") + if clump_flags: + msg += _(" including diagonal neighbors") + if minsize: + msg += _(" with minimum %s pixels") % minsize + gs.verbose(msg) + tools.r_clump( + flags=clump_flags, + input=input_map, + minsize=minsize, + output=clump_map, + quiet=True, + ) + return clump_map + + +def reclass( + clump_map: str, + output_map: str | None, + lower: float | None = None, + upper: float | None = None, + input_map: str | None = None, +) -> None: + """Perform raster based filtering.""" + tools = Tools(capture_output=True) + stats_input = clump_map + expected_fields_number = 4 + verbose_message = _("Generating a reclass map with area size") + range_filter_message = "" + if lower: + range_filter_message += _(" larger than or equal to %f hectares...") % lower else: - gs.message( - _( - "Generating a reclass map with area size greater " - "than or equal to %f hectares..." - ) - % limit - ) + lower = -inf - recfile = outfile + ".recl" - TMPRAST.append(recfile) + if upper: + if range_filter_message: + range_filter_message += " and" + range_filter_message += _(" smaller than or equal to %f hectares...") % upper + else: + upper = inf + gs.verbose(verbose_message + range_filter_message) + recfile = f"{TEMP_PREFIX}_recl" sflags = "aln" - if gs.raster_info(infile)["datatype"] in {"FCELL", "DCELL"}: - sflags += "i" - p1 = gs.pipe_command("r.stats", flags=sflags, input=(clumpfile, infile), sep=";") - p2 = gs.feed_command("r.reclass", input=clumpfile, output=recfile, rules="-") + if input_map: + expected_fields_number = 5 + stats_input = (clump_map, input_map) + if gs.raster_info(input_map)["datatype"] in {"FCELL", "DCELL"}: + sflags += "i" + clump_areas = ( + tools.r_stats( + flags=sflags, + input=stats_input, + sep=";", + quiet=True, + ) + .text.rstrip() + .split("\n") + ) + rules = "" - for line in p1.stdout: - f = decode(line).rstrip(os.linesep).split(";") - if len(f) < 5: + for line in clump_areas: + f = line.rstrip().split(";") + if len(f) < expected_fields_number: continue - hectares = float(f[4]) * 0.0001 - test = hectares <= limit if lesser else hectares >= limit - if test: - rules += "%s = %s %s\n" % (f[0], f[2], f[3]) + hectares = float(f[-1]) * 0.0001 + if lower <= hectares <= upper: + rules += "{} = {} {}\n".format(*[f[i] for i in [0, 2, -2]]) if rules: - p2.stdin.write(encode(rules)) - p1.wait() - p2.stdin.close() - p2.wait() - if p2.returncode != 0: - if lesser: - gs.fatal( - _("No areas of size less than or equal to %f hectares found.") % limit - ) - else: - gs.fatal( - _("No areas of size greater than or equal to %f hectares found.") - % limit - ) - gs.mapcalc("$outfile = $recfile", outfile=outfile, recfile=recfile) - - -def rmarea(infile, outfile, thresh, coef): + tools.r_reclass( + input=clump_map, + output=recfile, + rules=io.StringIO(rules), + quiet=True, + ) + else: + fatal_message = _("No areas of size ") + gs.fatal(fatal_message + range_filter_message) + tools.r_mapcalc(expression=f"{output_map} = {recfile}") + + +def rmarea( + input_map: str, + output_map: str, + lower: float | None = None, + upper: float | None = None, + *, + return_vector: bool = False, +) -> None: + """Perform vector based filtering.""" # transform user input from hectares to map units (kept this for future) # thresh = thresh * 10000.0 / (float(coef)**2) # grass.debug("Threshold: %d, coeff linear: %s, coef squared: %d" % @@ -181,62 +271,98 @@ def rmarea(infile, outfile, thresh, coef): # transform user input from hectares to meters because currently v.clean # rmarea accept only meters as threshold - thresh *= 10000.0 - vectfile = "%s_vect_%s" % (infile.split("@")[0], outfile) - TMPRAST.append(vectfile) - gs.run_command("r.to.vect", input=infile, output=vectfile, type="area") - cleanfile = "%s_clean_%s" % (infile.split("@")[0], outfile) - TMPRAST.append(cleanfile) - gs.run_command( - "v.clean", input=vectfile, output=cleanfile, tool="rmarea", threshold=thresh - ) + tools = Tools(capture_output=False) + + vectfile = f"{TEMP_PREFIX}_vect" + cleanfile = f"{TEMP_PREFIX}_clean" + edit_vector = vectfile + + tools.r_to_vect(input=input_map, output=vectfile, type="area", quiet=True) + + if lower: + lower *= 10000.0 + tools.v_clean( + input=vectfile, + output=cleanfile, + tool="rmarea", + threshold=lower, + quiet=True, + ) + edit_vector = cleanfile + + if upper: + tools.v_to_db( + map=edit_vector, + type="centroid", + option="area", + columns="area_ha", + units="hectares", + quiet=True, + ) + tools.v_edit( + map=edit_vector, tool="delete", where=f"area_ha >= {upper}", superquiet=True + ) - gs.run_command( - "v.to.rast", input=cleanfile, output=outfile, use="attr", attrcolumn="value" - ) + if not return_vector: + tools.v_to_rast( + input=edit_vector, + output=output_map, + use="attr", + attrcolumn="value", + quiet=True, + ) + else: + tools.v_clean( + flags="c", + input=edit_vector, + output=output_map, + tool=["rmdangle", "rmbridge"], + threshold=-1, + quiet=True, + ) -def main(): - infile = options["input"] - value = options["value"] - mode = options["mode"] - outfile = options["output"] - global method - method = options["method"] - clumped = flags["c"] - diagonal = flags["d"] +def main() -> None: + """Run area filtering.""" + if not gs.find_file(options["input"])["name"]: + gs.fatal(_("Raster map <%s> not found") % options["input"]) # check for unsupported locations - in_proj = gs.parse_command("g.proj", flags="p", format="shell") - if in_proj["unit"].lower() == "degree": - gs.fatal(_("Latitude-longitude locations are not supported")) - if in_proj["name"].lower() == "xy_location_unprojected": - gs.fatal(_("xy-locations are not supported")) - - # check lesser and greater parameters - limit = float(value) - if mode == "greater" and method == "rmarea": - gs.fatal(_("You have to specify mode='lesser' with method='rmarea'")) + resolution = check_projection() - if not gs.find_file(infile)["name"]: - gs.fatal(_("Raster map <%s> not found") % infile) - - if method == "reclass": - reclass(infile, outfile, limit, clumped, diagonal, mode == "lesser") - elif method == "rmarea": - rmarea(infile, outfile, limit, in_proj["meters"]) - - gs.message(_("Generating output raster map <%s>...") % outfile) + # Give deprecation warnings + if options["mode"]: + gs.warning( + _( + "Options and are deprecated. " + "Use 'lower' and/or 'upper'.", + ), + ) + options["lower" if options["mode"] == "greater" else "upper"] = options["value"] + + lower = float(options["lower"]) if options["lower"] else None + upper = float(options["upper"]) if options["upper"] else None + # Compute clump map if neede + clump_map = ( + options["input_map"] + if flags["c"] + else get_clumpfile( + options["input"], + resolution=resolution, + minsize=lower if flags["m"] else None, + clump_flags="d" if flags["d"] else None, + ) + ) + # Filter clumped map + filter_method = ( + partial(reclass, input_map=options["input"] if not flags["c"] else None) + if options["method"] == "reclass" + else partial(rmarea, return_vector=flags["v"]) + ) + filter_method(clump_map, options["output"], lower=lower, upper=upper) -def cleanup(): - """!Delete temporary maps""" - TMPRAST.reverse() # reclassed map first - for mapp in TMPRAST: - if method == "rmarea": - gs.run_command("g.remove", flags="f", type="vector", name=mapp, quiet=True) - else: - gs.run_command("g.remove", flags="f", type="raster", name=mapp, quiet=True) + gs.message(_("Generating output raster map <%s>...") % options["output"]) if __name__ == "__main__": From 43540a6fd03da206ef0b220772201279cdb30a75 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Fri, 30 Jan 2026 12:48:53 +0100 Subject: [PATCH 02/17] first improved version --- .../testsuite/test_r_reclass_area.py | 123 +++++++++++++++++- 1 file changed, 118 insertions(+), 5 deletions(-) diff --git a/scripts/r.reclass.area/testsuite/test_r_reclass_area.py b/scripts/r.reclass.area/testsuite/test_r_reclass_area.py index 96c922bda05..8da103bfd24 100644 --- a/scripts/r.reclass.area/testsuite/test_r_reclass_area.py +++ b/scripts/r.reclass.area/testsuite/test_r_reclass_area.py @@ -17,6 +17,8 @@ class TestReclassArea(TestCase): input = "geology_30m" output = "reclassarea" value = "20" + upper = "4000" + lower = "20" @classmethod def setUpClass(cls): @@ -30,11 +32,11 @@ def tearDownClass(cls): "g.remove", type="raster", flags="f", - name=(cls.output + "Greater", cls.output + "Lesser"), + name=(cls.output), # + "Greater", cls.output + "Lesser"), ) - def test_reclassaeaGreater(self): - """Testing r.reclass.area with greater""" + def test_reclassGreater(self): + """Testing r.reclass.area with greater (old mode).""" self.assertModule( "r.reclass.area", input=self.input, @@ -50,8 +52,24 @@ def test_reclassaeaGreater(self): msg="Range of data: min = 200 max = 1000", ) - def test_reclassareaLesser(self): - """Testing r.reclass.area with lesser""" + def test_reclass_lower(self): + """Testing r.reclass.area with lower.""" + self.assertModule( + "r.reclass.area", + input=self.input, + output=f"{self.output}_lower", + lower=self.lower, + method="reclass", + ) + self.assertRasterMinMax( + map=f"{self.output}_lower", + refmin=200, + refmax=1000, + msg="Range of data: min = 200 max = 1000", + ) + + def test_reclassLesser(self): + """Testing r.reclass.area with lesser (old mode).""" self.assertModule( "r.reclass.area", input=self.input, @@ -67,6 +85,101 @@ def test_reclassareaLesser(self): msg="Range of data: min = 900 max = 1000", ) + def test_reclass_upper(self): + """Testing r.reclass.area with upper.""" + self.assertModule( + "r.reclass.area", + input=self.input, + output=f"{self.output}_upper", + upper=self.upper, + method="reclass", + ) + self.assertRasterMinMax( + map=f"{self.output}_upper", + refmin=262, + refmax=1000, + msg="Range of data: min = 262 max = 1000", + ) + + def test_reclass_lower_upper(self): + """Testing r.reclass.area with lower and upper.""" + self.assertModule( + "r.reclass.area", + input=self.input, + output=f"{self.output}_lower_upper", + lower=self.lower, + upper=self.upper, + method="reclass", + ) + self.assertRasterMinMax( + map=f"{self.output}_lower_upper", + refmin=200, + refmax=1000, + msg="Range of data: min = 200 max = 1000", + ) + + def test_rmarea_lower(self): + """Testing r.reclass.area with rmarea and lower.""" + self.assertModule( + "r.reclass.area", + input=self.input, + output=f"{self.output}_rmarea_lower", + lower=self.lower, + method="rmarea", + ) + self.assertRasterMinMax( + map=f"{self.output}_rmarea_lower", + refmin=1, + refmax=11, + msg="Range of data: min = 1 max = 11", + ) + + def test_rmarea_upper(self): + """Testing r.reclass.area with rmarea and upper.""" + self.assertModule( + "r.reclass.area", + input=self.input, + output=f"{self.output}_rmarea_upper", + upper=self.upper, + method="rmarea", + ) + self.assertRasterMinMax( + map=f"{self.output}_rmarea_upper", + refmin=1, + refmax=13, + msg="Range of data: min = 1 max = 13", + ) + + def test_rmaea_lower_upper_vector(self): + """Testing r.reclass.area with rmarea, lower and upper.""" + self.assertModule( + "r.reclass.area", + flags="vd", + input=self.input, + output=f"{self.output}_rmarea_lower_upper", + lower=self.lower, + upper=self.upper, + method="rmarea", + ) + + def test_rmaea_lower_upper_vector(self): + """Testing r.reclass.area with rmarea, lower and upper.""" + self.assertModule( + "r.reclass.area", + flags="vd", + input="basin", + output=f"{self.output}_rmarea_lower_upper", + lower=self.lower, + upper=self.upper, + method="rmarea", + ) + # self.assertRasterMinMax( + # map=f"{self.output}_rmarea_lower_upper", + # refmin=200, + # refmax=1000, + # msg="Range of data: min = 200 max = 1000", + # ) + if __name__ == "__main__": test() From c15b5b1f4dd936d34f0bf6c46ab711936b35c6e4 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Wed, 22 Apr 2026 12:51:14 +0200 Subject: [PATCH 03/17] update manual --- scripts/r.reclass.area/r.reclass.area.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/scripts/r.reclass.area/r.reclass.area.md b/scripts/r.reclass.area/r.reclass.area.md index a20ff64d596..a4f735356ab 100644 --- a/scripts/r.reclass.area/r.reclass.area.md +++ b/scripts/r.reclass.area/r.reclass.area.md @@ -1,11 +1,18 @@ ## DESCRIPTION -*r.reclass.area* reclasses a raster map greater or less than a user -specified area size (in hectares). +*r.reclass.area* removes areas (pixel clumps) from a raster map +that are smaller than a user specified **lower** area size (in hectares). If the **-c** flag is used, *r.reclass.area* will skip the creation of a clumped raster and assume that the input raster is already clumped. +*r.reclass.area* provides two **method**s for filtering: + +- *reclass* +- *rmarea* + removes areas (pixel clumps) from a raster map +that are greater or lesser than a user specified area size (in hectares). + ## EXAMPLES In this example, the ZIP code map in the North Carolina sample dataset @@ -19,7 +26,7 @@ r.report zipcodes unit=h Extract only areas greater than 2000 ha, NULL otherwise: ```sh -r.reclass.area input=zipcodes output=zipcodes_larger2000ha mode=greater value=2000 +r.reclass.area input=zipcodes output=zipcodes_larger2000ha greater=2000 r.report zipcodes_larger2000ha unit=h ``` @@ -30,10 +37,10 @@ r.report zipcodes_larger2000ha unit=h In this example, the ZIP code map in the North Carolina sample dataset is filtered for smaller areas which are substituted with the value of the respective adjacent area with largest shared boundary. Reclass by -substitutional removing of areas minor of 1000 ha: +substitutional removing of areas smaller than 1000 ha: ```sh -r.reclass.area input=zipcodes output=zipcodes_minor1000ha mode=lesser value=1000 method=rmarea +r.reclass.area input=zipcodes output=zipcodes_minor1000ha lesser=1000 method=rmarea ``` ![Figure: r.reclass.area method=rmarea](zipcodes_minor1000ha.png) @@ -42,7 +49,7 @@ r.reclass.area input=zipcodes output=zipcodes_minor1000ha mode=lesser value=1000 ## SEE ALSO *[r.reclass](r.reclass.md), [r.clump](r.clump.md), -[r.stats](r.stats.md)* +[r.stats](r.stats.md), [v.clean](v.clean.md)* ## AUTHORS From 41f5ea2df36638d9b5ad3210a3db86f99d6a8961 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Wed, 22 Apr 2026 13:19:22 +0200 Subject: [PATCH 04/17] fix latest change author --- scripts/r.reclass.area/r.reclass.area.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/r.reclass.area/r.reclass.area.py b/scripts/r.reclass.area/r.reclass.area.py index 30aeb849631..15437e31907 100755 --- a/scripts/r.reclass.area/r.reclass.area.py +++ b/scripts/r.reclass.area/r.reclass.area.py @@ -14,7 +14,7 @@ # for details. # ############################################################################# -# 02/2026: rewrite with Tools API and new options (Eric Goddard) +# 04/2026: rewrite with Tools API and new options (Stefan Blumentrath) # 10/2013: added option to use a pre-clumped input map (Eric Goddard) # 8/2012: added fp maps support, cleanup, removed tabs AK # 3/2007: added label support MN From f0ae94a661834b157fdac7c2aea2bbc56b2b890f Mon Sep 17 00:00:00 2001 From: ninsbl Date: Wed, 22 Apr 2026 13:19:41 +0200 Subject: [PATCH 05/17] update manual --- scripts/r.reclass.area/r.reclass.area.md | 28 ++++++++++++++++-------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/scripts/r.reclass.area/r.reclass.area.md b/scripts/r.reclass.area/r.reclass.area.md index a4f735356ab..1e7ec3a947c 100644 --- a/scripts/r.reclass.area/r.reclass.area.md +++ b/scripts/r.reclass.area/r.reclass.area.md @@ -3,15 +3,23 @@ *r.reclass.area* removes areas (pixel clumps) from a raster map that are smaller than a user specified **lower** area size (in hectares). -If the **-c** flag is used, *r.reclass.area* will skip the creation of a -clumped raster and assume that the input raster is already clumped. - *r.reclass.area* provides two **method**s for filtering: -- *reclass* -- *rmarea* - removes areas (pixel clumps) from a raster map -that are greater or lesser than a user specified area size (in hectares). +- *reclass* - raster based filtering +- *rmarea* - vector based filtering + +With the *reclass* method, the input raster map is clumped and +then reclassified using the clump size. With the **-m** flag, +*reclass* method will remove small areas already during clumping, +using the *minsize* option there. If the **-c** flag is used, +*r.reclass.area* will skip the creation of a clumped raster and +assume that the input raster map is already clumped. + +The *rmarea* method converts the raster map to vector and removes +areas outside the given thresholds using [v.clean](v.clean.md) and +[v.edit](v.edit.md). With the **-v** flag the **output** is the +filtered vector map instead of a raster. + ## EXAMPLES @@ -48,10 +56,12 @@ r.reclass.area input=zipcodes output=zipcodes_minor1000ha lesser=1000 method=rma ## SEE ALSO -*[r.reclass](r.reclass.md), [r.clump](r.clump.md), -[r.stats](r.stats.md), [v.clean](v.clean.md)* +*[r.clump](r.clump.md), [r.reclass](r.reclass.md), +[r.stats](r.stats.md), [v.to.rast](v.to.rast.md), +[v.clean](v.clean.md), [v.edit](v.edit.md)* ## AUTHORS NRCS, Markus Neteler +Stefan Blumentath From 35fc8d7606f1a08d848952f1f86c9016692fc526 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Wed, 22 Apr 2026 13:26:05 +0200 Subject: [PATCH 06/17] update manual --- scripts/r.reclass.area/r.reclass.area.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/r.reclass.area/r.reclass.area.md b/scripts/r.reclass.area/r.reclass.area.md index 1e7ec3a947c..2d8d31fffbc 100644 --- a/scripts/r.reclass.area/r.reclass.area.md +++ b/scripts/r.reclass.area/r.reclass.area.md @@ -1,7 +1,8 @@ ## DESCRIPTION *r.reclass.area* removes areas (pixel clumps) from a raster map -that are smaller than a user specified **lower** area size (in hectares). +that are smaller than a user specified **lower** area size and/or +greater than a user specified **upper** theshold (both in hectares). *r.reclass.area* provides two **method**s for filtering: From 3fbda83cfed1e6c2ffd48e52681cc344647bd8c5 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Fri, 24 Apr 2026 09:03:59 +0200 Subject: [PATCH 07/17] fix rmarea and add comments --- scripts/r.reclass.area/r.reclass.area.py | 59 +++++++++++++++--------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/scripts/r.reclass.area/r.reclass.area.py b/scripts/r.reclass.area/r.reclass.area.py index 15437e31907..4f593d6bc1a 100755 --- a/scripts/r.reclass.area/r.reclass.area.py +++ b/scripts/r.reclass.area/r.reclass.area.py @@ -7,7 +7,7 @@ # Converted to Python by Glynn Clements # Added rmarea method by Luca Delucchi # PURPOSE: Reclasses a raster map greater or less than user specified area size (in hectares) -# COPYRIGHT: (C) 1999,2008,2014 by the GRASS Development Team +# COPYRIGHT: (C) 1999-2026 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 @@ -127,11 +127,11 @@ def cleanup() -> None: - """Delete temporary maps.""" + """Delete all temporary maps.""" tools = Tools(capture_output=False) tools.g_remove( flags="fb", - type=["raster", "vector"] if options["method"] == "rmarea" else "raster", + type="all", pattern=f"{TEMP_PREFIX}_*", superquiet=True, ) @@ -203,11 +203,14 @@ def reclass( expected_fields_number = 4 verbose_message = _("Generating a reclass map with area size") range_filter_message = "" + + # Define lower threshold if lower: range_filter_message += _(" larger than or equal to %f hectares...") % lower else: lower = -inf + # Define upper threshold if upper: if range_filter_message: range_filter_message += " and" @@ -219,6 +222,7 @@ def reclass( recfile = f"{TEMP_PREFIX}_recl" sflags = "aln" if input_map: + # For non-null clumps r.stats should produce 5 columns expected_fields_number = 5 stats_input = (clump_map, input_map) if gs.raster_info(input_map)["datatype"] in {"FCELL", "DCELL"}: @@ -264,21 +268,30 @@ def rmarea( return_vector: bool = False, ) -> None: """Perform vector based filtering.""" - # transform user input from hectares to map units (kept this for future) - # thresh = thresh * 10000.0 / (float(coef)**2) - # grass.debug("Threshold: %d, coeff linear: %s, coef squared: %d" % - # (thresh, coef, (float(coef)**2)), 0) - - # transform user input from hectares to meters because currently v.clean - # rmarea accept only meters as threshold tools = Tools(capture_output=False) + if gs.raster_info(input_map)["datatype"] != "CELL": + gs.warning( + _("Raster map <%s> is not of type 'CELL'. Results may not be as expected."), + ) + + # Setup temporary map names with pattern vectfile = f"{TEMP_PREFIX}_vect" cleanfile = f"{TEMP_PREFIX}_clean" edit_vector = vectfile - tools.r_to_vect(input=input_map, output=vectfile, type="area", quiet=True) + # Convert input map to vector raster values as category + tools.r_to_vect( + flags="v", + input=input_map, + output=vectfile, + type="area", + quiet=True, + ) + # Apply lower threshold + # transform user input from hectares to meters because currently v.clean + # rmarea accept only meters as threshold if lower: lower *= 10000.0 tools.v_clean( @@ -290,6 +303,7 @@ def rmarea( ) edit_vector = cleanfile + # Apply upper threshold if upper: tools.v_to_db( map=edit_vector, @@ -300,15 +314,18 @@ def rmarea( quiet=True, ) tools.v_edit( - map=edit_vector, tool="delete", where=f"area_ha >= {upper}", superquiet=True + map=edit_vector, + tool="delete", + where=f"area_ha >= {upper}", + superquiet=True, ) + # Produce final output map (raster / vector) if not return_vector: tools.v_to_rast( input=edit_vector, output=output_map, - use="attr", - attrcolumn="value", + use="cat", quiet=True, ) else: @@ -327,7 +344,7 @@ def main() -> None: if not gs.find_file(options["input"])["name"]: gs.fatal(_("Raster map <%s> not found") % options["input"]) - # check for unsupported locations + # Check for unsupported locations resolution = check_projection() # Give deprecation warnings @@ -340,12 +357,14 @@ def main() -> None: ) options["lower" if options["mode"] == "greater" else "upper"] = options["value"] + # Define filter thresholds lower = float(options["lower"]) if options["lower"] else None upper = float(options["upper"]) if options["upper"] else None - # Compute clump map if neede + + # Compute clump map if needed clump_map = ( - options["input_map"] - if flags["c"] + options["input"] + if flags["c"] or options["method"] == "rmarea" else get_clumpfile( options["input"], resolution=resolution, @@ -356,14 +375,12 @@ def main() -> None: # Filter clumped map filter_method = ( - partial(reclass, input_map=options["input"] if not flags["c"] else None) + partial(reclass, input_map=options["input"]) if options["method"] == "reclass" else partial(rmarea, return_vector=flags["v"]) ) filter_method(clump_map, options["output"], lower=lower, upper=upper) - gs.message(_("Generating output raster map <%s>...") % options["output"]) - if __name__ == "__main__": options, flags = gs.parser() From 1a310003f7c93d34d3ca130db27f3eeb9a808b67 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Fri, 24 Apr 2026 09:06:50 +0200 Subject: [PATCH 08/17] adjust tests ro rmarea fix and remove maps correctly --- .../testsuite/test_r_reclass_area.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/scripts/r.reclass.area/testsuite/test_r_reclass_area.py b/scripts/r.reclass.area/testsuite/test_r_reclass_area.py index 8da103bfd24..d15b75c441d 100644 --- a/scripts/r.reclass.area/testsuite/test_r_reclass_area.py +++ b/scripts/r.reclass.area/testsuite/test_r_reclass_area.py @@ -19,6 +19,7 @@ class TestReclassArea(TestCase): value = "20" upper = "4000" lower = "20" + map_list = [] @classmethod def setUpClass(cls): @@ -30,9 +31,9 @@ def tearDownClass(cls): cls.del_temp_region() cls.runModule( "g.remove", - type="raster", + type="all", flags="f", - name=(cls.output), # + "Greater", cls.output + "Lesser"), + name=cls.map_list, ) def test_reclassGreater(self): @@ -45,6 +46,7 @@ def test_reclassGreater(self): mode="greater", method="reclass", ) + self.map_list.append(self.output + "Greater") self.assertRasterMinMax( map=self.output + "Greater", refmin=200, @@ -61,6 +63,7 @@ def test_reclass_lower(self): lower=self.lower, method="reclass", ) + self.map_list.append(f"{self.output}_lower") self.assertRasterMinMax( map=f"{self.output}_lower", refmin=200, @@ -78,6 +81,7 @@ def test_reclassLesser(self): mode="lesser", method="reclass", ) + self.map_list.append(self.output + "Lesser") self.assertRasterMinMax( map=self.output + "Lesser", refmin=900, @@ -94,6 +98,7 @@ def test_reclass_upper(self): upper=self.upper, method="reclass", ) + self.map_list.append(f"{self.output}_upper") self.assertRasterMinMax( map=f"{self.output}_upper", refmin=262, @@ -111,6 +116,7 @@ def test_reclass_lower_upper(self): upper=self.upper, method="reclass", ) + self.map_list.append(f"{self.output}_lower_upper") self.assertRasterMinMax( map=f"{self.output}_lower_upper", refmin=200, @@ -127,10 +133,11 @@ def test_rmarea_lower(self): lower=self.lower, method="rmarea", ) + self.map_list.append(f"{self.output}_rmarea_lower") self.assertRasterMinMax( map=f"{self.output}_rmarea_lower", - refmin=1, - refmax=11, + refmin=217, + refmax=946, msg="Range of data: min = 1 max = 11", ) @@ -143,10 +150,11 @@ def test_rmarea_upper(self): upper=self.upper, method="rmarea", ) + self.map_list.append(f"{self.output}_rmarea_upper") self.assertRasterMinMax( map=f"{self.output}_rmarea_upper", - refmin=1, - refmax=13, + refmin=262, + refmax=948, msg="Range of data: min = 1 max = 13", ) @@ -161,6 +169,7 @@ def test_rmaea_lower_upper_vector(self): upper=self.upper, method="rmarea", ) + self.map_list.append(f"{self.output}_rmarea_lower_upper") def test_rmaea_lower_upper_vector(self): """Testing r.reclass.area with rmarea, lower and upper.""" @@ -173,6 +182,7 @@ def test_rmaea_lower_upper_vector(self): upper=self.upper, method="rmarea", ) + self.map_list.append(f"{self.output}_rmarea_lower_upper") # self.assertRasterMinMax( # map=f"{self.output}_rmarea_lower_upper", # refmin=200, From 5c5fa959157939a698f4efd05268bec655d08d9f Mon Sep 17 00:00:00 2001 From: ninsbl Date: Fri, 24 Apr 2026 09:07:39 +0200 Subject: [PATCH 09/17] correct expected output for lesser method --- scripts/r.reclass.area/testsuite/testrra.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/r.reclass.area/testsuite/testrra.py b/scripts/r.reclass.area/testsuite/testrra.py index 5e11bf6aecc..c27e07f11b7 100644 --- a/scripts/r.reclass.area/testsuite/testrra.py +++ b/scripts/r.reclass.area/testsuite/testrra.py @@ -89,9 +89,9 @@ def test_method_rmarea(self): ) self.assertRasterMinMax( map=self.output, - refmin=27603, - refmax=27607, - msg="Output Map in degrees must be between 27603 and 27607", + refmin=27511, + refmax=27610, + msg="Output Map in degrees must be between 27511 and 27610", ) From b441fa795f5ce381efafbea6c239ef35d6b22f0c Mon Sep 17 00:00:00 2001 From: ninsbl Date: Fri, 24 Apr 2026 09:20:46 +0200 Subject: [PATCH 10/17] md lint --- scripts/r.reclass.area/r.reclass.area.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/r.reclass.area/r.reclass.area.md b/scripts/r.reclass.area/r.reclass.area.md index 2d8d31fffbc..66c586760f2 100644 --- a/scripts/r.reclass.area/r.reclass.area.md +++ b/scripts/r.reclass.area/r.reclass.area.md @@ -1,6 +1,6 @@ ## DESCRIPTION -*r.reclass.area* removes areas (pixel clumps) from a raster map +*r.reclass.area* removes areas (pixel clumps) from a raster map that are smaller than a user specified **lower** area size and/or greater than a user specified **upper** theshold (both in hectares). @@ -21,7 +21,6 @@ areas outside the given thresholds using [v.clean](v.clean.md) and [v.edit](v.edit.md). With the **-v** flag the **output** is the filtered vector map instead of a raster. - ## EXAMPLES In this example, the ZIP code map in the North Carolina sample dataset From 6fc8ad9e5fce2b2d2514877199e79a8accb55456 Mon Sep 17 00:00:00 2001 From: ninsbl Date: Tue, 7 Jul 2026 22:22:54 +0200 Subject: [PATCH 11/17] fix typos, incosistencies --- .../testsuite/test_r_reclass_area.py | 178 +++++++++++------- 1 file changed, 106 insertions(+), 72 deletions(-) diff --git a/scripts/r.reclass.area/testsuite/test_r_reclass_area.py b/scripts/r.reclass.area/testsuite/test_r_reclass_area.py index d15b75c441d..dde916541c8 100644 --- a/scripts/r.reclass.area/testsuite/test_r_reclass_area.py +++ b/scripts/r.reclass.area/testsuite/test_r_reclass_area.py @@ -1,5 +1,4 @@ -""" -Name: r.reclass.area test +"""Name: r.reclass.area test Purpose: Tests r.reclass.area. Author: Shubham Sharma, Google Code-in 2018 @@ -36,159 +35,194 @@ def tearDownClass(cls): name=cls.map_list, ) - def test_reclassGreater(self): - """Testing r.reclass.area with greater (old mode).""" + def test_reclass_greater(self) -> None: + """Testing r.reclass.area with method reclass and greater (old mode).""" + output_map = f"{self.output}_greater" + ref_max = 946 + ref_min = 217 self.assertModule( "r.reclass.area", input=self.input, - output=self.output + "Greater", + output=output_map, value=self.value, mode="greater", method="reclass", ) - self.map_list.append(self.output + "Greater") + self.map_list.append(output_map) self.assertRasterMinMax( - map=self.output + "Greater", - refmin=200, - refmax=1000, - msg="Range of data: min = 200 max = 1000", + map=output_map, + refmin=ref_min, + refmax=ref_max, + msg=f"Range of data: min = {ref_min} max = {ref_max}", ) - def test_reclass_lower(self): - """Testing r.reclass.area with lower.""" + def test_reclass_lower(self) -> None: + """Testing r.reclass.area with method reclass and lower.""" + output_map = f"{self.output}_lower" + ref_max = 1000 + ref_min = 200 self.assertModule( "r.reclass.area", input=self.input, - output=f"{self.output}_lower", + output=output_map, lower=self.lower, method="reclass", ) - self.map_list.append(f"{self.output}_lower") + self.map_list.append(output_map) self.assertRasterMinMax( - map=f"{self.output}_lower", - refmin=200, - refmax=1000, - msg="Range of data: min = 200 max = 1000", + map=output_map, + refmin=ref_min, + refmax=ref_max, + msg=f"Range of data: min = {ref_min} max = {ref_max}", ) - def test_reclassLesser(self): - """Testing r.reclass.area with lesser (old mode).""" + def test_reclass_lesser(self) -> None: + """Testing r.reclass.area with method reclass and lesser (old mode).""" + output_map = f"{self.output}_lesser" + ref_max = 1000 + ref_min = 900 self.assertModule( "r.reclass.area", input=self.input, - output=self.output + "Lesser", + output=output_map, value=self.value, mode="lesser", method="reclass", ) - self.map_list.append(self.output + "Lesser") + self.map_list.append(output_map) self.assertRasterMinMax( - map=self.output + "Lesser", - refmin=900, - refmax=1000, - msg="Range of data: min = 900 max = 1000", + map=output_map, + refmin=ref_min, + refmax=ref_max, + msg=f"Range of data: min = {ref_min} max = {ref_max}", ) - def test_reclass_upper(self): - """Testing r.reclass.area with upper.""" + def test_reclass_upper(self) -> None: + """Testing r.reclass.area with method reclass and upper.""" + output_map = f"{self.output}_upper" + ref_max = 1000 + ref_min = 262 self.assertModule( "r.reclass.area", input=self.input, - output=f"{self.output}_upper", + output=output_map, upper=self.upper, method="reclass", ) - self.map_list.append(f"{self.output}_upper") + self.map_list.append(output_map) self.assertRasterMinMax( - map=f"{self.output}_upper", - refmin=262, - refmax=1000, - msg="Range of data: min = 262 max = 1000", + map=output_map, + refmin=ref_min, + refmax=ref_max, + msg=f"Range of data: min = {ref_min} max = {ref_max}", ) - def test_reclass_lower_upper(self): - """Testing r.reclass.area with lower and upper.""" + def test_reclass_lower_upper(self) -> None: + """Testing r.reclass.area with method reclass and lower and upper.""" + output_map = f"{self.output}_lower_upper" + ref_max = 1000 + ref_min = 200 self.assertModule( "r.reclass.area", input=self.input, - output=f"{self.output}_lower_upper", + output=output_map, lower=self.lower, upper=self.upper, method="reclass", ) - self.map_list.append(f"{self.output}_lower_upper") + self.map_list.append(output_map) self.assertRasterMinMax( - map=f"{self.output}_lower_upper", - refmin=200, - refmax=1000, - msg="Range of data: min = 200 max = 1000", + map=output_map, + refmin=ref_min, + refmax=ref_max, + msg=f"Range of data: min = {ref_min} max = {ref_max}", ) - def test_rmarea_lower(self): - """Testing r.reclass.area with rmarea and lower.""" + def test_rmarea_lower(self) -> None: + """Testing r.reclass.area with method rmarea and lower.""" + output_map = f"{self.output}_rmarea_lower" + ref_max = 946 + ref_min = 217 self.assertModule( "r.reclass.area", input=self.input, - output=f"{self.output}_rmarea_lower", + output=output_map, lower=self.lower, method="rmarea", ) - self.map_list.append(f"{self.output}_rmarea_lower") + self.map_list.append(output_map) self.assertRasterMinMax( - map=f"{self.output}_rmarea_lower", - refmin=217, - refmax=946, - msg="Range of data: min = 1 max = 11", + map=output_map, + refmin=ref_min, + refmax=ref_max, + msg=f"Range of data: min = {ref_min} max = {ref_max}", ) - def test_rmarea_upper(self): - """Testing r.reclass.area with rmarea and upper.""" + def test_rmarea_upper(self) -> None: + """Testing r.reclass.area with method rmarea and upper.""" + output_map = f"{self.output}_rmarea_upper" + ref_max = 948 + ref_min = 262 self.assertModule( "r.reclass.area", input=self.input, - output=f"{self.output}_rmarea_upper", + output=output_map, upper=self.upper, method="rmarea", ) - self.map_list.append(f"{self.output}_rmarea_upper") + self.map_list.append(output_map) self.assertRasterMinMax( - map=f"{self.output}_rmarea_upper", - refmin=262, - refmax=948, - msg="Range of data: min = 1 max = 13", + map=output_map, + refmin=ref_min, + refmax=ref_max, + msg=f"Range of data: min = {ref_min} max = {ref_max}", ) - def test_rmaea_lower_upper_vector(self): - """Testing r.reclass.area with rmarea, lower and upper.""" + def test_rmarea_lower_upper_vector(self) -> None: + """Testing r.reclass.area with method rmarea, lower and upper.""" + output_map = f"{self.output}_rmarea_lower_upper" self.assertModule( "r.reclass.area", flags="vd", input=self.input, - output=f"{self.output}_rmarea_lower_upper", + output=output_map, lower=self.lower, upper=self.upper, method="rmarea", ) - self.map_list.append(f"{self.output}_rmarea_lower_upper") + self.map_list.append(output_map) + self.assertVectorExists( + output_map, + msg=f"Output map {output_map} not found.", + ) + self.assertVectorFitsTopoInfo( + vector=output_map, + reference={"areas": 11, "primitives": 43}, + msg=f"Toplogy of output map {output_map} does not match reference.", + ) - def test_rmaea_lower_upper_vector(self): - """Testing r.reclass.area with rmarea, lower and upper.""" + def test_rmarea_lower_upper_vector_basins(self) -> None: + """Testing r.reclass.area with method rmarea, lower and upper and basins input.""" + output_map = f"{self.output}_rmarea_lower_upper_basin" self.assertModule( "r.reclass.area", flags="vd", input="basin", - output=f"{self.output}_rmarea_lower_upper", + output=output_map, lower=self.lower, upper=self.upper, method="rmarea", ) - self.map_list.append(f"{self.output}_rmarea_lower_upper") - # self.assertRasterMinMax( - # map=f"{self.output}_rmarea_lower_upper", - # refmin=200, - # refmax=1000, - # msg="Range of data: min = 200 max = 1000", - # ) + self.map_list.append(output_map) + self.assertVectorExists( + output_map, + msg=f"Output map {output_map} not found.", + ) + self.assertVectorFitsTopoInfo( + vector=output_map, + reference={"areas": 14, "primitives": 82}, + msg=f"Toplogy of output map {output_map} does not match reference.", + ) if __name__ == "__main__": From db7bdb73e409ffacaf0f85858a4c340fca3bcb9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Mon, 13 Jul 2026 09:09:25 +0000 Subject: [PATCH 12/17] fix typo --- scripts/r.reclass.area/r.reclass.area.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/r.reclass.area/r.reclass.area.md b/scripts/r.reclass.area/r.reclass.area.md index 66c586760f2..b46e7d4450c 100644 --- a/scripts/r.reclass.area/r.reclass.area.md +++ b/scripts/r.reclass.area/r.reclass.area.md @@ -2,12 +2,12 @@ *r.reclass.area* removes areas (pixel clumps) from a raster map that are smaller than a user specified **lower** area size and/or -greater than a user specified **upper** theshold (both in hectares). +greater than a user-specified **upper** threshold (both in hectares). *r.reclass.area* provides two **method**s for filtering: -- *reclass* - raster based filtering -- *rmarea* - vector based filtering +- *reclass* - raster-based filtering +- *rmarea* - vector-based filtering With the *reclass* method, the input raster map is clumped and then reclassified using the clump size. With the **-m** flag, From ef85a61d01652275c2f98a06324a23e87e80b25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Mon, 13 Jul 2026 09:09:51 +0000 Subject: [PATCH 13/17] Sync part of html --- scripts/r.reclass.area/r.reclass.area.html | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/scripts/r.reclass.area/r.reclass.area.html b/scripts/r.reclass.area/r.reclass.area.html index 206da0d0098..2fd8b315b97 100644 --- a/scripts/r.reclass.area/r.reclass.area.html +++ b/scripts/r.reclass.area/r.reclass.area.html @@ -20,7 +20,7 @@

EXAMPLES

Extract only areas greater than 2000 ha, NULL otherwise:
-r.reclass.area input=zipcodes output=zipcodes_larger2000ha mode=greater value=2000
+r.reclass.area input=zipcodes output=zipcodes_larger2000ha greater=2000
 
 r.report zipcodes_larger2000ha unit=h
 
@@ -33,7 +33,7 @@

EXAMPLES

In this example, the ZIP code map in the North Carolina sample dataset is filtered for smaller areas which are substituted with the value of the respective adjacent area with largest shared -boundary. Reclass by substitutional removing of areas minor of 1000 ha: +boundary. Reclass by substitutional removing of areas smaller than 1000 ha:
 r.reclass.area input=zipcodes output=zipcodes_minor1000ha mode=lesser value=1000 method=rmarea
@@ -47,12 +47,16 @@ 

EXAMPLES

SEE ALSO

-r.reclass, r.clump, -r.stats +r.reclass, +r.stats, +r.stats, +r.stats, +r.stats

AUTHORS

NRCS,
-Markus Neteler +Markus Neteler
+Stefan Blumentath From a8ca240c8c896eb1f9ce44acc49daa2725f40ad4 Mon Sep 17 00:00:00 2001 From: Markus Neteler Date: Mon, 13 Jul 2026 11:18:19 +0200 Subject: [PATCH 14/17] Sync MD to HTML Co-authored-by: Markus Neteler --- scripts/r.reclass.area/r.reclass.area.html | 26 +++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/scripts/r.reclass.area/r.reclass.area.html b/scripts/r.reclass.area/r.reclass.area.html index 2fd8b315b97..cb45d64a77e 100644 --- a/scripts/r.reclass.area/r.reclass.area.html +++ b/scripts/r.reclass.area/r.reclass.area.html @@ -5,7 +5,31 @@

DESCRIPTION

If the -c flag is used, r.reclass.area will skip the creation of a clumped raster and assume that the input raster is +r.reclass.area removes areas (pixel clumps) from a raster +map that are smaller than a user specified lower area +size and/or greater than a user-specified upper +threshold (both in hectares). + +r.reclass.area provides two methods for +filtering: + +

    +
  • reclass - raster-based filtering
  • +
  • rmarea - vector-based filtering
  • +
+ +With the reclass method, the input raster map is clumped and +then reclassified using the clump size. With the -m +flag, reclass method will remove small areas already during +clumping, using the minsize option there. If the +-c flag is used, r.reclass.area will skip the +creation of a clumped raster and assume that the input raster map is already clumped. +The rmarea method converts the raster map to vector and +removes areas outside the given thresholds using v.clean and v.edit. With +the -v flag the output is the filtered +vector map instead of a raster.

EXAMPLES

@@ -58,5 +82,5 @@

SEE ALSO

AUTHORS

NRCS,
-Markus Neteler
+Markus Neteler,
Stefan Blumentath From ae44aabaf29412d50c8e5d3175cb33608c8d5e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Mon, 13 Jul 2026 09:21:05 +0000 Subject: [PATCH 15/17] Edit html --- scripts/r.reclass.area/r.reclass.area.html | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/r.reclass.area/r.reclass.area.html b/scripts/r.reclass.area/r.reclass.area.html index cb45d64a77e..e070229d8f5 100644 --- a/scripts/r.reclass.area/r.reclass.area.html +++ b/scripts/r.reclass.area/r.reclass.area.html @@ -1,10 +1,5 @@

DESCRIPTION

-r.reclass.area reclasses a raster map greater or -less than a user specified area size (in hectares). -

-If the -c flag is used, r.reclass.area will skip the -creation of a clumped raster and assume that the input raster is r.reclass.area removes areas (pixel clumps) from a raster map that are smaller than a user specified lower area size and/or greater than a user-specified upper From 8b62d53c8f5419edf926c89f0891f4b71530b9a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Mon, 13 Jul 2026 09:24:17 +0000 Subject: [PATCH 16/17] Unchange header line --- scripts/r.reclass.area/testsuite/test_r_reclass_area.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/r.reclass.area/testsuite/test_r_reclass_area.py b/scripts/r.reclass.area/testsuite/test_r_reclass_area.py index dde916541c8..b2a59fdaee6 100644 --- a/scripts/r.reclass.area/testsuite/test_r_reclass_area.py +++ b/scripts/r.reclass.area/testsuite/test_r_reclass_area.py @@ -1,4 +1,5 @@ -"""Name: r.reclass.area test +""" +Name: r.reclass.area test Purpose: Tests r.reclass.area. Author: Shubham Sharma, Google Code-in 2018 From cd2acdeb5c2ca3d22283c539b77ec226756b8d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Mon, 13 Jul 2026 09:31:23 +0000 Subject: [PATCH 17/17] typos --- scripts/r.reclass.area/r.reclass.area.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/r.reclass.area/r.reclass.area.py b/scripts/r.reclass.area/r.reclass.area.py index 4f593d6bc1a..c0546ef163a 100755 --- a/scripts/r.reclass.area/r.reclass.area.py +++ b/scripts/r.reclass.area/r.reclass.area.py @@ -197,7 +197,7 @@ def reclass( upper: float | None = None, input_map: str | None = None, ) -> None: - """Perform raster based filtering.""" + """Perform raster-based filtering.""" tools = Tools(capture_output=True) stats_input = clump_map expected_fields_number = 4 @@ -267,7 +267,7 @@ def rmarea( *, return_vector: bool = False, ) -> None: - """Perform vector based filtering.""" + """Perform vector-based filtering.""" tools = Tools(capture_output=False) if gs.raster_info(input_map)["datatype"] != "CELL": @@ -291,7 +291,7 @@ def rmarea( # Apply lower threshold # transform user input from hectares to meters because currently v.clean - # rmarea accept only meters as threshold + # rmarea accepts only meters as threshold if lower: lower *= 10000.0 tools.v_clean(