vclean.py 18 KB

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