diff --git a/.coveragerc b/.coveragerc index 65e13c2234b..409fde55643 100644 --- a/.coveragerc +++ b/.coveragerc @@ -9,6 +9,7 @@ omit = ${INITIAL_PWD-.}/.github/* ${INITIAL_PWD-.}/bin.*/* ${INITIAL_PWD-.}/dist.*/* + **/gui/wxpython/*/** **/OBJ.*/* source = . diff --git a/.flake8 b/.flake8 index b62a3eab402..b8c498bfcb7 100644 --- a/.flake8 +++ b/.flake8 @@ -24,13 +24,11 @@ per-file-ignores = gui/scripts/d.wms.py: E501 gui/wxpython/image2target/g.gui.image2target.py: E501 gui/wxpython/modules/*: F841, E722 - gui/wxpython/nviz/*: F841, E266, E722, F403, F405 + gui/wxpython/nviz/*: E722, F403, F405 gui/wxpython/photo2image/*: F841, E722, E265 gui/wxpython/photo2image/g.gui.photo2image.py: E501, F841 - gui/wxpython/psmap/*: F841, E266, E722, F405, F403 + gui/wxpython/psmap/*: F841, E266, E722 gui/wxpython/vdigit/*: F841, E722, F405, F403 - gui/wxpython/vnet/*: F841 - gui/wxpython/wxgui.py: F841 gui/wxpython/animation/g.gui.animation.py: E501 gui/wxpython/tplot/frame.py: F841, E722 gui/wxpython/tplot/g.gui.tplot.py: E501 @@ -67,7 +65,6 @@ per-file-ignores = # TODO: Is this really needed? python/grass/pygrass/vector/__init__.py: E402 python/grass/pygrass/raster/__init__.py: E402 - python/grass/gunittest/invoker.py: E721 python/grass/pygrass/vector/__init__.py: E402 python/grass/pygrass/raster/category.py: E721 python/grass/pygrass/rpc/__init__.py: F403 @@ -81,13 +78,11 @@ per-file-ignores = python/grass/temporal/temporal_granularity.py: E722 python/grass/temporal/temporal_raster_base_algebra.py: E722 python/grass/temporal/temporal_topology_dataset_connector.py: E722 - python/grass/temporal/univar_statistics.py: E231 # Current benchmarks/tests are changing sys.path before import. # Possibly, a different approach should be taken there anyway. python/grass/pygrass/tests/benchmark.py: E402, F821 # Configuration file for Sphinx: # Ignoring import/code mix and line length. - python/grass/docs/conf.py: E402 # Files not managed by Black python/grass/imaging/images2gif.py: E226 # Unused imports in init files @@ -112,7 +107,7 @@ per-file-ignores = scripts/r.in.srtm/r.in.srtm.py: E722 scripts/r.fillnulls/r.fillnulls.py: E722 scripts/d.rast.edit/d.rast.edit.py: E722 - scripts/v.what.strds/v.what.strds.py: E722, E501 + scripts/v.what.strds/v.what.strds.py: E501 # Line too long (esp. module interface definitions) scripts/*/*.py: E501 temporal/t.rast.to.vect/t.rast.to.vect.py: E501 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 88158341f13..6672287794c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -56,7 +56,7 @@ jobs: if: ${{ matrix.language == 'c-cpp' }} - name: Initialize CodeQL - uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 + uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/codeql-config.yml @@ -81,6 +81,6 @@ jobs: run: .github/workflows/build_ubuntu-22.04.sh "${HOME}/install" - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 + uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/milestones.yml b/.github/workflows/milestones.yml new file mode 100644 index 00000000000..e2ade4eb091 --- /dev/null +++ b/.github/workflows/milestones.yml @@ -0,0 +1,70 @@ +--- +name: Assign Milestone + +on: + pull_request_target: + types: [closed] + +jobs: + assign-milestone: + runs-on: ubuntu-latest + if: github.event.pull_request.merged + steps: + # Retreiving the current milestoone from API instead of github context, + # so up-to-date information is used when running after being queued or for reruns + # Otherwise, the information should be available using + # ${{ github.event.pull_request.milestone.title }} + - name: Get current milestone title + id: current-milestone + run: | + echo "milestone<> "${GITHUB_OUTPUT}" + gh pr view ${{ github.event.pull_request.html_url }} --json milestone \ + --jq .milestone.title >> "${GITHUB_OUTPUT}" + echo 'EOF' >> "${GITHUB_OUTPUT}" + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + - name: PR already has a milestone + run: echo "PR already has a milestone" + if: ${{ steps.current-milestone.outputs.milestone }} + - name: PR does not have a milestone + run: echo "PR does not have a milestone" + if: ${{ !steps.current-milestone.outputs.milestone }} + - name: Get VERSION file + if: ${{ !steps.current-milestone.outputs.milestone }} + id: version-file + run: | + echo "version<> "${GITHUB_OUTPUT}" + gh api \ + -H "Accept: application/vnd.github.raw" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/{owner}/{repo}/contents/include/VERSION?ref=${{ github.sha }}" >> "${GITHUB_OUTPUT}" + echo "EOF" >> "${GITHUB_OUTPUT}" + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + - name: Show version file + if: ${{ !steps.current-milestone.outputs.milestone }} + run: echo "${VERSIONFILE}" + env: + VERSIONFILE: ${{ steps.version-file.outputs.version }} + - name: Get milestone title from VERSION file + if: ${{ !steps.current-milestone.outputs.milestone }} + id: milestone + run: | + version=$(echo "$VERSIONFILE" | head -n 3 | xargs | sed 's/ /./g; s/\(RC[0-9]*\|dev\)//g') + echo "title=$version" >> "${GITHUB_OUTPUT}" + env: + VERSIONFILE: ${{ steps.version-file.outputs.version }} + - name: Show milestone title + if: ${{ !steps.current-milestone.outputs.milestone }} + run: echo "${MILESTONE}" + env: + MILESTONE: ${{ steps.milestone.outputs.title }} + - name: Set PR milestone + if: ${{ !steps.current-milestone.outputs.milestone }} + run: gh pr edit ${{ github.event.pull_request.html_url }} --milestone "${MILESTONE}" + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + MILESTONE: ${{ steps.milestone.outputs.title }} diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index f2a96cf41ba..e1b3b84b54e 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -83,8 +83,13 @@ jobs: run: | export PYTHONPATH=`grass --config python_path`:$PYTHONPATH export LD_LIBRARY_PATH=$(grass --config path)/lib:$LD_LIBRARY_PATH + export INITIAL_GISBASE="$(grass --config path)" + export INITIAL_PWD="${PWD}" pytest --verbose --color=yes --durations=0 --durations-min=0.5 \ - --numprocesses auto -ra . \ + --numprocesses auto \ + --cov \ + --cov-context=test \ + -ra . \ -m 'not needs_solo_run' - name: Run pytest with a single worker (for tests marked with needs_solo_run) @@ -92,9 +97,11 @@ jobs: export PYTHONPATH=`grass --config python_path`:$PYTHONPATH export LD_LIBRARY_PATH=$(grass --config path)/lib:$LD_LIBRARY_PATH export INITIAL_GISBASE="$(grass --config path)" - INITIAL_PWD="${PWD}" pytest --verbose --color=yes --durations=0 --durations-min=0.5 \ + export INITIAL_PWD="${PWD}" + pytest --verbose --color=yes --durations=0 --durations-min=0.5 \ --cov \ --cov-context=test \ + --cov-append \ -ra . \ -m 'needs_solo_run' - name: Fix non-standard installed script paths in coverage data diff --git a/.github/workflows/python-code-quality.yml b/.github/workflows/python-code-quality.yml index b8b423b2b16..9f71c28e922 100644 --- a/.github/workflows/python-code-quality.yml +++ b/.github/workflows/python-code-quality.yml @@ -36,7 +36,7 @@ jobs: # renovate: datasource=pypi depName=bandit BANDIT_VERSION: "1.7.10" # renovate: datasource=pypi depName=ruff - RUFF_VERSION: "0.6.9" + RUFF_VERSION: "0.7.0" runs-on: ${{ matrix.os }} permissions: @@ -135,7 +135,7 @@ jobs: path: bandit.sarif - name: Upload SARIF File into Security Tab - uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 + uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 with: sarif_file: bandit.sarif diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2c136da40ff..4c82e138950 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: ) - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.6.9 + rev: v0.7.0 hooks: # Run the linter. - id: ruff diff --git a/flake.nix b/flake.nix index 51174e1da74..a105607417f 100644 --- a/flake.nix +++ b/flake.nix @@ -34,6 +34,8 @@ pytest ]; + LOCALE_ARCHIVE = "${pkgs.glibcLocales}/lib/locale/locale-archive"; + shellHook = '' function dev-help { echo -e "\nWelcome to a GRASS development environment !" diff --git a/gui/wxpython/core/utils.py b/gui/wxpython/core/utils.py index 3c1b4a7dd14..2b1f3f4997a 100644 --- a/gui/wxpython/core/utils.py +++ b/gui/wxpython/core/utils.py @@ -20,6 +20,7 @@ import re import inspect import operator +from string import digits from grass.script import core as grass from grass.script import task as gtask @@ -936,7 +937,7 @@ def SetAddOnPath(addonPath=None, key="PATH"): def color_resolve(color): - if len(color) > 0 and color[0] in "0123456789": + if len(color) > 0 and color[0] in digits: rgb = tuple(map(int, color.split(":"))) label = color else: diff --git a/gui/wxpython/gmodeler/g.gui.gmodeler.html b/gui/wxpython/gmodeler/g.gui.gmodeler.html index 631c06de9d8..a840768abeb 100644 --- a/gui/wxpython/gmodeler/g.gui.gmodeler.html +++ b/gui/wxpython/gmodeler/g.gui.gmodeler.html @@ -466,7 +466,7 @@

SEE ALSO

See also selected user models available from -GRASS Addons repository. +GRASS Addons repository.

