From d32ece4ff46828f6a8c702a2e3693fdefa470149 Mon Sep 17 00:00:00 2001 From: Caitlin Haedrich <69856275+chaedri@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:41:18 -0400 Subject: [PATCH 01/11] grass.script: add create_project() with same functionality as create_location() (#3513) --- python/grass/script/core.py | 106 +++++++++++------- .../tests/grass_script_core_location_test.py | 31 +++++ 2 files changed, 97 insertions(+), 40 deletions(-) diff --git a/python/grass/script/core.py b/python/grass/script/core.py index f35e487548b..d5e529bdaeb 100644 --- a/python/grass/script/core.py +++ b/python/grass/script/core.py @@ -28,9 +28,11 @@ import random import shlex from tempfile import NamedTemporaryFile +from pathlib import Path from .utils import KeyValue, parse_key_val, basename, encode, decode, try_remove from grass.exceptions import ScriptError, CalledModuleError +from grass.grassdb.manage import resolve_mapset_path # subprocess wrapper that uses shell on Windows @@ -1697,9 +1699,16 @@ def mapsets(search_path=False, env=None): # interface to `g.proj -c` -def create_location( - dbase, - location, +def create_location(*args, **kwargs): + if "location" in kwargs: + kwargs["name"] = kwargs["location"] + del kwargs["location"] + return create_project(*args, **kwargs) + + +def create_project( + path, + name=None, epsg=None, proj4=None, filename=None, @@ -1709,44 +1718,34 @@ def create_location( desc=None, overwrite=False, ): - """Create new location + """Create new project Raise ScriptError on error. - :param str dbase: path to GRASS database - :param str location: location name to create - :param epsg: if given create new location based on EPSG code - :param proj4: if given create new location based on Proj4 definition - :param str filename: if given create new location based on georeferenced file - :param str wkt: if given create new location based on WKT definition + :param str path: path to GRASS database or project; if path to database, project + name must be specified with name parameter + :param str name: project name to create + :param epsg: if given create new project based on EPSG code + :param proj4: if given create new project based on Proj4 definition + :param str filename: if given create new project based on georeferenced file + :param str wkt: if given create new project based on WKT definition (can be path to PRJ file or WKT string) :param datum: GRASS format datum code :param datum_trans: datum transformation parameters (used for epsg and proj4) - :param desc: description of the location (creates MYNAME file) - :param bool overwrite: True to overwrite location if exists(WARNING: - ALL DATA from existing location ARE DELETED!) + :param desc: description of the project (creates MYNAME file) + :param bool overwrite: True to overwrite project if exists (WARNING: + ALL DATA from existing project ARE DELETED!) """ - # create dbase if not exists - if not os.path.exists(dbase): - os.mkdir(dbase) + # Add default mapset to project path if needed + if not name: + path = os.path.join(path, "PERMANENT") - # check if location already exists - if os.path.exists(os.path.join(dbase, location)): - if not overwrite: - warning(_("Location <%s> already exists. Operation canceled.") % location) - return - else: - warning( - _("Location <%s> already exists and will be overwritten") % location - ) - shutil.rmtree(os.path.join(dbase, location)) + # resolve dbase, location and mapset + mapset_path = resolve_mapset_path(path=path, location=name) - stdin = None - kwargs = dict() - if datum: - kwargs["datum"] = datum - if datum_trans: - kwargs["datum_trans"] = datum_trans + # create dbase if not exists + if not os.path.exists(mapset_path.directory): + os.mkdir(mapset_path.directory) # Lazy-importing to avoid circular dependencies. # pylint: disable=import-outside-toplevel @@ -1761,8 +1760,30 @@ def create_location( if epsg or proj4 or filename or wkt: # The names don't really matter here. tmp_gisrc, env = create_environment( - dbase, "", "", env=env + mapset_path.directory, mapset_path.location, mapset_path.mapset, env=env + ) + + # check if location already exists + if Path(mapset_path.directory, mapset_path.location).exists(): + if not overwrite: + fatal( + _("Location <%s> already exists. Operation canceled.") + % mapset_path.location, + env=env, + ) + warning( + _("Location <%s> already exists and will be overwritten") + % mapset_path.location, + env=env, ) + shutil.rmtree(os.path.join(mapset_path.directory, mapset_path.location)) + + stdin = None + kwargs = dict() + if datum: + kwargs["datum"] = datum + if datum_trans: + kwargs["datum_trans"] = datum_trans if epsg: ps = pipe_command( @@ -1770,7 +1791,7 @@ def create_location( quiet=True, flags="t", epsg=epsg, - location=location, + location=mapset_path.location, stderr=PIPE, env=env, **kwargs, @@ -1781,7 +1802,7 @@ def create_location( quiet=True, flags="t", proj4=proj4, - location=location, + location=mapset_path.location, stderr=PIPE, env=env, **kwargs, @@ -1791,28 +1812,33 @@ def create_location( "g.proj", quiet=True, georef=filename, - location=location, + location=mapset_path.location, stderr=PIPE, env=env, ) elif wkt: if os.path.isfile(wkt): ps = pipe_command( - "g.proj", quiet=True, wkt=wkt, location=location, stderr=PIPE, env=env + "g.proj", + quiet=True, + wkt=wkt, + location=mapset_path.location, + stderr=PIPE, + env=env, ) else: ps = pipe_command( "g.proj", quiet=True, wkt="-", - location=location, + location=mapset_path.location, stderr=PIPE, stdin=PIPE, env=env, ) stdin = encode(wkt) else: - _create_location_xy(dbase, location) + _create_location_xy(mapset_path.directory, mapset_path.location) if epsg or proj4 or filename or wkt: error = ps.communicate(stdin)[1] @@ -1821,7 +1847,7 @@ def create_location( if ps.returncode != 0 and error: raise ScriptError(repr(error)) - _set_location_description(dbase, location, desc) + _set_location_description(mapset_path.directory, mapset_path.location, desc) def _set_location_description(path, location, text): diff --git a/python/grass/script/tests/grass_script_core_location_test.py b/python/grass/script/tests/grass_script_core_location_test.py index 3cd89a44213..db8696cfeea 100644 --- a/python/grass/script/tests/grass_script_core_location_test.py +++ b/python/grass/script/tests/grass_script_core_location_test.py @@ -99,6 +99,37 @@ def test_with_different_path(tmp_path): assert epsg == "EPSG:3358" +def test_path_only(tmp_path): + desired_location = "desired" + full_path = tmp_path / desired_location + gs.create_location(full_path, epsg="3358") + mapset_path = full_path / "PERMANENT" + wkt_file = mapset_path / "PROJ_WKT" + assert full_path.exists() + assert mapset_path.exists() + assert wkt_file.exists() + with gs.setup.init(full_path): + gs.run_command("g.gisenv", set=f"GISDBASE={tmp_path}") + gs.run_command("g.gisenv", set=f"LOCATION_NAME={desired_location}") + gs.run_command("g.gisenv", set="MAPSET=PERMANENT") + epsg = gs.parse_command("g.proj", flags="g")["srid"] + assert epsg == "EPSG:3358" + + +def test_create_project(tmp_path): + name = "desired" + gs.create_project(tmp_path / name, epsg="3358") + assert (tmp_path / name).exists() + wkt_file = tmp_path / name / "PERMANENT" / "PROJ_WKT" + assert wkt_file.exists() + with gs.setup.init(tmp_path / name): + gs.run_command("g.gisenv", set=f"GISDBASE={tmp_path}") + gs.run_command("g.gisenv", set=f"LOCATION_NAME={name}") + gs.run_command("g.gisenv", set="MAPSET=PERMANENT") + epsg = gs.parse_command("g.proj", flags="g")["srid"] + assert epsg == "EPSG:3358" + + def test_files(tmp_path): """Check expected files are created""" bootstrap_location = "bootstrap" From 692b3c9fce59433cd907ce284da0e861965e5948 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Apr 2024 16:59:06 +0000 Subject: [PATCH 02/11] CI(deps): Update super-linter/super-linter action to v6.3.1 (#3558) --- .github/workflows/super-linter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml index 0577fce5ca4..120b9baef3d 100644 --- a/.github/workflows/super-linter.yml +++ b/.github/workflows/super-linter.yml @@ -34,7 +34,7 @@ jobs: # list of files that changed across commits fetch-depth: 0 - name: Lint code base - uses: super-linter/super-linter/slim@e0fc164bba85f4b58c6cd17ba1dfd435d01e8a06 # v6.3.0 + uses: super-linter/super-linter/slim@92e2606383320f72e6129f8a50d8537cf9c84ed6 # v6.3.1 env: DEFAULT_BRANCH: main # To report GitHub Actions status checks From 0cd72beb6366743f746863f76be9a5ed58ff2d0a Mon Sep 17 00:00:00 2001 From: Jaden Abrams <96440993+jadenabrams100@users.noreply.github.com> Date: Fri, 5 Apr 2024 14:13:03 -0400 Subject: [PATCH 03/11] CI: specify coverity version in coverity.yml (#3556) --- .github/workflows/coverity.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index 18dceb1a6b1..32895e33e37 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -18,3 +18,4 @@ jobs: coverity_url: ${{ secrets.COVERITY_URL }} # The URL to Coverity coverity_user: ${{ secrets.COVERITY_USER }} # The user for the Coverity project coverity_passphrase: ${{ secrets.COVERITY_PASSPHRASE }} # The password for the Coverity user + coverity_version: '2023.6.2' # The version for Coverity Scan From e59203466773b61eda9e18a9a9f923f824e635ba Mon Sep 17 00:00:00 2001 From: Corey White Date: Fri, 5 Apr 2024 16:24:05 -0400 Subject: [PATCH 04/11] r.surf.random: Added seed option to module (#2930) --- raster/r.surf.random/main.c | 24 +++++++++++++ raster/r.surf.random/randsurf.c | 4 --- .../r.surf.random/testsuite/test_min_max.py | 34 +++++++++++++------ 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/raster/r.surf.random/main.c b/raster/r.surf.random/main.c index fc4a9ea4c96..a657ba00498 100644 --- a/raster/r.surf.random/main.c +++ b/raster/r.surf.random/main.c @@ -67,9 +67,12 @@ int main(int argc, char *argv[]) struct Option *out; struct Option *min; struct Option *max; + struct Option *seed; struct Flag *i_flag; double min_value; double max_value; + long seed_value; + char *seedptr; struct History history; char title[64]; @@ -97,6 +100,14 @@ int main(int argc, char *argv[]) max->type = TYPE_DOUBLE; max->answer = "100"; + seed = G_define_option(); + seed->key = "seed"; + seed->type = TYPE_INTEGER; + seed->required = NO; + seed->label = _("Seed for random number generator"); + seed->description = _("The same seed can be used to obtain same results" + " or random seed can be generated by other means."); + i_flag = G_define_flag(); i_flag->key = 'i'; i_flag->description = _("Create an integer raster map"); @@ -107,6 +118,19 @@ int main(int argc, char *argv[]) min_value = atof(min->answer); max_value = atof(max->answer); + if (seed->answer) { + seed_value = strtol(seed->answer, &seedptr, 10); + G_srand48(seed_value); + G_verbose_message(_("Read random seed from %s option: %ld"), seed->key, + seed_value); + } + else { + /* default as it used to be */ + seed_value = G_srand48_auto(); + G_verbose_message(_("Autogenerated random seed set to: %ld"), + seed_value); + } + /* We disallow max=5.5 for integer output since there are unclear * expectations on what it should do. */ if (i_flag->answer) { diff --git a/raster/r.surf.random/randsurf.c b/raster/r.surf.random/randsurf.c index a5214b26e4f..ca3a13ec20e 100644 --- a/raster/r.surf.random/randsurf.c +++ b/raster/r.surf.random/randsurf.c @@ -21,10 +21,6 @@ int randsurf(char *out, double min, double max, int int_map) /* open raster maps. */ int row_count, col_count; - /****** INITIALISE RANDOM NUMBER GENERATOR ******/ - /* You can set GRASS_RANDOM_SEED for repeatability */ - G_srand48_auto(); - /****** OPEN CELL FILES AND GET CELL DETAILS ******/ fd_out = Rast_open_new(out, int_map ? CELL_TYPE : DCELL_TYPE); diff --git a/raster/r.surf.random/testsuite/test_min_max.py b/raster/r.surf.random/testsuite/test_min_max.py index 6a7319ef69e..063f7965771 100644 --- a/raster/r.surf.random/testsuite/test_min_max.py +++ b/raster/r.surf.random/testsuite/test_min_max.py @@ -14,8 +14,6 @@ for details. """ -import os - import grass.script as gs from grass.gunittest.case import TestCase @@ -37,12 +35,9 @@ class MinMaxTestCase(TestCase): @classmethod def setUpClass(cls): """Ensures expected computational region""" - os.environ["GRASS_RANDOM_SEED"] = "42" # modifying region just for this script cls.use_temp_region() - # Only 100,000,000 seem to resonably (not 100%) ensure that all values - # are generated, so exceeding of ranges actually shows up. - cls.runModule("g.region", rows=10000, cols=10000) + cls.runModule("g.region", rows=10, cols=10) @classmethod def tearDownClass(cls): @@ -57,10 +52,9 @@ def test_min_max_double(self): """Check to see if double output has the expected range""" min_value = -3.3 max_value = 5.8 - # arbitrary, but with more cells, we expect higher precision precision = 0.00001 self.assertModule( - "r.surf.random", min=min_value, max=max_value, output=self.output + "r.surf.random", min=min_value, max=max_value, output=self.output, seed=42 ) self.assertRasterExists(self.output, msg="Output was not created") self.assertRasterMinMax( @@ -71,7 +65,7 @@ def test_min_max_double(self): ) self.assertRasterFitsInfo( raster=self.output, - reference=dict(min=min_value, max=max_value), + reference=dict(min=-3.20423, max=5.68621), precision=precision, msg="Output min and max too far from parameters", ) @@ -84,7 +78,12 @@ def test_min_max_int(self): max_value = 13 precision = 0 self.assertModule( - "r.surf.random", min=min_value, max=max_value, output=self.output, flags="i" + "r.surf.random", + min=min_value, + max=max_value, + output=self.output, + seed=42, + flags="i", ) self.assertRasterExists(self.output, msg="Output was not created") self.assertRasterMinMax( @@ -105,7 +104,12 @@ def test_double_params_with_int(self): min_value = -3.3 max_value = 5.8 self.assertModuleFail( - "r.surf.random", min=min_value, max=max_value, output=self.output, flags="i" + "r.surf.random", + min=min_value, + max=max_value, + output=self.output, + seed=42, + flags="i", ) def test_min_greater_than_max(self): @@ -113,6 +117,14 @@ def test_min_greater_than_max(self): min_value = 10 max_value = 5.8 self.assertModuleFail( + "r.surf.random", min=min_value, max=max_value, output=self.output, seed=42 + ) + + def test_auto_seed(self): + """Check if random seed is generated without seed""" + min_value = -3.3 + max_value = 5.8 + self.assertModule( "r.surf.random", min=min_value, max=max_value, output=self.output ) From 927b653307b033eb898414947855b1549442faff Mon Sep 17 00:00:00 2001 From: Paulo van Breugel Date: Fri, 5 Apr 2024 23:31:52 +0200 Subject: [PATCH 05/11] r.univar: add note that parallelization is disabled if mask is set (#3562) And remove older note about extended statistics that is not valid anymore. --- raster/r.univar/r.univar.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/raster/r.univar/r.univar.html b/raster/r.univar/r.univar.html index 5f966e61841..eaedaa0aae8 100644 --- a/raster/r.univar/r.univar.html +++ b/raster/r.univar/r.univar.html @@ -53,8 +53,7 @@

PERFORMANCE

r.univar supports parallel processing using OpenMP. The user can specify the number of threads to be used with the nprocs parameter. -However, parallelization is disabled when the -e extended statistics -flag is used. +However, parallelization is disabled when the MASK is set.

Due to the differences in summation order, users may encounter small floating points From d32dfb5e3c763de64a6a91d8950bbf1a5668e322 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Apr 2024 22:04:48 +0000 Subject: [PATCH 06/11] Update github/codeql-action action to v3.24.10 (#3564) --- .github/workflows/codeql-analysis.yml | 4 ++-- .github/workflows/python-code-quality.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index afcf69d8909..9f8b240eaed 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -56,7 +56,7 @@ jobs: sudo apt-get install -y --no-install-recommends --no-install-suggests - name: Initialize CodeQL - uses: github/codeql-action/init@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/init@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/codeql-config.yml @@ -82,6 +82,6 @@ jobs: run: .github/workflows/build_ubuntu-22.04.sh "${HOME}/install" - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/analyze@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/python-code-quality.yml b/.github/workflows/python-code-quality.yml index 438a21421f0..2d2021f1683 100644 --- a/.github/workflows/python-code-quality.yml +++ b/.github/workflows/python-code-quality.yml @@ -108,7 +108,7 @@ jobs: path: bandit.sarif - name: Upload SARIF File into Security Tab - uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/upload-sarif@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: sarif_file: bandit.sarif From 9f4963070bb41d0d9be40e1024ea1efa7da0dcf4 Mon Sep 17 00:00:00 2001 From: Anna Petrasova Date: Sat, 6 Apr 2024 11:13:07 -0400 Subject: [PATCH 07/11] grass.jupyter: fix reinitializing session (#3563) --- python/grass/jupyter/setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/grass/jupyter/setup.py b/python/grass/jupyter/setup.py index adbe07d5320..0ed64739039 100644 --- a/python/grass/jupyter/setup.py +++ b/python/grass/jupyter/setup.py @@ -16,6 +16,7 @@ import os import weakref +from pathlib import Path import grass.script as gs @@ -87,6 +88,7 @@ def switch_mapset(self, path, location=None, mapset=None): if ( not location and not mapset + and len(Path(path).parts) == 1 and mapset_exists( path=gisenv["GISDBASE"], location=gisenv["LOCATION_NAME"], mapset=path ) From dec42666cac1d09a271a8544e425117a25526536 Mon Sep 17 00:00:00 2001 From: Jaden Abrams <96440993+jadenabrams100@users.noreply.github.com> Date: Sun, 7 Apr 2024 08:59:43 -0400 Subject: [PATCH 08/11] libgis/r.gwflow: fixed security vulnerabilities and weaknesses (#3549) This fixes three vulnerabilities/weaknesses found with older scans of Coverity: - Issue 1208372 in lib/gis/error.c concerns an unbounded read of an environment variable into memory. An attacker could overwrite the environment variable that is accessed by G__home() and exploit it to overflow the buf array. - Issue 1501330 in lib/gis/mapset_msc.c concerns writing into an array that is not null terminated. If the path variable was not null terminated, the write could fill the whole array with data without a null terminator, causing trouble down the line. - Issue 1207344 in raster/r.gwflow/main.c concerns a constant variable guarding dead code. This is not exactly a security vulnerability, but is a code quality issue I was able to easily fix. --- lib/gis/error.c | 2 +- lib/gis/mapset_msc.c | 5 +++-- raster/r.gwflow/main.c | 4 ---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/gis/error.c b/lib/gis/error.c index 77c2138b43a..89c7488a1d6 100644 --- a/lib/gis/error.c +++ b/lib/gis/error.c @@ -370,7 +370,7 @@ void G_init_logging(void) if (!logfile) { char buf[GPATH_MAX]; - sprintf(buf, "%s/GIS_ERROR_LOG", G__home()); + snprintf(buf, GPATH_MAX, "%s/GIS_ERROR_LOG", G__home()); logfile = G_store(buf); } diff --git a/lib/gis/mapset_msc.c b/lib/gis/mapset_msc.c index 957a03f4c3e..8553ff3807d 100644 --- a/lib/gis/mapset_msc.c +++ b/lib/gis/mapset_msc.c @@ -183,14 +183,15 @@ int G_make_mapset_object_group_basedir(const char *type, const char *basedir) int make_mapset_element_impl(const char *p_path, const char *p_element, bool race_ok) { - char path[GPATH_MAX], *p; + char path[GPATH_MAX] = {'\0'}; + char *p; const char *element; element = p_element; if (*element == 0) return 0; - strncpy(path, p_path, GPATH_MAX); + strncpy(path, p_path, GPATH_MAX - 1); p = path; while (*p) p++; diff --git a/raster/r.gwflow/main.c b/raster/r.gwflow/main.c index 6bb9b8a0a22..3cf5131b4b7 100644 --- a/raster/r.gwflow/main.c +++ b/raster/r.gwflow/main.c @@ -212,7 +212,6 @@ int main(int argc, char *argv[]) N_gradient_field_2d *field = NULL; N_array_2d *xcomp = NULL; N_array_2d *ycomp = NULL; - char *buff = NULL; int with_river = 0, with_drain = 0; /* Initialize GRASS */ @@ -460,9 +459,6 @@ int main(int argc, char *argv[]) N_write_array_2d_to_rast(xcomp, param.vector_x->answer); N_write_array_2d_to_rast(ycomp, param.vector_y->answer); - if (buff) - G_free(buff); - if (xcomp) N_free_array_2d(xcomp); if (ycomp) From 544a78044fb61d74ecac5683268dccbb1320bd38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81ris=20Narti=C5=A1s?= Date: Sun, 7 Apr 2024 18:45:50 +0300 Subject: [PATCH 09/11] r.in.pdal: use NoFileWriter class for PDAL > 2.7.0 (fixes #3552) (#3567) * r.in.pdal: use NoFileWriter class for PDAL > 2.7.0 (fixes #3552) Starting from PDAL 2.7.0 filename is mandatory for Writer class, but there is a NoFileWriter class that maintains old behavior. https://github.com/PDAL/PDAL/pull/4342 --------- Co-authored-by: Nicklas Larsson --- configure | 41 ++++++++++++++++++---------- configure.ac | 22 ++++++++++----- include/grass/config.h.in | 3 ++ raster/r.in.pdal/grassrasterwriter.h | 5 ++++ 4 files changed, 49 insertions(+), 22 deletions(-) diff --git a/configure b/configure index bc58bfe0df2..fc8d39a7a05 100755 --- a/configure +++ b/configure @@ -10473,42 +10473,53 @@ pdal::PointTable table; _ACEOF if ac_fn_cxx_try_link "$LINENO" then : - + PDAL_LIBS="$PDAL_LIBS" else $as_nop + as_fn_error $? "*** Unable to locate suitable (>=1.7.1) PDAL library." "$LINENO" 5 + +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + + +printf "%s\n" "#define HAVE_PDAL 1" >>confdefs.h + + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to use PDAL NoFilenameWriter" >&5 +printf %s "checking whether to use PDAL NoFilenameWriter... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -#include - #include - class St:public pdal::Streamable {}; +#include + class St:public pdal::NoFilenameWriter {}; int main (void) { -pdal::PointTable table; + + class NFWTest : public pdal::NoFilenameWriter {}; + ; return 0; } _ACEOF if ac_fn_cxx_try_link "$LINENO" then : - PDAL_LIBS="$PDAL_LIBS" -else $as_nop - as_fn_error $? "*** Unable to locate suitable (>=1.7.1) PDAL library." "$LINENO" 5 -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam \ - conftest$ac_exeext conftest.$ac_ext +printf "%s\n" "#define HAVE_PDAL_NOFILENAMEWRITER 1" >>confdefs.h + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } +else $as_nop + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext + LIBS=${ac_save_libs} CFLAGS=${ac_save_cflags} - - -printf "%s\n" "#define HAVE_PDAL 1" >>confdefs.h - fi diff --git a/configure.ac b/configure.ac index 57af5a5923a..d7f7b578282 100644 --- a/configure.ac +++ b/configure.ac @@ -1076,17 +1076,25 @@ else CFLAGS="$CFLAGS $PDAL_CFLAGS" AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include #include - class St:public pdal::Streamable {};]], [[pdal::PointTable table;]])],[],[ - AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include - #include - class St:public pdal::Streamable {};]], [[pdal::PointTable table;]])],[PDAL_LIBS="$PDAL_LIBS"],[ + class St:public pdal::Streamable {};]], [[pdal::PointTable table;]])], + [PDAL_LIBS="$PDAL_LIBS"],[ AC_MSG_ERROR([*** Unable to locate suitable (>=1.7.1) PDAL library.]) ]) - ]) - LIBS=${ac_save_libs} - CFLAGS=${ac_save_cflags} AC_DEFINE(HAVE_PDAL, 1, [Define to 1 if PDAL exists.]) + + AC_MSG_CHECKING(whether to use PDAL NoFilenameWriter) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include + class St:public pdal::NoFilenameWriter {};]], [[ + class NFWTest : public pdal::NoFilenameWriter {}; + ]])], + [ + AC_DEFINE(HAVE_PDAL_NOFILENAMEWRITER, 1, [Define to 1 if PDAL NoFilenameWriter is present.]) + AC_MSG_RESULT(yes) + ],[AC_MSG_RESULT(no)]) + + LIBS=${ac_save_libs} + CFLAGS=${ac_save_cflags} fi AC_SUBST(PDAL_LIBS) diff --git a/include/grass/config.h.in b/include/grass/config.h.in index 5730328ccb5..ca90ef601c5 100644 --- a/include/grass/config.h.in +++ b/include/grass/config.h.in @@ -158,6 +158,9 @@ /* Define to 1 if PDAL exists. */ #undef HAVE_PDAL +/* Define to 1 if PDAL NoFilenameWriter is present. */ +#undef HAVE_PDAL_NOFILENAMEWRITER + /* Define to 1 if glXCreateGLXPixmap exists. */ #undef HAVE_PIXMAPS diff --git a/raster/r.in.pdal/grassrasterwriter.h b/raster/r.in.pdal/grassrasterwriter.h index f3df8ef04b1..4db8852aa8a 100644 --- a/raster/r.in.pdal/grassrasterwriter.h +++ b/raster/r.in.pdal/grassrasterwriter.h @@ -32,7 +32,12 @@ extern "C" { #include /* Binning code wrapped as a PDAL Writer class */ +#ifdef HAVE_PDAL_NOFILENAMEWRITER +class GrassRasterWriter : public pdal::NoFilenameWriter, + public pdal::Streamable { +#else class GrassRasterWriter : public pdal::Writer, public pdal::Streamable { +#endif public: GrassRasterWriter() : n_processed(0) {} From 597e2223c763839b2ad314284801c6106f49a588 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 07:25:07 -0400 Subject: [PATCH 10/11] Update docker/setup-buildx-action action to v3.3.0 (#3568) --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 160461f8394..e8616b01718 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -68,7 +68,7 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@2b51285047da1547ffb1b2203d8be4c0af6b1f20 # v3.2.0 + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Login to DockerHub uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: From 2c932490c555ac274611ad5ed0e496a78c983efd Mon Sep 17 00:00:00 2001 From: Anna Petrasova Date: Mon, 8 Apr 2024 09:29:59 -0400 Subject: [PATCH 11/11] r.horizon: add distance into output (#3565) --- raster/r.horizon/main.c | 62 ++++++++++++++----- raster/r.horizon/r.horizon.html | 4 ++ raster/r.horizon/testsuite/test_r_horizon.py | 65 ++++++++++++++++++++ 3 files changed, 116 insertions(+), 15 deletions(-) diff --git a/raster/r.horizon/main.c b/raster/r.horizon/main.c index 2e4aaf190f5..24d96ec873f 100644 --- a/raster/r.horizon/main.c +++ b/raster/r.horizon/main.c @@ -104,6 +104,7 @@ typedef struct { } Geometry; typedef struct { + bool horizonDistance; int degreeOutput; int compassOutput; double fixedMaxLength; @@ -120,8 +121,9 @@ int OUTGR(const Settings *settings, char *shad_filename, struct Cell_head *cellhd); void com_par(const Geometry *geometry, OriginAngle *origin_angle, double angle, double xp, double yp); -double horizon_height(const Geometry *geometry, const OriginPoint *origin_point, - const OriginAngle *origin_angle); +HorizonProperties horizon_height(const Geometry *geometry, + const OriginPoint *origin_point, + const OriginAngle *origin_angle); void calculate_point_mode(const Settings *settings, const Geometry *geometry, double xcoord, double ycoord, FILE *fp, enum OutputFormat format); @@ -163,7 +165,7 @@ int main(int argc, char *argv[]) } parm; struct { - struct Flag *degreeOutput, *compassOutput; + struct Flag *horizonDistance, *degreeOutput, *compassOutput; } flag; G_gisinit(argv[0]); @@ -312,6 +314,12 @@ int main(int argc, char *argv[]) _("Name of file for output (use output=- for stdout)"); parm.output->guisection = _("Point mode"); + flag.horizonDistance = G_define_flag(); + flag.horizonDistance->key = 'l'; + flag.horizonDistance->description = + _("Include horizon distance in the output"); + flag.horizonDistance->guisection = _("Point mode"); + flag.degreeOutput = G_define_flag(); flag.degreeOutput->key = 'd'; flag.degreeOutput->description = @@ -357,6 +365,7 @@ int main(int argc, char *argv[]) Settings settings; settings.degreeOutput = flag.degreeOutput->answer; settings.compassOutput = flag.compassOutput->answer; + settings.horizonDistance = flag.horizonDistance->answer; if (G_projection() == PROJECTION_LL) G_important_message(_("Note: In latitude-longitude coordinate system " @@ -796,14 +805,18 @@ void calculate_point_mode(const Settings *settings, const Geometry *geometry, origin_point.maxlength = settings->fixedMaxLength; /* JSON variables and formating */ - JSON_Value *root_value, *origin_value, *azimuths_value, *horizons_value; - JSON_Array *coordinates, *azimuths, *horizons; + JSON_Value *root_value, *origin_value, *azimuths_value, *horizons_value, + *distances_value; + JSON_Array *coordinates, *azimuths, *horizons, *distances; JSON_Object *origin; json_set_float_serialization_format("%lf"); switch (format) { case PLAIN: - fprintf(fp, "azimuth,horizon_height\n"); + fprintf(fp, "azimuth,horizon_height"); + if (settings->horizonDistance) + fprintf(fp, ",horizon_distance"); + fprintf(fp, "\n"); break; case JSON: root_value = json_value_init_array(); @@ -816,14 +829,17 @@ void calculate_point_mode(const Settings *settings, const Geometry *geometry, azimuths = json_value_get_array(azimuths_value); horizons_value = json_value_init_array(); horizons = json_value_get_array(horizons_value); + distances_value = json_value_init_array(); + distances = json_value_get_array(distances_value); break; } for (int i = 0; i < printCount; i++) { OriginAngle origin_angle; com_par(geometry, &origin_angle, angle, xp, yp); - double shadow_angle = + HorizonProperties horizon = horizon_height(geometry, &origin_point, &origin_angle); + double shadow_angle = atan(horizon.tanh0); if (settings->degreeOutput) { shadow_angle *= rad2deg; @@ -837,22 +853,32 @@ void calculate_point_mode(const Settings *settings, const Geometry *geometry, tmpangle = tmpangle - 360.; switch (format) { case PLAIN: - fprintf(fp, "%lf,%lf\n", tmpangle, shadow_angle); + fprintf(fp, "%lf,%lf", tmpangle, shadow_angle); + if (settings->horizonDistance) + fprintf(fp, ",%lf", horizon.length); + fprintf(fp, "\n"); break; case JSON: json_array_append_number(azimuths, tmpangle); json_array_append_number(horizons, shadow_angle); + if (settings->horizonDistance) + json_array_append_number(distances, horizon.length); break; } } else { switch (format) { case PLAIN: - fprintf(fp, "%lf,%lf\n", printangle, shadow_angle); + fprintf(fp, "%lf,%lf", printangle, shadow_angle); + if (settings->horizonDistance) + fprintf(fp, ",%lf", horizon.length); + fprintf(fp, "\n"); break; case JSON: json_array_append_number(azimuths, printangle); json_array_append_number(horizons, shadow_angle); + if (settings->horizonDistance) + json_array_append_number(distances, horizon.length); break; } } @@ -874,6 +900,8 @@ void calculate_point_mode(const Settings *settings, const Geometry *geometry, if (format == JSON) { json_object_set_value(origin, "azimuth", azimuths_value); json_object_set_value(origin, "horizon_height", horizons_value); + if (settings->horizonDistance) + json_object_set_value(origin, "horizon_distance", distances_value); json_array_append_value(coordinates, origin_value); char *json_string = json_serialize_to_string_pretty(root_value); fprintf(fp, "%s\n", json_string); @@ -1001,8 +1029,9 @@ int test_low_res(const Geometry *geometry, const OriginPoint *origin_point, } } -double horizon_height(const Geometry *geometry, const OriginPoint *origin_point, - const OriginAngle *origin_angle) +HorizonProperties horizon_height(const Geometry *geometry, + const OriginPoint *origin_point, + const OriginAngle *origin_angle) { SearchPoint search_point; HorizonProperties horizon; @@ -1018,8 +1047,10 @@ double horizon_height(const Geometry *geometry, const OriginPoint *origin_point, horizon.length = 0; horizon.tanh0 = 0; - if (search_point.zp == UNDEFZ) - return 0; + if (search_point.zp == UNDEFZ) { + HorizonProperties h = {0, 0}; + return h; + } while (1) { int succes = new_point(geometry, origin_point, origin_angle, @@ -1051,7 +1082,7 @@ double horizon_height(const Geometry *geometry, const OriginPoint *origin_point, } } - return atan(horizon.tanh0); + return horizon; } /*////////////////////////////////////////////////////////////////////// */ @@ -1159,8 +1190,9 @@ void calculate_raster_mode(const Settings *settings, const Geometry *geometry, if (origin_point.z_orig != UNDEFZ) { G_debug(4, "**************new line %d %d\n", i, j); - double shadow_angle = + HorizonProperties horizon = horizon_height(geometry, &origin_point, &origin_angle); + double shadow_angle = atan(horizon.tanh0); if (settings->degreeOutput) { shadow_angle *= rad2deg; diff --git a/raster/r.horizon/r.horizon.html b/raster/r.horizon/r.horizon.html index 418ece9571f..1b9972cc702 100644 --- a/raster/r.horizon/r.horizon.html +++ b/raster/r.horizon/r.horizon.html @@ -30,6 +30,10 @@

DESCRIPTION

Using the -c flag, the azimuthal angles will be printed in compass orientation (North=0, clockwise). +

+Activating the -l flag allows to additionally print the distance +to each horizon angle. +

Input parameters:

The elevation parameter is an input elevation raster map. If the buffer options are used (see below), this raster should extend diff --git a/raster/r.horizon/testsuite/test_r_horizon.py b/raster/r.horizon/testsuite/test_r_horizon.py index 2df976e6370..bc4043372d2 100644 --- a/raster/r.horizon/testsuite/test_r_horizon.py +++ b/raster/r.horizon/testsuite/test_r_horizon.py @@ -86,6 +86,27 @@ 340.000000,0.196863 """ +ref5 = """azimuth,horizon_height,horizon_distance +0.000000,0.197017,5010.039920 +20.000000,0.196832,5017.668781 +40.000000,0.196875,5017.818251 +60.000000,0.196689,5017.220346 +80.000000,0.196847,5014.299552 +100.000000,0.196645,5019.531851 +120.000000,0.196969,5014.957627 +140.000000,0.196778,5020.328674 +160.000000,0.196863,5013.431958 +180.000000,0.197017,5010.039920 +200.000000,0.196832,5014.229751 +220.000000,0.196875,5011.387034 +240.000000,0.196689,5017.220346 +260.000000,0.196847,5014.299552 +280.000000,0.196645,5019.531851 +300.000000,0.196969,5014.957627 +320.000000,0.196778,5020.328674 +340.000000,0.196863,5013.431958 +""" + class TestHorizon(TestCase): circle = "circle" @@ -196,6 +217,50 @@ def test_point_mode_multiple_direction_artificial(self): stdout = module.outputs.stdout self.assertMultiLineEqual(first=ref4, second=stdout) + def test_point_mode_multiple_direction_artificial_distance(self): + """With 1 point, more directions on artificial surface, distance in output""" + module = SimpleModule( + "r.horizon", + elevation=self.circle, + coordinates=(637505, 221755), + output=self.horizon, + direction=0, + step=20, + flags="l", + ) + self.assertModule(module) + stdout = module.outputs.stdout + self.assertMultiLineEqual(first=ref5, second=stdout) + + module = SimpleModule( + "r.horizon", + elevation=self.circle, + coordinates=(637505, 221755), + output=self.horizon, + direction=0, + step=20, + flags="l", + format="json", + ) + self.assertModule(module) + stdout = json.loads(module.outputs.stdout) + azimuths = [] + horizons = [] + distances = [] + reference = {} + for line in ref5.splitlines()[1:]: + azimuth, horizon, distance = line.split(",") + azimuths.append(float(azimuth)) + horizons.append(float(horizon)) + distances.append(float(distance)) + reference["x"] = 637505.0 + reference["y"] = 221755.0 + reference["azimuth"] = azimuths + reference["horizon_height"] = horizons + reference["horizon_distance"] = distances + + self.assertListEqual([reference], stdout) + def test_raster_mode_one_direction(self): """Test mode with one direction and against point mode""" module = SimpleModule(