vselect.py 14 KB

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