vselect.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. """
  2. @package gui_core.vselect
  3. @brief wxGUI classes for interactive selection of vector
  4. features. Allows creating 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 selecting vector features from map display and to export
  63. them as a new vector map. Current version allows selecting
  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 OnClose(self):
  128. self.selectedFeatures=[]
  129. self._draw()
  130. self.UnregisterMapEvtHandler()
  131. def OnCloseDialog(self,evt=None):
  132. if not self.onCloseDialog:
  133. return
  134. self.onCloseDialog.emit()
  135. self.selectedFeatures=[]
  136. self.painter.Clear()
  137. self._dialog.Destroy()
  138. self.UnregisterMapEvtHandler()
  139. def Reset(self):
  140. """Remove items from dialog list"""
  141. self.selectedFeatures = []
  142. if self._dialog:
  143. self.slist.DeleteAllItems()
  144. self._dialog.Raise()
  145. self.RegisterMapEvtHandler()
  146. def _onMapClickHandler(self, event):
  147. """Registred handler for clicking on grass disp
  148. """
  149. if event == "unregistered":
  150. return
  151. vWhatDic = self.QuerySelectedMap()
  152. if 'Category' in vWhatDic:
  153. self.AddVecInfo(vWhatDic)
  154. self._draw()
  155. if self._dialog:
  156. self._dialog.Raise()
  157. def AddVecInfo(self, vInfoDictTMP):
  158. """Update vector in list
  159. Note: click on features add category
  160. second click on the same vector remove category from list
  161. """
  162. if len(self.selectedFeatures) > 0:
  163. for sel in self.selectedFeatures:
  164. if sel['Category'] == vInfoDictTMP['Category']: #features is selected=> remove features
  165. self.selectedFeatures.remove(sel)
  166. if self._dialog:#if dialog initilized->update dialog
  167. self.slist.RemoveItem(vInfoDictTMP)
  168. return True
  169. self.selectedFeatures.append(vInfoDictTMP)
  170. if self._dialog:
  171. self.slist.AddItem(vInfoDictTMP)
  172. else: # only one is selected
  173. self.selectedFeatures.append(vInfoDictTMP)
  174. if self._dialog:
  175. self.slist.AddItem(vInfoDictTMP)
  176. if len(self.selectedFeatures) == 0:
  177. return False
  178. return True
  179. def _draw(self):
  180. """Call class 'VectorSelectHighlighter' to draw selected features"""
  181. self.updateLayer.emit()
  182. if len(self.selectedFeatures) > 0:
  183. self.painter.SetLayer(self.selectedFeatures[0]['Layer'])
  184. self.painter.SetMap(self.selectedFeatures[0]['Map'])
  185. tmp = list()
  186. for i in self.selectedFeatures:
  187. tmp.append(i['Category'])
  188. self.painter.SetCats(tmp)
  189. self.painter.DrawSelected()
  190. else:
  191. self.painter.Clear()
  192. def GetSelectedMap(self):
  193. """Return name of selected map in layer tree"""
  194. layerList = self._giface.GetLayerList()
  195. layerSelected = layerList.GetSelectedLayer()
  196. if layerSelected is None:
  197. return None
  198. if not layerSelected.maplayer.IsActive():
  199. GWarning(_("Selected map <%s> has been disabled for rendering. "
  200. "Operation canceled.") % str(layerSelected), parent=self.mapWin)
  201. return None
  202. if layerSelected:
  203. mapName = str(layerSelected)
  204. if self.mapName is not None:
  205. if self.mapName!=mapName:
  206. self.Reset()
  207. else:
  208. mapName = None
  209. self.UnregisterMapEvtHandler()
  210. GError(_("No map layer selected. Operation canceled."))
  211. return mapName
  212. def QuerySelectedMap(self):
  213. """Return w.what info from last clicked coords on display
  214. """
  215. self.mapName = self.GetSelectedMap()
  216. if not self.mapName:
  217. return {}
  218. mapInfo = self.mapWin.GetMap()
  219. threshold = 10.0 * ((mapInfo.region['e'] - mapInfo.region['w']) / mapInfo.width)
  220. try:
  221. query = grass.vector_what(map=[self.mapName],
  222. coord=self.mapWin.GetLastEN(),
  223. distance=threshold)
  224. except grass.ScriptError:
  225. GError(parent=self,
  226. message=_("Failed to query vector map(s) <%s>.") % self.map)
  227. return None
  228. return query[0]
  229. def GetLineStringSelectedCats(self):
  230. """Return line of categories separated by comma"""
  231. strTMP = ''
  232. for cat in self.selectedFeatures:
  233. strTMP += str(cat['Category']) + ','
  234. return strTMP[:-1]
  235. def _id_generator(self, size=6, chars=string.ascii_uppercase + string.digits):
  236. return ''.join(random.choice(chars) for _ in range(size))
  237. def OnExportMap(self, event):
  238. """Export selected features to a new map
  239. Add new map layer to layer tree and checked it
  240. @todo: set color of map to higlight color
  241. """
  242. if len(self.selectedFeatures)==0:
  243. GMessage(_('No features selected'))
  244. return
  245. lst = ''
  246. for cat in self.selectedFeatures: # build text string of categories for v.extract input
  247. lst += str(cat['Category']) + ','
  248. lst = lst[:-1]
  249. outMap = str(self.selectedFeatures[0]['Map']) + '_selection' + str(self._id_generator(3))
  250. ret, err = RunCommand('v.extract',
  251. input=self.selectedFeatures[0]['Map'],
  252. layer=self.selectedFeatures[0]['Layer'],
  253. output=outMap,
  254. cats=lst,
  255. getErrorMsg=True)
  256. if ret == 0:
  257. tree = self._giface.GetLayerTree()
  258. if tree:
  259. tree.AddLayer(ltype='vector', lname=outMap,
  260. lcmd=['d.vect', 'map=%s' % outMap],
  261. lchecked=True)
  262. #TODO colorize new map
  263. self.Reset()
  264. else:
  265. GMessage(_('Vector map <%s> was created') % outMap)
  266. self.Reset()
  267. else:
  268. GError(_("Unable to create a new vector map.\n\nReason: %s") % err)
  269. """
  270. def SetSelectedCat(self, cats):
  271. # allows setting selected vector categories by list of cats (per line)
  272. info = self.QuerySelectedMap()
  273. if 'Category' not in info:
  274. return
  275. for cat in cats.splitlines():
  276. tmpDict = {}
  277. tmpDict['Category'] = cat
  278. tmpDict['Map'] = info['Map']
  279. tmpDict['Layer'] = info['Layer']
  280. tmpDict['Type'] = '-'
  281. self.AddVecInfo(tmpDict)
  282. self._draw()
  283. """
  284. class VectorSelectHighlighter():
  285. """Class for highlighting selected features on display
  286. :param mapdisp: Map display frame
  287. """
  288. def __init__(self, mapdisp, giface):
  289. self.qlayer = None
  290. self.mapdisp = mapdisp
  291. self.giface = giface
  292. self.layerCat = {}
  293. self.data = {}
  294. self.data['Category'] = list()
  295. self.data['Map'] = None
  296. self.data['Layer']= None
  297. def SetMap(self, map):
  298. self.data['Map'] = map
  299. def SetLayer(self, layer):
  300. self.data['Layer'] = layer
  301. def SetCats(self, cats):
  302. self.data['Category'] = cats
  303. def Clear(self):
  304. self.mapdisp.RemoveQueryLayer()
  305. self.giface.GetMapWindow().UpdateMap(render = False)
  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