diff -Nru grass-8.3.1/.github/workflows/additional_checks.yml grass-8.3.2/.github/workflows/additional_checks.yml --- grass-8.3.1/.github/workflows/additional_checks.yml 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/.github/workflows/additional_checks.yml 2024-03-06 21:24:05.000000000 +0000 @@ -27,7 +27,7 @@ steps: - name: Checkout repository contents - uses: actions/checkout@v3 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 with: fetch-depth: 31 @@ -38,7 +38,7 @@ exclude: mswindows .*\.bat .*/testsuite/data/.* - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' diff -Nru grass-8.3.1/.github/workflows/clang-format-check.yml grass-8.3.2/.github/workflows/clang-format-check.yml --- grass-8.3.1/.github/workflows/clang-format-check.yml 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/.github/workflows/clang-format-check.yml 2024-03-06 21:24:05.000000000 +0000 @@ -17,7 +17,7 @@ name: Formatting Check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - name: Run clang-format style check for C/C++/Protobuf programs. uses: jidicula/clang-format-action@v4.11.0 with: diff -Nru grass-8.3.1/.github/workflows/create_release_draft.yml grass-8.3.2/.github/workflows/create_release_draft.yml --- grass-8.3.1/.github/workflows/create_release_draft.yml 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/.github/workflows/create_release_draft.yml 2024-03-06 21:24:05.000000000 +0000 @@ -22,12 +22,12 @@ steps: - name: Checks-out repository - uses: actions/checkout@v3 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' @@ -93,7 +93,7 @@ asset_content_type: application/gzip - name: Make the created files available - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: artifacts diff -Nru grass-8.3.1/.github/workflows/docker.yml grass-8.3.2/.github/workflows/docker.yml --- grass-8.3.1/.github/workflows/docker.yml 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/.github/workflows/docker.yml 2024-03-06 21:24:05.000000000 +0000 @@ -49,12 +49,12 @@ steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 with: fetch-depth: 0 - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: osgeo/grass-gis tags: | @@ -66,17 +66,17 @@ latest=false suffix=-${{ matrix.os }} - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push id: docker_build - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: push: true pull: true diff -Nru grass-8.3.1/.github/workflows/gcc.yml grass-8.3.2/.github/workflows/gcc.yml --- grass-8.3.1/.github/workflows/gcc.yml 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/.github/workflows/gcc.yml 2024-03-06 21:24:05.000000000 +0000 @@ -35,7 +35,7 @@ fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - name: Get dependencies run: | sudo apt-get update -y diff -Nru grass-8.3.1/.github/workflows/osgeo4w.yml grass-8.3.2/.github/workflows/osgeo4w.yml --- grass-8.3.1/.github/workflows/osgeo4w.yml 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/.github/workflows/osgeo4w.yml 2024-03-06 21:24:05.000000000 +0000 @@ -32,7 +32,7 @@ run: | git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@v3 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - uses: msys2/setup-msys2@v2 with: path-type: inherit diff -Nru grass-8.3.1/.github/workflows/pytest.yml grass-8.3.2/.github/workflows/pytest.yml --- grass-8.3.1/.github/workflows/pytest.yml 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/.github/workflows/pytest.yml 2024-03-06 21:24:05.000000000 +0000 @@ -30,10 +30,10 @@ runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} diff -Nru grass-8.3.1/.github/workflows/ubuntu.yml grass-8.3.2/.github/workflows/ubuntu.yml --- grass-8.3.1/.github/workflows/ubuntu.yml 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/.github/workflows/ubuntu.yml 2024-03-06 21:24:05.000000000 +0000 @@ -26,7 +26,7 @@ strategy: matrix: include: - - name: '22.04' + - name: "22.04" os: ubuntu-22.04 config: ubuntu-22.04 # This is without optional things but it still keeps things useful, @@ -38,7 +38,7 @@ fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - name: Get dependencies run: | @@ -86,7 +86,7 @@ - name: Make HTML test report available if: ${{ always() }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: testreport-${{ matrix.config }} path: testreport diff -Nru grass-8.3.1/Makefile grass-8.3.2/Makefile --- grass-8.3.1/Makefile 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/Makefile 2024-03-06 21:24:05.000000000 +0000 @@ -9,7 +9,7 @@ # PURPOSE: It provides the commands necessary to compile, install, # clean, and uninstall GRASS # See INSTALL.md file for usage. -# COPYRIGHT: (C) 2002-2023 by the GRASS Development Team +# COPYRIGHT: (C) 2002-2024 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 diff -Nru grass-8.3.1/REQUIREMENTS.md grass-8.3.2/REQUIREMENTS.md --- grass-8.3.1/REQUIREMENTS.md 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/REQUIREMENTS.md 2024-03-06 21:24:05.000000000 +0000 @@ -139,7 +139,7 @@ --- -© _GRASS Development Team 1997-2023_ +© _GRASS Development Team 1997-2024_ Please report bugs here: [https://grass.osgeo.org/contribute/](https://grass.osgeo.org/contribute/) diff -Nru grass-8.3.1/configure.ac grass-8.3.2/configure.ac --- grass-8.3.1/configure.ac 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/configure.ac 2024-03-06 21:24:05.000000000 +0000 @@ -9,7 +9,7 @@ # PURPOSE: This configure runs all the tests to determine what components # are installed on the current system. It also defines certain # configuration variables for compilation and installation. -# COPYRIGHT: (C) 2000-2023 by the GRASS Development Team +# COPYRIGHT: (C) 2000-2024 by the GRASS Development Team # # This program is free software under the GNU General # Public License (>=v2). Read the file COPYING that diff -Nru grass-8.3.1/db/db.connect/db.connect.html grass-8.3.2/db/db.connect/db.connect.html --- grass-8.3.1/db/db.connect/db.connect.html 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/db/db.connect/db.connect.html 2024-03-06 21:24:05.000000000 +0000 @@ -4,8 +4,8 @@ These parameters are then taken as default values by modules so that the user does not need to enter the parameters each time.

-The default database backend in GRASS GIS 7 -is SQLite. +The default database backend in GRASS GIS +is SQLite (since version 7).

NOTES

diff -Nru grass-8.3.1/debian/changelog grass-8.3.2/debian/changelog --- grass-8.3.1/debian/changelog 2024-01-26 19:00:00.000000000 +0000 +++ grass-8.3.2/debian/changelog 2024-03-08 07:48:58.000000000 +0000 @@ -1,8 +1,40 @@ -grass (8.3.1-1~jammy2) jammy; urgency=medium +grass (8.3.2-1~jammy1) jammy; urgency=medium - * No change rebuild for GDAL 3.8.3 transition. + * Rebuild for jammy. - -- Angelos Tzotsos Fri, 26 Jan 2024 21:00:00 +0200 + -- Martin Landa Fri, 08 Mar 2024 08:48:58 +0100 + +grass (8.3.2-1) unstable; urgency=medium + + * Move from experimental to unstable. + + -- Bas Couwenberg Fri, 08 Mar 2024 07:46:06 +0100 + +grass (8.3.2-1~exp1) experimental; urgency=medium + + * New upstream release. + + -- Bas Couwenberg Thu, 07 Mar 2024 18:18:27 +0100 + +grass (8.3.2~rc1-1~exp1~jammy1) jammy; urgency=medium + + * Rebuild for jammy. + + -- Martin Landa Thu, 22 Feb 2024 09:45:51 +0100 + +grass (8.3.2~rc1-1~exp1) experimental; urgency=medium + + * New upstream release candidate. + * Replace pkg-config (build) dependency with pkgconf. + * Update copyright file. + + -- Bas Couwenberg Wed, 21 Feb 2024 05:20:46 +0100 + +grass (8.3.1-2) unstable; urgency=medium + + * Drop unused python3-opengl dependency. + + -- Bas Couwenberg Sun, 17 Dec 2023 17:36:37 +0100 grass (8.3.1-1~jammy1) jammy; urgency=medium diff -Nru grass-8.3.1/debian/control grass-8.3.2/debian/control --- grass-8.3.1/debian/control 2023-10-26 08:07:16.000000000 +0000 +++ grass-8.3.2/debian/control 2024-03-08 07:48:20.000000000 +0000 @@ -39,7 +39,7 @@ netcdf-bin, # install optipng if compressing the PNG images in the programmers' manual # optipng, - pkg-config, + pkgconf, # proj-bin needed for nad2bin during config proj-bin, python3, @@ -122,8 +122,6 @@ Package: grass-gui Architecture: any Depends: grass-core, -# pyGL needed for wxNviz - python3-opengl, python3-wxgtk4.0, xterm | x-terminal-emulator, ${python3:Depends}, @@ -193,7 +191,7 @@ ${shlibs:Depends}, ${misc:Depends} Suggests: grass-dev-doc, - pkg-config + pkgconf Description: GRASS GIS development files Commonly referred to as GRASS, this is a Geographic Information System (GIS) used for geospatial data management and analysis, diff -Nru grass-8.3.1/debian/copyright grass-8.3.2/debian/copyright --- grass-8.3.1/debian/copyright 2023-10-26 08:06:26.000000000 +0000 +++ grass-8.3.2/debian/copyright 2024-03-08 07:47:47.000000000 +0000 @@ -4,7 +4,7 @@ Source: https://grass.osgeo.org/ Files: * -Copyright: 1989-2023, GRASS Development Team +Copyright: 1989-2024, GRASS Development Team 2003-2023, Markus Neteler 2003-2023, Luca Delucchi 2003-2023, Glynn Clements diff -Nru grass-8.3.1/display/d.vect/d.vect.html grass-8.3.2/display/d.vect/d.vect.html --- grass-8.3.1/display/d.vect/d.vect.html 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/display/d.vect/d.vect.html 2024-03-06 21:24:05.000000000 +0000 @@ -10,6 +10,13 @@ which allow the user to specify vector type, colors, data fields, SQL queries, label size and justification, etc. +

When d.vect is used with where parameter on MS Windows +Command Prompt, it is important to use ˆ +carret symbol for escaping special characters < > ( ) & | , ; ". +

+d.vect map=vector_map where="cat ˆ> 10 AND cat ˆ< 20"
+
+

By default d.vect areas are filled with fill_color and outlined with color. Area outlines can be suppressed with

