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.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__(
  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 = wx.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