From 43be353b3f6f75404a32d2ee51e651890afc5bc7 Mon Sep 17 00:00:00 2001 From: Linda Karlovska <49241681+lindakladivova@users.noreply.github.com> Date: Fri, 10 May 2024 15:15:50 +0200 Subject: [PATCH 1/4] wxGUI/history: New button for setting current computational region based on executed commands (#3421) --- gui/wxpython/history/browser.py | 125 ++++++++++++++++++++++++++++---- python/grass/grassdb/history.py | 11 +-- 2 files changed, 113 insertions(+), 23 deletions(-) diff --git a/gui/wxpython/history/browser.py b/gui/wxpython/history/browser.py index 82d86e91bd1..223c518d315 100644 --- a/gui/wxpython/history/browser.py +++ b/gui/wxpython/history/browser.py @@ -25,6 +25,9 @@ from gui_core.wrap import SearchCtrl, StaticText, StaticBox, Button from history.tree import HistoryBrowserTree +from icons.icon import MetaIcon + +import grass.script as gs from grass.grassdb import history @@ -76,6 +79,10 @@ def __init__(self, parent, giface, title=("Command Info"), style=wx.TAB_TRAVERSA self.giface = giface self.title = title + self.region_settings = None + + self._initImages() + self._createGeneralInfoBox() self._createRegionSettingsBox() @@ -97,6 +104,13 @@ def _layout(self): self.Layout() + def _initImages(self): + bmpsize = (16, 16) + self.icons = { + "check": MetaIcon(img="success").GetBitmap(bmpsize), + "cross": MetaIcon(img="cross").GetBitmap(bmpsize), + } + def _createGeneralInfoBox(self): """Create static box for general info about the command""" self.general_info_box = StaticBox( @@ -119,20 +133,34 @@ def _createGeneralInfoBox(self): def _createRegionSettingsBox(self): """Create a static box for displaying region settings of the command""" self.region_settings_box = StaticBox( - parent=self, id=wx.ID_ANY, label=_("Region settings") + parent=self, + id=wx.ID_ANY, + label=_("Computational region during command execution"), ) self.region_settings_box_sizer = wx.StaticBoxSizer( self.region_settings_box, wx.VERTICAL ) - self.sizer_region_settings = wx.GridBagSizer(hgap=0, vgap=0) - self.sizer_region_settings.SetCols(2) - self.sizer_region_settings.SetRows(9) + self.sizer_region_settings_match = wx.BoxSizer(wx.HORIZONTAL) + self.region_settings_box_sizer.Add( + self.sizer_region_settings_match, + proportion=0, + flag=wx.ALL | wx.EXPAND, + border=5, + ) + + self.sizer_region_settings_grid = wx.GridBagSizer(hgap=0, vgap=0) + self.sizer_region_settings_grid.SetCols(2) + self.sizer_region_settings_grid.SetRows(9) self.region_settings_box_sizer.Add( - self.sizer_region_settings, proportion=1, flag=wx.ALL | wx.EXPAND, border=5 + self.sizer_region_settings_grid, + proportion=1, + flag=wx.ALL | wx.EXPAND, + border=5, ) - self.sizer_region_settings.AddGrowableCol(1) + + self.sizer_region_settings_grid.AddGrowableCol(1) self.region_settings_box.Hide() def _general_info_filter(self, key, value): @@ -142,7 +170,7 @@ def _general_info_filter(self, key, value): ) def _region_settings_filter(self, key): - return (key != "projection") and (key != "zone") + return (key != "projection") and (key != "zone") and (key != "cells") def _updateGeneralInfoBox(self, command_info): """Update a static box for displaying general info about the command""" @@ -180,13 +208,13 @@ def _updateGeneralInfoBox(self, command_info): def _updateRegionSettingsBox(self, command_info): """Update a static box for displaying region settings of the command""" - self.sizer_region_settings.Clear(True) + self.sizer_region_settings_grid.Clear(True) - region_settings = command_info["region"] + self.region_settings = command_info["region"] idx = 0 - for key, value in region_settings.items(): + for key, value in self.region_settings.items(): if self._region_settings_filter(key): - self.sizer_region_settings.Add( + self.sizer_region_settings_grid.Add( StaticText( parent=self.region_settings_box, id=wx.ID_ANY, @@ -197,7 +225,7 @@ def _updateRegionSettingsBox(self, command_info): border=5, pos=(idx, 0), ) - self.sizer_region_settings.Add( + self.sizer_region_settings_grid.Add( StaticText( parent=self.region_settings_box, id=wx.ID_ANY, @@ -210,6 +238,58 @@ def _updateRegionSettingsBox(self, command_info): ) idx += 1 + self.sizer_region_settings_match.Clear(True) + + # Region condition + history_region = self.region_settings + current_region = self._get_current_region() + region_matches = history_region == current_region + + # Icon and button according to the condition + if region_matches: + icon = self.icons["check"] + button_label = None + else: + icon = self.icons["cross"] + button_label = _("Update current region") + + # Static text + textRegionMatch = StaticText( + parent=self.region_settings_box, + id=wx.ID_ANY, + label=_("Region match"), + ) + self.sizer_region_settings_match.Add( + textRegionMatch, + proportion=0, + flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, + border=10, + ) + + # Static bitmap for icon + iconRegionMatch = wx.StaticBitmap(self.region_settings_box, bitmap=icon) + self.sizer_region_settings_match.Add( + iconRegionMatch, + proportion=0, + flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, + border=10, + ) + + if button_label: + # Button for region update + buttonUpdateRegion = Button(self.region_settings_box, id=wx.ID_ANY) + buttonUpdateRegion.SetLabel(_("Update current region")) + buttonUpdateRegion.SetToolTip( + _("Set current computational region to the region of executed command") + ) + buttonUpdateRegion.Bind(wx.EVT_BUTTON, self.OnUpdateRegion) + self.sizer_region_settings_match.Add( + buttonUpdateRegion, + proportion=1, + flag=wx.ALIGN_CENTER_VERTICAL, + border=10, + ) + self.region_settings_box.Layout() self.region_settings_box.Show() @@ -226,11 +306,30 @@ def showCommandInfo(self, command_info): def clearCommandInfo(self): """Clear command info.""" self.sizer_general_info.Clear(True) - self.sizer_region_settings.Clear(True) + self.sizer_region_settings_grid.Clear(True) + self.sizer_region_settings_text.Clear(True) self._createGeneralInfoBox() self._createRegionSettingsBox() self._layout() + def _get_current_region(self): + """Get current computational region settings.""" + return gs.region() + + def _get_history_region(self): + """Get computational region settings of executed command.""" + history_region = {} + for key, value in self.region_settings.items(): + if self._region_settings_filter(key): + history_region[key] = value + return history_region + + def OnUpdateRegion(self, event): + """Set current region to the region of executed command.""" + history_region = self._get_history_region() + gs.run_command("g.region", **history_region) + self.giface.updateMap.emit(render=False, renderVector=False) + class HistoryBrowser(wx.SplitterWindow): """History browser window for executing the commands from history log diff --git a/python/grass/grassdb/history.py b/python/grass/grassdb/history.py index d20628dc820..b9a90bd896c 100644 --- a/python/grass/grassdb/history.py +++ b/python/grass/grassdb/history.py @@ -16,7 +16,6 @@ from datetime import datetime import grass.script as gs -from grass.script.utils import parse_key_val class Status(Enum): @@ -274,15 +273,7 @@ def get_initial_command_info(env_run): mask3d_present = (mapset_path / "grid3" / "RASTER3D_MASK").exists() # Computational region settings - region_settings = dict( - parse_key_val( - gs.read_command("g.region", flags="g", env=env_run), val_type=float - ) - ) - - # Convert floats to integers if possible - for key, value in region_settings.items(): - region_settings[key] = int(value) if value.is_integer() else value + region_settings = gs.region(env=env_run) # Finalize the command info dictionary cmd_info = { From 817af4813c165fa7f95b0726abd509759a4fb70f Mon Sep 17 00:00:00 2001 From: Martin Landa Date: Sat, 11 May 2024 22:17:21 +0200 Subject: [PATCH 2/4] grassTask.get_param(): full match also for strings (#3582) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * script.task: Create test file grassTask.get_param() Co-authored-by: Edouard Choinière <27212526+echoix@users.noreply.github.com> --- python/grass/script/task.py | 3 --- python/grass/script/tests/test_script_task.py | 11 +++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 python/grass/script/tests/test_script_task.py diff --git a/python/grass/script/task.py b/python/grass/script/task.py index 325b5640edc..eedf34738e7 100644 --- a/python/grass/script/task.py +++ b/python/grass/script/task.py @@ -148,9 +148,6 @@ def get_param(self, value, element="name", raiseError=True): if isinstance(val, (list, tuple)): if value in val: return p - elif isinstance(val, (bytes, str)): - if p[element][: len(value)] == value: - return p else: if p[element] == value: return p diff --git a/python/grass/script/tests/test_script_task.py b/python/grass/script/tests/test_script_task.py new file mode 100644 index 00000000000..6799885761e --- /dev/null +++ b/python/grass/script/tests/test_script_task.py @@ -0,0 +1,11 @@ +from grass.script.task import grassTask as gtask + + +def test_mapcalc_simple_e_name(): + gt = gtask("r.mapcalc.simple") + assert gt.get_param("e")["name"] == "e" + + +def test_mapcalc_simple_expession_name(): + gt = gtask("r.mapcalc.simple") + assert gt.get_param("expression")["name"] == "expression" From 9f7222b52aa8eddb0f59a46948dd4c00abcb9eb9 Mon Sep 17 00:00:00 2001 From: Tomas Zigo <50632337+tmszi@users.noreply.github.com> Date: Mon, 13 May 2024 04:28:39 +0200 Subject: [PATCH 3/4] wxGUI/gui_core: fix import Rasterlite DB raster (#2513) --- gui/wxpython/gui_core/gselect.py | 32 +++++++++++++++++++++++++++ gui/wxpython/modules/import_export.py | 16 ++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/gui/wxpython/gui_core/gselect.py b/gui/wxpython/gui_core/gselect.py index 12773f1c449..de9a7d67636 100644 --- a/gui/wxpython/gui_core/gselect.py +++ b/gui/wxpython/gui_core/gselect.py @@ -2370,6 +2370,17 @@ def getProjMatchCaption(projectionMatch): ) data.append((layerId, raster, int(projectionMatch), grassName)) layerId += 1 + elif self.dbWidgets["format"].GetStringSelection() == "Rasterlite": + rasters = self._getRasterliteDBRasters(dsn) + for raster in rasters: + grassName = GetValidLayerName(raster) + projectionMatch = hasRastSameProjAsLocation(dsn) + projectionMatchCaption = getProjMatchCaption(projectionMatch) + listData.append( + (layerId, raster, projectionMatchCaption, grassName) + ) + data.append((layerId, raster, int(projectionMatch), grassName)) + layerId += 1 # emit signal self.reloadDataRequired.emit(listData=listData, data=data) @@ -2652,6 +2663,27 @@ def _getPGDBRasters(self, dsn): Debug.msg(3, f"GdalSelect._getPGDBRasters(): return {rasters}") return rasters + def _getRasterliteDBRasters(self, dsn): + """Get Rasterlite DB rasters + + :param str dsn: Rasterlite DB data source name + + :return list: list of Rasterlite DB rasters + """ + try: + from osgeo import gdal + except ImportError: + GError( + parent=self, + message=_("The Python GDAL package is missing. Please install it."), + ) + return [] + rasterlite = gdal.Open(dsn) + rasters = rasterlite.GetSubDatasets() + if rasters: + return [r[0].rsplit("table=")[-1] for r in rasters] + return [os.path.basename(rasterlite.GetFileList()[0]).rsplit(".")[0]] + class ProjSelect(wx.ComboBox): """Widget for selecting input raster/vector map used by diff --git a/gui/wxpython/modules/import_export.py b/gui/wxpython/modules/import_export.py index da082feb68d..df1882c6ed6 100644 --- a/gui/wxpython/modules/import_export.py +++ b/gui/wxpython/modules/import_export.py @@ -470,8 +470,24 @@ def OnRun(self, event): if self.dsnInput.GetType() == "dir": idsn = os.path.join(dsn, layer) elif self.dsnInput.GetType() == "db": + idsn = dsn if "PG:" in dsn: idsn = f"{dsn} table={layer}" + elif os.path.exists(idsn): + try: + from osgeo import gdal + except ImportError: + GError( + parent=self, + message=_( + "The Python GDAL package is missing." + " Please install it." + ), + ) + return + dataset = gdal.Open(dsn) + if "Rasterlite" in dataset.GetDriver().ShortName: + idsn = f"RASTERLITE:{dsn},table={layer}" else: idsn = dsn From 0ec833ce53f7227dab55cb0f7d9090af7ebe898d Mon Sep 17 00:00:00 2001 From: Nicklas Larsson Date: Tue, 14 May 2024 13:11:12 +0200 Subject: [PATCH 4/4] r.flow: rename io.h file which causes conflict with MSYS2 (#3708) Closes: https://github.com/OSGeo/grass/issues/3700 --- raster/r.flow/{io.c => flow_io.c} | 0 raster/r.flow/{io.h => flow_io.h} | 0 raster/r.flow/main.c | 2 +- raster/r.flow/mem.c | 2 +- raster/r.flow/precomp.c | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename raster/r.flow/{io.c => flow_io.c} (100%) rename raster/r.flow/{io.h => flow_io.h} (100%) diff --git a/raster/r.flow/io.c b/raster/r.flow/flow_io.c similarity index 100% rename from raster/r.flow/io.c rename to raster/r.flow/flow_io.c diff --git a/raster/r.flow/io.h b/raster/r.flow/flow_io.h similarity index 100% rename from raster/r.flow/io.h rename to raster/r.flow/flow_io.h diff --git a/raster/r.flow/main.c b/raster/r.flow/main.c index 791cd9350c0..bb8c3a6d6b4 100644 --- a/raster/r.flow/main.c +++ b/raster/r.flow/main.c @@ -32,7 +32,7 @@ #include #include "r.flow.h" #include "mem.h" -#include "io.h" +#include "flow_io.h" #include "aspect.h" #include "precomp.h" diff --git a/raster/r.flow/mem.c b/raster/r.flow/mem.c index 44f24e55649..4dbe818bc11 100644 --- a/raster/r.flow/mem.c +++ b/raster/r.flow/mem.c @@ -28,7 +28,7 @@ #include #include #include "r.flow.h" -#include "io.h" +#include "flow_io.h" #include "mem.h" /************************** MEMORY MGMT/ACCESS **************************/ diff --git a/raster/r.flow/precomp.c b/raster/r.flow/precomp.c index 36d5678a8c2..eac8ca3f1fa 100644 --- a/raster/r.flow/precomp.c +++ b/raster/r.flow/precomp.c @@ -28,7 +28,7 @@ #include #include #include "r.flow.h" -#include "io.h" +#include "flow_io.h" #include "mem.h" #include "aspect.h"