diff -Nru grass-8.3.1/doc/howto_release.md grass-8.3.2/doc/howto_release.md
--- grass-8.3.1/doc/howto_release.md	2023-10-24 19:27:44.000000000 +0000
+++ grass-8.3.2/doc/howto_release.md	2024-03-06 21:24:05.000000000 +0000
@@ -33,12 +33,13 @@
 git diff --staged
 # Should give no output:
 git log upstream/releasebranch_8_3..HEAD
-# Should give the same as last commits visible on GitHub:
+# There should be no commits which are not visible on GitHub:
 git log --max-count=5
 ```
 
-Now you can merge (or rebase) updates from the remote your local branch
-and optionally update your own fork:
+Now you can rebase updates from the remote your local branch.
+Above, you confirmed you have no local commits, so this should happen
+without rebasing any local commits, i.e., it should just add the new commits:
 
 ```bash
 git merge upstream/releasebranch_8_3 && git push origin releasebranch_8_3
@@ -49,14 +50,16 @@
 ```bash
 # Should give no output:
 git log upstream/releasebranch_8_3..HEAD
-# Should give the same as last commits visible on GitHub:
+git log HEAD..upstream/releasebranch_8_3
+# Should give exactly the same as last commits visible on GitHub:
 git log --max-count=5
 ```
 
-Now or any time later, you can use `git log` and `git show` to see the latest
-commits and the last commit including the changes.
+Now or any time later, you can use `git status`, `git log`, and `git show`
+to see a branch, latest commits and a last commit including the changes.
 
 ```bash
+git status
 git log --max-count=5
 git show
 ```
@@ -323,7 +326,7 @@
 
 ### Upload source code tarball to OSGeo servers
 
-Note: servers 'osgeo7-grass' and 'osgeo7-download' only reachable via
+Note: servers 'osgeo8-grass' and 'osgeo7-download' only reachable via
 jumphost (managed by OSGeo-SAC) - see 
 
 ```bash
@@ -360,13 +363,13 @@
 update `grass-stable` redirect at `osgeo7-grass`:
 
 ```bash
-sudo vim /etc/apache2/sites-enabled/000-default.conf`
+sudo vim /etc/apache2/sites-enabled/000-default.conf
 ```
 
 Load the new configuration:
 
 ```bash
-sudo systemctl reload apache2`
+sudo systemctl reload apache2
 ```
 
 For new branches: Update `grass-devel` using the steps above.
@@ -410,51 +413,65 @@
 
 ## Improve release description
 
-For final releases only, go to Zenodo.org a get a Markdown badge for the release
-which Zenodo creates with a DOI for the published release.
+For final releases only, go to [Zenodo](https://doi.org/10.5281/zenodo.5176030)
+and get a Markdown badge for the release which Zenodo creates with a DOI
+for the published release.
 
 For all releases, click the Binder badge to get Binder to build. Use it to test
 it and to cache the built image. Add more links to (or badges for) more notebooks
 if there are any which show well specific features added or updated in the release.
 
-## Create entries for the new release
-
-### Trac Wiki release page entry
+## Create various entries for the new release
 
-Add entry in 
+### Cron jobs
 
-### Update Hugo web site and other pages to show the new version
+Only in case of major releases:
 
-For a (final) release (not release candidate), write announcement and publish it:
+- update '[cronjob(s)](https://github.com/OSGeo/grass-addons/tree/grass8/utils/cronjobs_osgeo_lxd/)'
+  on grass.osgeo.org to next but one release tag for the differences
 
-- News section, 
+### Update Hugo web site
 
-Increment the GRASS GIS version in
+Update website only for final releases (not release candidates). Submit the changes
+in a single PR.
 
-- 
-- 
-
-Update the version in the Wiki page: 
-
-Subsequently, verify the software pages:
+Software pages:
 
 - Linux: 
 - Windows: 
 - Mac: 
+- Releases: 
+- Website variables: 
 
-### Only in case of new major release
+Write announcement and publish it:
 
-- update '[cronjob(s)](https://github.com/OSGeo/grass-addons/tree/grass8/utils/cronjobs_osgeo_lxd/)'
-  on grass.osgeo.org to next but one release tag for the differences
-- wiki updates, only when new major release:
+- News section: 
+
+### GRASS Wiki
+
+For final releases (not release candidates), update the last version
+on the main page:
+
+- Wiki: 
+
+- For major release only:
   - {{cmd|xxxx}} macro: 
-  - update last version on main page
+
+### Trac wiki
+
+For all releases:
+
+- Add link to GitHub release page to 
+
+For major and minor releases:
+
 - Add trac Wiki Macro definitions for manual pages G8X:modulename
   - Edit: 
 
-## Packaging notes
 
-### WinGRASS notes
+## WinGRASS notes
+
+For new branches and final releases (see additional instructions in the repo):
 
 - Go to 
 - Update grass_packager_release.bat, eg.
@@ -477,11 +494,6 @@
      copy_addon 830RC1 8.3.0RC1
 ```
 
-### Ubuntu Launchpad notes
-
-- Create milestone and release: 
-- Upload tarball for created release
-
 ### Update grass.osgeo.org
 
 These updates are for final releases only.
@@ -506,12 +518,12 @@
 
 ## Tell others about release
 
-- If release candidate (send just a short invitation to test):
-  - 
+- If release candidate (just a short invitation to test):
   - 
+  - 
 
-If final release, send out an announcement (press release) which is a shortened
-version of release desciption and website news item (under `/announces/`).
+If final release, send out an announcement (press release)
+which is a shortened version of release desciption and website news item.
 Note: Do not use relative links.
 
 - Our main mailing lists:
@@ -519,8 +531,8 @@
     (ask a development coordinator to be added)
   -  | 
   -  | 
-- OSGeo.org: ,  (send an email, then it
-  will be approved)
+- OSGeo.org: , 
+  (send an email, then it will be approved)
 
 Via web and social media:
 
diff -Nru grass-8.3.1/doc/infrastructure.md grass-8.3.2/doc/infrastructure.md
--- grass-8.3.1/doc/infrastructure.md	2023-10-24 19:27:44.000000000 +0000
+++ grass-8.3.2/doc/infrastructure.md	2024-03-06 21:24:05.000000000 +0000
@@ -1,7 +1,7 @@
 # How the GRASS GIS Webserver and related infrastructure works
 
 Author: Markus Neteler
-Last update: Sep 2023
+Last update: Dec 2023
 
 ## GRASS GIS Source code repository
 
@@ -224,7 +224,7 @@
 
 ## GRASS CI: GitHub Actions
 
-Started Apr. 2020
+Started in Apr. 2020
 
 Maintainer: Vaclav Petras
 
@@ -247,10 +247,33 @@
 
 Helper files placed in .github/workflows/
 
+## GRASS docker images
+
+Maintainer: Carmen Tawalika, Vaclav Petras + OSGeo-SAC
+
+Docker images are created with a GitHub action. Subsequently, login is done
+to DockerHub using `docker/login-action` with username and password through
+CI secrets and the images pushed to Docker hub at:
+
+- 
+
+User settings:
+
+- The GRASS GIS CI user at Docker hub is "grassgis" (joined June 3, 2023),
+  see also 
+- Docker Hub access token are managed via grass-ci-admin@osgeo.org.
+- The OSGeo Org membership is managed at 
+  through OSGeo-SAC
+
+Helper files placed in .github/workflows/
+
 ## GRASS Coverity Scan
 
 Maintainer: Markus Neteler
 
+Coverity Scan is a service to find security issues. At time the service
+is used only occasionally.
+
 - 
 
 ## User message translation management (i18N)
diff -Nru grass-8.3.1/general/g.proj/output.c grass-8.3.2/general/g.proj/output.c
--- grass-8.3.1/general/g.proj/output.c	2023-10-24 19:27:44.000000000 +0000
+++ grass-8.3.2/general/g.proj/output.c	2024-03-06 21:24:05.000000000 +0000
@@ -22,6 +22,10 @@
 #include 
 #include 
 
+#ifdef HAVE_OGR
+#include 
+#endif
+
 #include "local_proto.h"
 
 static int check_xy(int shell);
@@ -274,7 +278,7 @@
 
     if (outwkt != NULL) {
         fprintf(stdout, "%s\n", outwkt);
-        G_free(outwkt);
+        CPLFree(outwkt);
     }
     else
         G_warning(_("Unable to convert to WKT"));
