vclean.py 18 KB

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