vclean.py 18 KB

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