See also diff --git a/gui/wxpython/gui_core/goutput.py b/gui/wxpython/gui_core/goutput.py index 3c8927f2233..6676eb69935 100644 --- a/gui/wxpython/gui_core/goutput.py +++ b/gui/wxpython/gui_core/goutput.py @@ -20,6 +20,7 @@ """ import textwrap +from string import digits import wx from wx import stc @@ -624,7 +625,7 @@ def AddStyledMessage(self, message, style=None): self.linePos = self.GetCurrentPos() if c != " ": last_c = c - if last_c not in ("0123456789"): + if last_c not in (digits): self.AddTextWrapped("\n", wrap=None) self.linePos = -1 else: diff --git a/gui/wxpython/lmgr/frame.py b/gui/wxpython/lmgr/frame.py index 2413d3b7c4a..963a36e3a58 100644 --- a/gui/wxpython/lmgr/frame.py +++ b/gui/wxpython/lmgr/frame.py @@ -1135,7 +1135,7 @@ def GetMenuCmd(self, event): layer = self.GetLayerTree().layer_selected name = self.GetLayerTree().GetLayerInfo(layer, key="maplayer").name type = self.GetLayerTree().GetLayerInfo(layer, key="type") - except AttributeError: + except (AttributeError, TypeError): layer = None if layer and len(cmdlist) == 1: # only if no parameters given @@ -1183,7 +1183,7 @@ def OnVDigit(self, event): # available only for vector map layers try: mapLayer = tree.GetLayerInfo(layer, key="maplayer") - except AttributeError: + except (AttributeError, TypeError): mapLayer = None if not mapLayer or mapLayer.GetType() != "vector": @@ -1860,7 +1860,7 @@ def OnShowAttributeTable(self, event, selection=None): # available only for vector map layers try: maptype = tree.GetLayerInfo(layer, key="maplayer").type - except AttributeError: + except (AttributeError, TypeError): maptype = None if not maptype or maptype != "vector": diff --git a/gui/wxpython/lmgr/giface.py b/gui/wxpython/lmgr/giface.py index eac9a7fb2d8..67669039d5d 100644 --- a/gui/wxpython/lmgr/giface.py +++ b/gui/wxpython/lmgr/giface.py @@ -54,7 +54,9 @@ def __init__(self, tree): self._tree = tree def __len__(self): - return len(list(self)) + # The list constructor calls __len__ as an optimization if available, + # causing a RecursionError + return len([layer for layer in self]) # noqa: C416 def __iter__(self): """Iterates over the contents of the list.""" diff --git a/gui/wxpython/lmgr/layertree.py b/gui/wxpython/lmgr/layertree.py index 86531bcb5ad..855896e35ab 100644 --- a/gui/wxpython/lmgr/layertree.py +++ b/gui/wxpython/lmgr/layertree.py @@ -1758,7 +1758,7 @@ def OnDeleteLayer(self, event): try: if self.GetLayerInfo(item, key="type") != "group": self.Map.DeleteLayer(self.GetLayerInfo(item, key="maplayer")) - except AttributeError: + except (AttributeError, TypeError): pass # redraw map if auto-rendering is enabled @@ -2399,7 +2399,7 @@ def __FindSubItemByName(self, item, value): while item and item.IsOk(): try: itemLayer = self.GetLayerInfo(item, key="maplayer") - except KeyError: + except (KeyError, TypeError): return None if itemLayer and value == itemLayer.GetName(): diff --git a/gui/wxpython/lmgr/workspace.py b/gui/wxpython/lmgr/workspace.py index 774c22bf4ed..b1e7aa400f0 100644 --- a/gui/wxpython/lmgr/workspace.py +++ b/gui/wxpython/lmgr/workspace.py @@ -519,12 +519,20 @@ def CreateRecentFilesMenu(self, menu=None): :return None """ if menu: - file_menu = menu.GetMenu( - menuIndex=menu.FindMenu(title=_("File")), - ) - workspace_item = file_menu.FindItem( - id=file_menu.FindItem(itemString=_("Workspace")), - )[0] + menu_index = menu.FindMenu(_("File")) + if menu_index == wx.NOT_FOUND: + # try untranslated version + menu_index = menu.FindMenu("File") + if menu_index == wx.NOT_FOUND: + return + file_menu = menu.GetMenu(menu_index) + workspace_index = file_menu.FindItem(_("Workspace")) + if workspace_index == wx.NOT_FOUND: + workspace_index = file_menu.FindItem("Workspace") + if workspace_index == wx.NOT_FOUND: + return + workspace_item = file_menu.FindItemById(workspace_index) + self._recent_files = RecentFilesMenu( app_name="main", parent_menu=workspace_item.GetSubMenu(), diff --git a/gui/wxpython/location_wizard/wizard.py b/gui/wxpython/location_wizard/wizard.py index d8b6b4180ba..1f8de66dda6 100644 --- a/gui/wxpython/location_wizard/wizard.py +++ b/gui/wxpython/location_wizard/wizard.py @@ -1604,9 +1604,12 @@ def __init__(self, wizard, parent): self, data=None, columns=[_("Code"), _("Description"), _("Parameters")] ) - # epsg.io hyperlink + # A hyperlink to a CRS database (PROJ related) self.tlink = HyperlinkCtrl( - self, id=wx.ID_ANY, label="epsg.io", url="https://epsg.io/" + self, + id=wx.ID_ANY, + label="spatialreference.org", + url="https://spatialreference.org/", ) self.tlink.SetNormalColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) self.tlink.SetVisitedColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) @@ -1688,14 +1691,14 @@ def EnableNext(self, enable=True): def OnTextChange(self, event): value = self.searchb.GetValue() if value == "": - self.tlink.SetURL("https://epsg.io/") + self.tlink.SetURL("https://spatialreference.org/") self.epsgcode = None self.epsgdesc = self.epsgparams = "" self.searchb.ChangeValue("") self.OnBrowseCodes(None) self.EnableNext(False) else: - self.tlink.SetURL(str("https://epsg.io/?q={0}".format(value))) + self.tlink.SetURL(f"https://spatialreference.org/ref/?&search={value}") data = self.epsglist.Search(index=[0, 1, 2], pattern=value, firstOnly=False) if data: index = 0 diff --git a/gui/wxpython/modules/histogram.py b/gui/wxpython/modules/histogram.py index 92510406ac7..c8001858dff 100644 --- a/gui/wxpython/modules/histogram.py +++ b/gui/wxpython/modules/histogram.py @@ -496,7 +496,6 @@ def SaveToFile(self, event): def PrintMenu(self, event): """Print options and output menu""" - point = wx.GetMousePosition() printmenu = Menu() # Add items to the menu setup = wx.MenuItem(printmenu, id=wx.ID_ANY, text=_("Page setup")) diff --git a/gui/wxpython/modules/import_export.py b/gui/wxpython/modules/import_export.py index f2f70e47831..c3106d0467b 100644 --- a/gui/wxpython/modules/import_export.py +++ b/gui/wxpython/modules/import_export.py @@ -18,9 +18,11 @@ @author Martin Landa @author Anna Kratochvilova (GroupDialog, SymbolDialog) +@author William Welch (commands running queue) """ import os +from collections import deque from pathlib import Path @@ -60,6 +62,8 @@ def __init__( self.commandId = -1 # id of running command + self._commands_running = deque() + wx.Dialog.__init__( self, parent, id, title, style=style, name="MultiImportDialog" ) @@ -122,8 +126,9 @@ def __init__( # buttons # # cancel + self._DEFAULT_CLOSE_BTN_TEXT = _("Close dialog") self.btn_close = CloseButton(parent=self.panel) - self.btn_close.SetToolTip(_("Close dialog")) + self.btn_close.SetToolTip(_(self._DEFAULT_CLOSE_BTN_TEXT)) self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose) # run self.btn_run = Button(parent=self.panel, id=wx.ID_OK, label=_("&Import")) @@ -131,7 +136,7 @@ def __init__( self.btn_run.SetDefault() self.btn_run.Bind(wx.EVT_BUTTON, self.OnRun) - self.Bind(wx.EVT_CLOSE, lambda evt: self.Destroy()) + self.Bind(wx.EVT_CLOSE, self._handleCloseEvent) self.notebook = GNotebook(parent=self, style=globalvar.FNPageDStyle) @@ -270,6 +275,33 @@ def _validateOutputMapName(self): return False return True + def _handleCloseEvent(self, event: wx.CloseEvent): + if event.CanVeto() and len(self._commands_running) > 0: + # prevent dialog close while async commands are running + event.Veto() + return + self.Destroy() + + def _addToCommandQueue(self): + self._commands_running.append(True) + + # disable the dialog close button + self.btn_close.SetToolTip( + _("Dialog cannot be closed while command is running.") + ) + self.btn_close.Disable() + + def _removeFromCommandQueue(self): + try: + self._commands_running.pop() + except IndexError: + pass + finally: + if len(self._commands_running) == 0: + # enable the dialog close button + self.btn_close.Enable() + self.btn_close.SetToolTip(self._DEFAULT_CLOSE_BTN_TEXT) + def OnClose(self, event=None): """Close dialog""" self.Close() @@ -461,7 +493,6 @@ def OnRun(self, event): dsn = self.dsnInput.GetDsn() if not dsn: return - ext = self.dsnInput.GetFormatExt() for layer, output, listId in data: userData = {} @@ -519,6 +550,7 @@ def OnRun(self, event): ): cmd.append("--overwrite") + self._addToCommandQueue() # run in Layer Manager self._giface.RunCmd( cmd, onDone=self.OnCmdDone, userData=userData, addLayer=False @@ -527,9 +559,11 @@ def OnRun(self, event): def OnCmdDone(self, event): """Load layers and close if required""" if not hasattr(self, "AddLayers"): + self._removeFromCommandQueue() return self.AddLayers(event.returncode, event.cmd, event.userData) + self._removeFromCommandQueue() if event.returncode == 0 and self.closeOnFinish.IsChecked(): self.Close() @@ -613,7 +647,7 @@ def OnRun(self, event): return if not data: - GMessage(_("No layers selected. Operation canceled."), parent=self) + GMessage(parent=self, message=_("No layers selected. Operation canceled.")) return if not self._validateOutputMapName(): @@ -644,13 +678,7 @@ def OnRun(self, event): if ext and layer.rfind(ext) > -1: layer = layer.replace("." + ext, "") if "|" in layer: - layer, geometry = layer.split("|", 1) - else: - geometry = None - - # TODO: v.import has no geometry option - # if geometry: - # cmd.append('geometry=%s' % geometry) + layer = layer.split("|", 1)[0] cmd = self.getSettingsPageCmd() cmd.append("input=%s" % dsn) @@ -670,6 +698,7 @@ def OnRun(self, event): ): cmd.append("--overwrite") + self._addToCommandQueue() # run in Layer Manager self._giface.RunCmd( cmd, onDone=self.OnCmdDone, userData=userData, addLayer=False @@ -678,10 +707,13 @@ def OnRun(self, event): def OnCmdDone(self, event): """Load layers and close if required""" if not hasattr(self, "AddLayers"): + self._removeFromCommandQueue() return self.AddLayers(event.returncode, event.cmd, event.userData) + self._removeFromCommandQueue() + if self.popOGR: os.environ.pop("GRASS_VECTOR_OGR") @@ -880,16 +912,20 @@ def OnRun(self, event): ): cmd.append("--overwrite") + self._commands_running.append(True) # run in Layer Manager self._giface.RunCmd(cmd, onDone=self.OnCmdDone, addLayer=False) def OnCmdDone(self, event): """Load layers and close if required""" if not hasattr(self, "AddLayers"): + self._removeFromCommandQueue() return self.AddLayers(event.returncode, event.cmd) + self._removeFromCommandQueue() + if self.closeOnFinish.IsChecked(): self.Close() diff --git a/gui/wxpython/modules/mapsets_picker.py b/gui/wxpython/modules/mapsets_picker.py index e7ea7a3e40a..73df8fde3cc 100755 --- a/gui/wxpython/modules/mapsets_picker.py +++ b/gui/wxpython/modules/mapsets_picker.py @@ -12,7 +12,7 @@ def main(): - app = wx.App() + wx.App() dlg = MapsetAccess(parent=None) dlg.CenterOnScreen() diff --git a/gui/wxpython/nviz/mapwindow.py b/gui/wxpython/nviz/mapwindow.py index 8cf972704e2..1eea5b278a9 100644 --- a/gui/wxpython/nviz/mapwindow.py +++ b/gui/wxpython/nviz/mapwindow.py @@ -412,7 +412,7 @@ def OnPaint(self, event): Debug.msg(1, "GLCanvas.OnPaint()") self.render["overlays"] = True - dc = wx.PaintDC(self) + wx.PaintDC(self) self.DoPaint() def DoPaint(self): @@ -1438,7 +1438,7 @@ def UnloadDataLayers(self, force=False): GError(parent=self, message=e.value) if force and self.baseId > 0: # unload base surface when quitting - ret = self._display.UnloadSurface(self.baseId) + self._display.UnloadSurface(self.baseId) self.baseId = -1 if update: self.lmgr.nviz.UpdateSettings() @@ -2134,10 +2134,10 @@ def UpdateVolumeProperties(self, id, data, isosurfId=None): # sliceId = 0 for slice in data["slice"]: - ret = self._display.AddSlice(id, slice_id=sliceId) + self._display.AddSlice(id, slice_id=sliceId) if "update" in slice["position"]: pos = slice["position"] - ret = self._display.SetSlicePosition( + self._display.SetSlicePosition( id, sliceId, pos["x1"], diff --git a/gui/wxpython/nviz/tools.py b/gui/wxpython/nviz/tools.py index 20b1df1ffbd..7ced7d157e1 100644 --- a/gui/wxpython/nviz/tools.py +++ b/gui/wxpython/nviz/tools.py @@ -667,7 +667,6 @@ def _createAnimationPage(self): parent=panel, id=wx.ID_ANY, label=" %s " % (_("Save image sequence")) ) boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL) - vSizer = wx.BoxSizer(wx.VERTICAL) gridSizer = wx.GridBagSizer(vgap=5, hgap=10) pwd = str(Path.cwd()) @@ -1197,33 +1196,6 @@ def _createSurfacePage(self, parent): flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=3, ) - # - # mask - # - # box = wx.StaticBox (parent = panel, id = wx.ID_ANY, - # label = " %s " % (_("Mask"))) - ## boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL) - ## gridSizer = wx.GridBagSizer(vgap = 5, hgap = 5) - ## - # gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, - # label = _("Mask zeros:")), - # pos = (0, 0), flag = wx.ALIGN_CENTER_VERTICAL) - ## - # elev = wx.CheckBox(parent = panel, id = wx.ID_ANY, - # label = _("by elevation")) - # elev.Enable(False) # TODO: not implemented yet - ## gridSizer.Add(item = elev, pos = (0, 1)) - ## - # color = wx.CheckBox(parent = panel, id = wx.ID_ANY, - # label = _("by color")) - # color.Enable(False) # TODO: not implemented yet - ## gridSizer.Add(item = color, pos = (0, 2)) - ## - # boxSizer.Add(item = gridSizer, proportion = 1, - # flag = wx.ALL | wx.EXPAND, border = 3) - # pageSizer.Add(item = boxSizer, proportion = 0, - ## flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, - # border = 3) panel.SetSizer(pageSizer) @@ -1853,24 +1825,6 @@ def _createVectorPage(self, parent): icolor.Bind(csel.EVT_COLOURSELECT, self.OnVectorPoints) gridSizer.Add(icolor, flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT, pos=(0, 4)) - # icon width - seems to do nothing - # gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, - # label = _("width")), - # pos = (1, 1), flag = wx.ALIGN_CENTER_VERTICAL | - # wx.ALIGN_RIGHT) - ## - # iwidth = wx.SpinCtrl(parent = panel, id = wx.ID_ANY, size = (65, -1), - ## initial = 1, - ## min = 1, - # max = 1e6) - # iwidth.SetName('value') - # iwidth.SetValue(100) - ## self.win['vector']['points']['width'] = iwidth.GetId() - ## iwidth.Bind(wx.EVT_SPINCTRL, self.OnVectorPoints) - ## iwidth.Bind(wx.EVT_TEXT, self.OnVectorPoints) - # gridSizer.Add(item = iwidth, pos = (1, 2), - # flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT) - # icon symbol gridSizer.Add( StaticText(parent=panel, id=wx.ID_ANY, label=_("symbol:")), pos=(0, 5), @@ -2817,7 +2771,6 @@ def OnAnimationFinished(self, mode): self.UpdateFrameIndex(index=0) slider = self.FindWindowById(self.win["anim"]["frameIndex"]["slider"]) - text = self.FindWindowById(self.win["anim"]["frameIndex"]["text"]) if mode == "record": count = anim.GetFrameCount() @@ -2922,7 +2875,7 @@ def OnConstantSelection(self, event): layerIdx = self.FindWindowById(self.win["constant"]["surface"]).GetSelection() if layerIdx == wx.NOT_FOUND: return - name = _("constant#") + str(layerIdx + 1) + data = self.mapWindow.constants[layerIdx] for attr, value in data["constant"].items(): if attr == "color": @@ -3086,7 +3039,6 @@ def _createIsosurfacePanel(self, parent): ) value.Bind(wx.EVT_TEXT_ENTER, self.OnVolumeIsosurfMap) value.Bind(wx.EVT_KILL_FOCUS, self.OnVolumeIsosurfMap) - ## value.Bind(wx.EVT_TEXT, self.OnVolumeIsosurfMap) else: size = (65, -1) value = SpinCtrl(parent=panel, id=wx.ID_ANY, size=size, initial=0) @@ -3123,8 +3075,6 @@ def _createIsosurfacePanel(self, parent): def _createSlicePanel(self, parent): panel = wx.Panel(parent=parent, id=wx.ID_ANY) - vSizer = wx.BoxSizer(wx.HORIZONTAL) - box = StaticBox( parent=panel, id=wx.ID_ANY, label=" %s " % (_("Slice attributes")) ) @@ -3412,12 +3362,11 @@ def OnSetSurface(self, event): """Surface selected, currently used for fringes""" name = event.GetString() try: - data = self._getLayerPropertiesByName(name, mapType="raster")["surface"] + self._getLayerPropertiesByName(name, mapType="raster")["surface"] except: self.EnablePage("fringe", False) return - layer = self._getMapLayerByName(name, mapType="raster") self.EnablePage("fringe", True) def OnSetRaster(self, event): @@ -3425,7 +3374,7 @@ def OnSetRaster(self, event): name = event.GetString() try: data = self._getLayerPropertiesByName(name, mapType="raster")["surface"] - except TypeError as e: + except TypeError: self.EnablePage("surface", False) return @@ -4590,10 +4539,6 @@ def OnVolumeSelect(self, event): if not winUp.IsEnabled(): winUp.Enable() - # update dialog - name = self.FindWindowById(self.win["volume"]["map"]).GetValue() - layer = self._getMapLayerByName(name, mapType="raster_3d") - if mode == "isosurf": data = self.GetLayerData("volume")["volume"]["isosurface"][selection] self.UpdateVolumeIsosurfPage(data) @@ -4694,8 +4639,6 @@ def OnVolumeDelete(self, event): if list.GetCount() > 0: list.SetSelection(list.GetCount() - 1) - name = self.FindWindowById(self.win["volume"]["map"]).GetValue() - layer = self._getMapLayerByName(name, mapType="raster_3d") data = self.GetLayerData("volume")["volume"] vid = data["object"]["id"] @@ -4736,8 +4679,6 @@ def OnVolumeMoveUp(self, event): if sel < 1: return # this should not happen - name = self.FindWindowById(self.win["volume"]["map"]).GetValue() - layer = self._getMapLayerByName(name, mapType="raster_3d") data = self.GetLayerData("volume")["volume"] id = data["object"]["id"] @@ -4776,8 +4717,6 @@ def OnVolumeMoveDown(self, event): if sel >= list.GetCount() - 1: return # this should not happen - name = self.FindWindowById(self.win["volume"]["map"]).GetValue() - layer = self._getMapLayerByName(name, mapType="raster_3d") data = self.GetLayerData("volume")["volume"] id = data["object"]["id"] diff --git a/gui/wxpython/psmap/dialogs.py b/gui/wxpython/psmap/dialogs.py index 66ac66d9348..0bbe779818a 100644 --- a/gui/wxpython/psmap/dialogs.py +++ b/gui/wxpython/psmap/dialogs.py @@ -42,9 +42,8 @@ import wx import wx.lib.agw.floatspin as fs -from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin - from core import globalvar +from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin if globalvar.wxPythonPhoenix: from wx import Validator @@ -52,24 +51,25 @@ from wx import PyValidator as Validator import grass.script as gs - -from core.utils import PilImageToWxImage +from core.gcmd import GError, GMessage, RunCommand +from core.utils import PilImageToWxImage, cmp from dbmgr.vinfo import VectorDBInfo -from gui_core.gselect import Select -from core.gcmd import RunCommand, GError, GMessage from gui_core.dialogs import SymbolDialog +from gui_core.gselect import Select from gui_core.wrap import ( BitmapButton, BitmapComboBox, BitmapFromImage, Button, CheckBox, + CheckListCtrlMixin, Choice, ClientDC, ColourPickerCtrl, Dialog, DirBrowseButton, EmptyBitmap, + EmptyImage, ExpandoTextCtrl, FileBrowseButton, FloatSpin, @@ -86,11 +86,44 @@ StaticText, TextCtrl, TextEntryDialog, - EmptyImage, - CheckListCtrlMixin, ) -from psmap.utils import * -from psmap.instructions import * + +# Explicit imports from psmap.instructions +from psmap.instructions import ( + Image, + Labels, + Line, + MapFrame, + Mapinfo, + NewId, + NorthArrow, + Point, + Raster, + RasterLegend, + Rectangle, + Scalebar, + Text, + Vector, + VectorLegend, + VProperties, +) + +# Explicit imports from psmap.utils +from psmap.utils import ( + AutoAdjust, + BBoxAfterRotation, + ComputeSetRegion, + PaperMapCoordinates, + PILImage, + Rect2D, + Rect2DPP, + SetResolution, + UnitConversion, + convertRGB, + getRasterType, + havePILImage, + projInfo, +) # grass.set_raise_on_error(True) diff --git a/gui/wxpython/psmap/frame.py b/gui/wxpython/psmap/frame.py index b3196d348fa..13fdc91a984 100644 --- a/gui/wxpython/psmap/frame.py +++ b/gui/wxpython/psmap/frame.py @@ -16,10 +16,9 @@ """ import os -import sys - import queue as Queue -from math import sin, cos, pi, sqrt +import sys +from math import cos, pi, sin, sqrt import wx @@ -29,25 +28,51 @@ import wx.lib.flatnotebook as FN import grass.script as gs - from core import globalvar -from gui_core.menu import Menu -from core.gconsole import CmdThread, EVT_CMD_DONE -from psmap.toolbars import PsMapToolbar -from core.gcmd import RunCommand, GError, GMessage +from core.gcmd import GError, GMessage, RunCommand +from core.gconsole import EVT_CMD_DONE, CmdThread from core.settings import UserSettings from core.utils import PilImageToWxImage -from gui_core.forms import GUI -from gui_core.widgets import GNotebook from gui_core.dialogs import HyperlinkDialog +from gui_core.forms import GUI from gui_core.ghelp import ShowAboutDialog -from gui_core.wrap import ClientDC, PseudoDC, Rect, StockCursor, EmptyBitmap -from psmap.menudata import PsMapMenuData +from gui_core.menu import Menu from gui_core.toolbars import ToolSwitcher - -from psmap.dialogs import * -from psmap.instructions import * -from psmap.utils import * +from gui_core.widgets import GNotebook +from gui_core.wrap import ClientDC, EmptyBitmap, PseudoDC, Rect, StockCursor + +from psmap.dialogs import ( + ImageDialog, + LabelsDialog, + LegendDialog, + MainVectorDialog, + MapDialog, + MapinfoDialog, + NorthArrowDialog, + PageSetupDialog, + PointDialog, + RasterDialog, + RectangleDialog, + ScalebarDialog, + TextDialog, +) +from psmap.instructions import InitMap, Instruction, NewId, SetResolution, PageSetup +from psmap.menudata import PsMapMenuData +from psmap.toolbars import PsMapToolbar +from psmap.utils import ( + AutoAdjust, + ComputeSetRegion, + GetMapBounds, + PaperMapCoordinates, + PILImage, + Rect2D, + Rect2DPP, + Rect2DPS, + UnitConversion, + convertRGB, + havePILImage, + projInfo, +) class PsMapFrame(wx.Frame): diff --git a/gui/wxpython/psmap/instructions.py b/gui/wxpython/psmap/instructions.py index 9db64679422..845e99b2e6a 100644 --- a/gui/wxpython/psmap/instructions.py +++ b/gui/wxpython/psmap/instructions.py @@ -35,17 +35,27 @@ import os import string from math import ceil -from time import strftime, localtime +from time import localtime, strftime -import wx import grass.script as gs -from grass.script.task import cmdlist_to_tuple - +import wx from core.gcmd import GError, GMessage, GWarning from core.utils import GetCmdString from dbmgr.vinfo import VectorDBInfo +from grass.script.task import cmdlist_to_tuple from gui_core.wrap import NewId as wxNewId -from psmap.utils import * + +from psmap.utils import ( # Add any additional required names from psmap.utils here + BBoxAfterRotation, + GetMapBounds, + PaperMapCoordinates, + Rect2D, + Rect2DPP, + SetResolution, + UnitConversion, + getRasterType, + projInfo, +) def NewId(): diff --git a/gui/wxpython/vnet/dialogs.py b/gui/wxpython/vnet/dialogs.py index 39cf2e4ae84..8d24b08df91 100644 --- a/gui/wxpython/vnet/dialogs.py +++ b/gui/wxpython/vnet/dialogs.py @@ -1245,8 +1245,6 @@ def __init__( wx.Dialog.__init__(self, parent, id, title, pos, size, style) self.vnet_mgr = vnet_mgr - - maxValue = 1e8 self.parent = parent self.settings = {} diff --git a/gui/wxpython/vnet/toolbars.py b/gui/wxpython/vnet/toolbars.py index 5f80e50cd9d..d31313091fe 100644 --- a/gui/wxpython/vnet/toolbars.py +++ b/gui/wxpython/vnet/toolbars.py @@ -235,6 +235,5 @@ def __init__(self, parent, vnet_mgr): self.Realize() def _toolbarData(self): - icons = {} return self._getToolbarData(()) diff --git a/gui/wxpython/vnet/vnet_core.py b/gui/wxpython/vnet/vnet_core.py index 8dda73b6770..2e98aa2ee8f 100644 --- a/gui/wxpython/vnet/vnet_core.py +++ b/gui/wxpython/vnet/vnet_core.py @@ -459,11 +459,6 @@ def _vnetPathRunAn(self, analysis, output, params, flags, catPts): return False mapName, mapSet = ParseMapStr(self.tmpTurnAn.GetVectMapName()) - cmdCopy = [ - "g.copy", - "vector=%s,%s" % (params["input"], mapName), - "--overwrite", - ] cmdParams.append("input=" + self.tmpTurnAn.GetVectMapName()) ret, msg, err = RunCommand( @@ -550,11 +545,6 @@ def _runTurnsAn(self, analysis, output, params, flags, catPts): # create and run commands which goes to analysis thread mapName, mapSet = ParseMapStr(self.tmpTurnAn.GetVectMapName()) - cmdCopy = [ - "g.copy", - "vector=%s,%s" % (params["input"], mapName), - "--overwrite", - ] cmdParams.append("input=" + self.tmpTurnAn.GetVectMapName()) ret, msg, err = RunCommand( @@ -580,14 +570,6 @@ def _updateTtbByGlobalCosts(self, vectMapName, tlayer): # TODO get layer number do not use it directly intervals = self.turnsData["global"].GetData() - cmdUpdGlob = [ - "v.db.update", - "map=", - self.inputData["input"].GetValue(), - "layer=%d" % tlayer, - "column=cost", - ] - dbInfo = VectorDBInfo(vectMapName) table = dbInfo.GetTable(tlayer) driver, database = dbInfo.GetDbSettings(tlayer) diff --git a/gui/wxpython/vnet/vnet_data.py b/gui/wxpython/vnet/vnet_data.py index 3adc5fa64e9..f2f17b0bfb5 100644 --- a/gui/wxpython/vnet/vnet_data.py +++ b/gui/wxpython/vnet/vnet_data.py @@ -1377,7 +1377,7 @@ def SetValue(self, value, line, col): def SetUTurns(self, value): """Checked if checeBox is checed""" - useUTurns = value + self.useUTurns = value def AppendRow(self, values): self.turn_data.append(values) diff --git a/gui/wxpython/vnet/vnet_utils.py b/gui/wxpython/vnet/vnet_utils.py index f0a354691bc..e3cc11aee17 100644 --- a/gui/wxpython/vnet/vnet_utils.py +++ b/gui/wxpython/vnet/vnet_utils.py @@ -133,7 +133,6 @@ def GetNearestNodeCat(e, n, layer, tresh, vectMap): vectlib.Vect_select_lines_by_box(openedMap, byref(box), vectlib.GV_POINT, List) found = 0 - dcost = 0 Cats = POINTER(vectlib.line_cats) Cats = vectlib.Vect_new_cats_struct() diff --git a/gui/wxpython/vnet/widgets.py b/gui/wxpython/vnet/widgets.py index ed1f2aa514c..71fcd0e0739 100644 --- a/gui/wxpython/vnet/widgets.py +++ b/gui/wxpython/vnet/widgets.py @@ -217,7 +217,6 @@ def GetCellValue(self, key, colName): if colNum < 0: return None - iColEd = self.dataTypes["colEditable"] if self.selIdxs[key][colNum] != -1: return self.selIdxs[key][colNum] @@ -230,7 +229,6 @@ def GetCellSelIdx(self, key, colName): :return: -1 if column does not has values to choose """ colNum = self._getColumnNum(colName) - iColEd = self.dataTypes["colEditable"] return self.selIdxs[key][colNum] def EditCellIndex(self, index, colName, cellData): diff --git a/gui/wxpython/wxgui.py b/gui/wxpython/wxgui.py index 447821c6724..8fac39c4d5a 100644 --- a/gui/wxpython/wxgui.py +++ b/gui/wxpython/wxgui.py @@ -164,7 +164,7 @@ def main(argv=None): app = GMApp(workspaceFile) # suppress wxPython logs - q = wx.LogNull() + wx.LogNull() set_raise_on_error(True) # register GUI PID diff --git a/imagery/i.atcorr/computations.cpp b/imagery/i.atcorr/computations.cpp index 53f8664ee06..6e5a38c5b07 100644 --- a/imagery/i.atcorr/computations.cpp +++ b/imagery/i.atcorr/computations.cpp @@ -102,14 +102,14 @@ double trunca() for (i = 0; i < 83; i++) { if (rmu[i] > 0.8) break; - k = i - 1; + k = i; } int kk = 0; for (i = 0; i < 83; i++) { if (rmu[i] > 0.94) break; - kk = i - 1; + kk = i; } double aa = @@ -118,7 +118,7 @@ double trunca() double x1 = (double)(log10(sixs_trunc.pha[kk])); double x2 = (double)acos(rmu[kk]); - for (i = kk + 1; i < 83; i++) { + for (i = kk; i < 83; i++) { double a; if (fabs(rmu[i] - 1) <= 1e-08) a = x1 - aa * x2; @@ -445,9 +445,14 @@ void os(const double tamoy, const double trmoy, const double pizmoy, /* compute position of the plane layer */ double taup = tap + trp; iplane = -1; - for (int i = 0; i <= ntp; i++) + for (int i = 0; i <= ntp; i++) { if (taup >= h[i]) iplane = i; + } + if (iplane == -1) { + G_fatal_error( + _("Position of the plane layer could not be determined")); + } /* update the layer from the end to the position to update if necessary */ @@ -1006,9 +1011,14 @@ void iso(const double tamoy, const double trmoy, const double pizmoy, /* compute position of the plane layer */ double taup = tap + trp; iplane = -1; - for (int i = 0; i <= ntp; i++) + for (int i = 0; i <= ntp; i++) { if (taup >= h[i]) iplane = i; + } + if (iplane == -1) { + G_fatal_error( + _("Position of the plane layer could not be determined")); + } /* update the layer from the end to the position to update if necessary */ diff --git a/imagery/i.fft/main.c b/imagery/i.fft/main.c index ad9a87a7890..051428b77ef 100644 --- a/imagery/i.fft/main.c +++ b/imagery/i.fft/main.c @@ -105,8 +105,9 @@ int main(int argc, char *argv[]) inputfd = Rast_open_old(Cellmap_orig, ""); if (Rast_maskfd() >= 0) - G_warning(_("Raster MASK found, consider to remove " - "(see man-page). Will continue...")); + G_warning(_("Raster mask active, consider removing it" + " and running again without it (see documentation for" + " details). This current process will now continue...")); G_get_set_window(&window); /* get the current window for later */ diff --git a/imagery/i.gensig/testsuite/test_i_gensig.py b/imagery/i.gensig/testsuite/test_i_gensig.py index d75e3da2af4..836550fd1e6 100644 --- a/imagery/i.gensig/testsuite/test_i_gensig.py +++ b/imagery/i.gensig/testsuite/test_i_gensig.py @@ -92,9 +92,13 @@ def tearDownClass(cls): """Remove the temporary region and generated data""" cls.del_temp_region() shutil.rmtree(cls.sig_dir1, ignore_errors=True) - cls.runModule("g.remove", flags="f", type="raster", name=cls.b1, quiet=True) - cls.runModule("g.remove", flags="f", type="raster", name=cls.b2, quiet=True) - cls.runModule("g.remove", flags="f", type="raster", name=cls.train, quiet=True) + cls.runModule( + "g.remove", + flags="f", + type="raster", + name=(cls.b1, cls.b2, cls.train), + quiet=True, + ) def test_creation(self): """Test creating a signature""" diff --git a/imagery/i.maxlik/testsuite/test_i_maxlik.py b/imagery/i.maxlik/testsuite/test_i_maxlik.py index e7fbf6c1a79..2245d0d6d58 100644 --- a/imagery/i.maxlik/testsuite/test_i_maxlik.py +++ b/imagery/i.maxlik/testsuite/test_i_maxlik.py @@ -163,13 +163,12 @@ def tearDownClass(cls): cls.del_temp_region() shutil.rmtree(cls.sig_dir1, ignore_errors=True) shutil.rmtree(cls.sig_dir2, ignore_errors=True) - cls.runModule("g.remove", flags="f", type="raster", name=cls.b1, quiet=True) - cls.runModule("g.remove", flags="f", type="raster", name=cls.b2, quiet=True) cls.runModule( - "g.remove", flags="f", type="raster", name=cls.v1_class, quiet=True - ) - cls.runModule( - "g.remove", flags="f", type="raster", name=cls.v2_class, quiet=True + "g.remove", + flags="f", + type="raster", + name=(cls.b1, cls.b2, cls.v1_class, cls.v2_class), + quiet=True, ) cls.runModule("g.remove", flags="f", type="group", name=cls.group, quiet=True) diff --git a/imagery/i.pca/i.pca.html b/imagery/i.pca/i.pca.html index 17b3f5ed163..6fee184c618 100644 --- a/imagery/i.pca/i.pca.html +++ b/imagery/i.pca/i.pca.html @@ -11,7 +11,7 @@

