""" @package modules.vclean @brief Dialog for interactive construction of vector cleaning operations Classes: - vclean::VectorCleaningFrame (C) 2010-2011 by the GRASS Development Team This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. @author Markus Metz """ import os import wx import wx.lib.scrolledpanel as scrolled from core.gcmd import RunCommand, GError from core import globalvar from gui_core.gselect import Select from core.settings import UserSettings from grass.script import core as grass from gui_core.wrap import Button, StaticText, StaticBox, TextCtrl class VectorCleaningFrame(wx.Frame): def __init__( self, parent, id=wx.ID_ANY, title=_("Set up vector cleaning tools"), style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER, **kwargs, ): """ Dialog for interactively defining vector cleaning tools """ wx.Frame.__init__(self, parent, id, title, style=style, **kwargs) self.parent = parent # GMFrame if self.parent: self.log = self.parent.GetLogWindow() else: self.log = None # grass command self.cmd = "v.clean" # statusbar self.CreateStatusBar() # icon self.SetIcon( wx.Icon(os.path.join(globalvar.ICONDIR, "grass.ico"), wx.BITMAP_TYPE_ICO) ) self.panel = wx.Panel(parent=self, id=wx.ID_ANY) # input map to clean self.inmap = "" # cleaned output map self.outmap = "" self.ftype = "" # cleaning tools self.toolslines = {} self.tool_desc_list = [ _("break lines/boundaries"), _("remove duplicates"), _("remove dangles"), _("change boundary dangles to lines"), _("remove bridges"), _("change bridges to lines"), _("snap lines/boundaries"), _("remove duplicate area centroids"), _("break polygons"), _("prune lines/boundaries"), _("remove small areas"), _("remove lines/boundaries of zero length"), _("remove small angles at nodes"), ] self.tool_list = [ "break", "rmdupl", "rmdangle", "chdangle", "rmbridge", "chbridge", "snap", "rmdac", "bpol", "prune", "rmarea", "rmline", "rmsa", ] self.ftype = ["point", "line", "boundary", "centroid", "area", "face"] self.n_ftypes = len(self.ftype) self.tools_string = "" self.thresh_string = "" self.ftype_string = "" self.SetStatusText(_("Set up vector cleaning tools")) self.elem = "vector" self.ctlabel = _("Choose cleaning tools and set thresholds") # top controls self.inmaplabel = StaticText( parent=self.panel, id=wx.ID_ANY, label=_("Select input vector map:") ) self.selectionInput = Select( parent=self.panel, id=wx.ID_ANY, size=globalvar.DIALOG_GSELECT_SIZE, type="vector", ) self.ftype_check = {} ftypeBox = StaticBox( parent=self.panel, id=wx.ID_ANY, label=_(" Feature type: ") ) self.ftypeSizer = wx.StaticBoxSizer(ftypeBox, wx.HORIZONTAL) self.outmaplabel = StaticText( parent=self.panel, id=wx.ID_ANY, label=_("Select output vector map:") ) self.selectionOutput = Select( parent=self.panel, id=wx.ID_ANY, size=globalvar.DIALOG_GSELECT_SIZE, mapsets=[ grass.gisenv()["MAPSET"], ], fullyQualified=False, type="vector", ) self.overwrite = wx.CheckBox( parent=self.panel, id=wx.ID_ANY, label=_("Allow output files to overwrite existing files"), ) self.overwrite.SetValue( UserSettings.Get(group="cmd", key="overwrite", subkey="enabled") ) # cleaning tools self.ct_label = StaticText(parent=self.panel, id=wx.ID_ANY, label=self.ctlabel) self.ct_panel = self._toolsPanel() # buttons to manage cleaning tools self.btn_add = Button(parent=self.panel, id=wx.ID_ADD) self.btn_remove = Button(parent=self.panel, id=wx.ID_REMOVE) self.btn_moveup = Button(parent=self.panel, id=wx.ID_UP) self.btn_movedown = Button(parent=self.panel, id=wx.ID_DOWN) # add one tool as default self.AddTool() self.selected = -1 # Buttons self.btn_close = Button(parent=self.panel, id=wx.ID_CLOSE) self.btn_run = Button(parent=self.panel, id=wx.ID_ANY, label=_("&Run")) self.btn_run.SetDefault() self.btn_clipboard = Button(parent=self.panel, id=wx.ID_COPY) self.btn_clipboard.SetToolTip( _("Copy the current command string to the clipboard (Ctrl+C)") ) self.btn_help = Button(parent=self.panel, id=wx.ID_HELP) # bindings self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose) self.btn_run.Bind(wx.EVT_BUTTON, self.OnCleaningRun) self.btn_clipboard.Bind(wx.EVT_BUTTON, self.OnCopy) self.btn_help.Bind(wx.EVT_BUTTON, self.OnHelp) self.btn_add.Bind(wx.EVT_BUTTON, self.OnAddTool) self.btn_remove.Bind(wx.EVT_BUTTON, self.OnClearTool) self.btn_moveup.Bind(wx.EVT_BUTTON, self.OnMoveToolUp) self.btn_movedown.Bind(wx.EVT_BUTTON, self.OnMoveToolDown) # layout self._layout() self.SetMinSize(self.GetBestSize()) self.CentreOnScreen() def _layout(self): sizer = wx.BoxSizer(wx.VERTICAL) # # input output # inSizer = wx.GridBagSizer(hgap=5, vgap=5) inSizer.Add( self.inmaplabel, pos=(0, 0), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border=1, ) inSizer.Add( self.selectionInput, pos=(1, 0), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border=1, ) self.ftype_check = [ wx.CheckBox(parent=self.panel, id=wx.ID_ANY, label=_("point")), wx.CheckBox(parent=self.panel, id=wx.ID_ANY, label=_("line")), wx.CheckBox(parent=self.panel, id=wx.ID_ANY, label=_("boundary")), wx.CheckBox(parent=self.panel, id=wx.ID_ANY, label=_("centroid")), wx.CheckBox(parent=self.panel, id=wx.ID_ANY, label=_("area")), wx.CheckBox(parent=self.panel, id=wx.ID_ANY, label=_("face")), ] typeoptSizer = wx.BoxSizer(wx.HORIZONTAL) for num in range(0, self.n_ftypes): type_box = self.ftype_check[num] if self.ftype[num] in ("point", "line", "area"): type_box.SetValue(True) typeoptSizer.Add(type_box, flag=wx.ALIGN_LEFT, border=1) self.ftypeSizer.Add( typeoptSizer, flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2 ) outSizer = wx.GridBagSizer(hgap=5, vgap=5) outSizer.Add( self.outmaplabel, pos=(0, 0), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border=1, ) outSizer.Add( self.selectionOutput, pos=(1, 0), flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border=1, ) replaceSizer = wx.BoxSizer(wx.HORIZONTAL) replaceSizer.Add( self.overwrite, proportion=1, flag=wx.ALL | wx.EXPAND, border=1 ) outSizer.Add(replaceSizer, pos=(2, 0), flag=wx.ALL | wx.EXPAND, border=1) # # tools selection # bodySizer = wx.GridBagSizer(hgap=5, vgap=5) bodySizer.Add(self.ct_label, pos=(0, 0), span=(1, 2), flag=wx.ALL, border=5) bodySizer.Add(self.ct_panel, pos=(1, 0), span=(1, 2)) manageBoxSizer = wx.GridBagSizer(hgap=10, vgap=1) # start with row 1 for nicer layout manageBoxSizer.Add(self.btn_add, pos=(1, 0), border=2, flag=wx.ALL | wx.EXPAND) manageBoxSizer.Add( self.btn_remove, pos=(2, 0), border=2, flag=wx.ALL | wx.EXPAND ) manageBoxSizer.Add( self.btn_moveup, pos=(3, 0), border=2, flag=wx.ALL | wx.EXPAND ) manageBoxSizer.Add( self.btn_movedown, pos=(4, 0), border=2, flag=wx.ALL | wx.EXPAND ) bodySizer.Add( manageBoxSizer, pos=(1, 2), flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5 ) bodySizer.AddGrowableCol(2) # # standard buttons # btnSizer = wx.BoxSizer(wx.HORIZONTAL) btnSizer.Add(self.btn_close, flag=wx.LEFT | wx.RIGHT, border=5) btnSizer.Add(self.btn_run, flag=wx.LEFT | wx.RIGHT, border=5) btnSizer.Add(self.btn_clipboard, flag=wx.LEFT | wx.RIGHT, border=5) btnSizer.Add(self.btn_help, flag=wx.LEFT | wx.RIGHT, border=5) # # put it all together # sizer.Add(inSizer, proportion=0, flag=wx.ALL | wx.EXPAND, border=5) sizer.Add(self.ftypeSizer, proportion=0, flag=wx.ALL | wx.EXPAND, border=5) sizer.Add(outSizer, proportion=0, flag=wx.ALL | wx.EXPAND, border=5) sizer.Add( wx.StaticLine(parent=self, id=wx.ID_ANY, style=wx.LI_HORIZONTAL), proportion=0, flag=wx.EXPAND | wx.ALL, border=5, ) sizer.Add(bodySizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=5) sizer.Add( wx.StaticLine(parent=self, id=wx.ID_ANY, style=wx.LI_HORIZONTAL), proportion=0, flag=wx.EXPAND | wx.ALL, border=5, ) sizer.Add(btnSizer, proportion=0, flag=wx.ALL | wx.ALIGN_RIGHT, border=5) self.panel.SetAutoLayout(True) self.panel.SetSizer(sizer) sizer.Fit(self.panel) self.Layout() def _toolsPanel(self): ct_panel = scrolled.ScrolledPanel( parent=self.panel, id=wx.ID_ANY, size=(500, 240), style=wx.SUNKEN_BORDER ) self.ct_sizer = wx.GridBagSizer(vgap=2, hgap=4) ct_panel.SetSizer(self.ct_sizer) ct_panel.SetAutoLayout(True) return ct_panel def OnAddTool(self, event): """Add tool button pressed""" self.AddTool() def AddTool(self): snum = len(self.toolslines.keys()) num = snum + 1 # tool tool_cbox = wx.ComboBox( parent=self.ct_panel, id=1000 + num, size=(300, -1), choices=self.tool_desc_list, style=wx.CB_DROPDOWN | wx.CB_READONLY | wx.TE_PROCESS_ENTER, ) self.Bind(wx.EVT_COMBOBOX, self.OnSetTool, tool_cbox) # threshold txt_ctrl = TextCtrl( parent=self.ct_panel, id=2000 + num, value="0.00", size=(100, -1), style=wx.TE_NOHIDESEL, ) self.Bind(wx.EVT_TEXT, self.OnThreshValue, txt_ctrl) # select with tool number select = wx.CheckBox(parent=self.ct_panel, id=num, label=str(num) + ".") select.SetValue(False) self.Bind(wx.EVT_CHECKBOX, self.OnSelect, select) # start with row 1 and col 1 for nicer layout self.ct_sizer.Add(select, pos=(num, 1), flag=wx.ALIGN_CENTER | wx.RIGHT) self.ct_sizer.Add( tool_cbox, pos=(num, 2), flag=wx.ALIGN_CENTER | wx.RIGHT, border=5 ) self.ct_sizer.Add( txt_ctrl, pos=(num, 3), flag=wx.ALIGN_CENTER | wx.RIGHT, border=5 ) self.toolslines[num] = {"tool_desc": "", "tool": "", "thresh": "0.00"} self.ct_panel.Layout() self.ct_panel.SetupScrolling() def OnClearTool(self, event): """Remove tool button pressed""" id = self.selected if id > 0: self.FindWindowById(id + 1000).SetValue("") self.toolslines[id]["tool_desc"] = "" self.toolslines[id]["tool"] = "" self.SetStatusText(_("%s. cleaning tool removed, will be ignored") % id) else: self.SetStatusText(_("Please select a cleaning tool to remove")) def OnMoveToolUp(self, event): """Move up tool button pressed""" id = self.selected if id > 1: id_up = id - 1 this_toolline = self.toolslines[id] up_toolline = self.toolslines[id_up] self.FindWindowById(id_up).SetValue(True) self.FindWindowById(id_up + 1000).SetValue(this_toolline["tool_desc"]) self.FindWindowById(id_up + 2000).SetValue(this_toolline["thresh"]) self.toolslines[id_up] = this_toolline self.FindWindowById(id).SetValue(False) self.FindWindowById(id + 1000).SetValue(up_toolline["tool_desc"]) self.FindWindowById(id + 2000).SetValue(up_toolline["thresh"]) self.toolslines[id] = up_toolline self.selected = id_up self.SetStatusText(_("%s. cleaning tool moved up") % id) elif id == 1: self.SetStatusText(_("1. cleaning tool can not be moved up ")) elif id == -1: self.SetStatusText(_("Please select a cleaning tool to move up")) def OnMoveToolDown(self, event): """Move down tool button pressed""" id = self.selected snum = len(self.toolslines.keys()) if id > 0 and id < snum: id_down = id + 1 this_toolline = self.toolslines[id] down_toolline = self.toolslines[id_down] self.FindWindowById(id_down).SetValue(True) self.FindWindowById(id_down + 1000).SetValue(this_toolline["tool_desc"]) self.FindWindowById(id_down + 2000).SetValue(this_toolline["thresh"]) self.toolslines[id_down] = this_toolline self.FindWindowById(id).SetValue(False) self.FindWindowById(id + 1000).SetValue(down_toolline["tool_desc"]) self.FindWindowById(id + 2000).SetValue(down_toolline["thresh"]) self.toolslines[id] = down_toolline self.selected = id_down self.SetStatusText(_("%s. cleaning tool moved down") % id) elif id == snum: self.SetStatusText(_("Last cleaning tool can not be moved down ")) elif id == -1: self.SetStatusText(_("Please select a cleaning tool to move down")) def OnSetTool(self, event): """Tool was defined""" id = event.GetId() tool_no = id - 1000 num = self.FindWindowById(id).GetCurrentSelection() self.toolslines[tool_no]["tool_desc"] = self.tool_desc_list[num] self.toolslines[tool_no]["tool"] = self.tool_list[num] self.SetStatusText( str(tool_no) + ". " + _("cleaning tool: '%s'") % (self.tool_list[num]) ) def OnThreshValue(self, event): """Threshold value was entered""" id = event.GetId() num = id - 2000 self.toolslines[num]["thresh"] = self.FindWindowById(id).GetValue() self.SetStatusText( _("Threshold for %(num)s. tool '%(tool)s': %(thresh)s") % { "num": num, "tool": self.toolslines[num]["tool"], "thresh": self.toolslines[num]["thresh"], } ) def OnSelect(self, event): """Tool was selected""" id = event.GetId() if self.selected > -1 and self.selected != id: win = self.FindWindowById(self.selected) win.SetValue(False) if self.selected != id: self.selected = id else: self.selected = -1 def OnDone(self, event): """Command done""" self.SetStatusText("") def OnCleaningRun(self, event): """Builds options and runs v.clean""" self.GetCmdStrings() err = list() for p, name in ( (self.inmap, _("Name of input vector map")), (self.outmap, _("Name for output vector map")), (self.tools_string, _("Tools")), (self.thresh_string, _("Threshold")), ): if not p: err.append(_("'%s' not defined") % name) if err: GError( _("Some parameters not defined. Operation " "canceled.\n\n%s") % "\n".join(err), parent=self, ) return self.SetStatusText(_("Executing selected cleaning operations...")) snum = len(self.toolslines.keys()) if self.log: cmd = [ self.cmd, "input=%s" % self.inmap, "output=%s" % self.outmap, "tool=%s" % self.tools_string, "thres=%s" % self.thresh_string, ] if self.ftype_string: cmd.append("type=%s" % self.ftype_string) if self.overwrite.IsChecked(): cmd.append("--overwrite") self.log.RunCmd(cmd, onDone=self.OnDone) self.parent.Raise() else: if self.overwrite.IsChecked(): overwrite = True else: overwrite = False RunCommand( self.cmd, input=self.inmap, output=self.outmap, type=self.ftype_string, tool=self.tools_string, thresh=self.thresh_string, overwrite=overwrite, ) def OnClose(self, event): self.Destroy() def OnHelp(self, event): """Show GRASS manual page""" RunCommand("g.manual", quiet=True, parent=self, entry=self.cmd) def OnCopy(self, event): """Copy the command""" cmddata = wx.TextDataObject() # get tool and thresh strings self.GetCmdStrings() cmdstring = "%s" % (self.cmd) # list -> string cmdstring += " input=%s output=%s type=%s tool=%s thres=%s" % ( self.inmap, self.outmap, self.ftype_string, self.tools_string, self.thresh_string, ) if self.overwrite.IsChecked(): cmdstring += " --overwrite" cmddata.SetText(cmdstring) if wx.TheClipboard.Open(): wx.TheClipboard.SetData(cmddata) wx.TheClipboard.Close() self.SetStatusText(_("Vector cleaning command copied to clipboard")) def GetCmdStrings(self): self.tools_string = "" self.thresh_string = "" self.ftype_string = "" # feature types first = 1 for num in range(0, self.n_ftypes - 1): if self.ftype_check[num].IsChecked(): if first: self.ftype_string = "%s" % self.ftype[num] first = 0 else: self.ftype_string += ",%s" % self.ftype[num] # cleaning tools first = 1 snum = len(self.toolslines.keys()) for num in range(1, snum + 1): if self.toolslines[num]["tool"]: if first: self.tools_string = "%s" % self.toolslines[num]["tool"] self.thresh_string = "%s" % self.toolslines[num]["thresh"] first = 0 else: self.tools_string += ",%s" % self.toolslines[num]["tool"] self.thresh_string += ",%s" % self.toolslines[num]["thresh"] self.inmap = self.selectionInput.GetValue() self.outmap = self.selectionOutput.GetValue() if __name__ == "__main__": app = wx.App() frame = VectorCleaningFrame(parent=None) frame.Show() app.MainLoop()