diff -Nru grass-8.3.1/general/g.proj/testsuite/test_g_proj.py grass-8.3.2/general/g.proj/testsuite/test_g_proj.py
--- grass-8.3.1/general/g.proj/testsuite/test_g_proj.py	1970-01-01 00:00:00.000000000 +0000
+++ grass-8.3.2/general/g.proj/testsuite/test_g_proj.py	2024-03-06 21:24:05.000000000 +0000
@@ -0,0 +1,27 @@
+"""g.proj tests
+
+(C) 2023 by the GRASS Development Team
+This program is free software under the GNU General Public
+License (>=v2). Read the file COPYING that comes with GRASS
+for details.
+
+:author: Anna Petrasova
+"""
+
+from grass.gunittest.case import TestCase
+from grass.gunittest.main import test
+from grass.gunittest.gmodules import SimpleModule
+
+
+class GProjWKTTestCase(TestCase):
+    """Test g.proj with WKT output"""
+
+    def test_wkt_output(self):
+        """Test if g.proj returns WKT"""
+        module = SimpleModule("g.proj", flags="w")
+        self.assertModule(module)
+        self.assertIn("PROJCRS", module.outputs.stdout)
+
+
+if __name__ == "__main__":
+    test()
diff -Nru grass-8.3.1/grasslib.dox grass-8.3.2/grasslib.dox
--- grass-8.3.1/grasslib.dox	2023-10-24 19:27:44.000000000 +0000
+++ grass-8.3.2/grasslib.dox	2024-03-06 21:24:05.000000000 +0000
@@ -1,7 +1,7 @@
 /*! \mainpage GRASS GIS 8 Programmer's Manual
 
 
 GRASS GIS (Geographic
@@ -23,7 +23,7 @@
 are cited within their module's source code and the contributed manual
 pages.
 
-© 2000-2023 by the GRASS Development Team
+© 2000-2024 by the GRASS Development Team
 
 This manual is published under GNU Free Documentation
diff -Nru grass-8.3.1/gui/wxpython/core/gconsole.py grass-8.3.2/gui/wxpython/core/gconsole.py
--- grass-8.3.1/gui/wxpython/core/gconsole.py	2023-10-24 19:27:44.000000000 +0000
+++ grass-8.3.2/gui/wxpython/core/gconsole.py	2024-03-06 21:24:05.000000000 +0000
@@ -633,14 +633,13 @@
             skipInterface = True
             if os.path.splitext(command[0])[1] in (".py", ".sh"):
                 try:
-                    sfile = open(command[0], "r")
-                    for line in sfile.readlines():
-                        if len(line) < 2:
-                            continue
-                        if line[0] == "#" and line[1] == "%":
-                            skipInterface = False
-                            break
-                    sfile.close()
+                    with open(command[0], "r") as sfile:
+                        for line in sfile.readlines():
+                            if len(line) < 3:
+                                continue
+                            if line.startswith(("#%", "# %")):
+                                skipInterface = False
+                                break
                 except IOError:
                     pass
 
diff -Nru grass-8.3.1/gui/wxpython/core/utils.py grass-8.3.2/gui/wxpython/core/utils.py
--- grass-8.3.1/gui/wxpython/core/utils.py	2023-10-24 19:27:44.000000000 +0000
+++ grass-8.3.2/gui/wxpython/core/utils.py	2024-03-06 21:24:05.000000000 +0000
@@ -280,38 +280,39 @@
 
 
 def ListOfMapsets(get="ordered"):
-    """Get list of available/accessible mapsets
+    """Get list of available/accessible mapsets.
+    Option 'ordered' returns list of all mapsets, first accessible
+    then not accessible. Raises ValueError for wrong paramater value.
 
     :param str get: method ('all', 'accessible', 'ordered')
 
     :return: list of mapsets
-    :return: None on error
+    :return: [] on error
     """
-    mapsets = []
-
     if get == "all" or get == "ordered":
         ret = RunCommand("g.mapsets", read=True, quiet=True, flags="l", sep="newline")
-
-        if ret:
-            mapsets = ret.splitlines()
-            ListSortLower(mapsets)
-        else:
-            return None
+        if not ret:
+            return []
+        mapsets_all = ret.splitlines()
+        ListSortLower(mapsets_all)
+        if get == "all":
+            return mapsets_all
 
     if get == "accessible" or get == "ordered":
         ret = RunCommand("g.mapsets", read=True, quiet=True, flags="p", sep="newline")
-        if ret:
-            if get == "accessible":
-                mapsets = ret.splitlines()
-            else:
-                mapsets_accessible = ret.splitlines()
-                for mapset in mapsets_accessible:
-                    mapsets.remove(mapset)
-                mapsets = mapsets_accessible + mapsets
-        else:
-            return None
+        if not ret:
+            return []
+        mapsets_accessible = ret.splitlines()
+        if get == "accessible":
+            return mapsets_accessible
+
+        mapsets_ordered = mapsets_accessible.copy()
+        for mapset in mapsets_all:
+            if mapset not in mapsets_accessible:
+                mapsets_ordered.append(mapset)
+        return mapsets_ordered
 
-    return mapsets
+    raise ValueError("Invalid value for 'get' parameter of ListOfMapsets()")
 
 
 def ListSortLower(list):
diff -Nru grass-8.3.1/gui/wxpython/gmodeler/frame.py grass-8.3.2/gui/wxpython/gmodeler/frame.py
--- grass-8.3.1/gui/wxpython/gmodeler/frame.py	2023-10-24 19:27:44.000000000 +0000
+++ grass-8.3.2/gui/wxpython/gmodeler/frame.py	2024-03-06 21:24:05.000000000 +0000
@@ -27,6 +27,7 @@
 import tempfile
 import random
 import six
+import math
 
 import wx
 from wx.lib import ogl
@@ -839,12 +840,13 @@
         action = ModelAction(
             self.model,
             cmd=cmd,
-            x=x + self._randomShift(),
-            y=y + self._randomShift(),
+            x=x,
+            y=y,
             id=self.model.GetNextId(),
             label=label,
             comment=comment,
         )
+
         overwrite = self.model.GetProperties().get("overwrite", None)
         if overwrite is not None:
             action.GetTask().set_flag("overwrite", overwrite)
@@ -862,24 +864,15 @@
         # show properties dialog
         win = action.GetPropDialog()
         if not win:
-            cmdLength = len(action.GetLog(string=False))
-            if cmdLength > 1 and action.IsValid():
-                self.GetOptData(
-                    dcmd=action.GetLog(string=False),
-                    layer=action,
-                    params=action.GetParams(),
-                    propwin=None,
-                )
-            else:
-                gmodule = GUI(
-                    parent=self,
-                    show=True,
-                    giface=GraphicalModelerGrassInterface(self.model),
-                )
-                gmodule.ParseCommand(
-                    action.GetLog(string=False),
-                    completed=(self.GetOptData, action, action.GetParams()),
-                )
+            gmodule = GUI(
+                parent=self,
+                show=True,
+                giface=GraphicalModelerGrassInterface(self.model),
+            )
+            gmodule.ParseCommand(
+                action.GetLog(string=False),
+                completed=(self.GetOptData, action, action.GetParams()),
+            )
         elif win and not win.IsShown():
             win.Show()
 
@@ -931,8 +924,8 @@
                 x, y = self.canvas.GetNewShapePos()
                 commentObj = ModelComment(
                     self.model,
-                    x=x + self._randomShift(),
-                    y=y + self._randomShift(),
+                    x=x,
+                    y=y,
                     id=self.model.GetNextId(),
                     label=comment,
                 )
@@ -968,9 +961,10 @@
     def GetOptData(self, dcmd, layer, params, propwin):
         """Process action data"""
         if params:  # add data items
-            width, height = self.canvas.GetSize()
-            x = width / 2 - 200 + self._randomShift()
-            y = height / 2 + self._randomShift()
+            data_items = []
+            x = layer.GetX()
+            y = layer.GetY()
+
             for p in params["params"]:
                 if p.get("prompt", "") not in (
                     "raster",
@@ -1019,9 +1013,10 @@
                         x=x,
                         y=y,
                     )
+                    data_items.append(data)
                     self._addEvent(data)
                     self.canvas.diagram.AddShape(data)
-                    data.Show(True)
+                    data.Show(False)
 
                     if p.get("age", "old") == "old":
                         rel = ModelRelation(
@@ -1057,11 +1052,21 @@
             # valid / parameterized ?
             layer.SetValid(params)
 
-            self.canvas.Refresh()
+            # arrange data items
+            if data_items:
+                dc = wx.ClientDC(self.canvas)
+                p = 360 / len(data_items)
+                r = 200
+                alpha = 270 * (math.pi / 180)
+                for data in data_items:
+                    data.Move(dc, x + r * math.sin(alpha), y + r * math.cos(alpha))
+                    alpha += p * (math.pi / 180)
+                    data.Show(True)
 
         if dcmd:
             layer.SetProperties(params, propwin)
 
+        self.canvas.Refresh()
         self.SetStatusText(layer.GetLog(), 0)
 
     def AddLine(self, rel):
@@ -1340,21 +1345,19 @@
 
         self.Refresh()
 
-    def GetNewShapePos(self):
+    def GetNewShapePos(self, yoffset=50):
         """Determine optimal position for newly added object
 
         :return: x,y
         """
-        xNew, yNew = map(lambda x: x / 2, self.GetSize())
         diagram = self.GetDiagram()
+        if diagram.GetShapeList():
+            last = diagram.GetShapeList()[-1]
+            y = last.GetY() + last.GetBoundingBoxMin()[1]
+        else:
+            y = 20
 
-        for shape in diagram.GetShapeList():
-            y = shape.GetY()
-            yBox = shape.GetBoundingBoxMin()[1] / 2
-            if yBox > 0 and y < yNew + yBox and y > yNew - yBox:
-                yNew += yBox * 3
-
-        return xNew, yNew
+        return (self.GetSize()[0] // 2, y + yoffset)
 
     def GetShapesSelected(self):
         """Get list of selected shapes"""
diff -Nru grass-8.3.1/gui/wxpython/gmodeler/model.py grass-8.3.2/gui/wxpython/gmodeler/model.py
--- grass-8.3.1/gui/wxpython/gmodeler/model.py	2023-10-24 19:27:44.000000000 +0000
+++ grass-8.3.2/gui/wxpython/gmodeler/model.py	2024-03-06 21:24:05.000000000 +0000
@@ -2731,7 +2731,7 @@
 
         self._writeHandler()
 
-        for item in self.model.GetItems():
+        for item in self.model.GetItems(ModelAction):
             if item.GetParameterizedParams()["flags"]:
                 self.fd.write(
                     r"""
@@ -2864,7 +2864,7 @@
         )
 
     def _writeHandler(self):
-        for item in self.model.GetItems():
+        for item in self.model.GetItems(ModelAction):
             self._writeItem(item, variables=item.GetParameterizedParams())
 
         self.fd.write("\n{}return response\n".format(" " * self.indent))
@@ -3132,7 +3132,7 @@
             )
         )
 
-        modelItems = self.model.GetItems()
+        modelItems = self.model.GetItems(ModelAction)
         for item in modelItems:
             for flag in item.GetParameterizedParams()["flags"]:
                 if flag["label"]:
@@ -3228,7 +3228,7 @@
             self.fd.write("    pass\n")
 
         self.fd.write("\ndef main(options, flags):\n")
-        for item in self.model.GetItems():
+        for item in self.model.GetItems(ModelAction):
             self._writeItem(item, variables=item.GetParameterizedParams())
 
         self.fd.write("    return 0\n")
diff -Nru grass-8.3.1/gui/wxpython/lmgr/layertree.py grass-8.3.2/gui/wxpython/lmgr/layertree.py
--- grass-8.3.1/gui/wxpython/lmgr/layertree.py	2023-10-24 19:27:44.000000000 +0000
+++ grass-8.3.2/gui/wxpython/lmgr/layertree.py	2024-03-06 21:24:05.000000000 +0000
@@ -149,7 +149,8 @@
         # when some layers are not visible in layer tree
         # self.SetAutoLayout(True)
         self.SetGradientStyle(1)
-        self.EnableSelectionGradient(True)
+        if sys.platform != "darwin":
+            self.EnableSelectionGradient(True)
         self._setGradient()
 
         # init associated map display
diff -Nru grass-8.3.1/gui/wxpython/main_window/frame.py grass-8.3.2/gui/wxpython/main_window/frame.py
--- grass-8.3.1/gui/wxpython/main_window/frame.py	2023-10-24 19:27:44.000000000 +0000
+++ grass-8.3.2/gui/wxpython/main_window/frame.py	2024-03-06 21:24:05.000000000 +0000
@@ -239,6 +239,11 @@
 
         self._show_demo_map()
 
+    def _repaintLayersPaneMapDisplayToolbar(self):
+        """Repaint Layers pane map display toolbar widget on the wxMac"""
+        if sys.platform == "darwin":
+            wx.CallLater(100, self.notebookLayers.Refresh)
+
     def _setTitle(self):
         """Set frame title"""
         gisenv = grass.gisenv()
@@ -340,6 +345,7 @@
         # bindings
         self.notebookLayers.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnCBPageChanged)
         self.notebookLayers.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CLOSING, self.OnCBPageClosing)
+        self.notebookLayers.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CLOSED, self.OnCBPageClosed)
 
     def _createSearchModule(self, parent):
         """Initialize Search module widget"""
@@ -482,6 +488,8 @@
         cb_boxsizer.Fit(self.GetLayerTree())
         self.currentPage.Layout()
         self.GetLayerTree().Layout()
+        # Repaint Layers pane map display toolbar widget on the wxMac
+        self._repaintLayersPaneMapDisplayToolbar()
 
         self.displayIndex += 1
 
@@ -956,6 +964,12 @@
 
         event.Skip()
 
+    def OnCBPageClosed(self, event):
+        """Page of notebook has been closed from the Layers pane via x
+        button or via closing map display notebook page"""
+        # Repaint Layers pane map display toolbar widget on the wxMac
+        self._repaintLayersPaneMapDisplayToolbar()
+
     def OnCBPageClosing(self, event):
         """Page of notebook is being closed
         from Layer Manager (x button next to arrows)
diff -Nru grass-8.3.1/gui/wxpython/startup/locdownload.py grass-8.3.2/gui/wxpython/startup/locdownload.py
--- grass-8.3.1/gui/wxpython/startup/locdownload.py	2023-10-24 19:27:44.000000000 +0000
+++ grass-8.3.2/gui/wxpython/startup/locdownload.py	2024-03-06 21:24:05.000000000 +0000
@@ -59,7 +59,7 @@
     },
     {
         "label": "Piemonte, Italy data set",
-        "url": "http://geodati.fmach.it/gfoss_geodata/libro_gfoss/grassdata_piemonte_utm32n_wgs84_grass7.tar.gz",
+        "url": "https://grass.osgeo.org/sampledata/grassdata_piemonte_utm32n_wgs84_grass7.tar.gz",
     },
     {
         "label": "Slovakia 3D precipitation voxel data set",
diff -Nru grass-8.3.1/imagery/i.evapo.time/i.evapo.time.html grass-8.3.2/imagery/i.evapo.time/i.evapo.time.html
--- grass-8.3.1/imagery/i.evapo.time/i.evapo.time.html	2023-10-24 19:27:44.000000000 +0000
+++ grass-8.3.2/imagery/i.evapo.time/i.evapo.time.html	2024-03-06 21:24:05.000000000 +0000
@@ -50,6 +50,21 @@
 For multi-year calculations, just continue incrementing DOY values above
 366, it will continue working, up to maximum input of 400 satellite images.
 
+

+Temporal integration from a weather station
+This is an example of a temporal integration from a weather station as done by +Chemin and Alexandridis (2004) +

+ +

References

+ +

+Chemin and Alexandridis, 2004. Spatial Resolution Improvement of Seasonal +Evapotranspiration for Irrigated Rice, Zhanghe Irrigation District, Hubei Province, China. +Asian Journal of Geoinformatics, Vol. 5, No. 1, September 2004 +(PDF) + +

SEE ALSO

Binary files /tmp/tmp9dcxvxfa/wmAwptHez1/grass-8.3.1/imagery/i.evapo.time/i_evapo_time.png and /tmp/tmp9dcxvxfa/0s9Te9WdQt/grass-8.3.2/imagery/i.evapo.time/i_evapo_time.png differ diff -Nru grass-8.3.1/include/VERSION grass-8.3.2/include/VERSION --- grass-8.3.1/include/VERSION 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/include/VERSION 2024-03-06 21:24:05.000000000 +0000 @@ -1,4 +1,4 @@ 8 3 -1 -2023 +2 +2024 diff -Nru grass-8.3.1/include/grass/gis.h grass-8.3.2/include/grass/gis.h --- grass-8.3.1/include/grass/gis.h 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/include/grass/gis.h 2024-03-06 21:24:05.000000000 +0000 @@ -6,7 +6,7 @@ * PURPOSE: This file contains definitions of variables and data types * for use with most, if not all, Grass programs. This file is * usually included in every Grass program. - * COPYRIGHT: (C) 2000-2023 by the GRASS Development Team + * COPYRIGHT: (C) 2000-2024 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 diff -Nru grass-8.3.1/lib/gis/env.c grass-8.3.2/lib/gis/env.c --- grass-8.3.1/lib/gis/env.c 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/lib/gis/env.c 2024-03-06 21:24:05.000000000 +0000 @@ -3,7 +3,7 @@ \brief GIS library - environment routines - (C) 2001-2023 by the GRASS Development Team + (C) 2001-2024 by the GRASS Development Team This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. diff -Nru grass-8.3.1/lib/gis/gislib_cmdline_parsing.dox grass-8.3.2/lib/gis/gislib_cmdline_parsing.dox --- grass-8.3.1/lib/gis/gislib_cmdline_parsing.dox 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/lib/gis/gislib_cmdline_parsing.dox 2024-03-06 21:24:05.000000000 +0000 @@ -4,7 +4,7 @@ diff -Nru grass-8.3.1/lib/gis/parser_html.c grass-8.3.2/lib/gis/parser_html.c --- grass-8.3.1/lib/gis/parser_html.c 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/lib/gis/parser_html.c 2024-03-06 21:24:05.000000000 +0000 @@ -3,7 +3,7 @@ \brief GIS Library - Argument parsing functions (HTML output) - (C) 2001-2023 by the GRASS Development Team + (C) 2001-2024 by the GRASS Development Team This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. diff -Nru grass-8.3.1/lib/init/grass.py grass-8.3.2/lib/init/grass.py --- grass-8.3.1/lib/init/grass.py 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/lib/init/grass.py 2024-03-06 21:24:05.000000000 +0000 @@ -18,7 +18,7 @@ # command line options for setting the GISDBASE, LOCATION, # and/or MAPSET. Finally it starts GRASS with the appropriate # user interface and cleans up after it is finished. -# COPYRIGHT: (C) 2000-2023 by the GRASS Development Team +# COPYRIGHT: (C) 2000-2024 by the GRASS Development Team # # This program is free software under the GNU General # Public License (>=v2). Read the file COPYING that diff -Nru grass-8.3.1/lib/init/grass.sh grass-8.3.2/lib/init/grass.sh --- grass-8.3.1/lib/init/grass.sh 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/lib/init/grass.sh 2024-03-06 21:24:05.000000000 +0000 @@ -13,7 +13,7 @@ # setting the GISDBASE, LOCATION, and/or MAPSET. # Finally it starts GRASS with the appropriate user # interface and cleans up after it is finished. -# COPYRIGHT: (C) 2000-2023 by the GRASS Development Team +# COPYRIGHT: (C) 2000-2024 by the GRASS Development Team # # This program is free software under the GNU General # Public License (>=v2). Read the file COPYING that diff -Nru grass-8.3.1/lib/proj/do_proj.c grass-8.3.2/lib/proj/do_proj.c --- grass-8.3.1/lib/proj/do_proj.c 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/lib/proj/do_proj.c 2024-03-06 21:24:05.000000000 +0000 @@ -698,7 +698,23 @@ /* following code copied from proj_create_crs_to_crs_from_pj() * in proj src/4D_api.cpp - * but using PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT */ + * using PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION + * this can cause problems and artefacts + * switch to PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT + * in case of problems + * but results can be different from gdalwarp: + * shifted geolocation in some areas + * in these cases there is no right or wrong, + * different pipelines are all regarded as valid by PROJ + * depending on the area of interest + * + * see also: + * OGRProjCT::ListCoordinateOperations() in GDAL ogr/ogrct.cpp + * create_operation_to_geog_crs() in PROJ src/4D_api.cpp + * proj_create_crs_to_crs_from_pj() in PROJ src/4D_api.cpp + * proj_operation_factory_context_set_spatial_criterion() in PROJ + * src/iso19111/c_api.cpp + * */ /* now use the current region as area of interest */ operation_ctx = @@ -707,10 +723,17 @@ PJ_DEFAULT_CTX, operation_ctx, xmin, ymin, xmax, ymax); proj_operation_factory_context_set_spatial_criterion( PJ_DEFAULT_CTX, operation_ctx, - PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT); + PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); + /* from GDAL OGRProjCT::ListCoordinateOperations() */ proj_operation_factory_context_set_grid_availability_use( PJ_DEFAULT_CTX, operation_ctx, - PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); +#if PROJ_VERSION_NUM >= 7000000 + proj_context_is_network_enabled(PJ_DEFAULT_CTX) + ? PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE + : +#endif + PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); + /* The operations are sorted with the most relevant ones first: * by descending area (intersection of the transformation area * with the area of interest, or intersection of the diff -Nru grass-8.3.1/lib/vector/Vlib/dangles.c grass-8.3.2/lib/vector/Vlib/dangles.c --- grass-8.3.1/lib/vector/Vlib/dangles.c 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/lib/vector/Vlib/dangles.c 2024-03-06 21:24:05.000000000 +0000 @@ -252,11 +252,10 @@ } lines_removed++; } + dangles_removed++; } /* delete the chain */ - - dangles_removed++; - } /* lcount == 1 */ - } /* node <= nnodes */ + } /* lcount == 1 */ + } /* node <= nnodes */ G_verbose_message(_("%s lines: %d"), lmsg, lines_removed); G_verbose_message(_("%s dangles: %d"), lmsg, dangles_removed); } diff -Nru grass-8.3.1/lib/vector/diglib/plus_struct.c grass-8.3.2/lib/vector/diglib/plus_struct.c --- grass-8.3.1/lib/vector/diglib/plus_struct.c 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/lib/vector/diglib/plus_struct.c 2024-03-06 21:24:05.000000000 +0000 @@ -656,6 +656,20 @@ return (0); } +/*! + \brief Write Plus_head to file + + ptr->off_t_size is used for both coor and topo files, but their sizes + (ptr->coor_size for coor and no variable for topo) can be different. If + either file is greater than PORT_LONG_MAX, ptr->off_t_size must be 8. This + function determines this value of ptr->off_t_size and writes it to the file. + + \param fp pointer to gvfile structure + \param[in,out] ptr pointer to Plus_head structure + + \return -1 error + \return 0 OK + */ int dig_Wr_Plus_head(struct gvfile *fp, struct Plus_head *ptr) { unsigned char buf[10]; @@ -678,8 +692,69 @@ /* can only happen when sizeof(off_t) == 8 */ ptr->off_t_size = 8; } - else - ptr->off_t_size = 4; + else if (ptr->off_t_size == 0) { + /* calculate the total size of topo file to get the correct off_t_size + * if coor file is less than PORT_LONG_MAX */ + off_t size = length; + int i; + + for (i = 1; i <= ptr->n_nodes; i++) { + /* from dig_Wr_P_node() */ + struct P_node *p = ptr->Node[i]; + + if (p == NULL) + size += 4; + else { + size += 20 + 8 * p->n_lines; + if (ptr->with_z) + size += 12; + } + } + + for (i = 1; i <= ptr->n_lines; i++) { + /* from dig_Wr_P_line() */ + struct P_line *p = ptr->Line[i]; + + if (p == NULL) + size += 1; + else { + /* for now, off_t_size = 4 */ + size += 5; + if (p->type & GV_CENTROID) + size += 4; + else if (p->type & GV_LINE) + size += 8; + else if (p->type & GV_BOUNDARY) + size += 16; + else if ((p->type & GV_FACE) && ptr->with_z) + size += 12; + else if ((p->type & GV_KERNEL) && ptr->with_z) + size += 4; + } + } + + for (i = 1; i <= ptr->n_areas; i++) { + /* from dig_Wr_P_area() */ + struct P_area *p = ptr->Area[i]; + + if (p == NULL) + size += 4; + else + size += 12 + 4 * p->n_lines + 4 * p->n_isles; + } + + for (i = 1; i <= ptr->n_isles; i++) { + /* from dig_Wr_P_isle() */ + struct P_isle *p = ptr->Isle[i]; + + if (p == NULL) + size += 4; + else + size += 8 + 4 * p->n_lines; + } + + ptr->off_t_size = size > (off_t)PORT_LONG_MAX ? 8 : 4; + } /* add a new field with off_t_size after byte_order? */ diff -Nru grass-8.3.1/man/build_graphical_index.py grass-8.3.2/man/build_graphical_index.py --- grass-8.3.1/man/build_graphical_index.py 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/man/build_graphical_index.py 2024-03-06 21:24:05.000000000 +0000 @@ -5,7 +5,7 @@ # MODULE: build_graphical_index # AUTHOR(S): Vaclav Petras # PURPOSE: Build graphical index -# COPYRIGHT: (C) 2015-2023 by Vaclav Petras and the GRASS Development Team +# COPYRIGHT: (C) 2015-2024 by Vaclav Petras and the GRASS Development Team # # This program is free software under the GNU General Public # License (>=v2). Read the file COPYING that comes with GRASS diff -Nru grass-8.3.1/man/build_html.py grass-8.3.2/man/build_html.py --- grass-8.3.1/man/build_html.py 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/man/build_html.py 2024-03-06 21:24:05.000000000 +0000 @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # utilities for generating HTML indices -# (C) 2003-2023 Markus Neteler and the GRASS Development Team +# (C) 2003-2024 Markus Neteler and the GRASS Development Team # Authors: # Markus Neteler # Glynn Clements diff -Nru grass-8.3.1/man/build_rest.py grass-8.3.2/man/build_rest.py --- grass-8.3.1/man/build_rest.py 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/man/build_rest.py 2024-03-06 21:24:05.000000000 +0000 @@ -9,7 +9,7 @@ """ # utilities for generating REST indices # utilities for generating HTML indices -# (C) 2003-2023 by Luca Delucchi and the GRASS Development Team +# (C) 2003-2024 by Luca Delucchi and the GRASS Development Team import os import string @@ -169,7 +169,7 @@ -------------- :doc:`Manual main page ` \| :doc:`Full Index ` - 2003-2023 `GRASS Development Team `_, GRASS GIS ${grass_version} Reference Manual + 2003-2024 `GRASS Development Team `_, GRASS GIS ${grass_version} Reference Manual """ ) diff -Nru grass-8.3.1/man/sphinx/conf.py grass-8.3.2/man/sphinx/conf.py --- grass-8.3.1/man/sphinx/conf.py 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/man/sphinx/conf.py 2024-03-06 21:24:05.000000000 +0000 @@ -38,7 +38,7 @@ # General information about the project. project = "GRASS GIS 8.3 Documentation" -copyright = "2023, GRASS Development Team" +copyright = "2024, GRASS Development Team" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff -Nru grass-8.3.1/mswindows/GRASS-Installer.nsi.tmpl grass-8.3.2/mswindows/GRASS-Installer.nsi.tmpl --- grass-8.3.1/mswindows/GRASS-Installer.nsi.tmpl 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/mswindows/GRASS-Installer.nsi.tmpl 2024-03-06 21:24:05.000000000 +0000 @@ -800,7 +800,7 @@ download_ok: InitPluginsDir - untgz::extract "-d" "$TEMP\$ORIGINAL_UNTAR_FOLDER" "-zbz2" "$TEMP\$ARCHIVE_NAME" + untgz::extract -d "$TEMP\$ORIGINAL_UNTAR_FOLDER" -zbz2 "$TEMP\$ARCHIVE_NAME" Pop $0 StrCmp $0 "success" untar_ok untar_failed @@ -872,7 +872,7 @@ download_ok: InitPluginsDir - untgz::extract "-d" "$GIS_DATABASE" "$TEMP\$ARCHIVE_NAME" + untgz::extract -d "$GIS_DATABASE" "$TEMP\$ARCHIVE_NAME" Pop $0 StrCmp $0 "success" untar_ok untar_failed diff -Nru grass-8.3.1/python/grass/docs/conf.py grass-8.3.2/python/grass/docs/conf.py --- grass-8.3.1/python/grass/docs/conf.py 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/python/grass/docs/conf.py 2024-03-06 21:24:05.000000000 +0000 @@ -124,7 +124,7 @@ # General information about the project. project = "Python library documentation" -copyright = "2023, GRASS Development Team" +copyright = "2024, GRASS Development Team" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff -Nru grass-8.3.1/python/grass/pygrass/modules/interface/parameter.py grass-8.3.2/python/grass/pygrass/modules/interface/parameter.py --- grass-8.3.1/python/grass/pygrass/modules/interface/parameter.py 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/python/grass/pygrass/modules/interface/parameter.py 2024-03-06 21:24:05.000000000 +0000 @@ -98,11 +98,22 @@ if (param.min is not None and newvalue < param.min) or ( param.max is not None and newvalue > param.max ): - err_str = ( - "The Parameter <%s>, must be between: " - "%g<=value<=%g, %r is outside." - ) - raise ValueError(err_str % (param.name, param.min, param.max, newvalue)) + if param.min is None: + err_str = ( + f"The Parameter <{param.name}> must be lower than " + f"{param.max}, {newvalue} is outside." + ) + elif param.max is None: + err_str = ( + f"The Parameter <{param.name}> must be higher than " + f"{param.min}, {newvalue} is out of range." + ) + else: + err_str = ( + f"The Parameter <{param.name}> must be between: " + f"{param.min}<=value<={param.max}, {newvalue} is outside." + ) + raise ValueError(err_str) # check if value is in the list of valid values if param.values is not None and newvalue not in param.values: raise ValueError(must_val % (param.name, param.values)) diff -Nru grass-8.3.1/python/grass/pygrass/modules/interface/testsuite/test_parameter.py grass-8.3.2/python/grass/pygrass/modules/interface/testsuite/test_parameter.py --- grass-8.3.1/python/grass/pygrass/modules/interface/testsuite/test_parameter.py 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/python/grass/pygrass/modules/interface/testsuite/test_parameter.py 2024-03-06 21:24:05.000000000 +0000 @@ -133,6 +133,84 @@ with self.assertRaises(ValueError): _check_value(param, 2.6) + def test_positive_min_float_double(self): + """Check range checking for a positive minimum. + + Tests positive cases, type of exception, and content of the message. + """ + name = "number" + for ptype in ("float", "double"): + param = Parameter( + diz=dict( + name=name, + required="yes", + multiple="no", + type=ptype, + values=[ + "2-", + ], + ) + ) + value = 2 + self.assertTupleEqual((float(value), value), _check_value(param, value)) + value = 2.2 + self.assertTupleEqual((value, value), _check_value(param, value)) + value = "2" + self.assertTupleEqual((float(value), value), _check_value(param, value)) + value = "2.5" + self.assertTupleEqual((float(value), value), _check_value(param, value)) + + # test errors + with self.assertRaisesRegex(ValueError, f"{name}.+elev"): + _check_value(param, "elev") + with self.assertRaisesRegex(TypeError, name): + _check_value(param, (1.0, 2.0)) + # Only the main parts of the message are checked, + # but the check is order-dependent. + with self.assertRaisesRegex(ValueError, f"{name}.+1.9"): + _check_value(param, 1.9) + with self.assertRaisesRegex(ValueError, f"{name}.+-1.0"): + _check_value(param, -1.0) + + def test_positive_max_float_double(self): + """Check range checking for a positive maximum. + + Tests positive cases, type of exception, and content of the message. + """ + name = "number" + for ptype in ("float", "double"): + param = Parameter( + diz=dict( + name=name, + required="yes", + multiple="no", + type=ptype, + values=[ + "-100", + ], + ) + ) + value = 1 + self.assertTupleEqual((float(value), value), _check_value(param, value)) + value = 1.2 + self.assertTupleEqual((value, value), _check_value(param, value)) + value = "0" + self.assertTupleEqual((float(value), value), _check_value(param, value)) + value = "2.5" + self.assertTupleEqual((float(value), value), _check_value(param, value)) + + # test errors + with self.assertRaisesRegex(ValueError, f"{name}.+elev"): + _check_value(param, "elev") + with self.assertRaisesRegex(TypeError, name): + _check_value(param, (1.0, 2.0)) + # Only the main parts of the message are checked, + # but the check is order-dependent. + with self.assertRaisesRegex(ValueError, f"{name}.+100.1"): + _check_value(param, 100.1) + with self.assertRaisesRegex(ValueError, f"{name}.+200"): + _check_value(param, 200.0) + def test_single_integer(self): param = Parameter( diz=dict(name="int_number", required="yes", multiple="no", type="integer") diff -Nru grass-8.3.1/python/grass/script/core.py grass-8.3.2/python/grass/script/core.py --- grass-8.3.1/python/grass/script/core.py 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/python/grass/script/core.py 2024-03-06 21:24:05.000000000 +0000 @@ -8,7 +8,7 @@ from grass.script import core as grass grass.parser() -(C) 2008-2023 by the GRASS Development Team +(C) 2008-2024 by the GRASS Development Team This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. diff -Nru grass-8.3.1/python/grass/script/setup.py grass-8.3.2/python/grass/script/setup.py --- grass-8.3.1/python/grass/script/setup.py 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/python/grass/script/setup.py 2024-03-06 21:24:05.000000000 +0000 @@ -65,7 +65,7 @@ session.finish() -(C) 2010-2023 by the GRASS Development Team +(C) 2010-2024 by the GRASS Development Team This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. diff -Nru grass-8.3.1/python/grass/script/utils.py grass-8.3.2/python/grass/script/utils.py --- grass-8.3.1/python/grass/script/utils.py 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/python/grass/script/utils.py 2024-03-06 21:24:05.000000000 +0000 @@ -326,13 +326,27 @@ def split(s): - """!Platform specific shlex.split""" - if sys.version_info >= (2, 6): - return shlex.split(s, posix=(sys.platform != "win32")) - elif sys.platform == "win32": - return shlex.split(s.replace("\\", r"\\")) - else: - return shlex.split(s) + """Same shlex.split() func on all OS platforms + + We don't use parameter posix=True on the OS MS Windows due to incorrectly + splitting command line parameters: + + e.g. d.vect where="cat < 10" + + is split incorrectly as follows: + + 'where="cat', '<', '10"' + + Should be: + + 'where=cat < 10' + + + :param str s: cmd string + + return list: cmd list + """ + return shlex.split(s) # source: diff -Nru grass-8.3.1/raster/r.cross/r.cross.html grass-8.3.2/raster/r.cross/r.cross.html --- grass-8.3.1/raster/r.cross/r.cross.html 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/raster/r.cross/r.cross.html 2024-03-06 21:24:05.000000000 +0000 @@ -3,16 +3,12 @@ r.cross creates an output raster map layer representing all unique combinations of category values in the raster input layers (input=name,name,name, ...). At least two, but not more than -ten, input map layers must be specified. The user must also +30, input map layers must be specified. The user must also specify a name to be assigned to the output raster map layer created by r.cross.