DESCRIPTION

principal component with the highest importance.

-The current geographic region definition and MASK settings are +The current geographic region definition and raster mask settings are respected when reading the input raster map layers. When the rescale option is used, the output files are rescaled to fit the min,max range. diff --git a/imagery/i.segment/i.segment.html b/imagery/i.segment/i.segment.html index 1e2596c1d7e..e30f21d3743 100644 --- a/imagery/i.segment/i.segment.html +++ b/imagery/i.segment/i.segment.html @@ -118,7 +118,7 @@

Mean shift

Boundary Constraints

Boundary constraints limit the adjacency of pixels and segments. Each unique value present in the bounds raster are -considered as a MASK. Thus no segments in the final segmentated map +considered as a mask. Thus, no segments in the final segmented map will cross a boundary, even if their spectral data is very similar.

Minimum Segment Size

diff --git a/imagery/i.segment/iseg.h b/imagery/i.segment/iseg.h index 1e301d2cefa..a8dfb5542c3 100644 --- a/imagery/i.segment/iseg.h +++ b/imagery/i.segment/iseg.h @@ -118,7 +118,7 @@ struct globals { /* processing flags */ FLAG *candidate_flag, - *null_flag; /*TODO, need some way to remember MASK/NULL values. Was + *null_flag; /*TODO, need some way to remember mask/NULL values. Was using -1, 0, 1 in int array. Better to use 2 FLAG structures, better readability? */ diff --git a/imagery/i.vi/testsuite/test_vi.py b/imagery/i.vi/testsuite/test_vi.py index 0c383417257..b2160f924ed 100644 --- a/imagery/i.vi/testsuite/test_vi.py +++ b/imagery/i.vi/testsuite/test_vi.py @@ -26,14 +26,12 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - cls.runModule("g.remove", flags="f", type="raster", name="ipvi") - cls.runModule("g.remove", flags="f", type="raster", name="ndwi") - cls.runModule("g.remove", flags="f", type="raster", name="dvi") - cls.runModule("g.remove", flags="f", type="raster", name="sr") - cls.runModule("g.remove", flags="f", type="raster", name="evi") - cls.runModule("g.remove", flags="f", type="raster", name="evi2") - cls.runModule("g.remove", flags="f", type="raster", name="gari") - cls.runModule("g.remove", flags="f", type="raster", name="gemi") + cls.runModule( + "g.remove", + flags="f", + type="raster", + name="ipvi,ndwi,dvi,sr,evi,evi2,gari,gemi", + ) cls.del_temp_region() def test_vinameipvi(self): diff --git a/imagery/imageryintro.html b/imagery/imageryintro.html index 4b80951af9e..9018b4871ae 100644 --- a/imagery/imageryintro.html +++ b/imagery/imageryintro.html @@ -2,6 +2,21 @@

Image processing in general

+GRASS GIS provides a powerful suite of tools for the processing and +analysis of geospatial raster data, including satellite imagery and +aerial photography. Its image processing capabilities encompass a broad +range of preprocessing operations, such as data import, georeferencing, +radiometric calibration, and atmospheric correction. It is particularly +suited for handling Earth observation data, enabling the analysis of +multispectral and temporal datasets. GRASS GIS supports advanced +functionalities such as image classification, sensor fusion, and point +cloud statistics. The calculation of vegetation indices further enables +analyses of environmental, agricultural, and land cover dynamics. An +extensive collection of +addons +further enhances its image processing capabilities, particularly in the +context of Earth observation and remote sensing applications. + Digital numbers and physical values (reflection/radiance-at-sensor):

Satellite imagery is commonly stored in Digital Numbers (DN) for diff --git a/lib/imagery/testsuite/test_imagery_sigfile.py b/lib/imagery/testsuite/test_imagery_sigfile.py index 8d0e288561d..731ef047063 100644 --- a/lib/imagery/testsuite/test_imagery_sigfile.py +++ b/lib/imagery/testsuite/test_imagery_sigfile.py @@ -349,9 +349,12 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): cls.del_temp_region() - cls.runModule("g.remove", flags="f", type="raster", name=cls.map1) - cls.runModule("g.remove", flags="f", type="raster", name=cls.map2) - cls.runModule("g.remove", flags="f", type="raster", name=cls.map3) + cls.runModule( + "g.remove", + flags="f", + type="raster", + name=(cls.map1, cls.map2, cls.map3), + ) def test_symmetric_complete_difference(self): # Prepare imagery group reference struct diff --git a/lib/imagery/testsuite/test_imagery_sigsetfile.py b/lib/imagery/testsuite/test_imagery_sigsetfile.py index d526e920885..80be7c0d3a5 100644 --- a/lib/imagery/testsuite/test_imagery_sigsetfile.py +++ b/lib/imagery/testsuite/test_imagery_sigsetfile.py @@ -242,9 +242,12 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): cls.del_temp_region() - cls.runModule("g.remove", flags="f", type="raster", name=cls.map1) - cls.runModule("g.remove", flags="f", type="raster", name=cls.map2) - cls.runModule("g.remove", flags="f", type="raster", name=cls.map3) + cls.runModule( + "g.remove", + flags="f", + type="raster", + name=(cls.map1, cls.map2, cls.map3), + ) def test_symmetric_complete_difference(self): # Prepare imagery group reference struct diff --git a/lib/init/helptext.html b/lib/init/helptext.html index d988b6b0376..4a14dd52df1 100644 --- a/lib/init/helptext.html +++ b/lib/init/helptext.html @@ -62,7 +62,7 @@

