vselect.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. """
  2. @package gui_core.vselect
  3. @brief wxGUI classes for interactive selection of vector
  4. features. Allows to create a new vector map from selected vector
  5. features or return their categories
  6. Classes:
  7. - vselect::VectorSelectList
  8. - vselect::VectorSelectDialog
  9. - vselect::VectorSelectBase
  10. - vselect::VectorSelectHighlighter
  11. (C) 2014-2015 by Matej Krejci, and the GRASS Development Team
  12. This program is free software under the GNU General Public License
  13. (>=v2). Read the file COPYING that comes with GRASS for details.
  14. @author Matej Krejci <matejkrejci gmail.com> (mentor: Martin Landa)
  15. """
  16. import string
  17. import random
  18. import wx
  19. import wx.lib.mixins.listctrl as listmix
  20. from core.utils import _
  21. from core.gcmd import GMessage, GError, GWarning
  22. from core.gcmd import RunCommand
  23. import grass.script as grass
  24. from grass.pydispatch.signal import Signal
  25. class VectorSelectList(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
  26. """Widget for managing vector features selected from map display
  27. """
  28. def __init__(self, parent):
  29. wx.ListCtrl.__init__(self, parent=parent, id=wx.ID_ANY, style=wx.LC_REPORT | wx.BORDER_SUNKEN)
  30. listmix.ListCtrlAutoWidthMixin.__init__(self)
  31. self.InsertColumn(col=0, heading=_('category'))
  32. self.InsertColumn(col=1, heading=_('type'))
  33. self.SetColumnWidth(0, 100)
  34. self.SetColumnWidth(1, 100)
  35. self.index = 0
  36. self.dictIndex = {}
  37. def AddItem(self, item):
  38. if 'Category' not in item:
  39. return
  40. pos = self.InsertStringItem(0, str(item['Category']))
  41. self.SetStringItem(pos, 1, str(item['Type']))
  42. self.dictIndex[str(item['Category'])] = pos
  43. def RemoveItem(self, item):
  44. index = self.dictIndex.get(str(item['Category']), -1)
  45. if index > -1:
  46. self.DeleteItem(index)
  47. class VectorSelectDialog(wx.Dialog):
  48. """Dialog for managing vector features selected from map display"""
  49. def __init__(self, parent, title=_("Select features"), size=(200, 300)):
  50. wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY,
  51. title=title, size=size, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
  52. self._layout()
  53. def AddWidget(self, widget, proportion=1, flag=wx.EXPAND):
  54. self.mainSizer.Add(widget, proportion=proportion, flag=flag)
  55. self.Layout()
  56. def _layout(self):
  57. self.mainSizer = wx.BoxSizer(wx.VERTICAL)
  58. self.SetSizer(self.mainSizer)
  59. self.Show()
  60. class VectorSelectBase():
  61. """@brief Main class of vector selection function
  62. It allows to select vector features from map display and to export
  63. them as a new vector map. Current version allows to select
  64. features one-by-one by single click in map display.
  65. This class can be initialized with (see CreateDialog()) or without
  66. (see gselect) dialog (see VectorSelectDialog).
  67. """
  68. def __init__(self, parent, giface):
  69. self.parent = parent
  70. self._giface = giface
  71. self.register = False
  72. self.mapWin = self._giface.GetMapWindow()
  73. self.mapDisp = giface.GetMapDisplay()
  74. self.RegisterMapEvtHandler()
  75. self.selectedFeatures = []
  76. self.mapName = None # chosen map for selecting features
  77. self._dialog = None
  78. self.onCloseDialog = None
  79. self.updateLayer = Signal('VectorSelectBase.updateLayer')
  80. self.painter = VectorSelectHighlighter(self.mapDisp, giface)
  81. def CreateDialog(self, createButton=True):
  82. """Create dialog
  83. :param createButton: True to add 'create new map' button
  84. """
  85. if self._dialog:
  86. return
  87. self._dialog = VectorSelectDialog(parent=self.parent)
  88. self._dialog.Bind(wx.EVT_CLOSE,self.OnCloseDialog)
  89. if createButton:
  90. createMap = wx.Button(self._dialog, wx.ID_ANY, _("Create a new map"))
  91. createMap.Bind(wx.EVT_BUTTON, self.OnExportMap)
  92. self._dialog.AddWidget(createMap, proportion=0.1)
  93. self.slist = VectorSelectList(self._dialog)
  94. self.slist.Bind(wx.EVT_LIST_KEY_DOWN, self.OnDelete)
  95. self.slist.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnDeleteRow)
  96. self._dialog.AddWidget(self.slist)
  97. self.onCloseDialog = Signal('VectorSelectBase.onCloseDialog')
  98. def OnDeleteRow(self, event=None):
  99. """Delete row in widget
  100. """
  101. index = self.slist.GetFocusedItem()
  102. category = self.slist.GetItemText(index)
  103. for item in self.selectedFeatures:
  104. if int(item['Category']) == int(category):
  105. self.selectedFeatures.remove(item)
  106. break
  107. self.slist.DeleteItem(index)
  108. self._draw()
  109. def OnDelete(self, event):
  110. """Delete row in widget by press key(delete)
  111. """
  112. keycode = event.GetKeyCode()
  113. if keycode == wx.WXK_DELETE:
  114. self.OnDeleteRow()
  115. def RegisterMapEvtHandler(self):
  116. if not self.register:
  117. self.mapWin.RegisterMouseEventHandler(wx.EVT_LEFT_DOWN,
  118. self._onMapClickHandler,
  119. 'cross')
  120. self.register=True
  121. def UnregisterMapEvtHandler(self):
  122. """Unregistrates _onMapClickHandler from mapWin"""
  123. if self.register:
  124. self.mapWin.UnregisterMouseEventHandler(wx.EVT_LEFT_DOWN,
  125. self._onMapClickHandler)
  126. self.register=False
  127. def OnCloseDialog(self,evt=None):
  128. if not self.onCloseDialog:
  129. return
  130. self.onCloseDialog.emit()
  131. self.selectedFeatures=[]
  132. self.painter.Clear()
  133. self._dialog.Destroy()
  134. self.UnregisterMapEvtHandler()
  135. def Reset(self):
  136. """Remove items from dialog list"""
  137. self.selectedFeatures = []
  138. if self._dialog:
  139. self.slist.DeleteAllItems()
  140. self._dialog.Raise()
  141. self.RegisterMapEvtHandler()
  142. self._draw()
  143. def _onMapClickHandler(self, event):
  144. """Registred handler for clicking on grass disp
  145. """
  146. if event == "unregistered":
  147. return
  148. vWhatDic = self.QuerySelectedMap()
  149. if 'Category' in vWhatDic:
  150. self.AddVecInfo(vWhatDic)
  151. self._draw()
  152. if self._dialog:
  153. self._dialog.Raise()
  154. def AddVecInfo(self, vInfoDictTMP):
  155. """Update vector in list
  156. Note: click on features add category
  157. second click on the same vector remove category from list
  158. """
  159. if len(self.selectedFeatures) > 0:
  160. for sel in self.selectedFeatures:
  161. if sel['Category'] == vInfoDictTMP['Category']: #features is selected=> remove features
  162. self.selectedFeatures.remove(sel)
  163. if self._dialog:#if dialog initilized->update dialog
  164. self.slist.RemoveItem(vInfoDictTMP)
  165. return True
  166. self.selectedFeatures.append(vInfoDictTMP)
  167. if self._dialog:
  168. self.slist.AddItem(vInfoDictTMP)
  169. else: # only one is selected
  170. self.selectedFeatures.append(vInfoDictTMP)
  171. if self._dialog:
  172. self.slist.AddItem(vInfoDictTMP)
  173. if len(self.selectedFeatures) == 0:
  174. return False
  175. return True
  176. def _draw(self):
  177. """Call class 'VectorSelectHighlighter' to draw selected features"""
  178. self.updateLayer.emit()
  179. if len(self.selectedFeatures) > 0:
  180. self.painter.SetLayer(self.selectedFeatures[0]['Layer'])
  181. self.painter.SetMap(self.selectedFeatures[0]['Map'])
  182. tmp = list()
  183. for i in self.selectedFeatures:
  184. tmp.append(i['Category'])
  185. self.painter.SetCats(tmp)
  186. self.painter.DrawSelected()
  187. else:
  188. self.painter.Clear()
  189. def GetSelectedMap(self):
  190. """Return name of selected map in layer tree"""
  191. layerList = self._giface.GetLayerList()
  192. layerSelected = layerList.GetSelectedLayer()
  193. if not layerSelected.maplayer.IsActive():
  194. GWarning(_("Selected map <%s> has been disabled for rendering. "
  195. "Operation canceled.") % str(layerSelected), parent=self.mapWin)
  196. return None
  197. if layerSelected:
  198. mapName = str(layerSelected)
  199. if self.mapName is not None:
  200. if self.mapName!=mapName:
  201. self.Reset()
  202. else:
  203. mapName = None
  204. self.UnregisterMapEvtHandler()
  205. GError(_("No map layer selected. Operation canceled."))
  206. return mapName
  207. def QuerySelectedMap(self):
  208. """Return w.what info from last clicked coords on display
  209. """
  210. self.mapName = self.GetSelectedMap()
  211. if not self.mapName:
  212. return {}
  213. mapInfo = self.mapWin.GetMap()
  214. threshold = 10.0 * ((mapInfo.region['e'] - mapInfo.region['w']) / mapInfo.width)
  215. try:
  216. query = grass.vector_what(map=[self.mapName],
  217. coord=self.mapWin.GetLastEN(),
  218. distance=threshold)
  219. except grass.ScriptError:
  220. GError(parent=self,
  221. message=_("Failed to query vector map(s) <%s>.") % self.map)
  222. return None
  223. return query[0]
  224. def GetLineStringSelectedCats(self):
  225. """Return line of categories separated by comma"""
  226. strTMP = ''
  227. for cat in self.selectedFeatures:
  228. strTMP += str(cat['Category']) + ','
  229. return strTMP[:-1]
  230. def _id_generator(self, size=6, chars=string.ascii_uppercase + string.digits):
  231. return ''.join(random.choice(chars) for _ in range(size))
  232. def OnExportMap(self, event):
  233. """Export selected features to a new map
  234. Add new map layer to layer tree and checked it
  235. @todo: set color of map to higlight color
  236. """
  237. if len(self.selectedFeatures)==0:
  238. GMessage(_('No features selected'))
  239. return
  240. lst = ''
  241. for cat in self.selectedFeatures: # build text string of categories for v.extract input
  242. lst += str(cat['Category']) + ','
  243. lst = lst[:-1]
  244. outMap = str(self.selectedFeatures[0]['Map']) + '_selection' + str(self._id_generator(3))
  245. ret, err = RunCommand('v.extract',
  246. input=self.selectedFeatures[0]['Map'],
  247. layer=self.selectedFeatures[0]['Layer'],
  248. output=outMap,
  249. cats=lst,
  250. getErrorMsg=True)
  251. if ret == 0:
  252. tree = self._giface.GetLayerTree()
  253. if tree:
  254. tree.AddLayer(ltype='vector', lname=outMap,
  255. lcmd=['d.vect', 'map=%s' % outMap],
  256. lchecked=True)
  257. #colorize new map
  258. ret, err =RunCommand('d.vect',
  259. map=outMap,
  260. color='red',getErrorMsg=True)
  261. self.Reset()
  262. else:
  263. GMessage(_('Vector map <%s> was created') % outMap)
  264. self.Reset()
  265. else:
  266. GError(_("Unable to create a new vector map.\n\nReason: %s") % err)
  267. """
  268. def SetSelectedCat(self, cats):
  269. # allows to set selected vector categories by list of cats (per line)
  270. info = self.QuerySelectedMap()
  271. if 'Category' not in info:
  272. return
  273. for cat in cats.splitlines():
  274. tmpDict = {}
  275. tmpDict['Category'] = cat
  276. tmpDict['Map'] = info['Map']
  277. tmpDict['Layer'] = info['Layer']
  278. tmpDict['Type'] = '-'
  279. self.AddVecInfo(tmpDict)
  280. self._draw()
  281. """
  282. class VectorSelectHighlighter():
  283. """Class for highlighting selected features on display
  284. :param mapdisp: Map display frame
  285. """
  286. def __init__(self, mapdisp, giface):
  287. self.qlayer = None
  288. self.mapdisp = mapdisp
  289. self.giface = giface
  290. self.layerCat = {}
  291. self.data = {}
  292. self.data['Category'] = list()
  293. self.data['Map'] = None
  294. self.data['Layer']= None
  295. def SetMap(self, map):
  296. self.data['Map'] = map
  297. def SetLayer(self, layer):
  298. self.data['Layer'] = layer
  299. def SetCats(self, cats):
  300. self.data['Category'] = cats
  301. def Clear(self):
  302. self.data['Category']=list()
  303. self.data['Layer']=1
  304. self.data['Map'] = None
  305. self.giface.updateMap.emit(render=True, renderVector=True)
  306. def DrawSelected(self):
  307. """Highlight selected features"""
  308. self.layerCat[int(self.data['Layer'])] = self.data['Category']
  309. # add map layer with higlighted vector features
  310. self.AddQueryMapLayer() # -> self.qlayer
  311. self.qlayer.SetOpacity(0.7)
  312. self.giface.updateMap.emit(render=True, renderVector=True)
  313. def AddQueryMapLayer(self):
  314. """Redraw a map
  315. :return: True if map has been redrawn, False if no map is given
  316. """
  317. if self.mapdisp.GetMap().GetLayerIndex(self.qlayer) < 0:
  318. self.qlayer = None
  319. if self.qlayer:
  320. self.qlayer.SetCmd(self.mapdisp.AddTmpVectorMapLayer(self.data['Map'], self.layerCat, addLayer=False))
  321. else:
  322. self.qlayer = self.mapdisp.AddTmpVectorMapLayer(self.data['Map'], self.layerCat)
  323. return self.qlayer