diff -Nru grass-8.3.0/binder/apt.txt grass-8.3.1/binder/apt.txt --- grass-8.3.0/binder/apt.txt 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/binder/apt.txt 2023-10-24 19:27:44.000000000 +0000 @@ -1,4 +1,4 @@ -autoconf2.13 +autoconf autotools-dev bison flex @@ -16,9 +16,10 @@ libglu1-mesa-dev libjpeg-dev liblapack-dev -liblas-c-dev libncurses5-dev libnetcdf-dev +libpdal-dev +libgeos-dev libpng-dev libpq-dev libproj-dev @@ -31,6 +32,7 @@ netcdf-bin p7zip proj-bin +python3-pip sqlite3 unixodbc-dev xvfb diff -Nru grass-8.3.0/binder/postBuild grass-8.3.1/binder/postBuild --- grass-8.3.0/binder/postBuild 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/binder/postBuild 2023-10-24 19:27:44.000000000 +0000 @@ -5,18 +5,17 @@ # compile ./configure \ - --enable-largefile=yes \ --with-nls \ --with-cxx \ --with-readline \ --with-bzlib \ - --with-pthread \ --with-proj-share=/usr/share/proj \ --with-geos=/usr/bin/geos-config \ --with-cairo \ --with-opengl-libs=/usr/include/GL \ --with-freetype=yes --with-freetype-includes="/usr/include/freetype2/" \ - --with-sqlite=yes + --with-sqlite=yes \ + --without-pdal make # put command on path diff -Nru grass-8.3.0/CITATION.cff grass-8.3.1/CITATION.cff --- grass-8.3.0/CITATION.cff 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/CITATION.cff 2023-10-24 19:27:44.000000000 +0000 @@ -1,18 +1,19 @@ cff-version: 1.2.0 -message: If you use this software, please cite it using the metadata from this file. - title: GRASS GIS +message: If you use this software, please cite it using the metadata from this file. version: 8.3.0 abstract: GRASS GIS (Geographic Resources Analysis Support System) is a free and open source Geographic Information System (GIS) software used for geospatial data management and analysis, image processing, graphics and maps production, spatial modeling, and visualization. - +type: software authors: - - name: GRASS Development Team + - family-names: GRASS Development Team email: grass-dev@lists.osgeo.org affiliation: Open Source Geospatial Foundation (OSGeo) - - name: Martin Landa + - given-names: Martin + family-names: Landa affiliation: Czech Technical University in Prague orcid: https://orcid.org/0000-0001-6869-3542 - - name: Markus Neteler + - given-names: Markus + family-names: Neteler affiliation: mundialis GmbH & Co. KG orcid: https://orcid.org/0000-0003-1916-1966 - given-names: Markus @@ -20,10 +21,12 @@ email: metz@mundialis.de affiliation: mundialis GmbH & Co. KG orcid: https://orcid.org/0000-0002-4038-8754 - - name: Anna Petrášová + - given-names: Anna + family-names: Petrášová affiliation: North Carolina State University orcid: https://orcid.org/0000-0002-5120-5538 - - name: Vaclav Petráš + - given-names: Vaclav + family-names: Petráš affiliation: North Carolina State University orcid: https://orcid.org/0000-0001-5566-9236 - given-names: Glynn @@ -104,11 +107,10 @@ - given-names: Hamish family-names: Bowman email: hamish_b@yahoo.com - repository-code: https://github.com/OSGeo/grass -license: GNU General Public License v2 or later +license: GPL-2.0-or-later doi: 10.5281/zenodo.4621728 -keywords: +keywords: - GIS - geospatial - spatial analysis @@ -117,7 +119,3 @@ - open source - free software - GNU GPL v2 - -citation: - - text: GRASS Development Team. (2023). Geographic Resources Analysis Support System (GRASS GIS) Software, Version 8.3.0. Open Source Geospatial Foundation. https://grass.osgeo.org - doi: 10.5281/zenodo.4621728 diff -Nru grass-8.3.0/debian/changelog grass-8.3.1/debian/changelog --- grass-8.3.0/debian/changelog 2023-09-13 08:52:16.000000000 +0000 +++ grass-8.3.1/debian/changelog 2023-10-26 08:07:29.000000000 +0000 @@ -1,3 +1,36 @@ +grass (8.3.1-1~jammy1) jammy; urgency=medium + + * Rebuild for jammy. + + -- Martin Landa Thu, 26 Oct 2023 10:07:29 +0200 + +grass (8.3.1-1) unstable; urgency=medium + + * New upstream release. + * Move from experimental to unstable. + + -- Bas Couwenberg Wed, 25 Oct 2023 21:04:19 +0200 + +grass (8.3.1~rc1-1~exp1~jammy1) jammy; urgency=medium + + * Rebuild for jammy. + + -- Martin Landa Wed, 18 Oct 2023 17:57:13 +0200 + +grass (8.3.1~rc1-1~exp1) experimental; urgency=medium + + * New upstream release candidate. + * Add libjs-jquery to grass-dev-doc dependencies. + (closes: #1040554) + * Move git Recommends from grass-dev to grass-core for g.extension, + and move subversion to grass-core Suggests for g.extension. + * Use execute_{before,after} instead of override in rules file. + * Enable Salsa CI. + * Switch to dh-sequence-*. + * Strip build path from programming-manual. + + -- Bas Couwenberg Fri, 13 Oct 2023 12:01:22 +0200 + grass (8.3.0-1~jammy1) jammy; urgency=medium * Rebuild for jammy. diff -Nru grass-8.3.0/debian/control grass-8.3.1/debian/control --- grass-8.3.0/debian/control 2023-09-13 08:50:11.000000000 +0000 +++ grass-8.3.1/debian/control 2023-10-26 08:07:16.000000000 +0000 @@ -7,6 +7,8 @@ Build-Depends: bison, debhelper-compat (= 13), dh-python, + dh-sequence-numpy3, + dh-sequence-python3, doxygen, fakeroot, flex, @@ -82,24 +84,28 @@ ${python3:Depends}, ${misc:Depends} Recommends: gdal-bin, +# used by g.extension + git, # cs2cs used by m.proj, gdalinfo+gdal_translate used by a number of scripts proj-bin Suggests: grass-dev, - grass-gui, + grass-gui, # used by v.in.e00 - e00compr, - avce00, + e00compr, + avce00, # used by i.spectral - gnuplot, + gnuplot, # used by v.in.gpsbabel - gpsbabel, + gpsbabel, # used by v.in.garmin - gpstrans, + gpstrans, # used by NVIZ, d.out.gpsdrive, r.out.mpeg - netpbm, + netpbm, # used by v.kridge - python3-rpy2, - python3-termcolor + python3-rpy2, + python3-termcolor, +# used by g.extension + subversion Breaks: grass (<< 6.4.2-1~) Provides: ${grass:Provides} Replaces: grass (<< 6.4.2-1~) @@ -160,7 +166,8 @@ Package: grass-dev-doc Section: doc Architecture: all -Depends: ${misc:Depends} +Depends: libjs-jquery, + ${misc:Depends} Suggests: grass Description: GRASS GIS Programmers' Manual Commonly referred to as GRASS, this is a Geographic Information @@ -185,9 +192,6 @@ ${python3:Depends}, ${shlibs:Depends}, ${misc:Depends} -# git is needed by g.extension, a primary motivator for installing the grass-dev pkg -Recommends: git, - subversion Suggests: grass-dev-doc, pkg-config Description: GRASS GIS development files diff -Nru grass-8.3.0/debian/grass-doc.lintian-overrides grass-8.3.1/debian/grass-doc.lintian-overrides --- grass-8.3.0/debian/grass-doc.lintian-overrides 2023-09-13 08:23:23.000000000 +0000 +++ grass-8.3.1/debian/grass-doc.lintian-overrides 2023-10-26 08:06:26.000000000 +0000 @@ -1,6 +1,9 @@ # html2man doesn't include NAME (see also: #379913) bad-whatis-entry * +# html2man issue +groff-message * + # Executable is in /usr/lib/grass??/bin/ spare-manual-page [usr/share/man/man1/*] diff -Nru grass-8.3.0/debian/rules grass-8.3.1/debian/rules --- grass-8.3.0/debian/rules 2023-09-13 08:23:23.000000000 +0000 +++ grass-8.3.1/debian/rules 2023-10-26 08:06:26.000000000 +0000 @@ -36,15 +36,14 @@ done %: - dh $@ --with python3,numpy3 - -override_dh_clean: - dh_clean debian/grass-gui.image-file-in-usr-lib.list + dh $@ +execute_after_dh_clean: $(RM) lib/proj/nad2bin $(RM) utils/timer/main.o $(RM) raster/r.terraflow/IOStream/lib/src/libiostream.a $(RM) debian/$(BASE_NAME).1 + $(RM) debian/grass-gui.image-file-in-usr-lib.list $(RM) error.log # clean doxygen documentation (programming man) @@ -106,17 +105,14 @@ --with-x \ --with-zstd -override_dh_auto_build: - dh_auto_build +execute_after_dh_auto_build: chmod 755 debian/fixpaths.sh # generate the Programmers' manual (in HTML) $(MAKE) htmldocs-single - # save ~7mb of disk space by compressing PNG images (but takes a long time) - ##for file in `find debian/tmp/programming-manual/ -iname \*.png` ; do \ - ## optipng -o5 $$file ; \ - ##done + # Strip build path + sed -i "s@$(CURDIR)@@g" lib/html/search/*.js override_dh_auto_install: # install grass core into debian/tmp @@ -207,13 +203,12 @@ # Remove empty files find debian/tmp/ -type f -empty -name "class_graphical*" -print -delete -override_dh_install: +execute_before_dh_install: # Strip binaries strip --strip-unneeded --remove-section=.comment --remove-section=.note debian/tmp/usr/lib/$(BASE_NAME)/bin/r.in.png strip --strip-unneeded --remove-section=.comment --remove-section=.note debian/tmp/usr/lib/$(BASE_NAME)/bin/r.out.png - dh_install - +execute_after_dh_install: # delete duplicated grass-gui stuff from grass-core package for COMPONENT in gui wxpython; do \ rm -rf debian/$(PKG_NAME)-core/usr/lib/$(BASE_NAME)/$$COMPONENT; \ diff -Nru grass-8.3.0/doc/development/rfc/version_numbering.md grass-8.3.1/doc/development/rfc/version_numbering.md --- grass-8.3.0/doc/development/rfc/version_numbering.md 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/doc/development/rfc/version_numbering.md 2023-10-24 19:27:44.000000000 +0000 @@ -2,7 +2,7 @@ Author: Vaclav Petras -Status: Draft +Status: Adopted (5 June 2023) ## Summary diff -Nru grass-8.3.0/doc/development/submitting/submitting.md grass-8.3.1/doc/development/submitting/submitting.md --- grass-8.3.0/doc/development/submitting/submitting.md 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/doc/development/submitting/submitting.md 2023-10-24 19:27:44.000000000 +0000 @@ -21,39 +21,61 @@ ### Use pre-commit -It is highly recommended to install and use [Pre-commit](https://pre-commit.com) -before submitting any new or modification of code or other content. The Pre-commit -git hooks set are checking validity and executes formatting of file formats for -a range of files types, including C/C++ and Python files. Pre-commit installs +It is highly recommended to install and use [pre-commit](https://pre-commit.com) +before submitting any new or modified code or any other content. The pre-commit +Git hooks set checks validity and executes automated formatting for +a range of file formats, including C/C++ and Python. Pre-commit installs all necessary tools in a virtual environment upon first use. +If you never used pre-commit before, you must start by installing it on your +system. You only do it once: + ```bash python -m pip install pre-commit +``` + +Pre-commit must then be activated in the code repository. Change the directory +to the root folder and use the `install` command: +```bash cd # once per repo pre-commit install ``` -Pre-commit will then be automatically triggered by git commit command. It is -also possible to run manually, e.g: +Pre-commit will then be automatically triggered by the `git commit` command. If +it finds any problem it will abort the commit and try to solve it automatically. +In that case review the changes and run again `git add` and +`git commit`. + +It is also possible to run pre-commit manually, e.g: ```bash pre-commit run clang-format --all-files pre-commit run black --all-files ``` -The Pre-commit hooks are defined in +Or to target a specific set of files: + +```bash +pre-commit run --files raster/r.somemodule/* +``` + +The pre-commit hooks set is defined in [.pre-commit-config.yaml](../../../.pre-commit-config.yaml). -It is possible to temporary disable the Pre-commit hooks in the repo, eg. while +It is possible to temporally disable the pre-commit hooks in the repo, e.g. while working on older branches: ```bash # backporting... pre-commit uninstall +``` + +And to reactivate pre-commit again: +```bash git switch main pre-commit install ``` diff -Nru grass-8.3.0/doc/howto_release.md grass-8.3.1/doc/howto_release.md --- grass-8.3.0/doc/howto_release.md 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/doc/howto_release.md 2023-10-24 19:27:44.000000000 +0000 @@ -11,8 +11,8 @@ - You don't have any local un-pushed or un-committed changes. - You are using Bash or a similar shell. -*Note: Some later steps in this text are to be done by the development coordinator -(currently Markus Neteler and Martin Landa) due to needed logins.* +_Note: Some later steps in this text are to be done by the development coordinator +(currently Markus Neteler and Martin Landa) due to needed logins._ ## Prepare the local repo @@ -63,13 +63,19 @@ ## Update VERSION file to release version number -Modify the VERSION file use the dedicated script, for RC1, e.g.: +For RCs, modify the VERSION file use the dedicated script. E.g., for RC1: ```bash ./utils/update_version.py status ./utils/update_version.py rc 1 ``` +For a release, change the version after the RC cycle to an official release: + +```bash +./utils/update_version.py release +``` + The script will compute the correct version string and print a message containing it into the terminal (e.g., "version: GRASS GIS 3.5.0RC1"). @@ -80,9 +86,6 @@ git commit include/VERSION -m "..." ``` -If you lost the script output with the suggested message use -`./utils/update_version.py suggest` to get it. - Check that there is exactly one commit on your local branch and that it is the version change: @@ -91,7 +94,7 @@ git show ``` -Push the tag to the upstream repo: +Push the commit to the upstream repo: ```bash git push upstream @@ -139,7 +142,8 @@ ``` Create an annotated tag (a lightweight tag is okay too, but there is more metadata -stored for annotated tags including a date; message is suggested by the version script): +stored for annotated tags including a date; message is suggested by the +`./utils/update_version.py` script): ```bash git tag $TAG -a -m "..." @@ -152,7 +156,8 @@ git tag -n --sort=-taggerdate ``` -Now push the tag upstream - this will trigger the automated workflows linked to tags: +Now push the tag upstream - this will trigger the +[automated workflows](https://github.com/OSGeo/grass/actions) linked to tags: ```bash git push upstream $TAG @@ -165,13 +170,13 @@ Generate a draft of release notes using a script. The script the script needs to run from the top directory and will expect its configuration files -to be in the *utils* directory. +to be in the _utils_ directory. #### Major and minor releases -For major and minor releases, GitHub API gives good results for the first -release candidate because it contains contributor handles and can identify -new contributors, so use with the *api* backend, e.g.: +For major (X.y.z) and minor (x.Y.z) releases, GitHub API gives good results for the +first release candidate because it contains contributor handles and can identify +new contributors, so use with the _api_ backend, e.g.: ```bash python ./utils/generate_release_notes.py api releasebranch_8_3 8.2.0 $VERSION @@ -179,9 +184,9 @@ #### Micro releases -For micro releases, GitHub API does not give good results because it uses PRs -while the backports are usually direct commits without PRs. -The *git log* command operates on commits, so use use the *log* backend: +For micro releases (x.y.Z), GitHub API does not give good results because it uses +PRs while the backports are usually direct commits without PRs. +The _git log_ command operates on commits, so use use the _log_ backend: ```bash python ./utils/generate_release_notes.py log releasebranch_8_3 8.3.0 $VERSION @@ -189,7 +194,7 @@ #### RCs -In between RCs and between last RC and final release, the *log* backend is useful +In between RCs and between last RC and final release, the _log_ backend is useful for showing updates since the last RC: ```bash @@ -199,13 +204,13 @@ #### Finalizing the release notes For the final release, the changes accumulated since the first RC need to be -added manually to the result from the *api* backend. +added manually to the result from the _api_ backend. -The script sorts them into categories defined in *utils/release.yml*. +The script sorts them into categories defined in _utils/release.yml_. However, these notes need to be manually edited to collapse related items into -one. Additionally, a *Highlights* section needs to be added with manually +one. Additionally, a _Highlights_ section needs to be added with manually identified new major features for major and minor releases. For all releases, a -*Major* section may need to be added showing critical fixes or breaking changes +_Major_ section may need to be added showing critical fixes or breaking changes if there are any. ### Modify the release draft @@ -225,27 +230,27 @@ Save the modified draft, but do not publish the release yet. -## Reset include/VERSION file to git development version +## Update include/VERSION file Use the dedicated `update_version.py` script to edit the VERSION file. -After an RC, switch to development version: +After a RC, update to development version: ```bash ./utils/update_version.py dev ``` -Next switch back to the development version for the next micro, minor, -or major version, e.g., for micro version, use: +After a final release, update to the next micro (x.y.Z), minor (x.Y.z), +or major (X.y.y) version. E.g., for micro version, use: ```bash ./utils/update_version.py micro ``` -Use *major* and *minor* operations for the other version updates. +Use _major_ and _minor_ operations for the other version updates. Use `--help` for details about the options. -Commit with the suggested commit message and push, e.g.: +Eventually, commit with the suggested commit message and push, e.g.: ```bash git show @@ -253,18 +258,16 @@ git push upstream ``` -The message was suggested by the script, but if you lost that output, -you can get the same or similar message again using the script -(the message provided this way is not precise after RCs): +## Publishing a final release -```bash -./utils/update_version.py suggest -``` +The published RC releases has the initial release notes (based on locally +auto-generated notes) which need to be refined further: -## Publish release +- add highlights +- verify that the subsections are well sorted -For the final release, edit the draft release again and publish it using the -"Publish release" button. +For the final release, edit these draft release again in order to publish it +using the "Publish release" button. ## Upload to OSGeo servers @@ -321,12 +324,12 @@ ### Upload source code tarball to OSGeo servers Note: servers 'osgeo7-grass' and 'osgeo7-download' only reachable via - jumphost (managed by OSGeo-SAC) - see +jumphost (managed by OSGeo-SAC) - see ```bash # Store the source tarball (twice) in (use scp -p FILES grass:): USER=neteler -SERVER1=osgeo7-grass +SERVER1=osgeo8-grass SERVER1DIR=/var/www/code_and_data/grass$MAJOR$MINOR/source/ SERVER2=osgeo7-download SERVER2DIR=/osgeo/download/grass/grass$MAJOR$MINOR/source/ @@ -370,14 +373,16 @@ ## Update winGRASS related files -Update the winGRASS version at : +Update the GRASS version at : -```bash -vim wingrass-maintenance-scripts/grass_packager_release.bat -vim wingrass-maintenance-scripts/grass_addons.sh -vim wingrass-maintenance-scripts/grass_copy_wwwroot.sh -vim wingrass-maintenance-scripts/cronjob.sh # major/minor release only -``` +- On major or minor version change (on release branch creation) update + [dev_packages.csv](https://github.com/landam/wingrass-maintenance-scripts/blob/master/dev_packages.csv) +- On release (inluding RC) update + [releases.csv](https://github.com/landam/wingrass-maintenance-scripts/blob/master/releases.csv) + and + [grass_packager_release.bat](https://github.com/landam/wingrass-maintenance-scripts/blob/master/grass_packager_release.bat#L12) + +Example for 8.3.0RC1: [commit](https://github.com/landam/wingrass-maintenance-scripts/commit/c47b0f30051108bd2e8b52d183e97930c24dfafd) ## Update binary and addon builders @@ -477,6 +482,22 @@ - Create milestone and release: - Upload tarball for created release +### Update grass.osgeo.org + +These updates are for final releases only. + +Update version: + +- + +Add release article to news section: + +- + +Add release to history page: + +- + ### Other notes - @@ -504,24 +525,3 @@ Via web and social media: - See: - -## Update VERSION file to next version number - -After the final release whole is done, modify the VERSION file use -the dedicated script, e.g., for next micro version, run: - -```bash -./utils/update_version.py micro -./utils/update_version.py status -``` - -Now commit the change to the branch with the commit message generated above -by the script: - -```bash -git diff -git commit include/VERSION -m "..." -``` - -If you lost the script output with the suggested message use -`./utils/update_version.py suggest` to get it. diff -Nru grass-8.3.0/doc/infrastructure.md grass-8.3.1/doc/infrastructure.md --- grass-8.3.0/doc/infrastructure.md 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/doc/infrastructure.md 2023-10-24 19:27:44.000000000 +0000 @@ -1,11 +1,7 @@ # How the GRASS GIS Webserver and related infrastructure works -written by M. Neteler -Last changed: June 2023 - -Related Wiki documents: - -* (historical document) +Author: Markus Neteler +Last update: Sep 2023 ## GRASS GIS Source code repository @@ -16,109 +12,105 @@ The GitHub repositories are: -* GRASS GIS core (7+): -* GRASS GIS legacy (3.x-6.x): -* GRASS GIS Add-ons: -* GRASS GIS promotional material: -* GRASS GIS Website (hugo site): -* Github mirror at OSGeo: +- GRASS GIS core (7+): +- GRASS GIS legacy (3.x-6.x): +- GRASS GIS Add-ons: +- GRASS GIS promotional material: +- GRASS GIS Website (Hugo site): +- Github mirror at OSGeo: Git usage: -* [CONTRIBUTING.md file](../CONTRIBUTING.md) -* +- [CONTRIBUTING.md file](../CONTRIBUTING.md) +- Issues: -* -* old trac instance: - -Trac related notes: - -* For easier linking in the Trac Wiki, some macro definitions are used for manual - page refs (G7:modulename) - * -* ZIP file download support in trac (was needed for g.extension) * on trac.osgeo.org: - -```text -/var/www/trac/grass/conf/trac.ini -[browser] -downloadable_paths = /grass-addons/grass7/*/*,/sandbox/*/* -``` +- +- trac instance: (old bugs only) Statistics: -* -* +- ## GRASS Web server Maintainer: M. Neteler -* - * osgeo7-grass: LXD container on osgeo7 () - * OS: Debian Buster - * Apache Server with hugo () - * for migration details (7/2020), see - * ssh login: via jumphost hop.osgeo7.osgeo.org - * deployment via cronjob: -* (CMSMS, replaced in 2020 by above hugo based solution) - * Shared virtual OSGeo machine (osgeo6) hosted at Oregon State University +- + + - osgeo7-grass: LXD container on osgeo7 () + - OS: Debian Buster + - Apache Server with Hugo () + - for migration details (7/2020), see + - ssh login: via jumphost hop.osgeo8.osgeo.org + - deployment via cronjob: + +- (CMSMS, replaced in 2020 by above Hugo based solution) + + - Shared virtual OSGeo machine (osgeo6) hosted at Oregon State University Open Source Lab server: osgeo6.osgeo.osuosl.org) - * Login: via OSGeo LDAP, there is a "grass" LDAP group - * Software: - * OS: Debian Wheezy - * Apache Server with PHP - * Login: via OSGeo LDAP, there is a "grass" LDAP group -* Backups: - * osgeo7-grass: container on osgeo7 is backup'ed, see -* Mirrors: - * rsync, see --> Mirror - * mirror list, see -* RSS feed: offered by hugo at , used at - -* Weekly software snapshots (generated Saturday morning Portland (OR), US time): - * Source code tarball of git (GitHub) - * Linux binary snapshot is compiled on osgeo7-grass - * GRASS is compiled with GDAL, PROJ, SQLite, MySQL, PostgreSQL, FFTW, C++ support - * binary tar.gz and manuals are moved into Web space - -* GRASS user manual HTML: - * generated during compilation of weekly Linux binary snapshot on osgeo7-grass - -* GRASS addons manual HTML: - * generated during compilation of weekly Linux binary snapshot on osgeo7-grass - -* GRASS programmer's manual () - * HTML: cronjob run Wednesday morning Portland (OR), US time - * HTML: cronjob run Saturday morning Portland (OR), US time - * disabled: PDF: cronjob run Saturday morning Portland (OR), US time - -* i18N translation statistics () - * generated during compilation of Linux binary snapshot, stats of - `(cd locale; make)` are extracted into text file - * text file parsed by PHP page and shown as table - * GRASS GIS version is coded in devel/i18n_stats.inc - * for Transifex integration, see below - -* Mailman mailing lists + greylisting (at lists.osgeo.org since 11/2007) - * Mailman is doing the job, only registered users can post - * messages from unsubscribed people is auto-discarded without notification - * the open "weblist" operates instead like this: - * User -> grass-web at lists osgeo.org -> greylisting -> Mailman - -* Backup of mailing lists (mbox files) - * nightly backup at OSGeo.org, bacula - -* Web statistics - * See URL at - * cronjob script: /osgeo/scripts/update_logs.sh + - Login: via OSGeo LDAP, there is a "grass" LDAP group + - Software: + - OS: Debian Wheezy + - Apache Server with PHP + - Login: via OSGeo LDAP, there is a "grass" LDAP group + +- Backups: + + - osgeo7-grass: container on osgeo8 is backup'ed, see + +- Mirrors: + + - rsync, see --> Mirror + - mirror list, see + +- RSS feed: offered by Hugo at , used at + +- Weekly software snapshots (generated Saturday morning Portland (OR), US time): + + - Source code tarball of git (GitHub) + - Linux binary snapshot is compiled on osgeo7-grass + - GRASS is compiled with GDAL, PROJ, SQLite, MySQL, PostgreSQL, FFTW, C++ support, + see + - binary tar.gz and manuals are moved into Web space + +- GRASS user manual HTML: + + - generated during compilation of weekly Linux binary snapshot on osgeo7-grass + +- GRASS addons manual HTML: + + - generated during compilation of weekly Linux binary snapshot on osgeo7-grass + +- GRASS programmer's manual () + + - HTML: cronjob run Wednesday morning Portland (OR), US time + - HTML: cronjob run Saturday morning Portland (OR), US time + - disabled: PDF: cronjob run Saturday morning Portland (OR), US time + +- Mailman mailing lists + automated greylisting (at lists.osgeo.org since 11/2007) + + - Mailman is doing the job, only registered users can post + - messages from unsubscribed people is auto-discarded without notification + - the open "weblist" operates instead like this: + - User -> grass-web at lists osgeo.org -> greylisting -> Mailman + - for greylisting, see + +- Backup of mailing lists (mbox files) + + - nightly backup at OSGeo.org, bacula + +- Web statistics + - Matomo: (not publicly accessible; + access: Markus Neteler) + - Selected stats: Summary: The system should run almost autonomously. ## WinGRASS maintenance scripts -* +See ## GRASS Mailing lists @@ -126,167 +118,109 @@ Available lists: -* at OSGeo.org (): - * grass-abm Integration of GRASS with JAVA based agent based modeling - (ABM) - * grass-announce GRASS announcements - * grass-commit Mailing list to distribute GRASS-CVS commits - * grass-dev GRASS GIS Development mailing list - * grass-es La lista de correo de GRASS GIS en español - * grass-psc GRASS-PSC: GRASS Project Steering Committee - * grass-stats GRASS and statistical software - * grass-translations Translation of GRASS (i18N) - * grass-user GRASS user list - * grass-web GRASS website mailing list - -* OLD, UNUSED: at FBK-irst (): - * grass-commit-addons Mailing list to distribute GRASS Addons-SVN commits - * grass-gui GRASSGUI mailing list - * grass-qa GRASS Quality Assessment and monitoring list - * grass-windows winGRASS * Using GRASS on MS-Windows systems mailing list +- at OSGeo.org (): + - grass-abm: Integration of GRASS with JAVA based agent based modeling + (ABM) + - grass-announce: GRASS announcements + - grass-commit: Mailing list to distribute GRASS Github commits + - grass-dev: GRASS GIS Development mailing list + - grass-es: La lista de correo de GRASS GIS en español + - grass-psc: GRASS-PSC: GRASS Project Steering Committee + - grass-stats: GRASS and statistical software + - grass-translations: Translation of GRASS (i18N) + - grass-user: GRASS user list + - grass-web: GRASS website mailing list Notes: -* grass-announce: - * moderated by M. Neteler - * has monthly password reminder disabled to avoid leakage into publicly +- grass-announce: + - moderated by M. Neteler + - monthly password reminder is disabled to avoid leakage into publicly archived lists -* grass-commit is receiving posts from the GRASS SVN at osgeo.org. Not open for +- grass-commit is receiving posts from the GRASS Github. Not open for other postings, they will be trashed automatically -* grass-web is an open list (posting without subscription possible) with (Google) - spam filter - * moderated by M. Neteler to avoid spam -* OLD, UNUSED: grass-qa is receiving posts from the GRASS Quality Control System - at Ecole Polytechnique de Montreal, Canada. Not open for other postings. +- grass-web is an open list (posting without subscription possible with + moderation), moderated by M. Neteler to avoid spam ## GRASS Wiki Maintainer: Martin Landa, Markus Neteler -* -* Mediawiki -* mirrored at CZ Tech University -* requires registration to keep spammers out - -Summary: The system should run almost autonomous. An eye must be be kept on people -trying to spam the site +- +- Mediawiki software +- requires registration to keep spammers out + +Summary: The system should run almost autonomous. An eye must be be kept +on people trying to spam the site. Several layers of registration protection +are in place due to excessive spam. Macros for manual pages (src, cmd, API, ...): -* +- ## GRASS IRC -Channel: irc://irc.freenode.net/grass +Channel: irc://irc.libera.chat/grass Web based client: See -* channel owner: Alessandro Frigeri < afrigeri unipg.it > ("geoalf") -* quasi guru level: Markus Neteler ("markusN") -* further operators: - * Jachym ("jachym") - * Luca ("doktoreas") - * Soeren ("huhabla") - * Brad ("bdouglas") +- channel owner: Alessandro Frigeri ("geoalf") +- quasi guru level: Markus Neteler ("markusN") +- original (freenode) operators: + - Jachym ("jachym") + - Luca ("doktoreas") + - Soeren ("huhabla") + - Brad ("bdouglas") ## GRASS Bugtracker Current bugtracker (Jan 2020 - today): -* +- -Old bugtracker (Jan 2008 - Jan 2020): +Old bugtrackers: see -* -* posted new bugs and comments to grass-dev list -* Settings: - -Old tracsvn (OSGeo server) (Dec 2007 * Mai 2019) - -```text -/var/www/trac/env/grass/conf/trac.ini - downloadable_paths = /grass-addons/grass7/*/*,/sandbox/*/* - path = /var/www/grass/htdocs - link = - src = site/grasslogo_vector_small.png - smtp_always_cc = grass-dev@lists.osgeo.org - smtp_replyto = grass-dev@lists.osgeo.org - url = - .dir = /var/www/svn/repos/grass - base_url = - database = postgres://postgres@/trac_grass -``` - -Very old bugtracker (Jan 2007 * Dec 2008): - -* -* gforce, sponsored by Intevation GmbH, Germany -* spamassasin spamfilter locally, bogofilter at grass-dev list -* needs `noreply*wald.intevation.org` to be enabled as alias in Mailman - -Very very old bugtracker (Dec 2000 * Dec 2006): - -* -* webRT, sponsored by Intevation GmbH, Germany -* spamassasin spamfilter locally, bogofilter at grass-dev list -* reports are directly sent to GRASS Developers mailing list for notification -* TODO: migrate to trac - -## GRASS Addons +## GRASS GIS Addons Maintainer: Martin Landa and Markus Neteler Details: -* Windows-addons: grass-addons/utils/addons/README.txt -* Addon manual pages cronjob: -* Rendered manuals: +- Windows-addons: grass-addons/utils/addons/README.txt +- Addon manual pages cronjob: +- Rendered manuals: -The redirect to the latest grass7x directory is defined on grass.osgeo.org: - /etc/apache2/includes/grass.osgeo.org.inc +The redirect to the latest grassX directory is defined on grass.osgeo.org: +/etc/apache2/includes/grass.osgeo.org.inc Procedure building of binaries (Windows): -* Addons module are compiled on build server, currently at the CTU in Prague) and +- Addons module are compiled on winGRASS build server, at the CTU in Prague) and publishing their manual pages on publishing server, i.e. grass.osgeo.org. -* A new compilation is triggered every time when a commit is done in the Addons-SVN. -* Logs: - * Linux log files: (compiled on +- A new compilation is triggered every time a commit is done in the Addons repo. +- Logs: + - Linux log files: (compiled on `grasslxd` on `osgeo7`) - * Windows log files: + - Windows log files: Procedure of granting write access to Addons repo: -* Request procedure: -* Adding OSGeo-ID: -* Adding contributor: - (via SVN commit) -* Confirm request in grass-psc and give instructions concerning code style etc +- Request procedure: +- Adding OSGeo-ID: +- Adding contributor: + (via git commit) +- Confirm request in grass-psc and give instructions concerning code style etc (see archive for examples) -XML file for g.extension: +XML file for g.extension: -* generated in grass-addons/utils/addons/grass-addons-publish.sh +- generated in grass-addons/utils/addons/grass-addons-publish.sh ## GRASS Travis CI Maintainer: Martin Landa -* -* -* OLD: -* - -Travis CI control files: - trunk/.travis/ - linux.before_install.sh - linux.install.sh - linux.script.sh - -Maintenance script: - -* - -The github update is run as a cronjob on server "geo102" (CTU, CZ). +- +- ## GRASS CI: GitHub Actions @@ -294,56 +228,69 @@ Maintainer: Vaclav Petras -* -* Details: - -* CI workflow with: - * A build job which is not parallelized and is meant for clear & relatively fast +- +- Details: +- CI workflow with: + - A build job which is not parallelized and is meant for clear & relatively fast check of compilation and building in general. (Duplicating what is running on Travis) - * A test job which of course needs to build, but the main focus is to run tests, + - A test job which of course needs to build, but the main focus is to run tests, so the compilation is parallelized (depending on nproc) and thus potentially less readable. This runs the whole test suite. (You need to run it locally to see the actual error, but you can see which tests are failing.) -* Static code analysis/Code quality check using Flake8 with separate checks for +- Static code analysis/Code quality check using Flake8 with separate checks for python/grass, gui/wxpython, scripts and temporal directories. - * Configurations ignore different lists of Flake8 errors. The idea is to reduce + - Configurations ignore different lists of Flake8 errors. The idea is to reduce that to minimum. - * Code in testsuite directories is also ignored for now, but should not be in + - Code in testsuite directories is also ignored for now, but should not be in the future. -Helper files placed to .github/workflows +Helper files placed in .github/workflows/ ## GRASS Coverity Scan Maintainer: Markus Neteler -* +- + +## User message translation management (i18N) + +Messages are extracted with `gettext` message macros. + +Translations may be done using the OSGeo Weblate platform: + +- Weblate: + - GRASS GIS Weblate server: + +Anyone with OSGeo-LDAP access can work on the translations. + +For technical background and access rights of the Weblate installation, +see . -## Transifex translation management +### How Weblate works -i18N gettext messages: +When a developer makes a GRASS GIS repo commit on GitHub, GitHub then calls +the webhook on Weblate which triggers a refresh of Weblate's git copy of the +GRASS GIS repo. -* Dashboard: -* Auto-update URL to fetch files: - * - * Menu: Resources - * Use: "Auto update resources" button -* Weblate: +For pushing translations back to GitHub, there is a setting in Weblate for that +which defaults to 24 hrs (accumulates translations over a day). Then a pull +request with the translations will be opened in the GRASS GIS GitHub repo. -## OLD: GRASS Quality Control +### Weblate troubleshooting -Maintainer: Prof. Giulio Antoniol +In case the Weblate's git copy of the GRASS GIS repo does not update due to +a conflict or whatever reason: -* offline. -* - was implemented and sponsored by Ecole Polytechnique de Montreal, Canada -* Realtime analysis has been sent to: +Log into Weblate (requires administrator rights) and switch to +. Therein click on +"Manage" -> "Repository Maintenance", choose the "Update" button, +"Update with merge without fast-forward". If successful, this will create +another pull request in the GRASS GIS repo (trigger with "Push" button). -Further notification/functionality test systems: +## Related Wiki documents -* posts into #grass IRC channel -* posts into #osgeo-commits IRC channel +- (historical document) ## Previous hosting sponsors @@ -353,10 +300,10 @@ Numerous institutions have sponsored the GRASS Project with Hardware/Bandwidth (list of master site hosting): -* 1997-1999: Institut fuer Landschaftspflege und Naturschutz (ILN), Universitaet +- 1997-1999: Institut fuer Landschaftspflege und Naturschutz (ILN), Universitaet Hannover, Germany -* 1999-2001: Institut fuer Physische Geographie und Landschaftsoekologie, +- 1999-2001: Institut fuer Physische Geographie und Landschaftsoekologie, Universitaet Hannover, Germany -* 2001-2008: ITC-irst, Trento, Italy -* 2009-2010: Telascience.org at San Diego Supercomputer Center, California, USA -* 2010-today: Oregon State University | Open Source Lab, USA +- 2001-2008: ITC-irst, Trento, Italy +- 2009-2010: Telascience.org at San Diego Supercomputer Center, California, USA +- 2010-today: Oregon State University | Open Source Lab, USA diff -Nru grass-8.3.0/doc/python/raster_example_ctypes.py grass-8.3.1/doc/python/raster_example_ctypes.py --- grass-8.3.0/doc/python/raster_example_ctypes.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/doc/python/raster_example_ctypes.py 2023-10-24 19:27:44.000000000 +0000 @@ -29,7 +29,7 @@ if len(sys.argv) == 2: input = sys.argv[1] else: - input = raw_input("Name of raster map? ") + input = input("Name of raster map? ") # initialize GRASS library G_gisinit("") diff -Nru grass-8.3.0/doc/python/vector_example_ctypes.py grass-8.3.1/doc/python/vector_example_ctypes.py --- grass-8.3.0/doc/python/vector_example_ctypes.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/doc/python/vector_example_ctypes.py 2023-10-24 19:27:44.000000000 +0000 @@ -17,7 +17,7 @@ if len(sys.argv) == 2: input = sys.argv[1] else: - input = raw_input("Name of vector map? ") + input = input("Name of vector map? ") # initialize GRASS library G_gisinit("") diff -Nru grass-8.3.0/docker/alpine/Dockerfile grass-8.3.1/docker/alpine/Dockerfile --- grass-8.3.0/docker/alpine/Dockerfile 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/docker/alpine/Dockerfile 2023-10-24 19:27:44.000000000 +0000 @@ -24,6 +24,16 @@ gcc \ gdal \ gdal-dev \ + gdal-driver-GMLAS \ + gdal-driver-HDF5 \ + gdal-driver-JP2OpenJPEG \ + gdal-driver-LIBKML \ + gdal-driver-MSSQLSpatial \ + gdal-driver-netCDF \ + gdal-driver-ODBC \ + gdal-driver-PG \ + gdal-driver-PNG \ + gdal-driver-WMS \ gdal-tools \ gettext \ geos \ @@ -166,14 +176,25 @@ make install && \ ldconfig /etc/ld.so.conf.d +# Get rid of version number here, restore in next stage via symbolic link +RUN mv $(grass --config path) /usr/local/grass + # Reduce the image size - Remove unnecessary grass files -RUN cp /usr/local/grass83/gui/wxpython/xml/module_items.xml module_items.xml; \ - rm -rf /usr/local/grass83/demolocation; \ - rm -rf /usr/local/grass83/fonts; \ - rm -rf /usr/local/grass83/gui; \ - rm -rf /usr/local/grass83/share; \ - mkdir -p /usr/local/grass83/gui/wxpython/xml/; \ - mv module_items.xml /usr/local/grass83/gui/wxpython/xml/module_items.xml; +RUN cp /usr/local/grass/gui/wxpython/xml/module_items.xml module_items.xml; \ + rm -rf /usr/local/grass/demolocation; \ + rm -rf /usr/local/grass/fonts; \ + rm -rf /usr/local/grass/gui; \ + rm -rf /usr/local/grass/share; \ + mkdir -p /usr/local/grass/gui/wxpython/xml/; \ + mv module_items.xml /usr/local/grass/gui/wxpython/xml/module_items.xml; + +RUN git clone https://github.com/OSGeo/gdal-grass /src/gdal-grass +WORKDIR /src/gdal-grass +RUN ./configure \ + --with-gdal=/usr/bin/gdal-config \ + --with-grass=/usr/local/grass && \ + make -j $NUMTHREADS && \ + make install -j $NUMTHREADS FROM common as grass @@ -188,9 +209,10 @@ GRASSBIN=grass \ LC_ALL="en_US.UTF-8" -# Copy GRASS GIS from build image +# Copy GRASS GIS and GDAL GRASS driver from build image COPY --from=build /usr/local/bin/grass /usr/local/bin/grass COPY --from=build /usr/local/grass* /usr/local/grass/ +COPY --from=build /usr/lib/gdalplugins/*_GRASS.so /usr/lib/gdalplugins/ # run simple LAZ test COPY docker/testdata/simple.laz /tmp/ COPY docker/testdata/test_grass_session.py docker/alpine/grass_tests.sh /scripts/ diff -Nru grass-8.3.0/.github/workflows/docker.yml grass-8.3.1/.github/workflows/docker.yml --- grass-8.3.0/.github/workflows/docker.yml 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/.github/workflows/docker.yml 2023-10-24 19:27:44.000000000 +0000 @@ -5,16 +5,11 @@ # # Summary # -# job docker-branch-os-matrix: -# * creates tags latest-alpine, latest-debian and latest-ubuntu for main branch -# * creates tags stable-alpine, stable-debian and stable-ubuntu for releasebranch_8_2 -# * creates tags -alpine, -debian and -ubuntu for all triggered branches -# -# job docker-main-latest: -# * creates tag latest for main branch -# -# job docker-release-os-matrix: +# job docker-os-matrix: # * creates tags -alpine, -debian and -ubuntu for each release +# * creates tags -alpine, -debian and -ubuntu for all triggered branches +# * creates tags current-alpine, current-debian and current-ubuntu for releasebranch_8_3 +# * creates tag latest for last stable release with ubuntu os on: push: @@ -22,26 +17,25 @@ - main - releasebranch_* - '!releasebranch_7_*' - tags: ['*.*.*'] + # tags: ['*.*.*'] paths-ignore: [doc/**] release: types: [published] -env: - # Additionally mentioned in docker-sha-release-latest - # as use of variable fails there - DOCKERHUB_REPOSITORY: osgeo/grass-gis - jobs: - # Only run for push to configured branches, do not run for releases. - # Take care of different os. For main branch, created tags are: - # latest-alpine, latest-debian, latest-ubuntu - # For releasebranch_8_2, created tags are: - # stable-alpine, stable-debian, stable-ubuntu - docker-branch-os-matrix: - name: build and push ${{ matrix.os }} for branch - if: startsWith(github.ref, 'refs/heads/') && github.repository_owner == 'OSGeo' + # Run for push to configured branches and all published releases. + # Take care of different os. + # For main branch, created tags are: + # main-alpine, main-debian, main-ubuntu + # For releasebranch_8_3, created tags are: + # current-alpine, current-debian, current-ubuntu, + # releasebranch_8_3-alpine, releasebranch_8_3-debian, releasebranch_8_3-ubuntu + # For a release, e.g. 8.3.0, created tags are: + # 8.3.0-alpine, 8.3.0-debian, 8.3.0-ubuntu and latest (with ubuntu) + docker-os-matrix: + name: build and push ${{ matrix.os }} for ${{ github.ref }} + if: github.repository_owner == 'OSGeo' runs-on: ubuntu-latest strategy: @@ -58,125 +52,19 @@ uses: actions/checkout@v3 with: fetch-depth: 0 - - id: meta - name: Create tag name - run: | - if [ "$GITHUB_REF" == "refs/heads/main" ] - then - TAG_PREFIX=latest - elif [ "$GITHUB_REF" == "refs/heads/releasebranch_8_2" ] - then - TAG_PREFIX=stable - else - # use branch name as TAG_PREFIX - TAG_PREFIX=`echo $GITHUB_REF|cut -d '/' -f3` - fi - tag="${DOCKERHUB_REPOSITORY}:${TAG_PREFIX}-${{ matrix.os }}" - echo "tags=$tag" >> $GITHUB_OUTPUT - - name: Log - run: | - echo ${{ steps.meta.outputs.tags }} - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login to DockerHub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push - id: docker_build - uses: docker/build-push-action@v4 - with: - push: true - pull: true - context: . - tags: ${{ steps.meta.outputs.tags }} - file: docker/${{ matrix.os }}/Dockerfile - - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} - - - # Only run for push to main branch - # Take care of tag latest - # This job needs to build the configured image (ubuntu) - # again for main branch to create latest tag. - docker-main-latest: - name: build and push latest for main branch - if: github.ref == 'refs/heads/main' && github.repository_owner == 'OSGeo' - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - id: meta - name: Create tag name - run: | - tag=${DOCKERHUB_REPOSITORY}:latest - echo "tags=$tag" >> $GITHUB_OUTPUT - - name: Log - run: echo ${{ steps.meta.outputs.tags }} - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login to DockerHub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push - id: docker_build - uses: docker/build-push-action@v4 - with: - push: true - pull: true - context: . - tags: ${{ steps.meta.outputs.tags }} - file: docker/ubuntu/Dockerfile - - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} - - - # run for releases, take care of release tags - docker-release-os-matrix: - name: build and push release for ${{ matrix.os }} - if: startsWith(github.ref, 'refs/tags/') && github.repository_owner == 'OSGeo' - runs-on: ubuntu-latest - strategy: - matrix: - os: - - alpine - - debian - - ubuntu - fail-fast: false - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Create image and tag name + - name: Docker meta id: meta uses: docker/metadata-action@v4 with: - # images: ${DOCKERHUB_REPOSITORY} images: osgeo/grass-gis tags: | type=ref,event=tag + type=ref,event=branch + type=raw,value=current,enable=${{ github.ref == format('refs/heads/{0}', 'releasebranch_8_3') }} + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/8.3') && matrix.os == 'ubuntu' }},suffix= flavor: | latest=false - - id: meta2 - name: Update tag name - run: | - tag="${{ steps.meta.outputs.tags }}-${{ matrix.os }}" - echo "tags=$tag" >> $GITHUB_OUTPUT - - name: Log - run: | - echo ${{ steps.meta2.outputs.tags }} + suffix=-${{ matrix.os }} - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx @@ -193,7 +81,7 @@ push: true pull: true context: . - tags: ${{ steps.meta2.outputs.tags }} + tags: ${{ steps.meta.outputs.tags }} file: docker/${{ matrix.os }}/Dockerfile - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} diff -Nru grass-8.3.0/.github/workflows/pylint.yml grass-8.3.1/.github/workflows/pylint.yml --- grass-8.3.0/.github/workflows/pylint.yml 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/.github/workflows/pylint.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,119 +0,0 @@ ---- -name: Python Pylint Code Quality - -on: - push: - branches: - - main - - releasebranch_* - pull_request: - branches: - - main - - releasebranch_* - -jobs: - pylint: - name: Pylint ${{ matrix.pylint-version }} - - concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}-${{ - matrix.pylint-version }} - cancel-in-progress: true - - # Using matrix just to get variables which are not environmental variables - # and also to sync with other workflows which use matrix. - strategy: - matrix: - include: - - os: ubuntu-22.04 - python-version: '3.10' - min-python-version: '3.7' - pylint-version: 2.12.2 - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v3 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Install non-Python dependencies - run: | - sudo apt-get update -y - sudo apt-get install -y wget git gawk findutils - xargs -a <(awk '! /^ *(#|$)/' ".github/workflows/apt.txt") -r -- \ - sudo apt-get install -y --no-install-recommends --no-install-suggests - - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install -r .github/workflows/python_requirements.txt - pip install -r .github/workflows/optional_requirements.txt - pip install pylint==${{ matrix.pylint-version }} - - - name: Create installation directory - run: | - mkdir $HOME/install - - - name: Set number of cores for compilation - run: | - echo "MAKEFLAGS=-j$(nproc)" >> $GITHUB_ENV - - - name: Build - run: .github/workflows/build_${{ matrix.os }}.sh $HOME/install - - - name: Add the bin directory to PATH - run: | - echo "$HOME/install/bin" >> $GITHUB_PATH - - - name: Test executing of the grass command - run: .github/workflows/test_simple.sh - - - name: Run Pylint on grass package - run: | - export PYTHONPATH=`grass --config python_path`:$PYTHONPATH - export LD_LIBRARY_PATH=$HOME/install/grass83/lib:$LD_LIBRARY_PATH - cd python - pylint --persistent=no --py-version=${{ matrix.min-python-version }} --jobs=$(nproc) grass - - - name: Run Pylint on wxGUI - run: | - export PYTHONPATH=`grass --config python_path`:$PYTHONPATH - export LD_LIBRARY_PATH=$HOME/install/grass83/lib:$LD_LIBRARY_PATH - cd gui/wxpython - pylint --persistent=no --py-version=${{ matrix.min-python-version }} --jobs=$(nproc) * - - - name: Run Pylint on other files using pytest - run: | - pip install pytest pytest-pylint - export PYTHONPATH=`grass --config python_path`:$PYTHONPATH - export LD_LIBRARY_PATH=$HOME/install/grass83/lib:$LD_LIBRARY_PATH - pytest --pylint -m pylint --pylint-rcfile=.pylintrc --pylint-jobs=$(nproc) \ - --pylint-ignore-patterns="${{ env.PylintIgnore }}" - env: - PylintIgnore: "python/.*,gui/wxpython/.*,doc/.*,man/.*,utils/.*,locale/.*,raster/.*,\ - imagery/.*,scripts/r.in.wms/wms_drv.py,scripts/g.extension/g.extension.py,\ - temporal/t.rast.accdetect/t.rast.accdetect.py,temporal/t.rast.accumulate/t.rast.accumulate.py,\ - scripts/d.rast.edit/d.rast.edit.py" - - - name: Test compiling example modules - run: | - ( cd doc/raster/r.example/ && make ) - ( cd doc/vector/v.example/ && make ) - - - name: Run Sphinx to check API documentation build - run: | - pip install sphinx - make sphinxdoclib - ARCH=$(cat include/Make/Platform.make | grep ^ARCH | cut -d'=' -f2 | xargs) - cp -rp dist.$ARCH/docs/html/libpython sphinx-grass - - - name: Make Sphinx documentation available - uses: actions/upload-artifact@v3 - with: - name: sphinx-grass - path: sphinx-grass - retention-days: 3 diff -Nru grass-8.3.0/gui/wxpython/animation/controller.py grass-8.3.1/gui/wxpython/animation/controller.py --- grass-8.3.0/gui/wxpython/animation/controller.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/animation/controller.py 2023-10-24 19:27:44.000000000 +0000 @@ -582,8 +582,8 @@ # paste decorations for decoration in decorations: # add image - x = decoration["pos"][0] / 100.0 * size[0] - y = decoration["pos"][1] / 100.0 * size[1] + x = int(decoration["pos"][0] / 100.0 * size[0]) + y = int(decoration["pos"][1] / 100.0 * size[1]) if decoration["name"] == "image": decImage = wx.Image(decoration["file"]) elif decoration["name"] == "time": diff -Nru grass-8.3.0/gui/wxpython/dbmgr/base.py grass-8.3.1/gui/wxpython/dbmgr/base.py --- grass-8.3.0/gui/wxpython/dbmgr/base.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/dbmgr/base.py 2023-10-24 19:27:44.000000000 +0000 @@ -481,7 +481,6 @@ self.popupId = { "sortAsc": NewId(), "sortDesc": NewId(), - "calculate": NewId(), "area": NewId(), "length": NewId(), "compact": NewId(), @@ -499,8 +498,9 @@ popupMenu.Append(self.popupId["sortDesc"], _("Sort descending")) popupMenu.AppendSeparator() subMenu = Menu() - popupMenu.AppendMenu( - self.popupId["calculate"], _("Calculate (only numeric columns)"), subMenu + subMenuItem = popupMenu.AppendSubMenu( + subMenu, + _("Calculate (only numeric columns)"), ) popupMenu.Append(self.popupId["calculator"], _("Field calculator")) popupMenu.AppendSeparator() @@ -519,7 +519,7 @@ if not self.dbMgrData["editable"] or self.columns[ self.GetColumn(self._col).GetText() ]["ctype"] not in (int, float): - popupMenu.Enable(self.popupId["calculate"], False) + subMenuItem.Enable(False) subMenu.Append(self.popupId["area"], _("Area size")) subMenu.Append(self.popupId["length"], _("Line length")) @@ -560,18 +560,19 @@ def OnColumnSort(self, event): """Column heading left mouse button -> sorting""" self._col = event.GetColumn() - + self._updateColSortFlag() self.ColumnSort() - event.Skip() def OnColumnSortAsc(self, event): """Sort values of selected column (ascending)""" + self._updateColSortFlag() self.SortListItems(col=self._col, ascending=True) event.Skip() def OnColumnSortDesc(self, event): """Sort values of selected column (descending)""" + self._updateColSortFlag() self.SortListItems(col=self._col, ascending=False) event.Skip() @@ -718,6 +719,14 @@ return True + def _updateColSortFlag(self): + """ + Update listmix.ColumnSorterMixin class self._colSortFlag list + private variable for new column which was added (required for + sorting new added column values) + """ + self._colSortFlag.extend([0] * (len(self.columns) - len(self._colSortFlag))) + class DbMgrBase: def __init__( @@ -1024,7 +1033,7 @@ del self.layerPage[layer] if self.GetSelection() >= 0: - self.selLayer = self.layers[self.GetSelection()] + self.selLayer = self.layers[-1] else: self.selLayer = None @@ -1356,6 +1365,9 @@ def OnSqlQuerySize(self, event, layer): """Adapts SQL Query Simple tab on current width""" + if layer not in self.layers: + return + sqlNtb = event.GetEventObject() if not self.sqlBestSize: self.sqlBestSize = sqlNtb.GetBestSize() @@ -2634,6 +2646,7 @@ ) self.FindWindowById(self.layerPage[self.selLayer]["renameCol"]).SetSelection(0) self.FindWindowById(self.layerPage[self.selLayer]["renameColTo"]).SetValue("") + self._updateTableColumnWidgetChoices(table=table) event.Skip() @@ -2721,6 +2734,7 @@ self.dbMgrData["mapDBInfo"].GetColumns(table) ) self.FindWindowById(self.layerPage[self.selLayer]["renameCol"]).SetSelection(0) + self._updateTableColumnWidgetChoices(table=table) event.Skip() @@ -2768,6 +2782,7 @@ self.dbMgrData["mapDBInfo"].GetColumns(table) ) self.FindWindowById(self.layerPage[self.selLayer]["renameCol"]).SetSelection(0) + self._updateTableColumnWidgetChoices(table=table) event.Skip() @@ -2794,14 +2809,6 @@ ).GetValue() ) - # add item to the list of table columns - tlist = self.FindWindowById(self.layerPage[self.selLayer]["tableData"]) - - index = tlist.InsertItem(tlist.GetItemCount(), str(name)) - tlist.SetItem(index, 0, str(name)) - tlist.SetItem(index, 1, str(ctype)) - tlist.SetItem(index, 2, str(length)) - self.AddColumn(name, ctype, length) # update widgets @@ -2811,7 +2818,7 @@ self.dbMgrData["mapDBInfo"].GetColumns(table) ) self.FindWindowById(self.layerPage[self.selLayer]["renameCol"]).SetSelection(0) - + self._updateTableColumnWidgetChoices(table=table) event.Skip() def UpdatePage(self, layer): @@ -2826,6 +2833,26 @@ ) self.OnTableReload(None) + def _updateTableColumnWidgetChoices(self, table): + """Update table column widget choices + + :param str table: table name + """ + cols = self.dbMgrData["mapDBInfo"].GetColumns(table) + # Browse data page SQL Query Simple page WHERE Combobox column names widget + self.FindWindowById( + self.pages["browse"].layerPage[self.selLayer]["whereColumn"] + ).SetItems(cols) + # Browse data page SQL Query Builder page SQL builder frame ListBox column names widget + if self.pages["browse"].builder: + self.pages["browse"].builder.list_columns.Set(cols) + # Browse data page column Field calculator frame ListBox column names widget + fieldCalc = self.FindWindowById( + self.pages["browse"].layerPage[self.selLayer]["data"], + ).fieldCalc + if fieldCalc: + fieldCalc.list_columns.Set(cols) + class DbMgrLayersPage(wx.Panel): def __init__(self, parent, parentDbMgrBase): @@ -3069,6 +3096,7 @@ self.parent = parent self.parentDialog = parentDialog self.mapDBInfo = self.parentDialog.dbMgrData["mapDBInfo"] + vectName = self.parentDialog.dbMgrData["vectName"] # # drivers @@ -3083,7 +3111,24 @@ # get default values # self.defaultConnect = {} - connect = RunCommand("db.connect", flags="p", read=True, quiet=True) + genv = grass.gisenv() + vectMap = grass.find_file( + name=vectName, + element="vector", + ) + vectGisrc, vectEnv = grass.create_environment( + gisdbase=genv["GISDBASE"], + location=genv["LOCATION_NAME"], + mapset=vectMap["mapset"], + ) + connect = RunCommand( + "db.connect", + flags="p", + env=vectEnv, + read=True, + quiet=True, + ) + grass.utils.try_remove(vectGisrc) for line in connect.splitlines(): item, value = line.split(":", 1) @@ -3846,7 +3891,7 @@ if ( self.modifyLayerWidgets["driver"][1].GetStringSelection() != self.mapDBInfo.layers[layer]["driver"] - or self.modifyLayerWidgets["database"][1].GetStringSelection() + or self.modifyLayerWidgets["database"][1].GetValue() != self.mapDBInfo.layers[layer]["database"] or self.modifyLayerWidgets["table"][1].GetStringSelection() != self.mapDBInfo.layers[layer]["table"] diff -Nru grass-8.3.0/gui/wxpython/gmodeler/frame.py grass-8.3.1/gui/wxpython/gmodeler/frame.py --- grass-8.3.0/gui/wxpython/gmodeler/frame.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/gmodeler/frame.py 2023-10-24 19:27:44.000000000 +0000 @@ -354,7 +354,7 @@ message = _("Do you want to save changes in the model?") else: message = _( - "Do you want to store current model settings " "to model file?" + "Do you want to store current model settings to model file?" ) # ask user to save current settings @@ -371,7 +371,7 @@ ret = dlg.ShowModal() if ret == wx.ID_YES: if not self.modelFile: - self.OnWorkspaceSaveAs() + self.OnModelSaveAs() else: self.WriteModelFile(self.modelFile) elif ret == wx.ID_CANCEL: @@ -560,9 +560,9 @@ self.SetStatusText(_("File <%s> saved") % self.modelFile, 0) self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile)) elif not self.modelFile: - self.OnModelSaveAs(None) + self.OnModelSaveAs() - def OnModelSaveAs(self, event): + def OnModelSaveAs(self, event=None): """Create model to file as""" filename = "" dlg = wx.FileDialog( diff -Nru grass-8.3.0/gui/wxpython/gui_core/mapdisp.py grass-8.3.1/gui/wxpython/gui_core/mapdisp.py --- grass-8.3.0/gui/wxpython/gui_core/mapdisp.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/gui_core/mapdisp.py 2023-10-24 19:27:44.000000000 +0000 @@ -833,7 +833,7 @@ return self.GetParent().IsFullScreen() def IsIconized(self): - self.GetParent().IsIconized() + return self.GetParent().IsIconized() def Maximize(self): self.GetParent().Maximize() @@ -869,3 +869,6 @@ def SetSize(self, *args): self.GetParent().SetSize(*args) + + def Close(self): + self.GetParent().Close() diff -Nru grass-8.3.0/gui/wxpython/gui_core/preferences.py grass-8.3.1/gui/wxpython/gui_core/preferences.py --- grass-8.3.0/gui/wxpython/gui_core/preferences.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/gui_core/preferences.py 2023-10-24 19:27:44.000000000 +0000 @@ -1850,10 +1850,9 @@ for mapdisp in self._giface.GetAllMapDisplays(): pos = mapdisp.GetPosition() size = mapdisp.GetSize() - # window size must be larger than zero, not minimized # do not save dim when mapdisp is docked within single window - if (not mapdisp.IsDockable() or not mapdisp.IsDocked()) and ( + if (hasattr(mapdisp, "IsIconized") and not mapdisp.IsIconized()) and ( size[0] > 0 and size[1] > 0 ): dim += f",{pos[0]},{pos[1]},{size[0]},{size[1]}" @@ -2035,7 +2034,6 @@ size = self.settings.Get(group="appearance", key="outputfont", subkey="size") if size is None or size == 0: size = 11 - size = float(size) if type is None or type == "": type = "Courier" diff -Nru grass-8.3.0/gui/wxpython/gui_core/widgets.py grass-8.3.1/gui/wxpython/gui_core/widgets.py --- grass-8.3.0/gui/wxpython/gui_core/widgets.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/gui_core/widgets.py 2023-10-24 19:27:44.000000000 +0000 @@ -133,7 +133,17 @@ self.widget.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnRemoveHighlight) def AddPage(self, *args, **kwargs): - """Add a new page""" + """Add a new page + + :param str name: use this param if notebooks has ability to + change position and then you must use page name + param arg to correctly delete notebook page. + If you do not use this parameter, make sure that + the notebooks does not have the ability to change + position, because in that case the deletion of + the page based on the position index would not + work correctly. + """ if "name" in kwargs: self.notebookPages[kwargs["name"]] = kwargs["page"] del kwargs["name"] @@ -141,7 +151,17 @@ self.classObject.AddPage(self.widget, *args, **kwargs) def InsertPage(self, *args, **kwargs): - """Insert a new page""" + """Insert a new page + + :param str name: use this param if notebooks has ability to + change position and then you must use page name + param arg to correctly delete notebook page. + If you do not use this parameter, make sure that + the notebooks does not have the ability to change + position, because in that case the deletion of + the page based on the position index would not + work correctly. + """ if "name" in kwargs: self.notebookPages[kwargs["name"]] = kwargs["page"] del kwargs["name"] @@ -158,8 +178,9 @@ def DeletePage(self, page): """Delete page - :param page: name - :return: True if page was deleted, False if not exists + :param str|int page: page name or page index position + + :return bool: True if page was deleted, False if not exists """ delPageIndex = self.GetPageIndexByName(page) if delPageIndex != -1: @@ -216,8 +237,12 @@ def GetPageIndexByName(self, page): """Get notebook page index - :param page: name + :param str|int page: page name or page index position + + :return int: page index """ + if not self.notebookPages: + return page if page not in self.notebookPages: return -1 for pageIndex in range(self.classObject.GetPageCount(self.widget)): @@ -260,8 +285,12 @@ def GetPageIndexByName(self, page): """Get notebook page index - :param page: name + :param str|int page: page name or page index position + + :return int: page index """ + if not self.notebookPages: + return page if page not in self.notebookPages: return -1 diff -Nru grass-8.3.0/gui/wxpython/image2target/ii2t_manager.py grass-8.3.1/gui/wxpython/image2target/ii2t_manager.py --- grass-8.3.0/gui/wxpython/image2target/ii2t_manager.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/image2target/ii2t_manager.py 2023-10-24 19:27:44.000000000 +0000 @@ -3119,13 +3119,13 @@ parent=panel, id=wx.ID_ANY, label=_("Select source map to display:") ), proportion=0, - flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, + flag=wx.ALIGN_LEFT | wx.ALL, border=5, ) sizer.Add( self.srcselection, proportion=0, - flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, + flag=wx.ALIGN_LEFT | wx.ALL, border=5, ) self.srcselection.SetValue(src_map) @@ -3136,13 +3136,13 @@ label=_("Select target raster map to display:"), ), proportion=0, - flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, + flag=wx.ALIGN_LEFT | wx.ALL, border=5, ) sizer.Add( self.tgtrastselection, proportion=0, - flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, + flag=wx.ALIGN_LEFT | wx.ALL, border=5, ) self.tgtrastselection.SetValue(tgt_map["raster"]) @@ -3153,13 +3153,13 @@ label=_("Select target vector map to display:"), ), proportion=0, - flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, + flag=wx.ALIGN_LEFT | wx.ALL, border=5, ) sizer.Add( self.tgtvectselection, proportion=0, - flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, + flag=wx.ALIGN_LEFT | wx.ALL, border=5, ) self.tgtvectselection.SetValue(tgt_map["vector"]) @@ -3225,7 +3225,7 @@ parent=panel, id=wx.ID_ANY, label=_("Extension for output maps:") ), proportion=0, - flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, + flag=wx.ALIGN_LEFT | wx.ALL, border=5, ) self.ext_txt = TextCtrl(parent=panel, id=wx.ID_ANY, value="", size=(350, -1)) @@ -3233,7 +3233,7 @@ sizer.Add( self.ext_txt, proportion=0, - flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, + flag=wx.ALIGN_LEFT | wx.ALL, border=5, ) diff -Nru grass-8.3.0/gui/wxpython/iscatt/frame.py grass-8.3.1/gui/wxpython/iscatt/frame.py --- grass-8.3.0/gui/wxpython/iscatt/frame.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/iscatt/frame.py 2023-10-24 19:27:44.000000000 +0000 @@ -278,6 +278,7 @@ del self.scatts[scatt_id] if pane.IsOk(): + pane.DestroyOnClose() self._mgr.ClosePane(pane) self._mgr.Update() diff -Nru grass-8.3.0/gui/wxpython/iscatt/plots.py grass-8.3.1/gui/wxpython/iscatt/plots.py --- grass-8.3.0/gui/wxpython/iscatt/plots.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/iscatt/plots.py 2023-10-24 19:27:44.000000000 +0000 @@ -315,7 +315,6 @@ def CleanUp(self): self.plotClosed.emit(scatt_id=self.scatt_id) - self.Destroy() def ZoomWheel(self, event): # get the current x and y limits @@ -619,7 +618,8 @@ def ShowMenu(self, menu): self.plot.PopupMenu(menu) menu.Destroy() - self.plot.ReleaseMouse() + if self.plot.HasCapture(): + self.plot.ReleaseMouse() class PolygonDrawer: @@ -1020,8 +1020,7 @@ @author: Chris Beaumont """ - if not axes._hold: - axes.cla() + axes.cla() if norm is not None: assert isinstance(norm, mcolors.Normalize) if aspect is None: @@ -1070,7 +1069,7 @@ # to tightly fit the image, regardless of dataLim. im.set_extent(im.get_extent()) - axes.images.append(im) + axes.add_image(im) im._remove_method = lambda h: axes.images.remove(h) return im diff -Nru grass-8.3.0/gui/wxpython/iscatt/toolbars.py grass-8.3.1/gui/wxpython/iscatt/toolbars.py --- grass-8.3.0/gui/wxpython/iscatt/toolbars.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/iscatt/toolbars.py 2023-10-24 19:27:44.000000000 +0000 @@ -21,6 +21,20 @@ from iscatt.dialogs import SettingsDialog +def get_tool_name(tool_name, tool_name_type=tuple): + """Get tool name + + :param str|tuple tool_name: tool name + :param type tool_name_type: tool name type with default + tuple type + + :return str: tool name + """ + if isinstance(tool_name, tool_name_type): + return tool_name[0] + return tool_name + + class MainToolbar(BaseToolbar): """Main toolbar""" @@ -133,7 +147,7 @@ self.scatt_mgr.modeSet.disconnect(self.ModeSet) if event.IsChecked(): for i_tool_data in self.controller.data: - i_tool_name = i_tool_data[0] + i_tool_name = get_tool_name(i_tool_data[0]) if not i_tool_name or i_tool_name in ["cats_mgr", "sel_pol_mode"]: continue if i_tool_name == tool_name: @@ -158,7 +172,7 @@ def UnsetMode(self): for i_tool_data in self.controller.data: - i_tool_name = i_tool_data[0] + i_tool_name = get_tool_name(i_tool_data[0]) if not i_tool_name or i_tool_name in ["cats_mgr", "sel_pol_mode"]: continue i_tool_id = vars(self)[i_tool_name] @@ -280,7 +294,7 @@ self.scatt_mgr.modeSet.disconnect(self.ModeSet) if event.IsChecked(): for i_tool_data in self.controller.data: - i_tool_name = i_tool_data[0] + i_tool_name = get_tool_name(i_tool_data[0]) if not i_tool_name: continue if i_tool_name == tool_name: @@ -298,7 +312,7 @@ def UnsetMode(self): for i_tool_data in self.controller.data: - i_tool_name = i_tool_data[0] + i_tool_name = get_tool_name(i_tool_data[0]) if not i_tool_name: continue i_tool_id = vars(self)[i_tool_name] diff -Nru grass-8.3.0/gui/wxpython/lmgr/frame.py grass-8.3.1/gui/wxpython/lmgr/frame.py --- grass-8.3.0/gui/wxpython/lmgr/frame.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/lmgr/frame.py 2023-10-24 19:27:44.000000000 +0000 @@ -65,10 +65,7 @@ from lmgr.statusbar import SbMain from lmgr.workspace import WorkspaceManager from lmgr.pyshell import PyShellWindow -from lmgr.giface import ( - LayerManagerGrassInterface, - LayerManagerGrassInterfaceForMapDisplay, -) +from lmgr.giface import LayerManagerGrassInterface from mapdisp.frame import MapDisplay from datacatalog.catalog import DataCatalog from gui_core.forms import GUI @@ -466,10 +463,10 @@ self.notebookLayers.AddPage(page=self.pg_panel, text=name, select=True) self.currentPage = self.notebookLayers.GetCurrentPage() - def CreateNewMapDisplay(layertree): + def CreateNewMapDisplay(giface, layertree): """Callback function which creates a new Map Display window + :param giface: giface for map display :param layertree: layer tree object - :param name: name of new map display window :return: reference to mapdisplay instance """ # count map display frame position @@ -485,14 +482,10 @@ title=name, ) - # create instance of Map Display interface - self._gifaceForDisplay = LayerManagerGrassInterfaceForMapDisplay( - self._giface, layertree - ) # create Map Display mapdisplay = MapDisplay( parent=mapframe, - giface=self._gifaceForDisplay, + giface=giface, id=wx.ID_ANY, size=globalvar.MAP_WINDOW_SIZE, tree=layertree, diff -Nru grass-8.3.0/gui/wxpython/lmgr/layertree.py grass-8.3.1/gui/wxpython/lmgr/layertree.py --- grass-8.3.0/gui/wxpython/lmgr/layertree.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/lmgr/layertree.py 2023-10-24 19:27:44.000000000 +0000 @@ -45,6 +45,7 @@ from icons.icon import MetaIcon from gui_core.widgets import MapValidator from gui_core.wrap import Menu, GenBitmapButton, TextCtrl, NewId +from lmgr.giface import LayerManagerGrassInterfaceForMapDisplay TREE_ITEM_HEIGHT = 25 @@ -152,7 +153,9 @@ self._setGradient() # init associated map display - self.mapdisplay = createNewMapDisplay(layertree=self) + # create instance of Map Display interface + self._gifaceForDisplay = LayerManagerGrassInterfaceForMapDisplay(giface, self) + self.mapdisplay = createNewMapDisplay(self._gifaceForDisplay, layertree=self) self.root = self.AddRoot(_("Map Layers")) self.SetPyData(self.root, (None, None)) diff -Nru grass-8.3.0/gui/wxpython/main_window/frame.py grass-8.3.1/gui/wxpython/main_window/frame.py --- grass-8.3.0/gui/wxpython/main_window/frame.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/main_window/frame.py 2023-10-24 19:27:44.000000000 +0000 @@ -6,6 +6,7 @@ Classes: - frame::GMFrame + - frame::SingleWindowAuiManager (C) 2006-2021 by the GRASS Development Team @@ -70,10 +71,7 @@ from lmgr.statusbar import SbMain from lmgr.workspace import WorkspaceManager from lmgr.pyshell import PyShellWindow -from lmgr.giface import ( - LayerManagerGrassInterface, - LayerManagerGrassInterfaceForMapDisplay, -) +from lmgr.giface import LayerManagerGrassInterface from mapdisp.frame import MapPanel from datacatalog.catalog import DataCatalog from gui_core.forms import GUI @@ -87,6 +85,18 @@ from grass.grassdb.checks import is_first_time_user +class SingleWindowAuiManager(aui.AuiManager): + """Custom AuiManager class which override OnClose window + close event handler method to prevent prematurely uninitialize + manager + + https://github.com/wxWidgets/Phoenix/pull/2460 + """ + + def OnClose(self, event): + event.Skip() + + class GMFrame(wx.Frame): """Single Window Layout which will be parallelly developed next to the current Multi Window layout solution.""" @@ -147,7 +157,7 @@ self._menuTreeBuilder = LayerManagerMenuData(message_handler=add_menu_error) # the search tree and command console self._moduleTreeBuilder = LayerManagerModuleTree(message_handler=add_menu_error) - self._auimgr = aui.AuiManager(self) + self._auimgr = SingleWindowAuiManager(self) # list of open dialogs self.dialogs = dict() @@ -417,20 +427,17 @@ self.currentPageNum = self.notebookLayers.GetSelection() self.notebookLayers.EnsureVisible(self.currentPageNum) - def CreateNewMapDisplay(layertree): + def CreateNewMapDisplay(giface, layertree): """Callback function which creates a new Map Display window + :param giface: giface for map display :param layertree: layer tree object :return: reference to mapdisplay instance """ - # create instance of Map Display interface - self._gifaceForDisplay = LayerManagerGrassInterfaceForMapDisplay( - self._giface, layertree - ) # create Map Display mapdisplay = MapPanel( parent=self.mapnotebook, - giface=self._gifaceForDisplay, + giface=giface, id=wx.ID_ANY, tree=layertree, lmgr=self, diff -Nru grass-8.3.0/gui/wxpython/mapwin/buffered.py grass-8.3.1/gui/wxpython/mapwin/buffered.py --- grass-8.3.0/gui/wxpython/mapwin/buffered.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/mapwin/buffered.py 2023-10-24 19:27:44.000000000 +0000 @@ -513,9 +513,11 @@ pdc.SetTextBackground(img["background"]) coords, bbox = self.TextBounds(img) if rotation == 0: - pdc.DrawText(img["text"], coords[0], coords[1]) + pdc.DrawText(img["text"], int(coords[0]), int(coords[1])) else: - pdc.DrawRotatedText(img["text"], coords[0], coords[1], rotation) + pdc.DrawRotatedText( + img["text"], int(coords[0]), int(coords[1]), rotation + ) pdc.SetIdBounds(drawid, bbox) pdc.EndDrawing() diff -Nru grass-8.3.0/gui/wxpython/nviz/tools.py grass-8.3.1/gui/wxpython/nviz/tools.py --- grass-8.3.0/gui/wxpython/nviz/tools.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/nviz/tools.py 2023-10-24 19:27:44.000000000 +0000 @@ -144,10 +144,6 @@ self.Update() wx.CallAfter(self.SetPage, "view") - wx.CallAfter( - self.UpdateScrolling, - (self.foldpanelData, self.foldpanelAppear, self.foldpanelAnalysis), - ) wx.CallAfter(self.SetInitialMaps) def SetInitialMaps(self): @@ -206,6 +202,10 @@ def OnPageChanged(self, event): new = event.GetSelection() # self.ChangeSelection(new) + # Data, Appearance, Analysis page + if new in (1, 2, 3): + foldpanel = self.GetPage(new).GetChildren()[0] + wx.CallLater(100, self.UpdateScrolling, (foldpanel,)) def PostViewEvent(self, zExag=False): """Change view settings""" @@ -243,7 +243,7 @@ def _createViewPage(self): """Create view settings page""" panel = SP.ScrolledPanel(parent=self, id=wx.ID_ANY) - panel.SetupScrolling(scroll_x=False) + panel.SetupScrolling() self.page["view"] = {"id": 0, "notebook": self.GetId()} pageSizer = wx.BoxSizer(wx.VERTICAL) @@ -478,7 +478,7 @@ def _createAnimationPage(self): """Create view settings page""" panel = SP.ScrolledPanel(parent=self, id=wx.ID_ANY) - panel.SetupScrolling(scroll_x=False) + panel.SetupScrolling() self.page["animation"] = {"id": 0, "notebook": self.GetId()} pageSizer = wx.BoxSizer(wx.VERTICAL) @@ -662,7 +662,6 @@ def _createDataPage(self): """Create data (surface, vector, volume) settings page""" - self.mainPanelData = ScrolledPanel(parent=self) self.mainPanelData.SetupScrolling(scroll_x=False) try: # wxpython <= 2.8.10 @@ -820,7 +819,7 @@ def _createSurfacePage(self, parent): """Create view settings page""" - panel = wx.Panel(parent=parent, id=wx.ID_ANY) + panel = ScrolledPanel(parent=parent) self.page["surface"] = {"id": 0, "notebook": self.foldpanelData.GetId()} pageSizer = wx.BoxSizer(wx.VERTICAL) @@ -1135,12 +1134,13 @@ panel.Layout() panel.Fit() + panel.SetupScrolling(scroll_y=False) return panel def _createCPlanePage(self, parent): """Create cutting planes page""" - panel = wx.Panel(parent=parent, id=wx.ID_ANY) + panel = ScrolledPanel(parent=parent) self.page["cplane"] = {"id": 4, "notebook": self.foldpanelData.GetId()} self.win["cplane"] = {} @@ -1379,12 +1379,13 @@ panel.SetSizer(pageSizer) panel.Fit() + panel.SetupScrolling(scroll_y=False) return panel def _createConstantPage(self, parent): """Create constant page""" - panel = wx.Panel(parent=parent, id=wx.ID_ANY) + panel = ScrolledPanel(parent=parent) self.page["constant"] = {"id": 1, "notebook": self.foldpanelData.GetId()} self.win["constant"] = {} @@ -1471,12 +1472,13 @@ panel.SetSizer(pageSizer) panel.Fit() + panel.SetupScrolling(scroll_y=False) return panel def _createVectorPage(self, parent): """Create view settings page""" - panel = wx.Panel(parent=parent, id=wx.ID_ANY) + panel = ScrolledPanel(parent=parent) self.page["vector"] = {"id": 2, "notebook": self.foldpanelData.GetId()} pageSizer = wx.BoxSizer(wx.VERTICAL) @@ -1903,6 +1905,7 @@ panel.SetSizer(pageSizer) panel.Fit() + panel.SetupScrolling(scroll_y=False) return panel @@ -1915,7 +1918,7 @@ def _createVolumePage(self, parent): """Create view settings page""" - panel = wx.Panel(parent=parent, id=wx.ID_ANY) + panel = ScrolledPanel(parent=parent) self.page["volume"] = {"id": 3, "notebook": self.foldpanelData.GetId()} pageSizer = wx.BoxSizer(wx.VERTICAL) @@ -2117,12 +2120,13 @@ ) panel.SetSizer(pageSizer) panel.Fit() + panel.SetupScrolling(scroll_y=False) return panel def _createLightPage(self, parent): """Create light page""" - panel = wx.Panel(parent=parent, id=wx.ID_ANY) + panel = ScrolledPanel(parent=parent) self.page["light"] = {"id": 0, "notebook": self.foldpanelAppear.GetId()} self.win["light"] = {} @@ -2298,12 +2302,13 @@ panel.SetSizer(pageSizer) panel.Layout() panel.Fit() + panel.SetupScrolling(scroll_y=False) return panel def _createFringePage(self, parent): """Create fringe page""" - panel = wx.Panel(parent=parent, id=wx.ID_ANY) + panel = ScrolledPanel(parent=parent) self.page["fringe"] = {"id": 1, "notebook": self.foldpanelAppear.GetId()} self.win["fringe"] = {} @@ -2394,12 +2399,13 @@ panel.SetSizer(pageSizer) panel.Layout() panel.Fit() + panel.SetupScrolling(scroll_y=False) return panel def _createDecorationPage(self, parent): """Create decoration (north arrow, scalebar, legend) page""" - panel = wx.Panel(parent=parent, id=wx.ID_ANY) + panel = ScrolledPanel(parent=parent) self.page["decoration"] = {"id": 2, "notebook": self.foldpanelAppear.GetId()} self.win["decoration"] = {} @@ -2528,6 +2534,7 @@ panel.SetSizer(pageSizer) panel.Layout() panel.Fit() + panel.SetupScrolling(scroll_y=False) return panel diff -Nru grass-8.3.0/gui/wxpython/psmap/dialogs.py grass-8.3.1/gui/wxpython/psmap/dialogs.py --- grass-8.3.0/gui/wxpython/psmap/dialogs.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/psmap/dialogs.py 2023-10-24 19:27:44.000000000 +0000 @@ -182,13 +182,13 @@ dc.DrawText( self.GetString(item), r.x + 3, - (r.y + 0) + ((r.height / 2) - dc.GetCharHeight()) / 2, + int((r.y + 0) + ((r.height / 2) - dc.GetCharHeight()) / 2), ) dc.DrawLine( r.x + 5, - r.y + ((r.height / 4) * 3) + 1, + int(r.y + ((r.height / 4) * 3) + 1), r.x + r.width - 5, - r.y + ((r.height / 4) * 3) + 1, + int(r.y + ((r.height / 4) * 3) + 1), ) def OnDrawBackground(self, dc, rect, item, flags): @@ -1861,11 +1861,13 @@ def OnVector(self, event): """Gets info about toplogy and enables/disables choices point/line/area""" vmap = self.select.GetValue() - try: - topoInfo = grass.vector_info_topo(map=vmap) - except grass.ScriptError: + if not grass.find_file( + vmap, + element="vector", + )["name"]: return + topoInfo = grass.vector_info_topo(map=vmap) if topoInfo: self.vectorType.EnableItem(2, bool(topoInfo["areas"])) self.vectorType.EnableItem( @@ -1963,16 +1965,14 @@ id = self.vectorList[pos][2] dlg = VPropertiesDialog( - self, + self.parent, id=id, - settings=self.instruction, - env=self.env, vectors=self.vectorList, tmpSettings=self.tmpDialogDict[id], ) - dlg.ShowModal() - - self.parent.FindWindowById(wx.ID_OK).SetFocus() + if dlg.ShowModal() == wx.ID_OK: + dlg.update() + dlg.Destroy() def enableButtons(self, enable=True): """Enable/disable up, down, properties, delete buttons""" @@ -2124,20 +2124,16 @@ pass -class VPropertiesDialog(PsmapDialog): - def __init__(self, parent, id, settings, vectors, tmpSettings, env): - PsmapDialog.__init__( +class VPropertiesDialog(Dialog): + def __init__(self, parent, id, vectors, tmpSettings): + Dialog.__init__( self, parent=parent, - id=id, - title="", - settings=settings, - env=env, - apply=False, ) vectorList = vectors self.vPropertiesDict = tmpSettings + self.spinCtrlSize = (65, -1) # determine map and its type for item in vectorList: @@ -2195,6 +2191,26 @@ self._layout(notebook) + def _layout(self, panel): + # buttons + btnCancel = Button(self, wx.ID_CANCEL) + btnOK = Button(self, wx.ID_OK) + btnOK.SetDefault() + + # sizers + btnSizer = wx.StdDialogButtonSizer() + btnSizer.AddButton(btnCancel) + btnSizer.AddButton(btnOK) + btnSizer.Realize() + + mainSizer = wx.BoxSizer(wx.VERTICAL) + mainSizer.Add(panel, proportion=1, flag=wx.EXPAND | wx.ALL, border=5) + mainSizer.Add(btnSizer, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) + + self.SetSizer(mainSizer) + mainSizer.Layout() + mainSizer.Fit(self) + def _DataSelectionPanel(self, notebook): panel = Panel( parent=notebook, id=wx.ID_ANY, size=(-1, -1), style=wx.TAB_TRAVERSAL @@ -2924,9 +2940,7 @@ styleText = StaticText(panel, id=wx.ID_ANY, label=_("Choose line style:")) penStyles = ["solid", "dashed", "dotted", "dashdotted"] - self.styleCombo = PenStyleComboBox( - panel, choices=penStyles, validator=TCValidator(flag="ZERO_AND_ONE_ONLY") - ) + self.styleCombo = PenStyleComboBox(panel, choices=penStyles) # self.styleCombo = wx.ComboBox(panel, id = wx.ID_ANY, ## choices = ["solid", "dashed", "dotted", "dashdotted"], # validator = TCValidator(flag = 'ZERO_AND_ONE_ONLY')) @@ -3287,10 +3301,6 @@ self.vPropertiesDict["linecap"] = self.linecapChoice.GetStringSelection() - def OnOK(self, event): - self.update() - event.Skip() - class LegendDialog(PsmapDialog): def __init__(self, parent, id, settings, page, env): @@ -5961,10 +5971,10 @@ return img if w > h: newW = self.previewSize[0] - newH = self.previewSize[0] * h / w + newH = self.previewSize[0] * h // w else: newH = self.previewSize[0] - newW = self.previewSize[0] * w / h + newW = self.previewSize[0] * w // h return img.Scale(newW, newH, wx.IMAGE_QUALITY_HIGH) def DrawWarningText(self, warning): diff -Nru grass-8.3.0/gui/wxpython/psmap/frame.py grass-8.3.1/gui/wxpython/psmap/frame.py --- grass-8.3.0/gui/wxpython/psmap/frame.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/psmap/frame.py 2023-10-24 19:27:44.000000000 +0000 @@ -407,8 +407,14 @@ if event.userData["pdfname"]: if sys.platform == "win32": + import platform + + arch = platform.architecture()[0] + pdf_rendering_prog = "gswin64c" + if "32" in arch: + pdf_rendering_prog = "gswin32c" command = [ - "gswin32c", + pdf_rendering_prog, "-P-", "-dSAFER", "-dCompatibilityLevel=1.4", @@ -430,14 +436,23 @@ "-f", event.userData["filename"], ] + title = _("Program {} is not available.").format(pdf_rendering_prog) + message = _("{title} Please install it to create PDF.\n\n").format( + title=title + ) else: + pdf_rendering_prog = "ps2pdf" command = [ - "ps2pdf", + pdf_rendering_prog, "-dPDFSETTINGS=/prepress", "-r1200", event.userData["filename"], event.userData["pdfname"], ] + message = _( + "Program {} is not available." + " Please install it to create PDF.\n\n " + ).format(pdf_rendering_prog) try: proc = grass.Popen(command) ret = proc.wait() @@ -450,13 +465,20 @@ else: self.SetStatusText(_("PDF generated"), 0) except OSError as e: - GError( - parent=self, - message=_( - "Program ps2pdf is not available. Please install it to create PDF.\n\n %s" + if sys.platform == "win32": + dlg = HyperlinkDialog( + self, + title=title, + message=message + str(e), + hyperlink="https://www.ghostscript.com/releases/gsdnld.html", + hyperlinkLabel=_("You can download {} version here.").format( + arch + ), ) - % e, - ) + dlg.ShowModal() + dlg.Destroy() + return + GError(parent=self, message=message + str(e)) elif not event.userData["temp"]: self.SetStatusText(_("PostScript file generated"), 0) @@ -2129,8 +2151,8 @@ rect=rect, canvasToPaper=True ) rect.Offset( - dx=rect.GetWidth() / 2, - dy=rect.GetHeight() / 2, + dx=int(rect.GetWidth() / 2), + dy=int(rect.GetHeight() / 2), ) self.instruction[id]["where"] = self.CanvasPaperCoordinates( rect=rect, canvasToPaper=True @@ -2529,7 +2551,7 @@ if rot == 0: pdc.DrawLabel(text=textDict["text"], rect=bounds) else: - pdc.DrawRotatedText(textDict["text"], coords[0], coords[1], rot) + pdc.DrawRotatedText(textDict["text"], int(coords[0]), int(coords[1]), rot) pdc.SetIdBounds(drawId, Rect(*bounds)) self.Refresh() diff -Nru grass-8.3.0/gui/wxpython/psmap/g.gui.psmap.html grass-8.3.1/gui/wxpython/psmap/g.gui.psmap.html --- grass-8.3.0/gui/wxpython/psmap/g.gui.psmap.html 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/psmap/g.gui.psmap.html 2023-10-24 19:27:44.000000000 +0000 @@ -196,7 +196,7 @@
Generates hardcopy map output in PostScript/EPS file.
icon  Generate hardcopy map output in PDF
-
Generates hardcopy map output in PDF using ps2pdf.
+
Generates hardcopy map output in PDF using ps2pdf or Ghostscript gswin32c/gswin64c (OS MS Windows platform only).
diff -Nru grass-8.3.0/gui/wxpython/startup/guiutils.py grass-8.3.1/gui/wxpython/startup/guiutils.py --- grass-8.3.0/gui/wxpython/startup/guiutils.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/startup/guiutils.py 2023-10-24 19:27:44.000000000 +0000 @@ -117,6 +117,13 @@ return is_location_name_valid(self.database, text) +def initialize_mapset(grassdb, location, mapset): + """Initialize mapset (database connection)""" + gisrc_file, env = create_environment(grassdb, location, mapset) + RunCommand("db.connect", flags="c", env=env) + try_remove(gisrc_file) + + def create_mapset_interactively(guiparent, grassdb, location): """ Create new mapset @@ -135,6 +142,7 @@ mapset = dlg.GetValue() try: create_mapset(grassdb, location, mapset) + initialize_mapset(grassdb, location, mapset) except OSError as err: mapset = None GError( diff -Nru grass-8.3.0/gui/wxpython/timeline/frame.py grass-8.3.1/gui/wxpython/timeline/frame.py --- grass-8.3.0/gui/wxpython/timeline/frame.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/gui/wxpython/timeline/frame.py 2023-10-24 19:27:44.000000000 +0000 @@ -30,6 +30,7 @@ # The recommended way to use wx with mpl is with the WXAgg # backend. matplotlib.use("WXAgg") + from matplotlib import gridspec from matplotlib.figure import Figure from matplotlib.backends.backend_wxagg import ( FigureCanvasWxAgg as FigCanvas, @@ -424,6 +425,30 @@ self.datasets = datasets self._redraw() + def _change_figure_geometry(self, nrows=1, ncols=1, num=1): + """Change figure geometry + + https://github.com/kecnry/autofig/commit/93e6debb953f5b2afe05f463064d60b9566afa59 + + :param int rows: number of rows + :param int cols: number of cols + :param int num: subplot order + """ + if not check_version(3, 4, 0): + self.axes2d.change_geometry(nrows, ncols, num) + else: + for i, ax in enumerate(self.fig.axes): + ax.set_subplotspec( + gridspec.SubplotSpec( + gridspec.GridSpec( + nrows, + ncols, + figure=self.fig, + ), + i, + ) + ) + def _redraw(self): """Readraw data. @@ -437,7 +462,7 @@ self._draw2dFigure() if check_version(1, 0, 0): if self.view3dCheck.IsChecked(): - self.axes2d.change_geometry(2, 1, 1) + self._change_figure_geometry(nrows=2, ncols=1, num=1) if not self.axes3d: # do not remove this import - unused but it is required for # 3D @@ -451,7 +476,8 @@ if self.axes3d: self.fig.delaxes(self.axes3d) self.axes3d = None - self.axes2d.change_geometry(1, 1, 1) + self._change_figure_geometry() + self._draw2dFigure() self.canvas.draw() def _checkDatasets(self, datasets): diff -Nru grass-8.3.0/imagery/i.ortho.photo/i.ortho.camera/i.ortho.camera.html grass-8.3.1/imagery/i.ortho.photo/i.ortho.camera/i.ortho.camera.html --- grass-8.3.0/imagery/i.ortho.photo/i.ortho.camera/i.ortho.camera.html 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/imagery/i.ortho.photo/i.ortho.camera/i.ortho.camera.html 2023-10-24 19:27:44.000000000 +0000 @@ -98,8 +98,8 @@ i.ortho.photo, -i.photo.2image, -i.photo.2target, +g.gui.photo2image, +g.gui.image2target, i.ortho.init diff -Nru grass-8.3.0/imagery/i.ortho.photo/i.ortho.elev/i.ortho.elev.html grass-8.3.1/imagery/i.ortho.photo/i.ortho.elev/i.ortho.elev.html --- grass-8.3.0/imagery/i.ortho.photo/i.ortho.elev/i.ortho.elev.html 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/imagery/i.ortho.photo/i.ortho.elev/i.ortho.elev.html 2023-10-24 19:27:44.000000000 +0000 @@ -7,8 +7,8 @@ i.ortho.photo
i.ortho.camera
-i.photo.2image
-i.photo.2target
+g.gui.photo2image
+g.gui.image2target
i.ortho.init
i.rectify
diff -Nru grass-8.3.0/imagery/i.ortho.photo/i.ortho.init/i.ortho.init.html grass-8.3.1/imagery/i.ortho.photo/i.ortho.init/i.ortho.init.html --- grass-8.3.0/imagery/i.ortho.photo/i.ortho.init/i.ortho.init.html 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/imagery/i.ortho.photo/i.ortho.init/i.ortho.init.html 2023-10-24 19:27:44.000000000 +0000 @@ -20,7 +20,7 @@ exposure station file is used for computation of the ortho-rectification parameters. If no initial camera exposure station file exist, the default values are computed from the control points file created in i.photo.2target. +HREF="g.gui.image2target.html">g.gui.image2target.