GRASS started in the default project, now what?

Creating a New project with the Project Wizard

If you know the CRS of your data or study area, -you can fill EPSG code +you can fill EPSG code or description and Project Wizard finds appropriate CRS from a predefined list of projections. @@ -97,4 +97,6 @@

See also

- List of EPSG codes (Database of worldwide coordinate systems) + List of EPSG codes (Database of worldwide coordinate systems), + CRS Explorer - PROJ codes, and + EPSG Geodetic Parameter Dataset diff --git a/lib/ogsf/gk.c b/lib/ogsf/gk.c index 3390598960a..b4fea635cc4 100644 --- a/lib/ogsf/gk.c +++ b/lib/ogsf/gk.c @@ -172,8 +172,8 @@ void gk_follow_frames(Viewnode *view, int numsteps, Keylist *keys, int step, GS_get_from(tmp); G_debug(3, "gk_follow_frames():"); - G_debug(3, " MASK: %lx", mask); - G_debug(3, " FROM: %f %f %f", tmp[X], tmp[Y], tmp[Z]); + G_debug(3, " mask: %lx", mask); + G_debug(3, " from: %f %f %f", tmp[X], tmp[Y], tmp[Z]); /* ACS 1 line: was GS_get_focus(tmp); with this kanimator works also for flythrough navigation diff --git a/lib/ogsf/gvd.c b/lib/ogsf/gvd.c index 6a6176526a2..84867123c5e 100644 --- a/lib/ogsf/gvd.c +++ b/lib/ogsf/gvd.c @@ -201,7 +201,7 @@ int gvd_vect(geovect *gv, geosurf *gs, int do_fast) } gsd_endline(); } - /* need to handle MASK! */ + /* need to handle mask! */ else if (src == CONST_ATT) { /* for now - but later, do seg intersect maskedge */ if (gs_point_is_masked(gs, bgn) || diff --git a/lib/raster/mask_info.c b/lib/raster/mask_info.c index 42102de86dd..317bab75b63 100644 --- a/lib/raster/mask_info.c +++ b/lib/raster/mask_info.c @@ -124,7 +124,7 @@ int Rast__mask_info(char *name, char *mapset) char rname[GNAME_MAX], rmapset[GMAPSET_MAX]; strcpy(rname, "MASK"); - strcpy(rmapset, G_mapset()); + (void)G_strlcpy(rmapset, G_mapset(), GMAPSET_MAX); if (!G_find_raster(rname, rmapset)) return -1; diff --git a/lib/vector/Vlib/geos.c b/lib/vector/Vlib/geos.c index 719e0fdd270..2c68a0a6fba 100644 --- a/lib/vector/Vlib/geos.c +++ b/lib/vector/Vlib/geos.c @@ -290,7 +290,7 @@ GEOSCoordSequence *V1_read_line_geos(struct Map_info *Map, long offset, long size; double *x, *y, *z; - GEOSCoordSequence *pseq; + GEOSCoordSequence *pseq = NULL; G_debug(3, "V1_read_line_geos(): offset = %ld", offset); @@ -353,8 +353,6 @@ GEOSCoordSequence *V1_read_line_geos(struct Map_info *Map, long offset, G_debug(3, " n_points = %d dim = %d", n_points, (Map->head.with_z) ? 3 : 2); - pseq = GEOSCoordSeq_create(n_points, (Map->head.with_z) ? 3 : 2); - x = (double *)G_malloc(n_points * sizeof(double)); y = (double *)G_malloc(n_points * sizeof(double)); if (Map->head.with_z) @@ -362,17 +360,22 @@ GEOSCoordSequence *V1_read_line_geos(struct Map_info *Map, long offset, else z = NULL; - if (0 >= dig__fread_port_D(x, n_points, &(Map->dig_fp))) - return NULL; /* end of file */ + if (0 >= dig__fread_port_D(x, n_points, &(Map->dig_fp))) { + goto free_return; /* end of file */ + } - if (0 >= dig__fread_port_D(y, n_points, &(Map->dig_fp))) - return NULL; /* end of file */ + if (0 >= dig__fread_port_D(y, n_points, &(Map->dig_fp))) { + goto free_return; /* end of file */ + } if (Map->head.with_z) { - if (0 >= dig__fread_port_D(z, n_points, &(Map->dig_fp))) - return NULL; /* end of file */ + if (0 >= dig__fread_port_D(z, n_points, &(Map->dig_fp))) { + goto free_return; /* end of file */ + } } + pseq = GEOSCoordSeq_create(n_points, (Map->head.with_z) ? 3 : 2); + for (i = 0; i < n_points; i++) { GEOSCoordSeq_setX(pseq, i, x[i]); GEOSCoordSeq_setY(pseq, i, y[i]); @@ -382,6 +385,7 @@ GEOSCoordSequence *V1_read_line_geos(struct Map_info *Map, long offset, G_debug(3, " off = %ld", (long)dig_ftell(&(Map->dig_fp))); +free_return: G_free((void *)x); G_free((void *)y); if (z) diff --git a/lib/vector/Vlib/open.c b/lib/vector/Vlib/open.c index 501b31eb4cb..0008156c34a 100644 --- a/lib/vector/Vlib/open.c +++ b/lib/vector/Vlib/open.c @@ -572,6 +572,7 @@ int Vect__open_old(struct Map_info *Map, const char *name, const char *mapset, if (access(file_path, F_OK) == 0) /* fidx file exists? */ unlink(file_path); } + Map->support_updated = TRUE; } return level; diff --git a/ps/ps.map/r_instructions.c b/ps/ps.map/r_instructions.c index 0e2c012fd92..87a2e4bbfcf 100644 --- a/ps/ps.map/r_instructions.c +++ b/ps/ps.map/r_instructions.c @@ -29,7 +29,7 @@ static char *help[] = { "read unix-file eps Encapsulated PostScript file", "border [y|n] mapinfo map information", "window region definition region region definition", - "maskcolor MASK color", + "maskcolor mask color", "rectangle east north east north", "scale 1:#|# inches|# panels|1 inch = # miles", "outline map composition outline", diff --git a/pyproject.toml b/pyproject.toml index e749b652641..b9fe3835105 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -175,6 +175,7 @@ ignore = [ "PLW1641", # eq-without-hash "PLW2901", # redefined-loop-name "PLW3201", # bad-dunder-method-name + "PT004", # pytest-missing-fixture-name-underscore # deprecated, so doesn't appear with --preview "PTH100", # os-path-abspath "PTH101", # os-chmod "PTH102", # os-mkdir diff --git a/python/grass/gunittest/invoker.py b/python/grass/gunittest/invoker.py index f07e8dd5a84..2fe9c898b8b 100644 --- a/python/grass/gunittest/invoker.py +++ b/python/grass/gunittest/invoker.py @@ -244,7 +244,7 @@ def try_decode(data, encodings): Path(stdout_path).write_text(stdout) with open(stderr_path, "w") as stderr_file: - if type(stderr) == "bytes": + if isinstance(stderr, bytes): stderr_file.write(decode(stderr)) elif isinstance(stderr, str): stderr_file.write(stderr) diff --git a/python/grass/gunittest/loader.py b/python/grass/gunittest/loader.py index f01467ef1ba..50fa368f62a 100644 --- a/python/grass/gunittest/loader.py +++ b/python/grass/gunittest/loader.py @@ -235,4 +235,18 @@ def discover(self, start_dir, pattern="test*.py", top_level_dir=None): if __name__ == "__main__": - GrassTestLoader().discover() + for expression in [r".*\.py$", r".*\.sh$"]: + modules = discover_modules( + start_dir=".", + grass_location="all", + file_regexp=expression, + skip_dirs=GrassTestLoader.skip_dirs, + testsuite_dir=GrassTestLoader.testsuite_dir, + all_locations_value=GrassTestLoader.all_tests_value, + universal_location_value=GrassTestLoader.universal_tests_value, + import_modules=False, + exclude=None, + ) + print("Expression:", expression) + print(len(modules)) + print([module.file_path for module in modules]) diff --git a/python/grass/imaging/images2ims.py b/python/grass/imaging/images2ims.py index a5cee9b49de..d86a2247952 100644 --- a/python/grass/imaging/images2ims.py +++ b/python/grass/imaging/images2ims.py @@ -32,6 +32,7 @@ import os from operator import itemgetter +from string import digits try: import numpy as np @@ -117,7 +118,7 @@ def _getSequenceNumber(filename, part1, part2): # Get all numeric chars seq2 = "" for c in seq: - if c in "0123456789": + if c in digits: seq2 += c else: break diff --git a/python/grass/pygrass/vector/abstract.py b/python/grass/pygrass/vector/abstract.py index 6700dca8e4c..79307f0d354 100644 --- a/python/grass/pygrass/vector/abstract.py +++ b/python/grass/pygrass/vector/abstract.py @@ -457,14 +457,14 @@ def close(self, build=False): if hasattr(self, "table") and self.table is not None: self.table.conn.close() if self.is_open(): - if libvect.Vect_close(self.c_mapinfo) != 0: - str_err = "Error when trying to close the map with Vect_close" - raise GrassError(str_err) if ( self.c_mapinfo.contents.mode in {libvect.GV_MODE_RW, libvect.GV_MODE_WRITE} ) and build: self.build() + if libvect.Vect_close(self.c_mapinfo) != 0: + str_err = "Error when trying to close the map with Vect_close" + raise GrassError(str_err) def remove(self): """Remove vector map""" @@ -474,16 +474,11 @@ def remove(self): def build(self): """Close the vector map and build vector Topology""" - self.close() - libvect.Vect_set_open_level(1) - if libvect.Vect_open_old2(self.c_mapinfo, self.name, self.mapset, "0") != 1: - str_err = "Error when trying to open the vector map." - raise GrassError(str_err) - # Vect_build returns 1 on success and 0 on error (bool approach) - if libvect.Vect_build(self.c_mapinfo) != 1: - str_err = "Error when trying build topology with Vect_build" - raise GrassError(str_err) - libvect.Vect_close(self.c_mapinfo) + if self.is_open(): + # Vect_build returns 1 on success and 0 on error (bool approach) + if libvect.Vect_build(self.c_mapinfo) != 1: + str_err = "Error when trying build topology with Vect_build" + raise GrassError(str_err) if __name__ == "__main__": diff --git a/raster/r.contour/testsuite/test_r_contour.py b/raster/r.contour/testsuite/test_r_contour.py index c9ee4c9876a..49617ef043d 100644 --- a/raster/r.contour/testsuite/test_r_contour.py +++ b/raster/r.contour/testsuite/test_r_contour.py @@ -32,10 +32,11 @@ def setUpClass(cls): def tearDownClass(cls): cls.del_temp_region() - cls.runModule("g.remove", type="vector", flags="f", name=cls.output) - cls.runModule("g.remove", type="vector", flags="f", name=cls.output + "_cut") cls.runModule( - "g.remove", type="vector", flags="f", name=cls.output + "_cut_flag_t" + "g.remove", + type="vector", + flags="f", + name=(cls.output, cls.output + "_cut", cls.output + "_cut_flag_t"), ) if os.path.isfile("testReport"): diff --git a/raster/r.fill.dir/r.fill.dir.html b/raster/r.fill.dir/r.fill.dir.html index 6ad8eb8019c..17173ea3dd3 100644 --- a/raster/r.fill.dir/r.fill.dir.html +++ b/raster/r.fill.dir/r.fill.dir.html @@ -69,11 +69,11 @@

DESCRIPTION

attributes required by other hydrological models.

-As any GRASS GIS module, r.fill.dir is sensitive to the -computational region settings. Thus +As any GRASS GIS module, r.fill.dir respects the +computational region settings. Thus, the module can be used to generate a flow direction map for any -sub-area within the full map layer. Also, r.fill.dir is -sensitive to any raster MASK in effect. +sub-area within the full raster map layer. Also, r.fill.dir +will take into account an active raster mask.

NOTES

diff --git a/raster/r.in.gridatb/file_io.c b/raster/r.in.gridatb/file_io.c index 256f9869473..52ad174df4d 100644 --- a/raster/r.in.gridatb/file_io.c +++ b/raster/r.in.gridatb/file_io.c @@ -12,6 +12,9 @@ void rdwr_gridatb(void) float idx; fp = fopen(file, "r"); + if (!fp) { + G_fatal_error(_("Unable to open file: %s"), file); + } buf[0] = 0; if (fscanf(fp, "%[^\n]", buf) != 1) diff --git a/raster/r.in.pdal/testsuite/test_r_in_pdal_binning.py b/raster/r.in.pdal/testsuite/test_r_in_pdal_binning.py index a8a793f5bc7..c256224590d 100644 --- a/raster/r.in.pdal/testsuite/test_r_in_pdal_binning.py +++ b/raster/r.in.pdal/testsuite/test_r_in_pdal_binning.py @@ -68,8 +68,12 @@ def tearDown(self): This is executed after each test run. """ - self.runModule("g.remove", flags="f", type="raster", name=self.bin_raster) - self.runModule("g.remove", flags="f", type="raster", name=self.ref_raster) + self.runModule( + "g.remove", + flags="f", + type="raster", + name=(self.bin_raster, self.ref_raster), + ) @unittest.skipIf(shutil.which("r.in.pdal") is None, "Cannot find r.in.pdal") def test_method_n(self): diff --git a/raster/r.in.pdal/testsuite/test_r_in_pdal_selection.py b/raster/r.in.pdal/testsuite/test_r_in_pdal_selection.py index b7d5acd6b28..4796702d2f6 100644 --- a/raster/r.in.pdal/testsuite/test_r_in_pdal_selection.py +++ b/raster/r.in.pdal/testsuite/test_r_in_pdal_selection.py @@ -67,8 +67,12 @@ def tearDown(self): This is executed after each test run. """ - self.runModule("g.remove", flags="f", type="raster", name=self.imp_raster) - self.runModule("g.remove", flags="f", type="raster", name=self.ref_raster) + self.runModule( + "g.remove", + flags="f", + type="raster", + name=(self.imp_raster, self.ref_raster), + ) try: self.runModule("g.remove", flags="f", type="raster", name=self.base_raster) except AttributeError: diff --git a/raster/r.kappa/testsuite/test_r_kappa.py b/raster/r.kappa/testsuite/test_r_kappa.py index 620a4f4979b..ff11f477925 100644 --- a/raster/r.kappa/testsuite/test_r_kappa.py +++ b/raster/r.kappa/testsuite/test_r_kappa.py @@ -51,8 +51,9 @@ def setUpClass(cls): def tearDownClass(cls): """Remove temporary data""" cls.del_temp_region() - cls.runModule("g.remove", flags="f", type="raster", name=cls.ref_1) - cls.runModule("g.remove", flags="f", type="raster", name=cls.class_1) + cls.runModule( + "g.remove", flags="f", type="raster", name=(cls.ref_1, cls.class_1) + ) def test_m(self): """Test printing matrix only @@ -119,8 +120,9 @@ def setUpClass(cls): def tearDownClass(cls): """Remove temporary data""" cls.del_temp_region() - cls.runModule("g.remove", flags="f", type="raster", name=cls.ref_1) - cls.runModule("g.remove", flags="f", type="raster", name=cls.class_1) + cls.runModule( + "g.remove", flags="f", type="raster", name=(cls.ref_1, cls.class_1) + ) def match(self, pat, ref): if pat == "NA" or ref == "NA": @@ -233,8 +235,9 @@ def setUpClass(cls): def tearDownClass(cls): """Remove temporary data""" cls.del_temp_region() - cls.runModule("g.remove", flags="f", type="raster", name=cls.ref_1) - cls.runModule("g.remove", flags="f", type="raster", name=cls.class_1) + cls.runModule( + "g.remove", flags="f", type="raster", name=(cls.ref_1, cls.class_1) + ) def match(self, pat, ref): if pat == "NA" or ref == "NA": diff --git a/raster/r.li/TODO b/raster/r.li/TODO index 61ddd6b0941..382195402a6 100644 --- a/raster/r.li/TODO +++ b/raster/r.li/TODO @@ -24,12 +24,13 @@ d.vect forests type=boundary ######## TODO: CHECK THIS: -# MASK test -g.copy rast=fields,MASK +# Test with raster mask +r.mask raster=fields r.li.patchdensity forests conf=movwindow7 output=forests_p_dens7mask --o d.erase d.rast.leg forests_p_dens7mask -# -> no negative values! but MASK is respected +r.mask -r +# -> no negative values! but mask is respected # zero data test r.mapcalc "forests = 0" diff --git a/raster/r.mask.status/main.c b/raster/r.mask.status/main.c index 5eb32300240..16790adf35c 100644 --- a/raster/r.mask.status/main.c +++ b/raster/r.mask.status/main.c @@ -109,6 +109,8 @@ int report_status(struct Parameters *params) else json_object_set_null(root_object, "is_reclass_of"); char *serialized_string = json_serialize_to_string_pretty(root_value); + if (!serialized_string) + G_fatal_error(_("Failed to initialize pretty JSON string.")); puts(serialized_string); json_free_serialized_string(serialized_string); json_value_free(root_value); diff --git a/raster/r.mfilter/benchmark/benchmark_r_mfilter_nprocs.py b/raster/r.mfilter/benchmark/benchmark_r_mfilter_nprocs.py index 2dc85b411d8..9a0591f7cfc 100644 --- a/raster/r.mfilter/benchmark/benchmark_r_mfilter_nprocs.py +++ b/raster/r.mfilter/benchmark/benchmark_r_mfilter_nprocs.py @@ -55,8 +55,7 @@ def benchmark(size, label, results): overwrite=True, ) results.append(bm.benchmark_nprocs(module, label=label, max_nprocs=16, repeat=3)) - Module("g.remove", quiet=True, flags="f", type="raster", name=reference) - Module("g.remove", quiet=True, flags="f", type="raster", name=output) + Module("g.remove", quiet=True, flags="f", type="raster", name=(reference, output)) def generate_map(rows, cols, fname): diff --git a/raster/r.neighbors/benchmark/benchmark_r_neighbors_nprocs.py b/raster/r.neighbors/benchmark/benchmark_r_neighbors_nprocs.py index d355792a734..e7b3196c246 100644 --- a/raster/r.neighbors/benchmark/benchmark_r_neighbors_nprocs.py +++ b/raster/r.neighbors/benchmark/benchmark_r_neighbors_nprocs.py @@ -38,8 +38,7 @@ def benchmark(size, label, results): overwrite=True, ) results.append(bm.benchmark_nprocs(module, label=label, max_nprocs=16, repeat=3)) - Module("g.remove", quiet=True, flags="f", type="raster", name=reference) - Module("g.remove", quiet=True, flags="f", type="raster", name=output) + Module("g.remove", quiet=True, flags="f", type="raster", name=(reference, output)) def generate_map(rows, cols, fname): diff --git a/raster/r.random/testsuite/test_r_random.py b/raster/r.random/testsuite/test_r_random.py index ce12c719c8e..16aae357ebf 100644 --- a/raster/r.random/testsuite/test_r_random.py +++ b/raster/r.random/testsuite/test_r_random.py @@ -28,30 +28,30 @@ def setUpClass(cls): def tearDownClass(cls): cls.del_temp_region() - cls.runModule("g.remove", type="raster", flags="f", name=cls.raster) - cls.runModule("g.remove", type="raster", flags="f", name=cls.raster + "_null") - cls.runModule( - "g.remove", type="raster", flags="f", name=cls.raster + "_without_topology" - ) - cls.runModule("g.remove", type="raster", flags="f", name=cls.raster + "_3D") cls.runModule( "g.remove", type="raster", flags="f", - name=cls.raster + "_cover_landcover_1m", + name=( + cls.raster, + cls.raster + "_null", + cls.raster + "_without_topology", + cls.raster + "_3D", + cls.raster + "_cover_landcover_1m", + ), ) - cls.runModule("g.remove", type="vector", flags="f", name=cls.vector) - cls.runModule("g.remove", type="vector", flags="f", name=cls.vector + "_null") - cls.runModule( - "g.remove", type="vector", flags="f", name=cls.vector + "_without_topology" - ) - cls.runModule("g.remove", type="vector", flags="f", name=cls.vector + "_3D") cls.runModule( "g.remove", type="vector", flags="f", - name=cls.vector + "_cover_landcover_1m", + name=( + cls.vector, + cls.vector + "_null", + cls.vector + "_without_topology", + cls.vector + "_3D", + cls.vector + "_cover_landcover_1m", + ), ) def test_random_raster(self): diff --git a/raster/r.resamp.filter/benchmark/benchmark_r_resamp_filter.py b/raster/r.resamp.filter/benchmark/benchmark_r_resamp_filter.py index 8c7799e74fc..9ec46132c81 100644 --- a/raster/r.resamp.filter/benchmark/benchmark_r_resamp_filter.py +++ b/raster/r.resamp.filter/benchmark/benchmark_r_resamp_filter.py @@ -40,8 +40,7 @@ def benchmark(size, label, results): overwrite=True, ) results.append(bm.benchmark_nprocs(module, label=label, max_nprocs=8, repeat=3)) - Module("g.remove", quiet=True, flags="f", type="raster", name=reference) - Module("g.remove", quiet=True, flags="f", type="raster", name=output) + Module("g.remove", quiet=True, flags="f", type="raster", name=(reference, output)) def generate_map(rows, cols, fname): diff --git a/raster/r.resamp.interp/benchmark/benchmark_r_resamp_interp.py b/raster/r.resamp.interp/benchmark/benchmark_r_resamp_interp.py index 20196bc44bc..616dc045aa3 100644 --- a/raster/r.resamp.interp/benchmark/benchmark_r_resamp_interp.py +++ b/raster/r.resamp.interp/benchmark/benchmark_r_resamp_interp.py @@ -38,8 +38,7 @@ def benchmark(size, label, results): overwrite=True, ) results.append(bm.benchmark_nprocs(module, label=label, max_nprocs=16, repeat=3)) - Module("g.remove", quiet=True, flags="f", type="raster", name=reference) - Module("g.remove", quiet=True, flags="f", type="raster", name=output) + Module("g.remove", quiet=True, flags="f", type="raster", name=(reference, output)) def generate_map(rows, cols, fname): diff --git a/raster/r.resamp.interp/r.resamp.interp.html b/raster/r.resamp.interp/r.resamp.interp.html index e4b77ac0d34..3c3eecc68a9 100644 --- a/raster/r.resamp.interp/r.resamp.interp.html +++ b/raster/r.resamp.interp/r.resamp.interp.html @@ -28,7 +28,7 @@

NOTES

Note that for bilinear, bicubic and lanczos interpolation, cells of the output raster that cannot be bounded by the appropriate number of input cell centers are set to NULL (NULL propagation). This could occur -due to the input cells being outside the current region, being NULL or MASKed. +due to the input cells being outside the current region, being NULL or masked.

For longitude-latitude coordinate reference systems, diff --git a/raster/r.series/benchmark/benchmark_r_series.py b/raster/r.series/benchmark/benchmark_r_series.py index ecf4432f2dd..72c9c3bd23c 100644 --- a/raster/r.series/benchmark/benchmark_r_series.py +++ b/raster/r.series/benchmark/benchmark_r_series.py @@ -37,8 +37,7 @@ def benchmark(size, label, results): overwrite=True, ) results.append(bm.benchmark_nprocs(module, label=label, max_nprocs=16, repeat=3)) - Module("g.remove", quiet=True, flags="f", type="raster", name=reference) - Module("g.remove", quiet=True, flags="f", type="raster", name=output) + Module("g.remove", quiet=True, flags="f", type="raster", name=(reference, output)) def generate_map(rows, cols, fname): diff --git a/raster/r.sim/r.sim.sediment/main.c b/raster/r.sim/r.sim.sediment/main.c index dd082fe0f81..d0197c2abb1 100644 --- a/raster/r.sim/r.sim.sediment/main.c +++ b/raster/r.sim/r.sim.sediment/main.c @@ -417,6 +417,8 @@ int main(int argc, char *argv[]) G_message(_("Using metric conversion factor %f, step=%f"), wp.conv, wp.step); + wp.observation = parm.observation->answer; + wp.logfile = parm.logfile->answer; init_library_globals(&wp); if ((wp.tc == NULL) && (wp.et == NULL) && (wp.conc == NULL) && diff --git a/raster/r.sim/r.sim.water/main.c b/raster/r.sim/r.sim.water/main.c index 61f128f5504..209be3e33d9 100644 --- a/raster/r.sim/r.sim.water/main.c +++ b/raster/r.sim/r.sim.water/main.c @@ -220,7 +220,7 @@ int main(int argc, char *argv[]) parm.logfile->required = NO; parm.logfile->description = _("Name for sampling points output text file. For each observation " - "vector point the time series of sediment transport is stored."); + "vector point the time series of water discharge is stored."); parm.logfile->guisection = _("Output"); parm.nwalk = G_define_option(); @@ -525,6 +525,8 @@ int main(int argc, char *argv[]) G_message(_("Using metric conversion factor %f, step=%f"), wp.conv, wp.step); + wp.observation = parm.observation->answer; + wp.logfile = parm.logfile->answer; init_library_globals(&wp); if ((wp.depth == NULL) && (wp.disch == NULL) && (wp.err == NULL)) diff --git a/raster/r.sim/simlib/hydro.c b/raster/r.sim/simlib/hydro.c index 74145ec9bff..eb7649763e9 100644 --- a/raster/r.sim/simlib/hydro.c +++ b/raster/r.sim/simlib/hydro.c @@ -151,9 +151,6 @@ void main_loop(void) maxwa = maxwa / nblock; } - /* Create the observation points */ - create_observation_points(); - G_debug(2, " maxwa, nblock %d %d", maxwa, nblock); for (iblock = 1; iblock <= nblock; iblock++) { diff --git a/raster/r.slope.aspect/benchmark/benchmark_r_slope_aspect.py b/raster/r.slope.aspect/benchmark/benchmark_r_slope_aspect.py index 220b813e323..522a938f26c 100644 --- a/raster/r.slope.aspect/benchmark/benchmark_r_slope_aspect.py +++ b/raster/r.slope.aspect/benchmark/benchmark_r_slope_aspect.py @@ -42,11 +42,13 @@ def benchmark(size, label, results): overwrite=True, ) results.append(bm.benchmark_nprocs(module, label=label, max_nprocs=16, repeat=3)) - Module("g.remove", quiet=True, flags="f", type="raster", name=reference) - Module("g.remove", quiet=True, flags="f", type="raster", name=slope) - Module("g.remove", quiet=True, flags="f", type="raster", name=aspect) - Module("g.remove", quiet=True, flags="f", type="raster", name=pcurv) - Module("g.remove", quiet=True, flags="f", type="raster", name=tcurv) + Module( + "g.remove", + quiet=True, + flags="f", + type="raster", + name=(reference, slope, aspect, pcurv, tcurv), + ) def generate_map(rows, cols, fname): diff --git a/raster/r.slope.aspect/benchmark/benchmark_r_slope_aspect_memory.py b/raster/r.slope.aspect/benchmark/benchmark_r_slope_aspect_memory.py index c8f5ac09f00..31824233bbc 100644 --- a/raster/r.slope.aspect/benchmark/benchmark_r_slope_aspect_memory.py +++ b/raster/r.slope.aspect/benchmark/benchmark_r_slope_aspect_memory.py @@ -46,10 +46,13 @@ def benchmark(memory, label, results, reference): ) results.append(bm.benchmark_nprocs(module, label=label, max_nprocs=20, repeat=10)) - Module("g.remove", quiet=True, flags="f", type="raster", name=slope) - Module("g.remove", quiet=True, flags="f", type="raster", name=aspect) - Module("g.remove", quiet=True, flags="f", type="raster", name=pcurv) - Module("g.remove", quiet=True, flags="f", type="raster", name=tcurv) + Module( + "g.remove", + quiet=True, + flags="f", + type="raster", + name=(slope, aspect, pcurv, tcurv), + ) def generate_map(rows, cols, fname): diff --git a/raster/r.tile/testsuite/test_r_tile.py b/raster/r.tile/testsuite/test_r_tile.py index f1dd3afcd3b..c2e62b60dd2 100644 --- a/raster/r.tile/testsuite/test_r_tile.py +++ b/raster/r.tile/testsuite/test_r_tile.py @@ -28,42 +28,20 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): cls.del_temp_region() - cls.runModule( - "g.remove", type="raster", flags="f", name=cls.output_prefix + "-000-000" - ) - cls.runModule( - "g.remove", type="raster", flags="f", name=cls.output_prefix + "-000-001" - ) - cls.runModule( - "g.remove", type="raster", flags="f", name=cls.output_prefix + "-001-000" - ) - cls.runModule( - "g.remove", type="raster", flags="f", name=cls.output_prefix + "-001-001" - ) - - cls.runModule( - "g.remove", - type="raster", - flags="f", - name=cls.output_prefix + "overlap" + "-000-000", - ) - cls.runModule( - "g.remove", - type="raster", - flags="f", - name=cls.output_prefix + "overlap" + "-000-001", - ) - cls.runModule( - "g.remove", - type="raster", - flags="f", - name=cls.output_prefix + "overlap" + "-001-000", - ) cls.runModule( "g.remove", type="raster", flags="f", - name=cls.output_prefix + "overlap" + "-001-001", + name=( + cls.output_prefix + "-000-000", + cls.output_prefix + "-000-001", + cls.output_prefix + "-001-000", + cls.output_prefix + "-001-001", + cls.output_prefix + "overlap" + "-000-000", + cls.output_prefix + "overlap" + "-000-001", + cls.output_prefix + "overlap" + "-001-000", + cls.output_prefix + "overlap" + "-001-001", + ), ) def test_raster_tile(self): diff --git a/raster/r.topidx/r.topidx.html b/raster/r.topidx/r.topidx.html index 56dd001f20a..75938e6a231 100644 --- a/raster/r.topidx/r.topidx.html +++ b/raster/r.topidx/r.topidx.html @@ -10,12 +10,14 @@

DESCRIPTION

the local surface topographic slope (delta vertical) / (delta horizontal).
-

Input maps may have NULL values. For example, if you have a MASK for a -watershed (basin map from r.water.outlet), the following command will -create a masked elevation map (belev): +

Input maps may have NULL values. For example, if you have a raster mask set +for a watershed (using basin map from r.water.outlet), the following +command will create a masked elevation map (belev): +

 r.mapcalc "belev = if(isnull(basin), basin, elev)"
 
+

r.stats -Anc prints out averaged statistics for topographic index. diff --git a/raster/r.univar/benchmark/benchmark_r_univar.py b/raster/r.univar/benchmark/benchmark_r_univar.py index 507e6b4b23e..58d607645c7 100644 --- a/raster/r.univar/benchmark/benchmark_r_univar.py +++ b/raster/r.univar/benchmark/benchmark_r_univar.py @@ -35,8 +35,7 @@ def benchmark(size, label, results): overwrite=True, ) results.append(bm.benchmark_nprocs(module, label=label, max_nprocs=16, repeat=3)) - Module("g.remove", quiet=True, flags="f", type="raster", name=reference) - Module("g.remove", quiet=True, flags="f", type="raster", name=output) + Module("g.remove", quiet=True, flags="f", type="raster", name=(reference, output)) def generate_map(rows, cols, fname): diff --git a/raster/r.univar/r.univar.html b/raster/r.univar/r.univar.html index 5220bc64a44..debde03a1d6 100644 --- a/raster/r.univar/r.univar.html +++ b/raster/r.univar/r.univar.html @@ -53,7 +53,7 @@

PERFORMANCE

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

Due to the differences in summation order, users may encounter small floating points diff --git a/raster/r.univar/testsuite/test_r_univar.py b/raster/r.univar/testsuite/test_r_univar.py index de28f0753d5..c2ae6669df6 100644 --- a/raster/r.univar/testsuite/test_r_univar.py +++ b/raster/r.univar/testsuite/test_r_univar.py @@ -23,11 +23,12 @@ def tearDownClass(cls): cls.del_temp_region() def tearDown(self): - self.runModule("g.remove", flags="f", type="raster", name="map_a") - self.runModule("g.remove", flags="f", type="raster", name="map_b") - self.runModule("g.remove", flags="f", type="raster", name="map_negative") - self.runModule("g.remove", flags="f", type="raster", name="zone_map") - self.runModule("g.remove", flags="f", type="raster", name="zone_map_with_gap") + self.runModule( + "g.remove", + flags="f", + type="raster", + name="map_a,map_b,map_negative,zone_map,zone_map_with_gap", + ) def setUp(self): """Create input data""" diff --git a/raster3d/r3.in.v5d/binio.c b/raster3d/r3.in.v5d/binio.c index f8582e97313..b47723a2929 100644 --- a/raster3d/r3.in.v5d/binio.c +++ b/raster3d/r3.in.v5d/binio.c @@ -147,7 +147,7 @@ static void if_to_c(long *t, const long *f) { if (*f != 0) { *t = (((*f & 0x8000000000000000) | - ((*f & 0x7f80000000000000) >> 7) + (16258 << 48)) | + ((*f & 0x7f80000000000000) >> 7) + (16258L << 48)) | (((*f & 0x007fffff00000000) >> 8) | (0x0000800000000000))); if ((*f << 1) == 0) *t = 0; @@ -160,7 +160,7 @@ static void if_to_c(long *t, const long *f) #define IF_TO_C(T, F) \ if (F != 0) { \ T = (((F & 0x8000000000000000) | \ - ((F & 0x7f80000000000000) >> 7) + (16258 << 48)) | \ + ((F & 0x7f80000000000000) >> 7) + (16258L << 48)) | \ (((F & 0x007fffff00000000) >> 8) | (0x0000800000000000))); \ if ((F << 1) == 0) \ T = 0; \ diff --git a/raster3d/r3.null/testsuite/test.r3.null.sh b/raster3d/r3.null/testsuite/test.r3.null.sh index 1cf93110b24..d6a6ec35810 100755 --- a/raster3d/r3.null/testsuite/test.r3.null.sh +++ b/raster3d/r3.null/testsuite/test.r3.null.sh @@ -56,14 +56,7 @@ diff data/test_volume_double_null_1.ref test_volume_double_null_1.txt diff data/test_volume_double_null_2.ref test_volume_double_null_2.txt # Cleanup -g.remove -f type=raster_3d name=test_volume_float_1 -g.remove -f type=raster_3d name=test_volume_float_2 -g.remove -f type=raster_3d name=test_volume_float_null_1 -g.remove -f type=raster_3d name=test_volume_float_null_2 -g.remove -f type=raster_3d name=test_volume_double_1 -g.remove -f type=raster_3d name=test_volume_double_2 -g.remove -f type=raster_3d name=test_volume_double_null_1 -g.remove -f type=raster_3d name=test_volume_double_null_2 +g.remove -f type=raster_3d name=test_volume_float_1,test_volume_float_2,test_volume_float_null_1,test_volume_float_null_2,test_volume_double_1,test_volume_double_2,test_volume_double_null_1,test_volume_double_null_2 rm test_volume_float_1.txt rm test_volume_float_2.txt rm test_volume_float_null_1.txt diff --git a/scripts/d.rast.edit/d.rast.edit.py b/scripts/d.rast.edit/d.rast.edit.py index 6519d10c7a1..ea6cc1e46bf 100755 --- a/scripts/d.rast.edit/d.rast.edit.py +++ b/scripts/d.rast.edit/d.rast.edit.py @@ -77,6 +77,8 @@ import sys import math import atexit +from string import digits + import grass.script as gs from grass.script.setup import set_gui_path @@ -448,7 +450,7 @@ def OnClose(self, ev): def OnReturn(self, ev): self.app.brush = self.newval.GetValue() - if self.app.brush != "*" and self.app.brush.strip("0123456789") != "": + if self.app.brush != "*" and self.app.brush.strip(digits) != "": self.app.brush = "*" self.brush_update() diff --git a/scripts/r.mapcalc.simple/testsuite/test_rmapcalcsimple.py b/scripts/r.mapcalc.simple/testsuite/test_rmapcalcsimple.py index d400ccc7980..f133da22f7d 100644 --- a/scripts/r.mapcalc.simple/testsuite/test_rmapcalcsimple.py +++ b/scripts/r.mapcalc.simple/testsuite/test_rmapcalcsimple.py @@ -24,8 +24,9 @@ def setUpClass(cls): def tearDownClass(cls): map_output1 = "test1" map_output2 = "test2" - cls.runModule("g.remove", flags="f", type="raster", name=map_output1) - cls.runModule("g.remove", flags="f", type="raster", name=map_output2) + cls.runModule( + "g.remove", flags="f", type="raster", name=(map_output1, map_output2) + ) cls.del_temp_region() def test_rmapcalcsimple(self): diff --git a/scripts/r.reclass.area/testsuite/test_r_reclass_area.py b/scripts/r.reclass.area/testsuite/test_r_reclass_area.py index c8009ce5a1d..96c922bda05 100644 --- a/scripts/r.reclass.area/testsuite/test_r_reclass_area.py +++ b/scripts/r.reclass.area/testsuite/test_r_reclass_area.py @@ -26,8 +26,12 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): cls.del_temp_region() - cls.runModule("g.remove", type="raster", flags="f", name=cls.output + "Greater") - cls.runModule("g.remove", type="raster", flags="f", name=cls.output + "Lesser") + cls.runModule( + "g.remove", + type="raster", + flags="f", + name=(cls.output + "Greater", cls.output + "Lesser"), + ) def test_reclassaeaGreater(self): """Testing r.reclass.area with greater""" diff --git a/scripts/v.rast.stats/testsuite/test_v_rast_stats.py b/scripts/v.rast.stats/testsuite/test_v_rast_stats.py index 56acbce5964..01ae6d4a7d2 100644 --- a/scripts/v.rast.stats/testsuite/test_v_rast_stats.py +++ b/scripts/v.rast.stats/testsuite/test_v_rast_stats.py @@ -24,11 +24,12 @@ def tearDownClass(cls): cls.del_temp_region() def tearDown(self): - self.runModule("g.remove", flags="f", type="raster", name="map_a") - self.runModule("g.remove", flags="f", type="raster", name="map_b") - self.runModule("g.remove", flags="f", type="raster", name="zone_map") - self.runModule("g.remove", flags="f", type="raster", name="row_map") - self.runModule("g.remove", flags="f", type="raster", name="test_line") + self.runModule( + "g.remove", + flags="f", + type="raster", + name="map_a,map_b,zone_map,row_map,test_line", + ) def setUp(self): """Create input data""" diff --git a/scripts/v.what.strds/v.what.strds.py b/scripts/v.what.strds/v.what.strds.py index 0e1bf893f98..8be00f3536d 100644 --- a/scripts/v.what.strds/v.what.strds.py +++ b/scripts/v.what.strds/v.what.strds.py @@ -225,7 +225,7 @@ def main(): pymap = Vector(output) try: pymap.open("r") - except: + except Exception: dbif.close() gs.fatal(_("Unable to create vector map <%s>") % output) diff --git a/temporal/t.rast.accdetect/testsuite/test_simple.py b/temporal/t.rast.accdetect/testsuite/test_simple.py index 522c13f93e4..d67537487d5 100644 --- a/temporal/t.rast.accdetect/testsuite/test_simple.py +++ b/temporal/t.rast.accdetect/testsuite/test_simple.py @@ -62,8 +62,7 @@ def tearDownClass(cls): def tearDown(self): """Remove generated data""" - self.runModule("t.remove", flags="df", type="strds", inputs="B") - self.runModule("t.remove", flags="df", type="strds", inputs="C") + self.runModule("t.remove", flags="df", type="strds", inputs="B,C") def test_simple(self): self.assertModule( diff --git a/temporal/t.rast.accumulate/testsuite/test_accumulation.py b/temporal/t.rast.accumulate/testsuite/test_accumulation.py index 59aa227006d..6c277c320cf 100644 --- a/temporal/t.rast.accumulate/testsuite/test_accumulation.py +++ b/temporal/t.rast.accumulate/testsuite/test_accumulation.py @@ -94,9 +94,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): """Remove the temporary region""" - cls.runModule("t.remove", flags="df", type="strds", inputs="A") - cls.runModule("t.remove", flags="df", type="strds", inputs="Lower") - cls.runModule("t.remove", flags="df", type="strds", inputs="Upper") + cls.runModule("t.remove", flags="df", type="strds", inputs="A,Lower,Upper") cls.del_temp_region() def tearDown(self): diff --git a/temporal/t.rast.series/testsuite/test_series.py b/temporal/t.rast.series/testsuite/test_series.py index 73951725d93..97df7c048fc 100644 --- a/temporal/t.rast.series/testsuite/test_series.py +++ b/temporal/t.rast.series/testsuite/test_series.py @@ -60,11 +60,15 @@ def tearDownClass(cls): type="raster", maps="series_average,series_maximum,series_minimum,series_minimum_2", ) - cls.runModule("g.remove", flags="f", type="raster", name="series_average") - cls.runModule("g.remove", flags="f", type="raster", name="series_maximum") - cls.runModule("g.remove", flags="f", type="raster", name="series_minimum") - cls.runModule("g.remove", flags="f", type="raster", name="series_minimum_2") - cls.runModule("g.remove", flags="f", type="raster", name="series_quantile") + cls.runModule( + "g.remove", + flags="f", + type="raster", + name=( + "series_average,series_maximum" + + ",series_minimum,series_minimum_2,series_quantile" + ), + ) def test_time_stamp(self): self.assertModule( @@ -205,10 +209,12 @@ def tearDownClass(cls): type="raster", maps="series_average,series_maximum,series_minimum,series_minimum_2", ) - cls.runModule("g.remove", flags="f", type="raster", name="series_average") - cls.runModule("g.remove", flags="f", type="raster", name="series_maximum") - cls.runModule("g.remove", flags="f", type="raster", name="series_minimum") - cls.runModule("g.remove", flags="f", type="raster", name="series_minimum_2") + cls.runModule( + "g.remove", + flags="f", + type="raster", + name="series_average,series_maximum,series_minimum,series_minimum_2", + ) def test_average(self): self.assertModule( diff --git a/temporal/t.rast.univar/testsuite/test_t_rast_univar.py b/temporal/t.rast.univar/testsuite/test_t_rast_univar.py index 643194f0c78..83e5c2d6229 100644 --- a/temporal/t.rast.univar/testsuite/test_t_rast_univar.py +++ b/temporal/t.rast.univar/testsuite/test_t_rast_univar.py @@ -148,8 +148,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): """Remove the temporary region""" - cls.runModule("t.remove", flags="df", type="strds", inputs="A") - cls.runModule("t.remove", flags="df", type="strds", inputs="B") + cls.runModule("t.remove", flags="df", type="strds", inputs="A,B") cls.runModule("g.remove", flags="f", type="raster", name="zones") cls.del_temp_region() diff --git a/temporal/t.topology/test.t.topology.reltime.sh b/temporal/t.topology/test.t.topology.reltime.sh index 847289c4b2f..60e49355e9a 100755 --- a/temporal/t.topology/test.t.topology.reltime.sh +++ b/temporal/t.topology/test.t.topology.reltime.sh @@ -96,6 +96,5 @@ cat "${n5}" t.topology input=precip_rel_y t.topology -m input=precip_rel_y -t.remove type=strds input=precip_rel_d -t.remove type=strds input=precip_rel_y +t.remove type=strds input=precip_rel_d,precip_rel_y t.unregister type=raster file="${n1}" diff --git a/temporal/t.vect.extract/test.t.vect.extract.sh b/temporal/t.vect.extract/test.t.vect.extract.sh index b5cd6882e94..8b176dcc8ed 100755 --- a/temporal/t.vect.extract/test.t.vect.extract.sh +++ b/temporal/t.vect.extract/test.t.vect.extract.sh @@ -44,5 +44,4 @@ t.vect.list input=soil_abs3 columns=name,start_time,end_time,primitives # @postprocess t.unregister type=vector maps=soil_1,soil_2,soil_3,soil_4,soil_5,soil_6,soil_7,soil_8 t.remove type=stvds input=soil_abs1,soil_abs2,soil_abs3 -g.remove -f type=vector name=soil_1,soil_2,soil_3,soil_4,soil_5,soil_6,soil_7,soil_8 -g.remove -f type=vector name=new_vect_1,new_vect_2,new_vect_3,new_vect_4,new_vect_5,new_vect_6 +g.remove -f type=vector name=soil_1,soil_2,soil_3,soil_4,soil_5,soil_6,soil_7,soil_8,new_vect_1,new_vect_2,new_vect_3,new_vect_4,new_vect_5,new_vect_6 diff --git a/testsuite/Makefile b/testsuite/Makefile deleted file mode 100644 index 7ed92b67442..00000000000 --- a/testsuite/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -MODULE_TOPDIR = .. - -SUBDIRS = \ - raster - -include $(MODULE_TOPDIR)/include/Make/Dir.make - -default: parsubdirs diff --git a/testsuite/README.md b/testsuite/README.md index 491fa4597af..36dda7d3da2 100644 --- a/testsuite/README.md +++ b/testsuite/README.md @@ -1,29 +1,39 @@ # Test suite -This directory contains scripts to check some functionality of GRASS GIS. +Tests are in directories `tests` and `testsuite` under each directory which has tests. +This directory contains additional scripts and information to test functionality +without a focus on a specific part of the code. -GRASS GIS testsuite documentation: +There are two testing mechanism in place, _pytest_ which is the modern way of testing +GRASS GIS. Tests using _pytest_ are written just as any other Python tests. -## Simple test data +In parallel, there is also custom unittest-based framework centered around +_grass.gunittest_ package. These tests run in the NC sample datasets and can be +executed using _pytest_ or directly. The unittest-based adds a number of custom +assert methods to accommodate different data and outputs typical in GRASS GIS. +_grass.gunittest_ documentation: + -Some tests may be launched in the location `../demolocation/`: +## Running tests + +Tests can be executed using _pytest_: ```bash -# create new mapset for test -grass ../demolocation/user1 -c -# run the test -make +# Setup the Python environment (if not set up already). +# Replace grass by path to the executable if not installed on path. +export PYTHONPATH=\$(grass --config python_path):\$PYTHONPATH +export LD_LIBRARY_PATH=\$(grass --config path)/lib:\$LD_LIBRARY_PATH +# Run the test. +pytest ``` -## Extended test data - -Most tests require the North Carolina Sample dataset, available from - +## Test data -## Notes +To test manually or to write tests, you may need to use the North Carolina +Sample dataset, available from +. -Since 2020: For a more advanced test suite, see - +## CI -Until 2019: For a more advanced test suite, see - +Most tests run in the CI. See the `.github` directory for details and +use it as a reference. diff --git a/testsuite/examples/test_framework_GRASS_GIS_with_NC.conf b/testsuite/examples/test_framework_GRASS_GIS_with_NC.conf deleted file mode 100644 index 6256108f6a8..00000000000 --- a/testsuite/examples/test_framework_GRASS_GIS_with_NC.conf +++ /dev/null @@ -1,29 +0,0 @@ -### CONFIGURATION -# -# name of binary: -GRASSBIN=grass -# source code directory as full path: -GRASSSRC="$(realpath ../../)" -# temporary grassdata directory -GRASSDATA="$HOME/grassdata" - -# leave 1 or more CPU free for other usage than testing -FREECPU=1 - -# Python binary to be used (python|python3) -PYTHON=python - -# here we suppose default compilation settings of GRASS GIS and no 'make install' -# may be no|yes -COMPILE="no" -# configure metascript with compiler flags: -CONFIGURE="${GRASSSRC}/conf_grass8.sh" - -# directory to store reports, e.g. in a subdirectory -REPORTS="testreports" - -# publish report on WWW Server (not needed for local tests) -# may be no|yes -PUBLISH="no" -# upload WWW dir on server for report publication (not used for local tests) -SERVERDIR="/var/www/html/grassgistestreports" diff --git a/testsuite/examples/test_framework_GRASS_GIS_with_NC.sh b/testsuite/examples/test_framework_GRASS_GIS_with_NC.sh deleted file mode 100755 index 808b4b9c716..00000000000 --- a/testsuite/examples/test_framework_GRASS_GIS_with_NC.sh +++ /dev/null @@ -1,144 +0,0 @@ -#!/bin/bash -############################################################################ -# -# MODULE: Example script to run testsuite -# AUTHOR(S): Markus Neteler, Sören Gebbert, Vaclav Petras -# PURPOSE: Test GRASS GIS using the test framework -# Documentation: -# https://trac.osgeo.org/grass/wiki/GSoC/2014/TestingFrameworkForGRASS -# https://grass.osgeo.org/grass-devel/manuals/libpython/gunittest_running_tests.html#example-bash-script-to-run-be-used-as-a-cron-job -# -# Data: -# We use the full NC dataset (nc_spm_full_v2_alpha.tar.gz) -# -# COPYRIGHT: (C) 2019-2021 by Markus Neteler, and the GRASS Development Team -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -############################################################################ - -### Fetch CONFIGURATION - -CONF="test_framework_GRASS_GIS_with_NC.conf" - -usage_msg(){ -echo "Usage: - $0 [conf_file] - -Example: - $0 ./${CONF} -" -} - -if [ ! -z "$1" ] ; then - case "$1" in - -h | --h | -help | --help) - usage_msg - exit 0 - ;; - *) - if [ -f ${1} ] ; then - CONF="$1" - else - echo "ERROR: $1 is not a file" - exit 1 - fi - ;; - esac -else - usage_msg - exit 0 -fi - -source ${CONF} - -######### nothing to change below - -set -e # fail fast - -# computer architecture: -ARCH=`${GRASSBIN} --config arch` - -# here we suppose default compilation settings of GRASS GIS and no make install -GRASSBIN="$GRASSSRC/bin.${ARCH}/${GRASSBIN}" -GRASSDIST="$GRASSSRC/dist.${ARCH}" - -# necessary hardcoded GRASS paths -GRASSDIST_PYTHON="$GRASSDIST/etc/python" -GRASS_MULTI_RUNNER="$GRASSSRC/python/grass/gunittest/multirunner.py" -GRASS_MULTI_REPORTER="$GRASSSRC/python/grass/gunittest/multireport.py" - -DATE_FLAGS="--utc +%Y-%m-%d-%H-%M" -NOW=$(date $DATE_FLAGS) - -# get number of processors of current machine -MYNPROC=`getconf _NPROCESSORS_ONLN` -# leave some free for other tasks -GCCTHREADS=`expr $MYNPROC - $FREECPU` -if [ $GCCTHREADS -lt 1 ] ; then - GCCTHREADS=1 -fi - -# contains last executed command stdout and stderr -# here were rely on reports being absolute -OUTPUT_LOGFILE="$REPORTS/output-$NOW.txt" - -# these are relative to REPORTS -CURRENT_REPORT_BASENAME="reports_for_date-" -FINAL_REPORT_DIR="summary_report" -CURRENT_REPORTS_DIR="$CURRENT_REPORT_BASENAME$NOW" -LOGFILE="$REPORTS/runs.log" - -mkdir -p $REPORTS/$CURRENT_REPORTS_DIR -mkdir -p $GRASSDATA - -# fetch sample data -SAMPLEDATA=nc_spm_full_v2alpha -(cd $GRASSDATA ; wget -c https://grass.osgeo.org/sampledata/north_carolina/$SAMPLEDATA.tar.gz ; tar xfz $SAMPLEDATA.tar.gz --strip-components 2) - -set -x - -echo "Testing of GRASS GIS started: $NOW" >> ${LOGFILE} - -if [ "$COMPILE" = "yes" ] ; then - ## compile current source code from scratch - cd $GRASSSRC - make distclean -j$GCCTHREADS - git pull - ./$CONFIGURE ... # configure meta script containing all the compiler flags - make -j$GCCTHREADS -fi - -# run tests for the current source code -cd $REPORTS/$CURRENT_REPORTS_DIR -$PYTHON $GRASS_MULTI_RUNNER \ - --grassbin $GRASSBIN \ - --grasssrc $GRASSSRC \ - --grassdata $GRASSDATA \ - --location $SAMPLEDATA --location-type nc # \ -# --location other_location --location-type other_type - -# create overall report of all so far executed tests -# the script depends on GRASS but just Python part is enough -export PYTHONPATH="$GRASSDIST_PYTHON:$PYTHONPATH" -$PYTHON $GRASS_MULTI_REPORTER --output $FINAL_REPORT_DIR \ - $CURRENT_REPORT_BASENAME*/* - -# publish on Web site -if [ "$PUBLISH" = "yes" ] ; then - ## although we cannot be sure the tests were executed was successfully - ## so publish or archive results - rsync -rtvu --delete $REPORTS/ $SERVERDIR -fi - -echo "Nightly ($NOW) GRASS GIS test finished: $(date $DATE_FLAGS)" >> ${LOGFILE} - -exit 0 diff --git a/testsuite/raster/Makefile b/testsuite/raster/Makefile deleted file mode 100644 index 21c1f8eb6c0..00000000000 --- a/testsuite/raster/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -all: - ./raster_md5test.sh - ./rmapcalc_test.sh diff --git a/testsuite/raster/README b/testsuite/raster/README deleted file mode 100644 index bc5b0940789..00000000000 --- a/testsuite/raster/README +++ /dev/null @@ -1 +0,0 @@ -Raster map tests go here diff --git a/testsuite/raster/rhemisphere.sh b/testsuite/raster/rhemisphere.sh deleted file mode 100755 index e312f7b36d1..00000000000 --- a/testsuite/raster/rhemisphere.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/sh - -# Markus Neteler, 2006 -# This program is free software under the GNU General Public -# License (>=v2). Read the file COPYING that comes with GRASS -# for details. -# Test cases for 2D raster data -# generate a hemisphere to test slope, aspect, curvatures - -# some definitions: -BOXLENGTH=1000 # side length of test area -RADIUS=500 # half BOXLENGTH - -############ - -if [ -z "$GISBASE" ] ; then - echo "You must be in GRASS GIS to run this program." >&2 - exit 1 -fi - -# some functions - keep order here -TMP="disk.$$" - -cleanup() -{ - echo "Removing temporary map" - g.remove --q -f type=raster name=$TMP > /dev/null -} - -######################## - -g.region n=$BOXLENGTH s=0 w=0 e=$BOXLENGTH -p res=1 - -X="(col() - $RADIUS)" -Y="($RADIUS - row())" -r="sqrt($X^2 + $Y^2)" - -#Mask out unwanted parts (check for <= ??): -r.mapcalc "$TMP = if($r<$RADIUS,$r,null())" - -ALPHA="acos ($TMP/$RADIUS)" -HEIGHT="$RADIUS * sin($ALPHA)" - - -r.mapcalc "hemisphere = $HEIGHT" -cleanup -g.message "Generated raster map " -#echo "Now generate aspect + slope on " diff --git a/testsuite/raster/raster_md5test.sh b/testsuite/raster_md5test.sh similarity index 67% rename from testsuite/raster/raster_md5test.sh rename to testsuite/raster_md5test.sh index 0bda0c3428c..b3281d4c505 100755 --- a/testsuite/raster/raster_md5test.sh +++ b/testsuite/raster_md5test.sh @@ -14,19 +14,23 @@ if [ -z "$GISBASE" ] ; then fi #### check if we have sed -if [ ! -x "`which sed`" ] ; then +if [ ! -x "$(which sed)" ] ; then echo "$PROG: sed required, please install first" 1>&2 exit 1 fi -#### check if we have md5sum -if [ ! -x "`which md5sum`" ] ; then - echo "$PROG: md5sum required, please install first" 1>&2 - exit 1 +#### check if we have md5sum or md5 +if [ -x "$(which md5sum)" ] ; then + MD5="md5sum | cut -d' ' -f1" +elif [ -x "$(which md5)" ] ; then + MD5="md5 -q" +else + echo "$PROG: md5sum or md5 required, please install first" 1>&2 + exit 1 fi #### check if we have cut -if [ ! -x "`which cut`" ] ; then +if [ ! -x "$(which cut)" ] ; then echo "$PROG: cut required, please install first" 1>&2 exit 1 fi @@ -38,47 +42,47 @@ export LC_NUMERIC=C # enforce ZLIB export GRASS_COMPRESSOR=ZLIB -eval `g.gisenv` -: ${GISBASE?} ${GISDBASE?} ${LOCATION_NAME?} ${MAPSET?} +eval "$(g.gisenv)" +: "${GISBASE?}" "${GISDBASE?}" "${LOCATION_NAME?}" "${MAPSET?}" MAPSET_PATH=$GISDBASE/$LOCATION_NAME/$MAPSET # some definitions PIXEL=3 PID=$$ -TMPNAME="`echo ${PID}_tmp_testmap | sed 's+\.+_+g'`" +TMPNAME=$(echo ${PID}_tmp_testmap | sed 's+\.+_+g') # some functions - keep order here cleanup() { echo "Removing temporary map" - g.remove -f type=raster name=$TMPNAME > /dev/null + g.remove -f type=raster name="$TMPNAME" > /dev/null } # check if a MASK is already present: -MASKTMP=mask.$TMPNAME -USERMASK=usermask_${MASKTMP} -if test -f $MAPSET_PATH/cell/MASK +MASKTMP="mask.${TMPNAME}" +USERMASK="usermask_${MASKTMP}" +if test -f "${MAPSET_PATH}/cell/MASK" then echo "A user raster mask (MASK) is present. Saving it..." - g.rename raster=MASK,$USERMASK > /dev/null + g.rename raster=MASK,"$USERMASK" > /dev/null fi finalcleanup() { echo "Restoring user region" - g.region region=$TMPNAME - g.remove -f type=region name=$TMPNAME > /dev/null + g.region region="$TMPNAME" + g.remove -f type=region name="$TMPNAME" > /dev/null #restore user mask if present: - if test -f $MAPSET_PATH/cell/$USERMASK ; then + if test -f "${MAPSET_PATH}/cell/${USERMASK}" ; then echo "Restoring user MASK" g.remove -f type=raster name=MASK > /dev/null - g.rename raster=$USERMASK,MASK > /dev/null + g.rename raster="$USERMASK",MASK > /dev/null fi } check_exit_status() { - if [ $1 -ne 0 ] ; then + if [ "$1" -ne 0 ] ; then echo "An error occurred." cleanup ; finalcleanup exit 1 @@ -106,7 +110,7 @@ check_md5sum() } echo "Saving current & setting test region." -g.region save=$TMPNAME +g.region save="$TMPNAME" check_exit_status $? g.region s=0 n=$PIXEL w=0 e=$PIXEL res=1 tbres=1 check_exit_status $? @@ -118,8 +122,8 @@ r.mapcalc "$TMPNAME = 1" check_exit_status $? echo "MD5 checksum on output of INT/CELL test." -MD5="`r.out.ascii $TMPNAME precision=15 | md5sum | cut -d' ' -f1`" -check_md5sum "549e7dabe70df893803690571d2e1503" "$MD5" +SUM=$(r.out.ascii "$TMPNAME" precision=15 | eval "$MD5") +check_md5sum "549e7dabe70df893803690571d2e1503" "$SUM" cleanup echo "INT/CELL md5sum test successful" @@ -132,8 +136,8 @@ r.mapcalc "$TMPNAME = $VALUE" check_exit_status $? echo "MD5 checksum on output of FLOAT/FCELL test." -MD5="`r.out.ascii $TMPNAME precision=15 | md5sum | cut -d' ' -f1`" -check_md5sum "379f3d880b6d509051af6b4ccf470762" "$MD5" +SUM=$(r.out.ascii "$TMPNAME" precision=15 | eval "$MD5") +check_md5sum "379f3d880b6d509051af6b4ccf470762" "$SUM" cleanup echo "FLOAT/FCELL md5sum test successful" diff --git a/testsuite/vector/v.in.gps_test.sh b/testsuite/vector/v.in.gps_test.sh deleted file mode 100755 index 62b2f5b651c..00000000000 --- a/testsuite/vector/v.in.gps_test.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh -# v.in.garmin and v.in.gpsbabel test script -# by Hamish Bowman 18 May 2007 ; public domain -# assumes downloading from a garmin on port /dev/gps - -for PGM in garmin gpsbabel ; do - for DO_PTS in pts lines ; do - for TASK in w t r ; do - if [ $TASK = w ] && [ $DO_PTS = "lines" ] ; then - continue - fi - if [ $DO_PTS = "pts" ] ; then - PTFLAG="-p" - else - PTFLAG="" - fi - MAPNAME="test_${PGM}_${TASK}_${DO_PTS}_$$" - echo "-- Running [v.in.$PGM] for [$TASK] download as [$DO_PTS] into <$MAPNAME> --" - v.in.$PGM -$TASK $PTFLAG out="$MAPNAME" - if [ $? -ne 0 ] ; then - exit 1 - fi - awk 'BEGIN {printf("\n\n\n\n")}' - done - done -done diff --git a/utils/coverage_mapper.py b/utils/coverage_mapper.py index 9a2f1389781..88fa30f9b54 100644 --- a/utils/coverage_mapper.py +++ b/utils/coverage_mapper.py @@ -22,12 +22,14 @@ def map_scripts_paths(old_path): if INITIAL_GISBASE is None or INITIAL_PWD is None: return old_path p = Path(old_path) + extension = ".py" + p_name = p.stem if p.suffix == extension else p.name temporal_base = Path(INITIAL_GISBASE) / "scripts" / "t.*" base = Path(INITIAL_GISBASE) / "scripts" / "*" if p.match(str(temporal_base)): - return str(Path(INITIAL_PWD) / "temporal" / (p.name) / (p.name)) + ".py" + return str(Path(INITIAL_PWD) / "temporal" / (p_name) / (p_name)) + extension if p.match(str(base)): - return str(Path(INITIAL_PWD) / "scripts" / (p.name) / (p.name)) + ".py" + return str(Path(INITIAL_PWD) / "scripts" / (p_name) / (p_name)) + extension return old_path diff --git a/utils/thumbnails.py b/utils/thumbnails.py index ee9bd5e17ab..181a16aa666 100755 --- a/utils/thumbnails.py +++ b/utils/thumbnails.py @@ -23,13 +23,14 @@ def cleanup(): + names = [] if tmp_grad_rel: - gs.run_command( - "g.remove", flags="f", type="raster", name=tmp_grad_rel, quiet=True - ) + names.append(tmp_grad_rel) if tmp_grad_abs: + names.append(tmp_grad_abs) + if len(names) > 0: gs.run_command( - "g.remove", flags="f", type="raster", name=tmp_grad_abs, quiet=True + "g.remove", flags="f", type="raster", name=",".join(names), quiet=True ) diff --git a/vector/v.kernel/main.c b/vector/v.kernel/main.c index 96198a4d8f7..c1e7f77a36a 100644 --- a/vector/v.kernel/main.c +++ b/vector/v.kernel/main.c @@ -769,7 +769,9 @@ double compute_all_net_distances(struct Map_info *In, struct Map_info *Net, G_debug(3, " kk = %d", kk); } } - + Vect_destroy_line_struct(APoints); + Vect_destroy_line_struct(BPoints); + Vect_destroy_boxlist(List); return (kk); } diff --git a/vector/v.net.timetable/main.c b/vector/v.net.timetable/main.c index 5fb382009bb..424d8a851bd 100644 --- a/vector/v.net.timetable/main.c +++ b/vector/v.net.timetable/main.c @@ -176,9 +176,7 @@ void write_subroute(struct segment *seg, struct line_pnts *line, int line_id) struct line_cats *Cats; struct ilist *list; - Points = Vect_new_line_struct(); Cats = Vect_new_cats_struct(); - list = Vect_new_list(); r = seg->route; Vect_cat_set(Cats, 2, line_id); @@ -188,6 +186,9 @@ void write_subroute(struct segment *seg, struct line_pnts *line, int line_id) return; } + Points = Vect_new_line_struct(); + list = Vect_new_list(); + for (i = 0; i < nnodes; i++) edges[i] = 0; for (i = 0; i < lines[r]->n_values; i++) diff --git a/vector/v.random/testsuite/test_v_random.py b/vector/v.random/testsuite/test_v_random.py index e5cc95a93db..d11faf458f9 100644 --- a/vector/v.random/testsuite/test_v_random.py +++ b/vector/v.random/testsuite/test_v_random.py @@ -31,8 +31,9 @@ def tearDownClass(cls): cls.del_temp_region() def tearDown(cls): - cls.runModule("g.remove", type="vector", flags="f", name=cls.output) - cls.runModule("g.remove", type="vector", flags="f", name=cls.output2) + cls.runModule( + "g.remove", type="vector", flags="f", name=(cls.output, cls.output2) + ) def test_num_points(self): """Checking if number of points equals 100""" diff --git a/vector/v.surf.rst/benchmark/benchmark_v_surf_rst.py b/vector/v.surf.rst/benchmark/benchmark_v_surf_rst.py index 91fd784e0b3..ac69b441010 100644 --- a/vector/v.surf.rst/benchmark/benchmark_v_surf_rst.py +++ b/vector/v.surf.rst/benchmark/benchmark_v_surf_rst.py @@ -36,8 +36,7 @@ def benchmark(size, label, results): ) results.append(bm.benchmark_nprocs(module, label=label, max_nprocs=8, repeat=3)) - Module("g.remove", quiet=True, flags="f", type="raster", name=reference) - Module("g.remove", quiet=True, flags="f", type="raster", name=output) + Module("g.remove", quiet=True, flags="f", type="raster", name=(reference, output)) def generate_map(npoints, rows, cols, fname): diff --git a/vector/v.surf.rst/benchmark/benchmark_v_surf_rst_cv.py b/vector/v.surf.rst/benchmark/benchmark_v_surf_rst_cv.py index 21a3c7459eb..f140ee67b8a 100644 --- a/vector/v.surf.rst/benchmark/benchmark_v_surf_rst_cv.py +++ b/vector/v.surf.rst/benchmark/benchmark_v_surf_rst_cv.py @@ -37,8 +37,7 @@ def benchmark(size, label, results): ) results.append(bm.benchmark_nprocs(module, label=label, max_nprocs=8, repeat=3)) - Module("g.remove", quiet=True, flags="f", type="raster", name=reference) - Module("g.remove", quiet=True, flags="f", type="raster", name=output) + Module("g.remove", quiet=True, flags="f", type="raster", name=(reference, output)) def generate_map(npoints, rows, cols, fname):