vclean.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. """
  2. @package modules.vclean
  3. @brief Dialog for interactive construction of vector cleaning
  4. operations
  5. Classes:
  6. - vclean::VectorCleaningFrame
  7. (C) 2010-2011 by the GRASS Development Team
  8. This program is free software under the GNU General Public License
  9. (>=v2). Read the file COPYING that comes with GRASS for details.
  10. @author Markus Metz
  11. """
  12. import os
  13. import sys
  14. import wx
  15. import wx.lib.scrolledpanel as scrolled
  16. if __name__ == '__main__':
  17. gui_wx_path = os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'wxpython')
  18. if gui_wx_path not in sys.path:
  19. sys.path.append(gui_wx_path)
  20. from core.gcmd import RunCommand, GError
  21. from core import globalvar
  22. from core.utils import _
  23. from gui_core.gselect import Select
  24. from core.settings import UserSettings
  25. from grass.script import core as grass
  26. class VectorCleaningFrame(wx.Frame):
  27. def __init__(self, parent, id=wx.ID_ANY, title=_('Set up vector cleaning tools'),
  28. style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER,
  29. **kwargs):
  30. """!
  31. Dialog for interactively defining vector cleaning tools
  32. """
  33. wx.Frame.__init__(self, parent, id, title, style=style, **kwargs)
  34. self.parent = parent # GMFrame
  35. if self.parent:
  36. self.log = self.parent.GetLogWindow()
  37. else:
  38. self.log = None
  39. # grass command
  40. self.cmd = 'v.clean'
  41. # statusbar
  42. self.CreateStatusBar()
  43. # icon
  44. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  45. self.panel = wx.Panel(parent=self, id=wx.ID_ANY)
  46. # input map to clean
  47. self.inmap = ''
  48. # cleaned output map
  49. self.outmap = ''
  50. self.ftype = ''
  51. # cleaning tools
  52. self.toolslines = {}
  53. self.tool_desc_list = [
  54. _('break lines/boundaries'),
  55. _('remove duplicates'),
  56. _('remove dangles'),
  57. _('change boundary dangles to lines'),
  58. _('remove bridges'),
  59. _('change bridges to lines'),
  60. _('snap lines/boundaries'),
  61. _('remove duplicate area centroids'),
  62. _('break polygons'),
  63. _('prune lines/boundaries'),
  64. _('remove small areas'),
  65. _('remove lines/boundaries of zero length'),
  66. _('remove small angles at nodes')
  67. ]
  68. self.tool_list = [
  69. 'break',
  70. 'rmdupl',
  71. 'rmdangle',
  72. 'chdangle',
  73. 'rmbridge',
  74. 'chbridge',
  75. 'snap',
  76. 'rmdac',
  77. 'bpol',
  78. 'prune',
  79. 'rmarea',
  80. 'rmline',
  81. 'rmsa'
  82. ]
  83. self.ftype = [
  84. 'point',
  85. 'line',
  86. 'boundary',
  87. 'centroid',
  88. 'area',
  89. 'face']
  90. self.n_ftypes = len(self.ftype)
  91. self.tools_string = ''
  92. self.thresh_string = ''
  93. self.ftype_string = ''
  94. self.SetStatusText(_("Set up vector cleaning tools"))
  95. self.elem = 'vector'
  96. self.ctlabel = _('Choose cleaning tools and set thresholds')
  97. # top controls
  98. self.inmaplabel = wx.StaticText(parent=self.panel, id=wx.ID_ANY,
  99. label=_('Select input vector map:'))
  100. self.selectionInput = Select(parent=self.panel, id=wx.ID_ANY,
  101. size=globalvar.DIALOG_GSELECT_SIZE,
  102. type='vector')
  103. self.ftype_check = {}
  104. ftypeBox = wx.StaticBox(parent=self.panel, id=wx.ID_ANY,
  105. label=_(' Feature type: '))
  106. self.ftypeSizer = wx.StaticBoxSizer(ftypeBox, wx.HORIZONTAL)
  107. self.outmaplabel = wx.StaticText(parent=self.panel, id=wx.ID_ANY,
  108. label=_('Select output vector map:'))
  109. self.selectionOutput = Select(parent=self.panel, id=wx.ID_ANY,
  110. size=globalvar.DIALOG_GSELECT_SIZE,
  111. mapsets=[grass.gisenv()['MAPSET'],],
  112. fullyQualified = False,
  113. type='vector')
  114. self.overwrite = wx.CheckBox(parent=self.panel, id=wx.ID_ANY,
  115. label=_('Allow output files to overwrite existing files'))
  116. self.overwrite.SetValue(UserSettings.Get(group='cmd', key='overwrite', subkey='enabled'))
  117. # cleaning tools
  118. self.ct_label = wx.StaticText(parent=self.panel, id=wx.ID_ANY,
  119. label=self.ctlabel)
  120. self.ct_panel = self._toolsPanel()
  121. # buttons to manage cleaning tools
  122. self.btn_add = wx.Button(parent=self.panel, id=wx.ID_ADD)
  123. self.btn_remove = wx.Button(parent=self.panel, id=wx.ID_REMOVE)
  124. self.btn_moveup = wx.Button(parent=self.panel, id=wx.ID_UP)
  125. self.btn_movedown = wx.Button(parent=self.panel, id=wx.ID_DOWN)
  126. # add one tool as default
  127. self.AddTool()
  128. self.selected = -1
  129. # Buttons
  130. self.btn_close = wx.Button(parent=self.panel, id=wx.ID_CLOSE)
  131. self.btn_run = wx.Button(parent=self.panel, id=wx.ID_ANY, label=_("&Run"))
  132. self.btn_run.SetDefault()
  133. self.btn_clipboard = wx.Button(parent=self.panel, id=wx.ID_COPY)
  134. self.btn_clipboard.SetToolTipString(_("Copy the current command string to the clipboard (Ctrl+C)"))
  135. self.btn_help = wx.Button(parent=self.panel, id=wx.ID_HELP)
  136. # bindings
  137. self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
  138. self.btn_run.Bind(wx.EVT_BUTTON, self.OnCleaningRun)
  139. self.btn_clipboard.Bind(wx.EVT_BUTTON, self.OnCopy)
  140. self.btn_help.Bind(wx.EVT_BUTTON, self.OnHelp)
  141. self.btn_add.Bind(wx.EVT_BUTTON, self.OnAddTool)
  142. self.btn_remove.Bind(wx.EVT_BUTTON, self.OnClearTool)
  143. self.btn_moveup.Bind(wx.EVT_BUTTON, self.OnMoveToolUp)
  144. self.btn_movedown.Bind(wx.EVT_BUTTON, self.OnMoveToolDown)
  145. # layout
  146. self._layout()
  147. self.SetMinSize(self.GetBestSize())
  148. self.CentreOnScreen()
  149. def _layout(self):
  150. sizer = wx.BoxSizer(wx.VERTICAL)
  151. #
  152. # input output
  153. #
  154. inSizer = wx.GridBagSizer(hgap=5, vgap=5)
  155. inSizer.Add(item=self.inmaplabel, pos=(0, 0),
  156. flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border=1)
  157. inSizer.Add(item=self.selectionInput, pos=(1, 0),
  158. flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border=1)
  159. self.ftype_check = [
  160. wx.CheckBox(parent=self.panel, id=wx.ID_ANY, label=_('point')),
  161. wx.CheckBox(parent=self.panel, id=wx.ID_ANY, label=_('line')),
  162. wx.CheckBox(parent=self.panel, id=wx.ID_ANY, label=_('boundary')),
  163. wx.CheckBox(parent=self.panel, id=wx.ID_ANY, label=_('centroid')),
  164. wx.CheckBox(parent=self.panel, id=wx.ID_ANY, label=_('area')),
  165. wx.CheckBox(parent=self.panel, id=wx.ID_ANY, label=_('face'))
  166. ]
  167. typeoptSizer = wx.BoxSizer(wx.HORIZONTAL)
  168. for num in range(0, self.n_ftypes):
  169. type_box = self.ftype_check[num]
  170. if self.ftype[num] in ('point', 'line', 'area'):
  171. type_box.SetValue(True);
  172. typeoptSizer.Add(item=type_box, flag=wx.ALIGN_LEFT, border=1)
  173. self.ftypeSizer.Add(item=typeoptSizer,
  174. flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2)
  175. outSizer = wx.GridBagSizer(hgap=5, vgap=5)
  176. outSizer.Add(item=self.outmaplabel, pos=(0, 0),
  177. flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border=1)
  178. outSizer.Add(item=self.selectionOutput, pos=(1, 0),
  179. flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border=1)
  180. replaceSizer = wx.BoxSizer(wx.HORIZONTAL)
  181. replaceSizer.Add(item=self.overwrite, proportion=1,
  182. flag=wx.ALL | wx.EXPAND, border=1)
  183. outSizer.Add(item=replaceSizer, pos=(2, 0),
  184. flag=wx.ALL | wx.EXPAND, border=1)
  185. #
  186. # tools selection
  187. #
  188. bodySizer = wx.GridBagSizer(hgap=5, vgap=5)
  189. bodySizer.Add(item=self.ct_label, pos=(0, 0), span=(1, 2),
  190. flag=wx.ALL, border=5)
  191. bodySizer.Add(item=self.ct_panel, pos=(1, 0), span=(1, 2))
  192. manageBoxSizer = wx.GridBagSizer(hgap=10, vgap=1)
  193. # start with row 1 for nicer layout
  194. manageBoxSizer.Add(item=self.btn_add, pos=(1, 0), border=2, flag=wx.ALL | wx.EXPAND)
  195. manageBoxSizer.Add(item=self.btn_remove, pos=(2, 0), border=2, flag=wx.ALL | wx.EXPAND)
  196. manageBoxSizer.Add(item=self.btn_moveup, pos=(3, 0), border=2, flag=wx.ALL | wx.EXPAND)
  197. manageBoxSizer.Add(item=self.btn_movedown, pos=(4, 0), border=2, flag=wx.ALL | wx.EXPAND)
  198. bodySizer.Add(item=manageBoxSizer, pos=(1, 2),
  199. flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5)
  200. bodySizer.AddGrowableCol(2)
  201. #
  202. # standard buttons
  203. #
  204. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  205. btnSizer.Add(self.btn_close,
  206. flag=wx.LEFT | wx.RIGHT, border=5)
  207. btnSizer.Add(self.btn_run,
  208. flag=wx.LEFT | wx.RIGHT, border=5)
  209. btnSizer.Add(self.btn_clipboard,
  210. flag=wx.LEFT | wx.RIGHT, border=5)
  211. btnSizer.Add(self.btn_help,
  212. flag=wx.LEFT | wx.RIGHT, border=5)
  213. #
  214. # put it all together
  215. #
  216. sizer.Add(item=inSizer, proportion=0,
  217. flag=wx.ALL | wx.EXPAND, border=5)
  218. sizer.Add(item=self.ftypeSizer, proportion=0,
  219. flag=wx.ALL | wx.EXPAND, border=5)
  220. sizer.Add(item=outSizer, proportion=0,
  221. flag=wx.ALL | wx.EXPAND, border=5)
  222. sizer.Add(item=wx.StaticLine(parent=self, id=wx.ID_ANY,
  223. style=wx.LI_HORIZONTAL), proportion=0,
  224. flag=wx.EXPAND | wx.ALL, border=5)
  225. sizer.Add(item=bodySizer, proportion=1,
  226. flag=wx.ALL | wx.EXPAND, border=5)
  227. sizer.Add(item=wx.StaticLine(parent=self, id=wx.ID_ANY,
  228. style=wx.LI_HORIZONTAL), proportion=0,
  229. flag=wx.EXPAND | wx.ALL, border=5)
  230. sizer.Add(item=btnSizer, proportion=0,
  231. flag=wx.ALL | wx.ALIGN_RIGHT, border=5)
  232. self.panel.SetAutoLayout(True)
  233. self.panel.SetSizer(sizer)
  234. sizer.Fit(self.panel)
  235. self.Layout()
  236. def _toolsPanel(self):
  237. ct_panel = scrolled.ScrolledPanel(parent=self.panel, id=wx.ID_ANY,
  238. size=(500, 240),
  239. style=wx.SUNKEN_BORDER)
  240. self.ct_sizer = wx.GridBagSizer(vgap=2, hgap=4)
  241. ct_panel.SetSizer(self.ct_sizer)
  242. ct_panel.SetAutoLayout(True)
  243. return ct_panel
  244. def OnAddTool(self, event):
  245. """!Add tool button pressed"""
  246. self.AddTool()
  247. def AddTool(self):
  248. snum = len(self.toolslines.keys())
  249. num = snum + 1
  250. # tool
  251. tool_cbox = wx.ComboBox(parent=self.ct_panel, id=1000 + num,
  252. size=(300, -1), choices=self.tool_desc_list,
  253. style=wx.CB_DROPDOWN |
  254. wx.CB_READONLY | wx.TE_PROCESS_ENTER)
  255. self.Bind(wx.EVT_COMBOBOX, self.OnSetTool, tool_cbox)
  256. # threshold
  257. txt_ctrl = wx.TextCtrl(parent=self.ct_panel, id=2000 + num, value='0.00',
  258. size=(100, -1),
  259. style=wx.TE_NOHIDESEL)
  260. self.Bind(wx.EVT_TEXT, self.OnThreshValue, txt_ctrl)
  261. # select with tool number
  262. select = wx.CheckBox(parent=self.ct_panel, id=num, label=str(num) + '.')
  263. select.SetValue(False)
  264. self.Bind(wx.EVT_CHECKBOX, self.OnSelect, select)
  265. # start with row 1 and col 1 for nicer layout
  266. self.ct_sizer.Add(item=select, pos=(num, 1),
  267. flag=wx.ALIGN_CENTER | wx.RIGHT)
  268. self.ct_sizer.Add(item=tool_cbox, pos=(num, 2),
  269. flag=wx.ALIGN_CENTER | wx.RIGHT, border=5)
  270. self.ct_sizer.Add(item=txt_ctrl, pos=(num, 3),
  271. flag=wx.ALIGN_CENTER | wx.RIGHT, border=5)
  272. self.toolslines[num] = {'tool_desc': '',
  273. 'tool': '',
  274. 'thresh': '0.00'}
  275. self.ct_panel.Layout()
  276. self.ct_panel.SetupScrolling()
  277. def OnClearTool(self, event):
  278. """!Remove tool button pressed"""
  279. id = self.selected
  280. if id > 0:
  281. self.FindWindowById(id + 1000).SetValue('')
  282. self.toolslines[id]['tool_desc'] = ''
  283. self.toolslines[id]['tool'] = ''
  284. self.SetStatusText(_("%s. cleaning tool removed, will be ignored") % id)
  285. else:
  286. self.SetStatusText(_("Please select a cleaning tool to remove"))
  287. def OnMoveToolUp(self, event):
  288. """!Move up tool button pressed"""
  289. id = self.selected
  290. if id > 1:
  291. id_up = id - 1
  292. this_toolline = self.toolslines[id]
  293. up_toolline = self.toolslines[id_up]
  294. self.FindWindowById(id_up).SetValue(True)
  295. self.FindWindowById(id_up + 1000).SetValue(this_toolline['tool_desc'])
  296. self.FindWindowById(id_up + 2000).SetValue(this_toolline['thresh'])
  297. self.toolslines[id_up] = this_toolline
  298. self.FindWindowById(id).SetValue(False)
  299. self.FindWindowById(id + 1000).SetValue(up_toolline['tool_desc'])
  300. self.FindWindowById(id + 2000).SetValue(up_toolline['thresh'])
  301. self.toolslines[id] = up_toolline
  302. self.selected = id_up
  303. self.SetStatusText(_("%s. cleaning tool moved up") % id)
  304. elif id == 1:
  305. self.SetStatusText(_("1. cleaning tool can not be moved up "))
  306. elif id == -1:
  307. self.SetStatusText(_("Please select a cleaning tool to move up"))
  308. def OnMoveToolDown(self, event):
  309. """!Move down tool button pressed"""
  310. id = self.selected
  311. snum = len(self.toolslines.keys())
  312. if id > 0 and id < snum:
  313. id_down = id + 1
  314. this_toolline = self.toolslines[id]
  315. down_toolline = self.toolslines[id_down]
  316. self.FindWindowById(id_down).SetValue(True)
  317. self.FindWindowById(id_down + 1000).SetValue(this_toolline['tool_desc'])
  318. self.FindWindowById(id_down + 2000).SetValue(this_toolline['thresh'])
  319. self.toolslines[id_down] = this_toolline
  320. self.FindWindowById(id).SetValue(False)
  321. self.FindWindowById(id + 1000).SetValue(down_toolline['tool_desc'])
  322. self.FindWindowById(id + 2000).SetValue(down_toolline['thresh'])
  323. self.toolslines[id] = down_toolline
  324. self.selected = id_down
  325. self.SetStatusText(_("%s. cleaning tool moved down") % id)
  326. elif id == snum:
  327. self.SetStatusText(_("Last cleaning tool can not be moved down "))
  328. elif id == -1:
  329. self.SetStatusText(_("Please select a cleaning tool to move down"))
  330. def OnSetTool(self, event):
  331. """!Tool was defined"""
  332. id = event.GetId()
  333. tool_no = id - 1000
  334. num = self.FindWindowById(id).GetCurrentSelection()
  335. self.toolslines[tool_no]['tool_desc'] = self.tool_desc_list[num]
  336. self.toolslines[tool_no]['tool'] = self.tool_list[num]
  337. self.SetStatusText(str(tool_no) + '. ' + _("cleaning tool: '%s'") % (self.tool_list[num]))
  338. def OnThreshValue(self, event):
  339. """!Threshold value was entered"""
  340. id = event.GetId()
  341. num = id - 2000
  342. self.toolslines[num]['thresh'] = self.FindWindowById(id).GetValue()
  343. self.SetStatusText(_("Threshold for %(num)s. tool '%(tool)s': %(thresh)s") % \
  344. {'num': num,
  345. 'tool': self.toolslines[num]['tool'],
  346. 'thresh': self.toolslines[num]['thresh']})
  347. def OnSelect(self, event):
  348. """!Tool was selected"""
  349. id = event.GetId()
  350. if self.selected > -1 and self.selected != id:
  351. win = self.FindWindowById(self.selected)
  352. win.SetValue(False)
  353. if self.selected != id:
  354. self.selected = id
  355. else:
  356. self.selected = -1
  357. def OnDone(self, cmd, returncode):
  358. """!Command done"""
  359. self.SetStatusText('')
  360. def OnCleaningRun(self, event):
  361. """!Builds options and runs v.clean
  362. """
  363. self.GetCmdStrings()
  364. err = list()
  365. for p, name in ((self.inmap, _('Name of input vector map')),
  366. (self.outmap, _('Name for output vector map')),
  367. (self.tools_string, _('Tools')),
  368. (self.thresh_string, _('Threshold'))):
  369. if not p:
  370. err.append(_("'%s' not defined") % name)
  371. if err:
  372. GError(_("Some parameters not defined. Operation "
  373. "canceled.\n\n%s") % '\n'.join(err),
  374. parent=self)
  375. return
  376. self.SetStatusText(_("Executing selected cleaning operations..."))
  377. snum = len(self.toolslines.keys())
  378. if self.log:
  379. cmd = [self.cmd,
  380. 'input=%s' % self.inmap,
  381. 'output=%s' % self.outmap,
  382. 'tool=%s' % self.tools_string,
  383. 'thres=%s' % self.thresh_string]
  384. if self.ftype_string:
  385. cmd.append('type=%s' % self.ftype_string)
  386. if self.overwrite.IsChecked():
  387. cmd.append('--overwrite')
  388. self.log.RunCmd(cmd, onDone=self.OnDone)
  389. self.parent.Raise()
  390. else:
  391. if self.overwrite.IsChecked():
  392. overwrite = True
  393. else:
  394. overwrite = False
  395. RunCommand(self.cmd,
  396. input=self.inmap,
  397. output=self.outmap,
  398. type=self.ftype_string,
  399. tool=self.tools_string,
  400. thresh=self.thresh_string,
  401. overwrite=overwrite)
  402. def OnClose(self, event):
  403. self.Destroy()
  404. def OnHelp(self, event):
  405. """!Show GRASS manual page"""
  406. RunCommand('g.manual',
  407. quiet=True,
  408. parent=self,
  409. entry=self.cmd)
  410. def OnCopy(self, event):
  411. """!Copy the command"""
  412. cmddata = wx.TextDataObject()
  413. # get tool and thresh strings
  414. self.GetCmdStrings()
  415. cmdstring = '%s' % (self.cmd)
  416. # list -> string
  417. cmdstring += ' input=%s output=%s type=%s tool=%s thres=%s' % \
  418. (self.inmap, self.outmap, self.ftype_string, self.tools_string, self.thresh_string)
  419. if self.overwrite.IsChecked():
  420. cmdstring += ' --overwrite'
  421. cmddata.SetText(cmdstring)
  422. if wx.TheClipboard.Open():
  423. wx.TheClipboard.SetData(cmddata)
  424. wx.TheClipboard.Close()
  425. self.SetStatusText(_("Vector cleaning command copied to clipboard"))
  426. def GetCmdStrings(self):
  427. self.tools_string = ''
  428. self.thresh_string = ''
  429. self.ftype_string = ''
  430. # feature types
  431. first = 1
  432. for num in range(0, self.n_ftypes - 1):
  433. if self.ftype_check[num].IsChecked():
  434. if first:
  435. self.ftype_string = '%s' % self.ftype[num]
  436. first = 0
  437. else:
  438. self.ftype_string += ',%s' % self.ftype[num]
  439. # cleaning tools
  440. first = 1
  441. snum = len(self.toolslines.keys())
  442. for num in range(1, snum + 1):
  443. if self.toolslines[num]['tool']:
  444. if first:
  445. self.tools_string = '%s' % self.toolslines[num]['tool']
  446. self.thresh_string = '%s' % self.toolslines[num]['thresh']
  447. first = 0
  448. else:
  449. self.tools_string += ',%s' % self.toolslines[num]['tool']
  450. self.thresh_string += ',%s' % self.toolslines[num]['thresh']
  451. self.inmap = self.selectionInput.GetValue()
  452. self.outmap = self.selectionOutput.GetValue()
  453. if __name__ == '__main__':
  454. app = wx.App()
  455. frame = VectorCleaningFrame(parent=None)
  456. frame.Show()
  457. app.MainLoop()