OPTIONS

-The program will be run non-interactively if the user specifies -the names of between 2-10 raster map layers be used as input, -and the name of a raster map layer to hold program output. -

With the -z flag NULL values are not crossed. This means that if a NULL value occurs in any input data layer, diff -Nru grass-8.3.1/raster/r.horizon/main.c grass-8.3.2/raster/r.horizon/main.c --- grass-8.3.1/raster/r.horizon/main.c 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/raster/r.horizon/main.c 2024-03-06 21:24:05.000000000 +0000 @@ -87,7 +87,7 @@ double amin1(double, double); int min(int, int); int max(int, int); -void com_par(double angle); +void com_par(void); int is_shadow(void); double horizon_height(void); void calculate_shadow(void); @@ -233,6 +233,7 @@ parm.bufferzone->description = _("For horizon rasters, read from the DEM an extra buffer around the " "present region"); + parm.bufferzone->options = "0-"; parm.bufferzone->guisection = _("Raster mode"); parm.e_buff = G_define_option(); @@ -241,6 +242,7 @@ parm.e_buff->required = NO; parm.e_buff->description = _("For horizon rasters, read from the DEM an " "extra buffer eastward the present region"); + parm.e_buff->options = "0-"; parm.e_buff->guisection = _("Raster mode"); parm.w_buff = G_define_option(); @@ -249,6 +251,7 @@ parm.w_buff->required = NO; parm.w_buff->description = _("For horizon rasters, read from the DEM an " "extra buffer westward the present region"); + parm.w_buff->options = "0-"; parm.w_buff->guisection = _("Raster mode"); parm.n_buff = G_define_option(); @@ -257,6 +260,7 @@ parm.n_buff->required = NO; parm.n_buff->description = _("For horizon rasters, read from the DEM an " "extra buffer northward the present region"); + parm.n_buff->options = "0-"; parm.n_buff->guisection = _("Raster mode"); parm.s_buff = G_define_option(); @@ -265,6 +269,7 @@ parm.s_buff->required = NO; parm.s_buff->description = _("For horizon rasters, read from the DEM an " "extra buffer southward the present region"); + parm.s_buff->options = "0-"; parm.s_buff->guisection = _("Raster mode"); parm.maxdistance = G_define_option(); @@ -499,6 +504,12 @@ if (nbufferZone == 0.) nbufferZone = bufferZone; + /* adjust buffer to multiples of resolution */ + ebufferZone = (int)(ebufferZone / stepx) * stepx; + wbufferZone = (int)(wbufferZone / stepx) * stepx; + sbufferZone = (int)(sbufferZone / stepy) * stepy; + nbufferZone = (int)(nbufferZone / stepy) * stepy; + new_cellhd.rows += (int)((nbufferZone + sbufferZone) / stepy); new_cellhd.cols += (int)((ebufferZone + wbufferZone) / stepx); @@ -718,13 +729,11 @@ /**********************************************************/ -void com_par(double angle) +void com_par(void) { - sinangle = sin(angle); if (fabs(sinangle) < 0.0000001) { sinangle = 0.; } - cosangle = cos(angle); if (fabs(cosangle) < 0.0000001) { cosangle = 0.; } @@ -748,6 +757,7 @@ tanh0 = 0.; length = 0; + zp = z_orig; height = searching(); @@ -792,6 +802,7 @@ yp = ymin + yy0; angle = (single_direction * deg2rad) + pihalf; + printangle = single_direction; maxlength = fixedMaxLength; fprintf(fp, "azimuth,horizon_height\n"); @@ -836,19 +847,15 @@ delt_dist = sqrt(delt_east * delt_east + delt_nor * delt_nor); - stepsinangle = stepxy * delt_nor / delt_dist; - stepcosangle = stepxy * delt_east / delt_dist; + sinangle = delt_nor / delt_dist; + cosangle = delt_east / delt_dist; + com_par(); shadow_angle = horizon_height(); if (degreeOutput) { shadow_angle *= rad2deg; } - printangle = angle * rad2deg - 90.; - if (printangle < 0.) - printangle += 360; - else if (printangle >= 360.) - printangle -= 360; if (compassOutput) { double tmpangle; @@ -863,11 +870,17 @@ } angle += dfr_rad; + printangle += step; if (angle < 0.) angle += twopi; else if (angle > twopi) angle -= twopi; + + if (printangle < 0.) + printangle += 360; + else if (printangle > 360.) + printangle -= 360; } /* end of for loop over angles */ } @@ -960,7 +973,7 @@ else if (sinangle < 0.) { sy = yy0 * invstepy + offsety; dely = floor( - fabs((floor(jp / 100.) - (sy / 100.)) * distsinangle)); + fabs((floor(sy / 100.) - (sy / 100.)) * distsinangle)); } mindel = min(delx, dely); @@ -1190,25 +1203,8 @@ sqrt(delt_east * delt_east + delt_nor * delt_nor); sinangle = delt_nor / delt_dist; - if (fabs(sinangle) < 0.0000001) { - sinangle = 0.; - } cosangle = delt_east / delt_dist; - if (fabs(cosangle) < 0.0000001) { - cosangle = 0.; - } - distsinangle = 32000; - distcosangle = 32000; - - if (sinangle != 0.) { - distsinangle = 100. / (distxy * sinangle); - } - if (cosangle != 0.) { - distcosangle = 100. / (distxy * cosangle); - } - - stepsinangle = stepxy * sinangle; - stepcosangle = stepxy * cosangle; + com_par(); z_orig = zp = z[j][i]; maxlength = (zmax - z_orig) / TANMINANGLE; diff -Nru grass-8.3.1/raster/r.horizon/testsuite/test_r_horizon.py grass-8.3.2/raster/r.horizon/testsuite/test_r_horizon.py --- grass-8.3.1/raster/r.horizon/testsuite/test_r_horizon.py 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/raster/r.horizon/testsuite/test_r_horizon.py 2024-03-06 21:24:05.000000000 +0000 @@ -63,8 +63,30 @@ 160.000000,0.015356 """ +ref4 = """azimuth,horizon_height +0.000000,0.197017 +20.000000,0.196832 +40.000000,0.196875 +60.000000,0.196689 +80.000000,0.196847 +100.000000,0.196645 +120.000000,0.196969 +140.000000,0.196778 +160.000000,0.196863 +180.000000,0.197017 +200.000000,0.196832 +220.000000,0.196875 +240.000000,0.196689 +260.000000,0.196847 +280.000000,0.196645 +300.000000,0.196969 +320.000000,0.196778 +340.000000,0.196863 +""" + class TestHorizon(TestCase): + circle = "circle" horizon = "test_horizon_from_elevation" horizon_output = "test_horizon_output_from_elevation" @@ -72,9 +94,19 @@ def setUpClass(cls): cls.use_temp_region() cls.runModule("g.region", raster="elevation") + cls.runModule( + "r.circle", + flags="b", + output=cls.circle, + coordinates=(637505, 221755), + min=5000, + multiplier=1000, + ) + cls.runModule("r.null", map=cls.circle, null=0) @classmethod def tearDownClass(cls): + cls.runModule("g.remove", flags="f", type="raster", name=cls.circle) cls.del_temp_region() def tearDown(self): @@ -112,6 +144,26 @@ stdout = module.outputs.stdout self.assertMultiLineEqual(first=ref2, second=stdout) + # include nulls along the edge + self.runModule("g.region", raster="elevation", w="w-100") + self.assertModule(module) + stdout = module.outputs.stdout + self.assertMultiLineEqual(first=ref2, second=stdout) + + def test_point_mode_multiple_direction_artificial(self): + """Test mode with 1 point and multiple directions with artificial surface""" + module = SimpleModule( + "r.horizon", + elevation=self.circle, + coordinates=(637505, 221755), + output=self.horizon, + direction=0, + step=20, + ) + self.assertModule(module) + stdout = module.outputs.stdout + self.assertMultiLineEqual(first=ref4, second=stdout) + def test_raster_mode_one_direction(self): """Test mode with 1 point and one direction""" module = SimpleModule( @@ -176,6 +228,86 @@ second=stdout, ) + def test_raster_mode_bufferzone(self): + """Test buffer 100 m and 109 m with resolution 10 gives the same result""" + self.runModule( + "g.region", + raster="elevation", + n="n-5000", + s="s+5000", + e="e-5000", + w="w+5000", + ) + # raises ValueError from pygrass parameter check + self.assertRaises( + ValueError, + SimpleModule, + "r.horizon", + elevation="elevation", + output=self.horizon_output, + direction=50, + bufferzone=-100, + ) + self.assertRaises( + ValueError, + SimpleModule, + "r.horizon", + elevation="elevation", + output=self.horizon_output, + direction=50, + e_buff=100, + n_buff=0, + s_buff=-100, + w_buff=-100, + ) + module = SimpleModule( + "r.horizon", + elevation="elevation", + output=self.horizon_output, + direction=50, + bufferzone=100, + ) + self.assertModule(module) + ref = { + "mean": 0.0344791, + } + output = "test_horizon_output_from_elevation_050" + self.assertRasterFitsUnivar( + raster=output, + reference=ref, + precision=1e-6, + ) + module = SimpleModule( + "r.horizon", + elevation="elevation", + output=self.horizon_output, + direction=50, + bufferzone=103, + ) + self.assertModule(module) + self.assertRasterFitsUnivar( + raster=output, + reference=ref, + precision=1e-6, + ) + module = SimpleModule( + "r.horizon", + elevation="elevation", + output=self.horizon_output, + direction=50, + bufferzone=95, + ) + self.assertModule(module) + ref = { + "mean": 0.0344624, + } + self.assertRasterFitsUnivar( + raster=output, + reference=ref, + precision=1e-6, + ) + self.runModule("g.region", raster="elevation") + if __name__ == "__main__": test() diff -Nru grass-8.3.1/raster/r.mapcalc/Makefile grass-8.3.2/raster/r.mapcalc/Makefile --- grass-8.3.1/raster/r.mapcalc/Makefile 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/raster/r.mapcalc/Makefile 2024-03-06 21:24:05.000000000 +0000 @@ -20,7 +20,9 @@ $(BIN)/$(PGM2)$(EXE): LIBES = $(LIBES2) $(BIN)/$(PGM3)$(EXE): LIBES = $(LIBES3) -$(OBJDIR)/mapcalc.yy.o: mapcalc.tab.h +$(OBJDIR)/*.o: mapcalc.tab.h mapcalc.tab.c +$(OBJDIR)/mapcalc.yy.o: mapcalc.tab.h mapcalc.tab.c + .SECONDARY: mapcalc.tab.c mapcalc.tab.h mapcalc.output diff -Nru grass-8.3.1/raster/r.texture/r.texture.html grass-8.3.2/raster/r.texture/r.texture.html --- grass-8.3.1/raster/r.texture/r.texture.html 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/raster/r.texture/r.texture.html 2024-03-06 21:24:05.000000000 +0000 @@ -52,8 +52,8 @@ or for the identification of objects in i.segment, and/or for the characterization of these objects and thus, for example, as one of the raster inputs of the - - i.segment.stats addon. +i.segment.stats +addon.

In general, several variables constitute texture: differences in grey level values, @@ -68,7 +68,15 @@ is a two-dimensional histogram of grey levels for a pair of pixels which are separated by a fixed spatial relationship. The matrix approximates the joint probability distribution of a pair of pixels. Several texture measures are -directly computed from the grey level co-occurrence matrix. +directly computed from the Grey Level Co-occurrence Matrix (GLCM). + +The provided measures can be categorized under first-order and +second-order statistics, with each playing a unique role in texture +analysis. First-order statistics consider the distribution of +individual pixel values without regard to spatial relationships, while +second-order statistics, particularly those derived from the Grey Level +Co-occurrence Matrix (GLCM), consider the spatial relationship of +pixels.

The following part offers brief explanations of the Haralick et al texture @@ -76,24 +84,45 @@

First-order statistics in the spatial domain

    -
  • Sum Average (SA)
  • +
  • Sum Average (SA): + Sum Average measures the average gray level intensity of the sum of + pixel pairs within the moving window. It reflects the average intensity + of pixel pairs at specific distances and orientations, highlighting the + overall brightness level within the area.
  • Entropy (ENT): This measure analyses the randomness. It is high when the values of the moving window have similar values. It is low when the values are close - to either 0 or 1 (i.e. when the pixels in the local window are uniform).
  • - -
  • Difference Entropy (DE)
  • + to either 0 or 1 (i.e. when the pixels in the local window are + uniform). -
  • Sum Entropy (SE)
  • +
  • Difference Entropy (DE): + This metric quantifies the randomness or unpredictability in the + distribution of differences between the grey levels of pixel pairs. It + is a measure of the entropy of the pixel-pair difference histogram, + capturing texture granularity.
  • + +
  • Sum Entropy (SE): Similar to Difference Entropy, Sum Entropy measures + the randomness or unpredictability, but in the context of the sum of the + grey levels of pixel pairs. It evaluates the entropy of the pixel-pair + sum distribution, providing insight into the complexity of texture in + terms of intensity variation.
  • Variance (VAR): - A measure of gray tone variance within the moving window (second-order -moment about the mean)
  • + A measure of gray tone variance within the moving window (second-order + moment about the mean) -
  • Difference Variance (DV)
  • - -
  • Sum Variance (SV)
  • +
  • Difference Variance (DV): + This is a measure of the variance or spread of the differences in grey + levels between pairs of pixels within the moving window. It quantifies + the contrast variability between pixels, indicating texture smoothness + or roughness.
  • + +
  • Sum Variance (SV): + In contrast to Difference Variance, Sum Variance measures the variance + of the sum of grey levels of pixel pairs. It assesses the variability + in the intensity levels of pairs of pixels, contributing to an + understanding of texture brightness or intensity variation.
Note that measures "mean", "kurtosis", "range", "skewness", and "standard @@ -128,9 +157,19 @@ pixels. Typically high, when the scale of local texture is larger than the distance. -
  • Information Measures of Correlation (MOC)
  • - -
  • Maximal Correlation Coefficient (MCC)
  • +
  • Information Measures of Correlation (MOC): + These measures evaluate the complexity of the texture in terms of the + mutual dependence between the grey levels of pixel pairs. They + quantify how one pixel value informs or correlates with another, + offering insight into pattern predictability and structure regularity.
  • + +
  • Maximal Correlation Coefficient (MCC): + This statistic measures the highest correlation between any two + features of the texture, providing a single value that summarizes the + degree of linear dependency between grey levels in the texture. It's + often used to assess the overall correlation in the image, indicating + how predictable the texture patterns are from one pixel to the + next.
  • diff -Nru grass-8.3.1/raster/r.walk/r.walk.html grass-8.3.2/raster/r.walk/r.walk.html --- grass-8.3.1/raster/r.walk/r.walk.html 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/raster/r.walk/r.walk.html 2024-03-06 21:24:05.000000000 +0000 @@ -64,6 +64,9 @@

    The friction cost parameter represents a time penalty in seconds of additional walking time to cross 1 meter distance. +Friction cost can be any floating point value ≥ 0. +A friction map is a required parameter; if no friction costs are desired, +a friction map should be a raster in which all cells have a value of 0.

    The lambda parameter is a dimensionless scaling factor of the friction cost:

    diff -Nru grass-8.3.1/scripts/db.dropcolumn/db.dropcolumn.py grass-8.3.2/scripts/db.dropcolumn/db.dropcolumn.py
    --- grass-8.3.1/scripts/db.dropcolumn/db.dropcolumn.py	2023-10-24 19:27:44.000000000 +0000
    +++ grass-8.3.2/scripts/db.dropcolumn/db.dropcolumn.py	2024-03-06 21:24:05.000000000 +0000
    @@ -35,6 +35,13 @@
     # % required : yes
     # %end
     
    +# %option G_OPT_DB_DATABASE
    +# %end
    +
    +# %option G_OPT_DB_DRIVER
    +# % options: dbf,odbc,ogr,sqlite,pg
    +# %end
    +
     import sys
     import string
     
    @@ -45,14 +52,19 @@
     def main():
         table = options["table"]
         column = options["column"]
    +    database = options["database"]
    +    driver = options["driver"]
         force = flags["f"]
     
         # check if DB parameters are set, and if not set them.
         gscript.run_command("db.connect", flags="c")
     
    -    kv = gscript.db_connection()
    -    database = kv["database"]
    -    driver = kv["driver"]
    +    if not database or not driver:
    +        kv = gscript.db_connection()
    +        if not database:
    +            database = kv["database"]
    +        if not driver:
    +            driver = kv["driver"]
         # schema needed for PG?
     
         if force:
    @@ -67,7 +79,10 @@
                 % column
             )
     
    -    cols = [f[0] for f in gscript.db_describe(table)["cols"]]
    +    cols = [
    +        f[0]
    +        for f in gscript.db_describe(table, database=database, driver=driver)["cols"]
    +    ]
         if column not in cols:
             gscript.fatal(_("Column <%s> not found in table") % column)
     
    @@ -80,30 +95,42 @@
             return 0
     
         if driver == "sqlite":
    -        # echo "Using special trick for SQLite"
    -        # http://www.sqlite.org/faq.html#q13
    -        colnames = []
    -        coltypes = []
    -        for f in gscript.db_describe(table)["cols"]:
    -            if f[0] != column:
    -                colnames.append(f[0])
    -                coltypes.append("%s %s" % (f[0], f[1]))
    -
    -        colnames = ", ".join(colnames)
    -        coltypes = ", ".join(coltypes)
    -
    -        cmds = [
    -            "BEGIN TRANSACTION",
    -            "CREATE TEMPORARY TABLE ${table}_backup(${coldef})",
    -            "INSERT INTO ${table}_backup SELECT ${colnames} FROM ${table}",
    -            "DROP TABLE ${table}",
    -            "CREATE TABLE ${table}(${coldef})",
    -            "INSERT INTO ${table} SELECT ${colnames} FROM ${table}_backup",
    -            "DROP TABLE ${table}_backup",
    -            "COMMIT",
    -        ]
    -        tmpl = string.Template(";\n".join(cmds))
    -        sql = tmpl.substitute(table=table, coldef=coltypes, colnames=colnames)
    +        sqlite3_version = gscript.read_command(
    +            "db.select",
    +            sql="SELECT sqlite_version();",
    +            flags="c",
    +            database=database,
    +            driver=driver,
    +        ).split(".")[0:2]
    +
    +        if [int(i) for i in sqlite3_version] >= [int(i) for i in "3.35".split(".")]:
    +            sql = "ALTER TABLE %s DROP COLUMN %s" % (table, column)
    +            if column == "cat":
    +                sql = "DROP INDEX %s_%s; %s" % (table, column, sql)
    +        else:
    +            # for older sqlite3 versions, use old way to remove column
    +            colnames = []
    +            coltypes = []
    +            for f in gscript.db_describe(table)["cols"]:
    +                if f[0] != column:
    +                    colnames.append(f[0])
    +                    coltypes.append("%s %s" % (f[0], f[1]))
    +
    +            colnames = ", ".join(colnames)
    +            coltypes = ", ".join(coltypes)
    +
    +            cmds = [
    +                "BEGIN TRANSACTION",
    +                "CREATE TEMPORARY TABLE ${table}_backup(${coldef})",
    +                "INSERT INTO ${table}_backup SELECT ${colnames} FROM ${table}",
    +                "DROP TABLE ${table}",
    +                "CREATE TABLE ${table}(${coldef})",
    +                "INSERT INTO ${table} SELECT ${colnames} FROM ${table}_backup",
    +                "DROP TABLE ${table}_backup",
    +                "COMMIT",
    +            ]
    +            tmpl = string.Template(";\n".join(cmds))
    +            sql = tmpl.substitute(table=table, coldef=coltypes, colnames=colnames)
         else:
             sql = "ALTER TABLE %s DROP COLUMN %s" % (table, column)
     
    diff -Nru grass-8.3.1/scripts/db.in.ogr/db.in.ogr.py grass-8.3.2/scripts/db.in.ogr/db.in.ogr.py
    --- grass-8.3.1/scripts/db.in.ogr/db.in.ogr.py	2023-10-24 19:27:44.000000000 +0000
    +++ grass-8.3.2/scripts/db.in.ogr/db.in.ogr.py	2024-03-06 21:24:05.000000000 +0000
    @@ -143,6 +143,13 @@
             else:
                 grass.fatal(_("Input DSN <%s> not found or not readable") % input)
     
    +    # save db connection settings of the output
    +    f = grass.vector_layer_db(output, "1")
    +
    +    table = f["table"]
    +    database = f["database"]
    +    driver = f["driver"]
    +
         # rename ID col if requested from cat to new name
         if key:
             grass.write_command(
    @@ -173,14 +180,16 @@
             "db.dropcolumn",
             quiet=True,
             flags="f",
    -        table=output,
    +        table=table,
    +        database=database,
    +        driver=driver,
             column="cat",
             stdout=nuldev,
             stderr=nuldev,
         )
         nuldev.close()
     
    -    records = grass.db_describe(output)["nrows"]
    +    records = grass.db_describe(table, database=database, driver=driver)["nrows"]
         grass.message(_("Imported table <%s> with %d rows") % (output, records))
     
     
    diff -Nru grass-8.3.1/scripts/v.db.renamecolumn/v.db.renamecolumn.py grass-8.3.2/scripts/v.db.renamecolumn/v.db.renamecolumn.py
    --- grass-8.3.1/scripts/v.db.renamecolumn/v.db.renamecolumn.py	2023-10-24 19:27:44.000000000 +0000
    +++ grass-8.3.2/scripts/v.db.renamecolumn/v.db.renamecolumn.py	2024-03-06 21:24:05.000000000 +0000
    @@ -90,7 +90,7 @@
     
         # describe old col
         oldcoltype = None
    -    for f in grass.db_describe(table)["cols"]:
    +    for f in grass.db_describe(table, database=database, driver=driver)["cols"]:
             if f[0] != oldcol:
                 continue
             oldcoltype = f[1]
    diff -Nru grass-8.3.1/utils/mkhtml.py grass-8.3.2/utils/mkhtml.py
    --- grass-8.3.1/utils/mkhtml.py	2023-10-24 19:27:44.000000000 +0000
    +++ grass-8.3.2/utils/mkhtml.py	2024-03-06 21:24:05.000000000 +0000
    @@ -7,7 +7,7 @@
     #               Glynn Clements
     #               Martin Landa 
     # PURPOSE:      Create HTML manual page snippets
    -# COPYRIGHT:    (C) 2007-2023 by Glynn Clements
    +# COPYRIGHT:    (C) 2007-2024 by Glynn Clements
     #                and the GRASS Development Team
     #
     #               This program is free software under the GNU General
    @@ -449,7 +449,8 @@
     """
     
     sourcecode = string.Template(
    -    """

    SOURCE CODE

    + """ +

    SOURCE CODE

    Available at: ${PGM} source code @@ -905,36 +906,39 @@ if sys.platform == "win32": url_source = url_source.replace(os.path.sep, "/") -if index_name: - branches = "branches" - tree = "tree" - commits = "commits" - - if branches in url_source: - url_log = url_source.replace(branches, commits) - url_source = url_source.replace(branches, tree) - else: - url_log = url_source.replace(tree, commits) +# Process Source code section +branches = "branches" +tree = "tree" +commits = "commits" + +if branches in url_source: + url_log = url_source.replace(branches, commits) + url_source = url_source.replace(branches, tree) +else: + url_log = url_source.replace(tree, commits) - git_commit = get_last_git_commit( - src_dir=curdir, - addon_path=addon_path if addon_path else None, - is_addon=True if addon_path else False, +git_commit = get_last_git_commit( + src_dir=curdir, + addon_path=addon_path if addon_path else None, + is_addon=True if addon_path else False, +) +if git_commit["commit"] == "unknown": + date_tag = "Accessed: {date}".format(date=git_commit["date"]) +else: + date_tag = "Latest change: {date} in commit: {commit}".format( + date=git_commit["date"], commit=git_commit["commit"] ) - if git_commit["commit"] == "unknown": - date_tag = "Accessed: {date}".format(date=git_commit["date"]) - else: - date_tag = "Latest change: {date} in commit: {commit}".format( - date=git_commit["date"], commit=git_commit["commit"] - ) - sys.stdout.write( - sourcecode.substitute( - URL_SOURCE=url_source, - PGM=pgm, - URL_LOG=url_log, - DATE_TAG=date_tag, - ) +sys.stdout.write( + sourcecode.substitute( + URL_SOURCE=url_source, + PGM=pgm, + URL_LOG=url_log, + DATE_TAG=date_tag, ) +) + +# Process footer +if index_name: sys.stdout.write( footer_index.substitute( INDEXNAME=index_name, diff -Nru grass-8.3.1/utils/release.yml grass-8.3.2/utils/release.yml --- grass-8.3.1/utils/release.yml 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/utils/release.yml 2024-03-06 21:24:05.000000000 +0000 @@ -2,7 +2,7 @@ notes: categories: - title: Modules - regexp: '(d|db|g|i|m|ps|r|r3|t|v)\.[^ ]*: |(modules|tools): ' + regexp: '((d|db|g|i|m|ps|r|r3|t|v)\.[^ ]*)(, (d|db|g|i|m|ps|r|r3|t|v)\.[^ ]*)?: |(modules|tools): ' - title: Graphical User Interface regexp: '(wxGUI.*|gui|GUI): ' @@ -20,16 +20,16 @@ regexp: '(init|startup): ' - title: Translations, Internationalization, and Localization - regexp: '(i18n|i18N|L10n|L10N|t9n|translations?): ' + regexp: '(i18n|i18N|L10n|L10N|t9n|translations?): |Translations update from ' - title: Windows regexp: '(winGRASS|win|[Ww]indows): ' - title: Packaging, Configuration, Portability, and Compilation - regexp: '(pkg|rpm|deb|pkg-config|configure|config|[Mm]ake): ' + regexp: '(packaging|pkg|rpm|deb|pkg-config|configure|config|[Mm]ake|build): ' - title: Docker - regexp: '[Dd]ocker: ' + regexp: '[Dd]ocker(/[^ ]+)?: ' - title: Singularity regexp: '[Ss]ingularity: ' @@ -38,7 +38,7 @@ regexp: '(CI|ci|CI\(deps\)|ci\(deps\)|[Tt]ests|[Cc]hecks|pytest): ' - title: Contributing and Management - regexp: '(contributing|CONTRIBUTING.md|contributors.csv): ' + regexp: '(contributing|CONTRIBUTING.md|contributors|contributors.csv): ' exclude: regexp: diff -Nru grass-8.3.1/vector/v.clean/main.c grass-8.3.2/vector/v.clean/main.c --- grass-8.3.1/vector/v.clean/main.c 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/vector/v.clean/main.c 2024-03-06 21:24:05.000000000 +0000 @@ -127,7 +127,8 @@ opt.thresh->type = TYPE_DOUBLE; opt.thresh->required = NO; opt.thresh->multiple = YES; - opt.thresh->label = _("Threshold in map units, one value for each tool"); + opt.thresh->label = + _("One value for each tool; for threshold units, see each tool"); opt.thresh->description = _("Default: 0.0[,0.0,...])"); flag.no_build = G_define_flag(); diff -Nru grass-8.3.1/vector/v.out.ogr/main.c grass-8.3.2/vector/v.out.ogr/main.c --- grass-8.3.1/vector/v.out.ogr/main.c 2023-10-24 19:27:44.000000000 +0000 +++ grass-8.3.2/vector/v.out.ogr/main.c 2024-03-06 21:24:05.000000000 +0000 @@ -615,7 +615,7 @@ _("Layer <%s> already exists in OGR data source '%s'"), options.layer->answer, options.dsn->answer); } - else if (overwrite) { + else if (overwrite && !flags.append->answer) { G_warning( _("OGR layer <%s> already exists and will be overwritten"), options.layer->answer);