vselect.py 14 KB

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