@@ -103,8 +103,8 @@ i.ortho.photo, -i.photo.2image, -i.photo.2target, +g.gui.photo2image, +g.gui.image2target, i.ortho.elev, i.ortho.camera, i.ortho.transform, diff -Nru grass-8.3.0/imagery/i.ortho.photo/i.ortho.rectify/i.ortho.rectify.html grass-8.3.1/imagery/i.ortho.photo/i.ortho.rectify/i.ortho.rectify.html --- grass-8.3.0/imagery/i.ortho.photo/i.ortho.rectify/i.ortho.rectify.html 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/imagery/i.ortho.photo/i.ortho.rectify/i.ortho.rectify.html 2023-10-24 19:27:44.000000000 +0000 @@ -1,13 +1,13 @@

DESCRIPTION

i.photo.rectify rectifies an image by using the image to photo -coordinate transformation matrix created by i.photo.2image -and the rectification parameters created by i.photo.2target. +coordinate transformation matrix created by g.gui.photo2image +and the rectification parameters created by g.gui.image2target. Rectification is the process by which the geometry of an image is made planimetric. This is accomplished by mapping an image from one coordinate system to another. In i.photo.rectify the parameters computed by -i.photo.2image and -i.photo.2target are used in equations to +g.gui.photo2image and +g.gui.image2target are used in equations to convert x,y image coordinates to standard map coordinates for each pixel in the image. The result is an image with a standard map coordinate system, compensated for relief distortions and photographic tilt. Upon completion of @@ -94,8 +94,8 @@ i.ortho.photo
i.ortho.camera
-i.photo.2image
-i.photo.2target
+g.gui.photo2image
+g.gui.image2target
i.ortho.init
i.rectify
diff -Nru grass-8.3.0/imagery/i.ortho.photo/i.ortho.target/i.ortho.target.html grass-8.3.1/imagery/i.ortho.photo/i.ortho.target/i.ortho.target.html --- grass-8.3.0/imagery/i.ortho.photo/i.ortho.target/i.ortho.target.html 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/imagery/i.ortho.photo/i.ortho.target/i.ortho.target.html 2023-10-24 19:27:44.000000000 +0000 @@ -9,8 +9,8 @@ i.ortho.photo
i.ortho.elev
i.ortho.camera
-i.photo.2image
-i.photo.2target
+g.gui.photo2image
+g.gui.image2target
i.ortho.init
i.ortho.rectify
diff -Nru grass-8.3.0/imagery/i.ortho.photo/lib/conz_points.c grass-8.3.1/imagery/i.ortho.photo/lib/conz_points.c --- grass-8.3.0/imagery/i.ortho.photo/lib/conz_points.c 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/imagery/i.ortho.photo/lib/conz_points.c 2023-10-24 19:27:44.000000000 +0000 @@ -5,7 +5,6 @@ #define POINT_FILE "CONTROL_POINTS" -/* This is used in i.image.2target */ /* read the control points from group file "CONTROL_POINTS" into */ /* the struct Con_Points */ int I_read_con_points(FILE *fd, struct Ortho_Control_Points *cp) diff -Nru grass-8.3.0/imagery/i.topo.corr/i.topo.corr.html grass-8.3.1/imagery/i.topo.corr/i.topo.corr.html --- grass-8.3.0/imagery/i.topo.corr/i.topo.corr.html 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/imagery/i.topo.corr/i.topo.corr.html 2023-10-24 19:27:44.000000000 +0000 @@ -3,12 +3,11 @@ i.topo.corr is used to topographically correct reflectance from imagery files, e.g. obtained with i.landsat.toar, using a sun illumination terrain model. This illumination model represents the -cosine of the incident angle i, i.e. the angle between the normal to the -ground and the sun rays. +cosine of the incident angle i, i.e. the angle between the normal +to the ground and the sun rays.

-Note: If needed, the sun position can be calculated for a given date with -r.sunmask. - +Note: If needed, the sun position can be calculated for a given date and +time with r.sunmask.

Figure showing terrain and solar angles
@@ -18,15 +17,20 @@

Using the -i flag and given an elevation basemap (metric), i.topo.corr creates a simple illumination model using the formula: +

  • cos_i = cos(s) * cos(z) + sin(s) * sin(z) * cos(a - o)
+ where, -i is the incident angle to be calculated, -s is the terrain slope angle, -z is the solar zenith angle, -a the solar azimuth angle, -o the terrain aspect angle. + +
    +
  • i is the incident angle to be calculated,
  • +
  • s is the terrain slope angle (from r.slope.aspect),
  • +
  • z is the solar zenith angle (i.e., 90° - solar horizon angle from r.sunmask),
  • +
  • a the solar azimuth angle (from r.sunmask),
  • +
  • o the terrain aspect angle (from r.slope.aspect).
  • +

For each band file, the corrected reflectance (ref_c) is calculate from diff -Nru grass-8.3.0/include/VERSION grass-8.3.1/include/VERSION --- grass-8.3.0/include/VERSION 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/include/VERSION 2023-10-24 19:27:44.000000000 +0000 @@ -1,4 +1,4 @@ 8 3 -0 +1 2023 diff -Nru grass-8.3.0/lib/bitmap/bitmap.c grass-8.3.1/lib/bitmap/bitmap.c --- grass-8.3.0/lib/bitmap/bitmap.c 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/lib/bitmap/bitmap.c 2023-10-24 19:27:44.000000000 +0000 @@ -315,8 +315,11 @@ if (NULL == (map = (struct BM *)malloc(sizeof(struct BM)))) return (NULL); - if (fread(&c, sizeof(char), sizeof(char), fp) != sizeof(char)) + if (fread(&c, sizeof(char), sizeof(char), fp) != sizeof(char)) { + free(map); return NULL; + } + if (c != BM_MAGIC) { free(map); return NULL; diff -Nru grass-8.3.0/lib/gis/renamed_options grass-8.3.1/lib/gis/renamed_options --- grass-8.3.0/lib/gis/renamed_options 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/lib/gis/renamed_options 2023-10-24 19:27:44.000000000 +0000 @@ -386,6 +386,7 @@ r.terraflow|stream_dir:directory # r.texture r.texture|measure:method +r.texture|prefix:output # r.to.vect r.to.vect|feature:type # r.topmodel diff -Nru grass-8.3.0/misc/m.nviz.image/surface.c grass-8.3.1/misc/m.nviz.image/surface.c --- grass-8.3.0/misc/m.nviz.image/surface.c 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/misc/m.nviz.image/surface.c 2023-10-24 19:27:44.000000000 +0000 @@ -99,14 +99,14 @@ for (i = 0; i < nsurfs; i++) { id = surf_list[i]; - mapset = G_find_raster2(params->color_map->answers[i], ""); - if (mapset == NULL) { - G_fatal_error(_("Raster map <%s> not found"), - params->color_map->answers[i]); - } /* color */ /* check for color map */ if (i < ncolor_map && strcmp(params->color_map->answers[i], "")) { + mapset = G_find_raster2(params->color_map->answers[i], ""); + if (mapset == NULL) { + G_fatal_error(_("Raster map <%s> not found"), + params->color_map->answers[i]); + } Nviz_set_attr( id, MAP_OBJ_SURF, ATT_COLOR, MAP_ATT, G_fully_qualified_name(params->color_map->answers[i], mapset), diff -Nru grass-8.3.0/python/grass/script/array.py grass-8.3.1/python/grass/script/array.py --- grass-8.3.0/python/grass/script/array.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/python/grass/script/array.py 2023-10-24 19:27:44.000000000 +0000 @@ -210,6 +210,9 @@ else: raise ValueError(_("Invalid kind <%s>") % kind) + # ensure all array content is written to the file + self.flush() + reg = gcore.region(env=self._env) try: @@ -315,6 +318,9 @@ else: raise ValueError(_("Invalid kind <%s>") % kind) + # ensure all array content is written to the file + self.flush() + reg = gcore.region(True, env=self._env) try: diff -Nru grass-8.3.0/raster/r.neighbors/main.c grass-8.3.1/raster/r.neighbors/main.c --- grass-8.3.0/raster/r.neighbors/main.c 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/raster/r.neighbors/main.c 2023-10-24 19:27:44.000000000 +0000 @@ -148,7 +148,7 @@ int *readrow; int nrows, ncols, brows; int i, n, t; - size_t size; + size_t size, in_buf_size, out_buf_size; struct Colors colr; struct Cell_head cellhd; struct Cell_head window; @@ -338,15 +338,6 @@ nrows = Rast_window_rows(); ncols = Rast_window_cols(); - brows = atoi(parm.memory->answer) * ((1 << 20) / sizeof(DCELL)) / ncols; - /* set the output buffer rows to be at most covering the entire map */ - if (brows > nrows) { - brows = nrows; - } - /* but at least the number of threads */ - if (brows < ncb.threads) { - brows = ncb.threads; - } /* open raster maps */ in_fd = G_malloc(sizeof(int) * ncb.threads); @@ -368,6 +359,27 @@ outputs = G_calloc(num_outputs, sizeof(struct output)); + /* memory reserved for input */ + in_buf_size = (Rast_window_cols() + 2 * ncb.dist) * sizeof(DCELL) * + ncb.nsize * ncb.threads; + /* memory available for output buffer */ + out_buf_size = (size_t)atoi(parm.memory->answer) * (1 << 20); + /* size_t is unsigned, check if any memory is left for output buffer */ + if (out_buf_size <= in_buf_size) + out_buf_size = 0; + else + out_buf_size -= in_buf_size; + /* number of buffered rows for all output maps */ + brows = out_buf_size / (sizeof(DCELL) * ncols * num_outputs); + /* set the output buffer rows to be at most covering the entire map */ + if (brows > nrows) { + brows = nrows; + } + /* but at least the number of threads */ + if (brows < ncb.threads) { + brows = ncb.threads; + } + /* read the weights */ weights = 0; ncb.weights = NULL; diff -Nru grass-8.3.0/raster/r.patch/main.c grass-8.3.1/raster/r.patch/main.c --- grass-8.3.0/raster/r.patch/main.c 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/raster/r.patch/main.c 2023-10-24 19:27:44.000000000 +0000 @@ -41,7 +41,7 @@ int colr_ok; int outfd; RASTER_MAP_TYPE out_type, map_type; - size_t out_cell_size; + size_t out_cell_size, in_buf_size, out_buf_size; struct History history; void **presult, **patch; void *outbuf; @@ -135,7 +135,7 @@ for (t = 0; t < nprocs; t++) infd[t] = G_malloc(nfiles * sizeof(int)); thread_statf = G_malloc(nprocs * (nfiles * sizeof(struct Cell_stats))); - for (int t = 0; t < nprocs; t++) { + for (t = 0; t < nprocs; t++) { thread_statf[t] = G_malloc(nfiles * sizeof(struct Cell_stats)); } cellhd = G_malloc(nfiles * sizeof(struct Cell_head)); @@ -180,7 +180,17 @@ nrows = Rast_window_rows(); ncols = Rast_window_cols(); - bufrows = atoi(memory->answer) * (((1 << 20) / out_cell_size) / ncols); + /* memory reserved for presult and patch */ + in_buf_size = out_cell_size * ncols * nprocs * 2; + /* memory available for output buffer */ + out_buf_size = (size_t)atoi(memory->answer) * (1 << 20); + /* size_t is unsigned, check if any memory is left for output buffer */ + if (out_buf_size <= in_buf_size) + out_buf_size = 0; + else + out_buf_size -= in_buf_size; + /* number of buffered output rows */ + bufrows = out_buf_size / (out_cell_size * ncols); /* set the output buffer rows to be at most covering the entire map */ if (bufrows > nrows) { bufrows = nrows; diff -Nru grass-8.3.0/raster/r.resamp.bspline/main.c grass-8.3.1/raster/r.resamp.bspline/main.c --- grass-8.3.0/raster/r.resamp.bspline/main.c 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/raster/r.resamp.bspline/main.c 2023-10-24 19:27:44.000000000 +0000 @@ -718,6 +718,8 @@ else { if (observ) G_free(observ); + if (observ_marked) + G_free(observ_marked); if (npoints == 0) G_warning(_("No data within this subregion. " "Consider increasing the spline step.")); diff -Nru grass-8.3.0/raster/r.resamp.filter/main.c grass-8.3.1/raster/r.resamp.filter/main.c --- grass-8.3.0/raster/r.resamp.filter/main.c 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/raster/r.resamp.filter/main.c 2023-10-24 19:27:44.000000000 +0000 @@ -355,7 +355,6 @@ if (row0 >= read_row && row0 < read_row + num_rows) { int m = row0 - read_row; int n = read_row + num_rows - row0; - int i; for (i = 0; i < n; i++) { DCELL *tmp = bufs[t_id][i]; @@ -415,6 +414,7 @@ char title[64]; int i, t; int nprocs; + size_t in_buf_size, out_buf_size; G_gisinit(argv[0]); @@ -623,11 +623,25 @@ Rast_set_input_window(&src_w); Rast_set_output_window(&dst_w); - bufrows = - atoi(parm.memory->answer) * (((1 << 20) / sizeof(DCELL)) / dst_w.cols); + /* memory reserved for input */ + in_buf_size = dst_w.cols * sizeof(DCELL) * row_scale * nprocs; + /* memory available for output buffer */ + out_buf_size = (size_t)atoi(parm.memory->answer) * (1 << 20); + /* size_t is unsigned, check if any memory is left for output buffer */ + if (out_buf_size <= in_buf_size) + out_buf_size = 0; + else + out_buf_size -= in_buf_size; + /* number of buffered output rows */ + bufrows = out_buf_size / (sizeof(DCELL) * dst_w.cols); + /* set the output buffer rows to be at most covering the entire map */ if (bufrows > dst_w.rows) { bufrows = dst_w.rows; } + /* but at least the number of threads */ + if (bufrows < nprocs) { + bufrows = nprocs; + } inbuf = G_malloc(nprocs * sizeof(DCELL *)); for (t = 0; t < nprocs; t++) diff -Nru grass-8.3.0/raster/r.series/main.c grass-8.3.1/raster/r.series/main.c --- grass-8.3.0/raster/r.series/main.c 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/raster/r.series/main.c 2023-10-24 19:27:44.000000000 +0000 @@ -125,6 +125,7 @@ int num_inputs; struct input **inputs = NULL; int bufrows; + size_t in_buf_size, out_buf_size; #if defined(_OPENMP) omp_lock_t fd_lock; @@ -399,16 +400,6 @@ nrows = Rast_window_rows(); ncols = Rast_window_cols(); - bufrows = atoi(parm.memory->answer) * (((1 << 20) / sizeof(DCELL)) / ncols); - /* set the output buffer rows to be at most covering the entire map */ - if (bufrows > nrows) { - bufrows = nrows; - } - /* but at least the number of threads */ - if (bufrows < nprocs) { - bufrows = nprocs; - } - /* set the locks for lazily opening raster files */ #if defined(_OPENMP) if (flag.lazy->answer && threaded) { @@ -429,6 +420,26 @@ outputs = G_calloc(num_outputs, sizeof(struct output)); + /* memory reserved for input */ + in_buf_size = ncols * sizeof(DCELL) * num_inputs * nprocs; + /* memory available for output buffer */ + out_buf_size = (size_t)atoi(parm.memory->answer) * (1 << 20); + /* size_t is unsigned, check if any memory is left for output buffer */ + if (out_buf_size <= in_buf_size) + out_buf_size = 0; + else + out_buf_size -= in_buf_size; + /* number of buffered rows for all output maps */ + bufrows = out_buf_size / (sizeof(DCELL) * ncols * num_outputs); + /* set the output buffer rows to be at most covering the entire map */ + if (bufrows > nrows) { + bufrows = nrows; + } + /* but at least the number of threads */ + if (bufrows < nprocs) { + bufrows = nprocs; + } + for (i = 0; i < num_outputs; i++) { struct output *out = &outputs[i]; const char *output_name = parm.output->answers[i]; diff -Nru grass-8.3.0/raster/r.sim/simlib/hydro.c grass-8.3.1/raster/r.sim/simlib/hydro.c --- grass-8.3.0/raster/r.sim/simlib/hydro.c 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/raster/r.sim/simlib/hydro.c 2023-10-24 19:27:44.000000000 +0000 @@ -305,7 +305,7 @@ } if (zz[k][l] != UNDEF) { - if (infil != NULL) { /* infiltration part */ + if (inf[k][l] != UNDEF) { /* infiltration part */ if (inf[k][l] - si[k][l] > 0.) { decr = pow( diff -Nru grass-8.3.0/raster/r.sim/simlib/input.c grass-8.3.1/raster/r.sim/simlib/input.c --- grass-8.3.0/raster/r.sim/simlib/input.c 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/raster/r.sim/simlib/input.c 2023-10-24 19:27:44.000000000 +0000 @@ -299,7 +299,6 @@ int rows = my, cols = mx; /* my and mx are global variables */ int max_walkers; double unitconv = 0.000000278; /* mm/hr to m/s */ - int if_rain = 0; G_debug(1, "Running MAR 2011 version, started modifications on 20080211"); G_debug(1, "Reading input data"); @@ -328,39 +327,33 @@ /* Rain: read rain map or use a single value for all cells */ if (rain != NULL) { si = read_double_raster_map(rows, cols, rain, unitconv); - if_rain = 1; } else if (rain_val >= 0.0) { /* If no value set its set to -999.99 */ si = create_double_matrix(rows, cols, rain_val * unitconv); - if_rain = 1; } else { si = create_double_matrix(rows, cols, (double)UNDEF); - if_rain = 0; } /* Update elevation map */ copy_matrix_undef_double_to_float_values(rows, cols, si, zz); - /* Load infiltration and traps if rain is present */ - if (if_rain == 1) { - /* Infiltration: read map or use a single value */ - if (infil != NULL) { - inf = read_double_raster_map(rows, cols, infil, unitconv); - } - else if (infil_val >= 0.0) { /* If no value set its set to -999.99 */ - inf = create_double_matrix(rows, cols, infil_val * unitconv); - } - else { - inf = create_double_matrix(rows, cols, (double)UNDEF); - } - - /* Traps */ - if (traps != NULL) - trap = read_float_raster_map(rows, cols, traps, 1.0); - else - trap = create_float_matrix(rows, cols, (double)UNDEF); + /* Infiltration: read map or use a single value */ + if (infil != NULL) { + inf = read_double_raster_map(rows, cols, infil, unitconv); } + else if (infil_val >= 0.0) { /* If no value set its set to -999.99 */ + inf = create_double_matrix(rows, cols, infil_val * unitconv); + } + else { + inf = create_double_matrix(rows, cols, (double)UNDEF); + } + + /* Traps */ + if (traps != NULL) + trap = read_float_raster_map(rows, cols, traps, 1.0); + else + trap = create_float_matrix(rows, cols, (double)UNDEF); if (detin != NULL) { dc = read_float_raster_map(rows, cols, detin, 1.0); diff -Nru grass-8.3.0/raster/r.sunmask/r.sunmask.html grass-8.3.1/raster/r.sunmask/r.sunmask.html --- grass-8.3.0/raster/r.sunmask/r.sunmask.html 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/raster/r.sunmask/r.sunmask.html 2023-10-24 19:27:44.000000000 +0000 @@ -1,34 +1,57 @@

DESCRIPTION

r.sunmask creates an output map layer based on an input elevation -raster map layer and the sun position. The output map layer contains the -cast shadow areas arising from sun shine and elevations. The user can define -the sun position either directly or the module calculates it from given -location and date/time parameters using the -NREL sun position algorithm. So either -"A:"-parameters to specify the exact known sun position or "B:-parameters" -to specify date/time for sun position calculation by r.sunmask itself -have to be used. - -

The module performs sunset/sunrise checks and refraction correction for sun -position calculation. Local coordinate systems are internally transformed to -latitude/longitude for the SOLPOS algorithm. The elevation is not considered -in the sunset/sunrise calculations. +raster map layer and the position of the sun. The output map layer contains the +cast shadow areas resulting from sunlight and elevation. The user can either +specify the sun position directly or the module calculates it from given +location and date/time parameters using the SOLPOS (Solar and Moon Position Algorithm) +developed by the +National Renewable Energy Laboratory +(NREL). SOLPOS operates in two modes, either + +

    +
  • (A) parameters to specify the exact known position of the sun, or
  • +
  • (B) parameters to specify the date/time for the sun position calculation + by r.sunmask itself
  • +
+must be used. + +

+The module performs sunset/sunrise checks and refraction correction for +sun position calculation. Local coordinate systems are internally transformed +to latitude/longitude for the SOLPOS algorithm. Elevation is not taken into +account for sunset/sunrise calculations. + +

+The solar zenith angle ("sun angle above horizon") is defined as the angle +between the horizon and the vertical (directly overhead or zenith). Its +values can range from 90°, when the sun is directly overhead, to 0°, +when the sun is on the horizon. Values lower than 0° indicate that the +sun is below the horizon. + +

+The solar azimuth angle ("sun azimuth") defines the direction of the sun. +It is the angle between north and the projection of the sun's rays onto the +horizontal plane. This angle is measured in a clockwise direction and can +vary between 0° and 360°. Specifically, an azimuth of 0° means +the sun is in the north, 90° in the east, 180° in the south and +270° in the west.

NOTES

-r.sunmask and daylight savings: Rather than converting the -local time to GMT, the SOLPOS algorithm uses what is called Local -Standard Time, which is generally politically defined as an offset -from GMT. So the key is the offset from GMT, which the solpos Time +r.sunmask and daylight saving time: Instead of converting the +local time to GMT, the SOLPOS algorithm uses what is known as Local +Standard Time, which is generally defined as an offset from GMT. +So the key is the offset from GMT, which is the solpos Time Zone parameter. If the user specifies clock time (different for -winter and summer), s/he would have to change the Time Zone -parameter seasonally in r.sunmask (timezone parameter). See also -Daylight saving time by region and country. +winter and summer), s/he would have to change the Time Zone parameter in +r.sunmask (timezone parameter) seasonally. See also +Daylight saving time by region and country. +

Note: In latitude/longitude locations the position coordinates pair -(east/west) has to be specified in decimal degree (not D:M:S). If +(east/west) has to be specified in decimal degree (not DD:MM:SS). If not specified, the map center's coordinates will be used. Also g.region -l displays the map center's coordinates in latitude/longitude (or g.region -c in the actual coordinate @@ -98,7 +121,7 @@

Acknowledgements

Acknowledgements: National Renewable Energy Laboratory for their -SOLPOS 2.0 sun position +SOLPOS 2.0 sun position algorithm.

SEE ALSO

@@ -112,6 +135,6 @@

AUTHORS

-Janne Soimasuo, Finland 1994
-update to FP by Huidae Cho 2001
-SOLPOS algorithm feature added by Markus Neteler 2001 +Janne Soimasuo, Finland, 1994
+update to FP by Huidae Cho, 2001
+SOLPOS algorithm feature added by Markus Neteler, 2001 Binary files /tmp/tmpk4da_blk/eiu9i7Bfq6/grass-8.3.0/raster/r.terraflow/rterraflow_direction_encoding.png and /tmp/tmpk4da_blk/MFQ68OQpgu/grass-8.3.1/raster/r.terraflow/rterraflow_direction_encoding.png differ diff -Nru grass-8.3.0/raster/r.terraflow/r.terraflow.html grass-8.3.1/raster/r.terraflow/r.terraflow.html --- grass-8.3.0/raster/r.terraflow/r.terraflow.html 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/raster/r.terraflow/r.terraflow.html 2023-10-24 19:27:44.000000000 +0000 @@ -62,6 +62,18 @@ a sink-less terrain in which every cell has a downslope flow path leading outside the terrain and therefore every cell in the terrain can be assigned SFD/MFD flow directions as above. +Flow directions are encoded using powers of two clockwise starting from +20 for east to 27 for north-east. + +
+ +r.terraflow direction encoding +
+Flow direction encoding clockwise starting from 20 for east to +27 for north-east; 0 for undetermined (sinks) and 1 for undefined +(null cells) +(source) +

Once flow directions are computed for every cell in the terrain, r.terraflow computes flow accumulation by routing water using diff -Nru grass-8.3.0/raster/r.texture/r.texture.html grass-8.3.1/raster/r.texture/r.texture.html --- grass-8.3.0/raster/r.texture/r.texture.html 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/raster/r.texture/r.texture.html 2023-10-24 19:27:44.000000000 +0000 @@ -159,7 +159,7 @@ # extract grey levels r.mapcalc "ortho_2001_t792_1m.greylevel = ortho_2001_t792_1m" # texture analysis -r.texture ortho_2001_t792_1m.greylevel prefix=ortho_texture method=asm -s +r.texture ortho_2001_t792_1m.greylevel output=ortho_texture method=asm -s # display g.region n=221461 s=221094 w=638279 e=638694 d.shade color=ortho_texture_ASM_0 shade=ortho_2001_t792_1m diff -Nru grass-8.3.0/raster/r.watershed/ram/do_cum.c grass-8.3.1/raster/r.watershed/ram/do_cum.c --- grass-8.3.0/raster/r.watershed/ram/do_cum.c 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/raster/r.watershed/ram/do_cum.c 2023-10-24 19:27:44.000000000 +0000 @@ -600,10 +600,12 @@ aspect = -aspect; asp[this_index] = aspect; } + /* if (mfd_cells == 1) - /* mfdir = (1 << nextmfd[max_side]); */ + mfdir = (1 << nextmfd[max_side]); + */ - is_swale = FLAG_GET(swale, r, c); + is_swale = FLAG_GET(swale, r, c); /* start new stream */ value = ABS(value) + 0.5; if (!is_swale && (int)value >= threshold && stream_cells < 1 && diff -Nru grass-8.3.0/raster/r.watershed/seg/do_cum.c grass-8.3.1/raster/r.watershed/seg/do_cum.c --- grass-8.3.0/raster/r.watershed/seg/do_cum.c 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/raster/r.watershed/seg/do_cum.c 2023-10-24 19:27:44.000000000 +0000 @@ -664,10 +664,12 @@ else af.asp = drain[r - r_max + 1][c - c_max + 1]; } + /* if (mfd_cells == 1) - /* mfdir = (1 << nextmfd[max_side]); */ + mfdir = (1 << nextmfd[max_side]); + */ - is_swale = FLAG_GET(af.flag, SWALEFLAG); + is_swale = FLAG_GET(af.flag, SWALEFLAG); /* start new stream */ if (!is_swale && fabs(value) >= threshold && stream_cells < 1 && swale_cells < 1 && !flat) { diff -Nru grass-8.3.0/raster/r.watershed/testsuite/r_watershed_test.py grass-8.3.1/raster/r.watershed/testsuite/r_watershed_test.py --- grass-8.3.0/raster/r.watershed/testsuite/r_watershed_test.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/raster/r.watershed/testsuite/r_watershed_test.py 2023-10-24 19:27:44.000000000 +0000 @@ -163,7 +163,12 @@ ) # it is expected that 100k Threshold has a min=2 and max=12 for this # data - self.assertRasterMinMax(self.basin, 2, 12) + reference = "min=2\nmax=12" + self.assertRasterFitsUnivar( + self.basin, + reference=reference, + msg="Basin values must be in the range [2, 12]", + ) # it is expected that 100k Threshold has a min=2 and max=256 for this # data self.assertModule( @@ -173,7 +178,12 @@ basin=self.basin, overwrite=True, ) - self.assertRasterMinMax(self.basin, 2, 256) + reference = "min=2\nmax=256" + self.assertRasterFitsUnivar( + self.basin, + reference=reference, + msg="Basin values must be in the range [2, 256]", + ) def test_drainageDirection(self): """Test if the drainage direction is between -8 and 8.""" @@ -198,11 +208,11 @@ # TODO: test just min, max is theoretically unlimited # or set a lower value according to what is expected with this data # TODO: add test which tests that 'max basin id' == 'num of basins' - self.assertRasterMinMax( + reference = "min=2\nmax=256" + self.assertRasterFitsUnivar( self.basin, - 0, - 1000000, - msg="A basin value is less than 0 or greater than 1000000", + reference=reference, + msg="Basin values must be in the range [2, 256]", ) diff -Nru grass-8.3.0/rpm/grass-pkgconfig.patch grass-8.3.1/rpm/grass-pkgconfig.patch --- grass-8.3.0/rpm/grass-pkgconfig.patch 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/rpm/grass-pkgconfig.patch 2023-10-24 19:27:44.000000000 +0000 @@ -1,10 +1,10 @@ -Index: grass-7.8.6/grass.pc.in -=================================================================== ---- grass-7.8.6.orig/grass.pc.in -+++ grass-7.8.6/grass.pc.in +diff --git a/grass.pc.in b/grass.pc.in +index 94ca57b65..aec77d095 100644 +--- a/grass.pc.in ++++ b/grass.pc.in @@ -2,13 +2,13 @@ # - # See also: grass@GRASS_VERSION_MAJOR@@GRASS_VERSION_MINOR@ --config + # See also: grass --config -prefix=@prefix@/grass-@GRASS_VERSION_MAJOR@.@GRASS_VERSION_MINOR@.@GRASS_VERSION_RELEASE@ -exec_prefix=@prefix@/grass-@GRASS_VERSION_MAJOR@.@GRASS_VERSION_MINOR@.@GRASS_VERSION_RELEASE@ diff -Nru grass-8.3.0/rpm/grass.spec grass-8.3.1/rpm/grass.spec --- grass-8.3.0/rpm/grass.spec 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/rpm/grass.spec 2023-10-24 19:27:44.000000000 +0000 @@ -1,9 +1,9 @@ -%global shortver 82 +%global shortver 83 %global macrosdir %(d=%{_rpmconfigdir}/macros.d; [ -d $d ] || d=%{_sysconfdir}/rpm; echo $d) Name: grass -Version: 8.2.1 -Release: 1%{?dist} +Version: 8.3.0 +Release: 3%{?dist} Summary: GRASS GIS - Geographic Resources Analysis Support System %if 0%{?fedora} >= 33 || 0%{?rhel} >= 9 @@ -29,7 +29,7 @@ Source0: https://grass.osgeo.org/%{name}%{shortver}/source/%{name}-%{version}.tar.gz # fix pkgconfig file -Patch0: grass-pkgconfig.patch +Patch 0: grass-pkgconfig.patch BuildRequires: bison %if %{with flexiblas} @@ -42,16 +42,12 @@ BuildRequires: desktop-file-utils BuildRequires: fftw-devel BuildRequires: flex -%if (0%{?rhel} > 6 || 0%{?fedora}) BuildRequires: freetype-devel -%endif BuildRequires: gdal-devel BuildRequires: geos-devel BuildRequires: gettext BuildRequires: laszip-devel -%if (0%{?rhel} > 6 || 0%{?fedora}) BuildRequires: libappstream-glib -%endif BuildRequires: libpng-devel BuildRequires: libtiff-devel BuildRequires: libXmu-devel @@ -62,9 +58,7 @@ %else BuildRequires: mysql-devel %endif -%if (0%{?rhel} > 6 || 0%{?fedora}) BuildRequires: netcdf-devel -%endif BuildRequires: python3 %if 0%{?rhel} == 7 # EPEL7 @@ -72,7 +66,7 @@ %else BuildRequires: python3-numpy %endif -%if 0%{?rhel} && 0%{?rhel} <= 7 +%if 0%{?rhel} && 0%{?rhel} == 7 BuildRequires: postgresql-devel %else BuildRequires: libpq-devel @@ -122,23 +116,13 @@ %else Requires: python3-numpy %endif -%if 0%{?rhel} > 6 -# EPEL7/EPEL8 -#Requires: python3-matplotlib-wx -%else -Requires: python3-matplotlib -%endif %if 0%{?rhel} == 7 # EPEL7 Requires: python%{python3_version_nodots}-dateutil %else Requires: python3-dateutil %endif -%if 0%{?rhel} && 0%{?rhel} < 7 -Requires: wxPython -%else Requires: python3-wxpython4 -%endif Requires: PDAL Requires: PDAL-libs @@ -180,7 +164,7 @@ %prep %setup -q -%patch0 -p1 -b.libdir +%patch 0 -p1 -b.libdir # Correct mysql_config query sed -i -e 's/--libmysqld-libs/--libs/g' configure @@ -214,9 +198,7 @@ --with-lapack-includes=%{_includedir}/flexiblas \ %endif --with-cairo \ -%if (0%{?rhel} > 6 || 0%{?fedora}) --with-freetype \ -%endif --with-nls \ --with-pdal \ --with-readline \ @@ -225,9 +207,7 @@ --with-gdal=%{_bindir}/gdal-config \ --with-wxwidgets=%{_bindir}/wx-config \ --with-geos=%{_bindir}/geos-config \ -%if (0%{?rhel} > 6 || 0%{?fedora}) --with-netcdf=%{_bindir}/nc-config \ -%endif --with-mysql-includes=%{_includedir}/mysql \ %if (0%{?fedora} >= 27) --with-mysql-libs=%{_libdir} \ @@ -316,7 +296,7 @@ %{_libdir}/%{name}%{shortver}/lib EOF -%if 0%{?rhel} && 0%{?rhel} <= 7 +%if 0%{?rhel} && 0%{?rhel} == 7 %post /bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null || : @@ -352,7 +332,6 @@ %license AUTHORS COPYING GPL.TXT CHANGES %{_sysconfdir}/ld.so.conf.d/%{name}-%{_arch}.conf %{_libdir}/%{name}%{shortver}/lib/*.so -%{_libdir}/%{name}%{shortver}/lib/*.a %dir %{_libdir}/%{name}%{shortver}/driver %dir %{_libdir}/%{name}%{shortver}/driver/db %{_libdir}/%{name}%{shortver}/driver/db/* @@ -368,6 +347,18 @@ %{_libdir}/%{name}%{shortver}/include %changelog +* Sun Aug 06 2023 Alexandre Detiste - 8.3.0-3 +- Remove support for RHEL6: Grass is now Python3 only + +* Thu Jul 20 2023 Fedora Release Engineering - 8.3.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild + +* Sun Jun 25 2023 Markus Neteler 8.3.0-1 +- New upstream version GRASS GIS 8.3.0 + +* Thu May 11 2023 Sandro Mani - 8.2.1-2 +- Rebuild (gdal) + * Sat Jan 21 2023 Markus Neteler 8.2.1-1 - New upstream version GRASS GIS 8.2.1 diff -Nru grass-8.3.0/scripts/g.extension/g.extension.py grass-8.3.1/scripts/g.extension/g.extension.py --- grass-8.3.0/scripts/g.extension/g.extension.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/scripts/g.extension/g.extension.py 2023-10-24 19:27:44.000000000 +0000 @@ -144,7 +144,6 @@ # TODO: solve addon-extension(-module) confusion import fileinput -import http import os import codecs import sys @@ -188,7 +187,6 @@ HEADERS = { "User-Agent": "Mozilla/5.0", } -HTTP_STATUS_CODES = list(http.HTTPStatus) GIT_URL = "https://github.com/OSGeo/grass-addons/" # MAKE command @@ -258,7 +256,7 @@ git_version, stderr = git_version.communicate() if stderr: gs.fatal( - _("Failed to get Git version.\n{error}").format( + _("Failed to get Git version.\n{}").format( gs.decode(stderr), ) ) @@ -504,23 +502,27 @@ if not, take branch for the previous version For the official repo we assume that at least one version branch is present""" version_branch = f"grass{major_version}" - branch = gs.Popen( - ["git", "ls-remote", "--heads", GIT_URL, f"refs/heads/{version_branch}"], - stdout=PIPE, - stderr=PIPE, - ) - branch, stderr = branch.communicate() - if stderr: - gs.fatal( - _( - "Failed to get branch from the Git repository <{repo_path}>.\n" - "{error}" - ).format( - repo_path=GIT_URL, - error=gs.decode(stderr), - ) + if sys.platform == "win32": + return version_branch + else: + branch = gs.Popen( + ["git", "ls-remote", "--heads", GIT_URL, f"refs/heads/{version_branch}"], + stdout=PIPE, + stderr=PIPE, ) - if version_branch not in gs.decode(branch): + branch, stderr = branch.communicate() + if stderr: + gs.fatal( + _( + "Failed to get branch from the Git repository <{repo_path}>.\n" + "{error}" + ).format( + repo_path=GIT_URL, + error=gs.decode(stderr), + ) + ) + branch = gs.decode(branch) + if version_branch not in branch: version_branch = "grass{}".format(int(major_version) - 1) return version_branch @@ -1147,7 +1149,7 @@ file_.close() -def install_extension(source, url, xmlurl, branch): +def install_extension(source=None, url=None, xmlurl=None, branch=None): """Install extension (e.g. one module) or a toolbox (list of modules)""" gisbase = os.getenv("GISBASE") if not gisbase: @@ -1514,11 +1516,12 @@ # read XML file tree = etree_fromfile(xml_file) - # Filter multi-addon addons - if len(mlist) > 1: - mlist = filter_multi_addon_addons( - mlist.copy() - ) # mlist.copy() keep the original list of add-ons + if sys.platform != "win32": + # Filter multi-addon addons + if len(mlist) > 1: + mlist = filter_multi_addon_addons( + mlist.copy() + ) # mlist.copy() keep the original list of add-ons # update tree for name in mlist: @@ -1630,10 +1633,13 @@ # collect module names and file names module_list = list() + module_name_pattern = re.compile( + r"^([d,g,i,m,p,r,s,t,v]|^db|^ps|^r3|^wx)\..*[\.py,\.exe]$" + ) for r, d, f in os.walk(srcdir): for file in f: # Filter GRASS module name patterns - if re.search(r"^[d,db,g,i,m,p,ps,r,r3,s,t,v,wx]\..*[\.py,\.exe]$", file): + if re.search(module_name_pattern, file): modulename = os.path.splitext(file)[0] module_list.append(modulename) # remove duplicates in case there are .exe wrappers for python scripts @@ -2409,11 +2415,12 @@ # find URIs pattern = r"""([^>]+)""" addons = get_installed_extensions(force=True) - # Multi-addon - if len(addons) > 1: - for a in get_multi_addon_addons_which_install_only_html_man_page(): - # Add multi-addon addons which install only manual html page - addons.append(a) + if sys.platform != "win32": + # Multi-addon + if len(addons) > 1: + for a in get_multi_addon_addons_which_install_only_html_man_page(): + # Add multi-addon addons which install only manual html page + addons.append(a) for match in re.finditer(pattern, shtml): if match.group(1)[:4] == "http": @@ -2822,17 +2829,23 @@ if options["operation"] == "add": check_dirs() - if original_url == "" or flags["o"]: - """ - Query GitHub API only if extension will be downloaded - from official GRASS GIS addon repository - """ - get_addons_paths(gg_addons_base_dir=options["prefix"]) - source, url = resolve_source_code( - name=options["extension"], url=original_url, branch=branch, fork=flags["o"] - ) - xmlurl = resolve_xmlurl_prefix(original_url, source=source) - install_extension(source=source, url=url, xmlurl=xmlurl, branch=branch) + if sys.platform == "win32": + install_extension() + else: + if original_url == "" or flags["o"]: + """ + Query GitHub API only if extension will be downloaded + from official GRASS GIS addon repository + """ + get_addons_paths(gg_addons_base_dir=options["prefix"]) + source, url = resolve_source_code( + name=options["extension"], + url=original_url, + branch=branch, + fork=flags["o"], + ) + xmlurl = resolve_xmlurl_prefix(original_url, source=source) + install_extension(source=source, url=url, xmlurl=xmlurl, branch=branch) else: # remove remove_extension(force=flags["f"]) diff -Nru grass-8.3.0/scripts/r.drain/r.drain.py grass-8.3.1/scripts/r.drain/r.drain.py --- grass-8.3.0/scripts/r.drain/r.drain.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/scripts/r.drain/r.drain.py 2023-10-24 19:27:44.000000000 +0000 @@ -108,17 +108,16 @@ # % required: start_coordinates, start_points # %end -import os import sys import atexit -import grass.script as grass +import grass.script as gs def cleanup(): """Delete temporary direction map.""" if tmp_maps: - grass.run_command( + gs.run_command( "g.remove", flags="f", quiet=True, @@ -141,12 +140,13 @@ atexit.register(cleanup) if not dirmap: + valmap_name = valmap.split("@", maxsplit=1)[0] # get directions with r.fill.dir, without sink filling - dirmap = "%s_tmp_%d" % (valmap, os.getpid()) - fill_map = "%s_fill_%d" % (valmap, os.getpid()) - area_map = "%s_area_%d" % (valmap, os.getpid()) + dirmap = gs.append_node_pid(f"{valmap_name}_dir") + fill_map = gs.append_node_pid(f"{valmap_name}_fill") + area_map = gs.append_node_pid(f"{valmap_name}_area") tmp_maps = dirmap + "," + fill_map + "," + area_map - grass.run_command( + gs.run_command( "r.fill.dir", input=valmap, output=fill_map, @@ -179,11 +179,11 @@ if flags["n"]: pathflags += "n" - grass.run_command("r.path", flags=pathflags, **kwargs) + gs.run_command("r.path", flags=pathflags, **kwargs) return 0 if __name__ == "__main__": - options, flags = grass.parser() + options, flags = gs.parser() sys.exit(main()) diff -Nru grass-8.3.0/scripts/r.fillnulls/r.fillnulls.html grass-8.3.1/scripts/r.fillnulls/r.fillnulls.html --- grass-8.3.0/scripts/r.fillnulls/r.fillnulls.html 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/scripts/r.fillnulls/r.fillnulls.html 2023-10-24 19:27:44.000000000 +0000 @@ -27,7 +27,7 @@ may pay attention to below notes.

If interpolation fails, temporary raster and vector maps are left in place to allow -unfilled map hole (NULL area) identification and manual repair. +unfilled map holes (NULL areas) to be identified and manually repaired.

When using the default RST method, the algorithm is based diff -Nru grass-8.3.0/scripts/r.fillnulls/r.fillnulls.py grass-8.3.1/scripts/r.fillnulls/r.fillnulls.py --- grass-8.3.0/scripts/r.fillnulls/r.fillnulls.py 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/scripts/r.fillnulls/r.fillnulls.py 2023-10-24 19:27:44.000000000 +0000 @@ -296,7 +296,16 @@ os.remove(cats_file_name) if len(cat_list) < 1: - grass.fatal(_("Input map has no holes. Check region settings.")) + # no holes found in current region + grass.run_command( + "g.copy", raster="%s,%sfilled" % (input, prefix), overwrite=True + ) + grass.warning( + _( + "Input map <%s> has no holes. Copying to output without modification." + ) + % (input,) + ) # GTC Hole is NULL area in a raster map grass.message(_("Processing %d map holes") % len(cat_list)) diff -Nru grass-8.3.0/vector/v.reclass/v.reclass.html grass-8.3.1/vector/v.reclass/v.reclass.html --- grass-8.3.0/vector/v.reclass/v.reclass.html 2023-06-24 10:01:41.000000000 +0000 +++ grass-8.3.1/vector/v.reclass/v.reclass.html 2023-10-24 19:27:44.000000000 +0000 @@ -27,6 +27,8 @@

For dissolving common boundaries, see v.dissolve. +

Either the rules or column option must be specified. +

EXAMPLES

Example 1: Reclass by rules

@@ -35,7 +37,7 @@ v.reclass input=land output=land_u type=boundary rules=land.rcl -the rules file contains : +The rules file contains:
 # land reclass file
@@ -71,7 +73,7 @@
 
 

KNOWN ISSUES

-No table is created for reclassed layer if rules option is used. +No table is created for reclassed layer if the rules option is used.

SEE ALSO