123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- """
- @package gui_core.vselect
- @brief wxGUI classes for interactive selection of vector
- features. Allows creating a new vector map from selected vector
- features or return their categories
- Classes:
- - vselect::VectorSelectList
- - vselect::VectorSelectDialog
- - vselect::VectorSelectBase
- - vselect::VectorSelectHighlighter
- (C) 2014-2015 by Matej Krejci, and the GRASS Development Team
- This program is free software under the GNU General Public License
- (>=v2). Read the file COPYING that comes with GRASS for details.
- @author Matej Krejci <matejkrejci gmail.com> (mentor: Martin Landa)
- """
- import string
- import random
- import wx
- import wx.lib.mixins.listctrl as listmix
- from core.utils import _
- from core.gcmd import GMessage, GError, GWarning
- from core.gcmd import RunCommand
- import grass.script as grass
- from grass.pydispatch.signal import Signal
- class VectorSelectList(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
- """Widget for managing vector features selected from map display
- """
- def __init__(self, parent):
- wx.ListCtrl.__init__(self, parent=parent, id=wx.ID_ANY, style=wx.LC_REPORT | wx.BORDER_SUNKEN)
- listmix.ListCtrlAutoWidthMixin.__init__(self)
-
- self.InsertColumn(col=0, heading=_('category'))
- self.InsertColumn(col=1, heading=_('type'))
- self.SetColumnWidth(0, 100)
- self.SetColumnWidth(1, 100)
- self.index = 0
- self.dictIndex = {}
- def AddItem(self, item):
- if 'Category' not in item:
- return
-
- pos = self.InsertStringItem(0, str(item['Category']))
- self.SetStringItem(pos, 1, str(item['Type']))
- self.dictIndex[str(item['Category'])] = pos
- def RemoveItem(self, item):
- index = self.dictIndex.get(str(item['Category']), -1)
- if index > -1:
- self.DeleteItem(index)
-
- class VectorSelectDialog(wx.Dialog):
- """Dialog for managing vector features selected from map display"""
- def __init__(self, parent, title=_("Select features"), size=(200, 300)):
- wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY,
- title=title, size=size, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
- self._layout()
- def AddWidget(self, widget, proportion=1, flag=wx.EXPAND):
- self.mainSizer.Add(widget, proportion=proportion, flag=flag)
- self.Layout()
- def _layout(self):
- self.mainSizer = wx.BoxSizer(wx.VERTICAL)
- self.SetSizer(self.mainSizer)
- self.Show()
- class VectorSelectBase():
- """@brief Main class of vector selection function
- It allows selecting vector features from map display and to export
- them as a new vector map. Current version allows selecting
- features one-by-one by single click in map display.
- This class can be initialized with (see CreateDialog()) or without
- (see gselect) dialog (see VectorSelectDialog).
- """
- def __init__(self, parent, giface):
- self.parent = parent
- self._giface = giface
- self.register = False
- self.mapWin = self._giface.GetMapWindow()
- self.mapDisp = giface.GetMapDisplay()
- self.RegisterMapEvtHandler()
- self.selectedFeatures = []
- self.mapName = None # chosen map for selecting features
- self._dialog = None
- self.onCloseDialog = None
-
- self.updateLayer = Signal('VectorSelectBase.updateLayer')
-
- self.painter = VectorSelectHighlighter(self.mapDisp, giface)
- def CreateDialog(self, createButton=True):
- """Create dialog
- :param createButton: True to add 'create new map' button
- """
- if self._dialog:
- return
-
- self._dialog = VectorSelectDialog(parent=self.parent)
- self._dialog.Bind(wx.EVT_CLOSE,self.OnCloseDialog)
- if createButton:
- createMap = wx.Button(self._dialog, wx.ID_ANY, _("Create a new map"))
- createMap.Bind(wx.EVT_BUTTON, self.OnExportMap)
- self._dialog.AddWidget(createMap, proportion=0.1)
- self.slist = VectorSelectList(self._dialog)
- self.slist.Bind(wx.EVT_LIST_KEY_DOWN, self.OnDelete)
- self.slist.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnDeleteRow)
- self._dialog.AddWidget(self.slist)
-
- self.onCloseDialog = Signal('VectorSelectBase.onCloseDialog')
-
- def OnDeleteRow(self, event=None):
- """Delete row in widget
- """
- index = self.slist.GetFocusedItem()
- category = self.slist.GetItemText(index)
- for item in self.selectedFeatures:
- if int(item['Category']) == int(category):
- self.selectedFeatures.remove(item)
- break
- self.slist.DeleteItem(index)
- self._draw()
- def OnDelete(self, event):
- """Delete row in widget by press key(delete)
- """
- keycode = event.GetKeyCode()
- if keycode == wx.WXK_DELETE:
- self.OnDeleteRow()
- def RegisterMapEvtHandler(self):
- if not self.register:
- self.mapWin.RegisterMouseEventHandler(wx.EVT_LEFT_DOWN,
- self._onMapClickHandler,
- 'cross')
- self.register=True
- def UnregisterMapEvtHandler(self):
- """Unregistrates _onMapClickHandler from mapWin"""
- if self.register:
- self.mapWin.UnregisterMouseEventHandler(wx.EVT_LEFT_DOWN,
- self._onMapClickHandler)
- self.register=False
- def OnClose(self):
- self.selectedFeatures=[]
- self._draw()
- self.UnregisterMapEvtHandler()
- def OnCloseDialog(self,evt=None):
- if not self.onCloseDialog:
- return
-
- self.onCloseDialog.emit()
- self.selectedFeatures=[]
- self.painter.Clear()
- self._dialog.Destroy()
- self.UnregisterMapEvtHandler()
- def Reset(self):
- """Remove items from dialog list"""
- self.selectedFeatures = []
- if self._dialog:
- self.slist.DeleteAllItems()
- self._dialog.Raise()
- self.RegisterMapEvtHandler()
- def _onMapClickHandler(self, event):
- """Registred handler for clicking on grass disp
- """
- if event == "unregistered":
- return
- vWhatDic = self.QuerySelectedMap()
- if 'Category' in vWhatDic:
- self.AddVecInfo(vWhatDic)
- self._draw()
- if self._dialog:
- self._dialog.Raise()
- def AddVecInfo(self, vInfoDictTMP):
- """Update vector in list
-
- Note: click on features add category
- second click on the same vector remove category from list
- """
- if len(self.selectedFeatures) > 0:
- for sel in self.selectedFeatures:
- if sel['Category'] == vInfoDictTMP['Category']: #features is selected=> remove features
- self.selectedFeatures.remove(sel)
- if self._dialog:#if dialog initilized->update dialog
- self.slist.RemoveItem(vInfoDictTMP)
- return True
- self.selectedFeatures.append(vInfoDictTMP)
- if self._dialog:
- self.slist.AddItem(vInfoDictTMP)
- else: # only one is selected
- self.selectedFeatures.append(vInfoDictTMP)
- if self._dialog:
- self.slist.AddItem(vInfoDictTMP)
- if len(self.selectedFeatures) == 0:
- return False
- return True
- def _draw(self):
- """Call class 'VectorSelectHighlighter' to draw selected features"""
- self.updateLayer.emit()
- if len(self.selectedFeatures) > 0:
- self.painter.SetLayer(self.selectedFeatures[0]['Layer'])
- self.painter.SetMap(self.selectedFeatures[0]['Map'])
- tmp = list()
- for i in self.selectedFeatures:
- tmp.append(i['Category'])
- self.painter.SetCats(tmp)
- self.painter.DrawSelected()
- else:
- self.painter.Clear()
- def GetSelectedMap(self):
- """Return name of selected map in layer tree"""
- layerList = self._giface.GetLayerList()
- layerSelected = layerList.GetSelectedLayer()
- if layerSelected is None:
- return None
- if not layerSelected.maplayer.IsActive():
- GWarning(_("Selected map <%s> has been disabled for rendering. "
- "Operation canceled.") % str(layerSelected), parent=self.mapWin)
- return None
- if layerSelected:
- mapName = str(layerSelected)
- if self.mapName is not None:
- if self.mapName!=mapName:
- self.Reset()
- else:
- mapName = None
- self.UnregisterMapEvtHandler()
- GError(_("No map layer selected. Operation canceled."))
- return mapName
- def QuerySelectedMap(self):
- """Return w.what info from last clicked coords on display
- """
- self.mapName = self.GetSelectedMap()
- if not self.mapName:
- return {}
- mapInfo = self.mapWin.GetMap()
- threshold = 10.0 * ((mapInfo.region['e'] - mapInfo.region['w']) / mapInfo.width)
- try:
- query = grass.vector_what(map=[self.mapName],
- coord=self.mapWin.GetLastEN(),
- distance=threshold, skip_attributes=True)
- except grass.ScriptError:
- GError(parent=self,
- message=_("Failed to query vector map(s) <%s>.") % self.map)
- return None
- return query[0]
- def GetLineStringSelectedCats(self):
- """Return line of categories separated by comma"""
- strTMP = ''
- for cat in self.selectedFeatures:
- strTMP += str(cat['Category']) + ','
- return strTMP[:-1]
- def _id_generator(self, size=6, chars=string.ascii_uppercase + string.digits):
- return ''.join(random.choice(chars) for _ in range(size))
- def OnExportMap(self, event):
- """Export selected features to a new map
-
- Add new map layer to layer tree and checked it
- @todo: set color of map to higlight color
- """
- if len(self.selectedFeatures)==0:
- GMessage(_('No features selected'))
- return
- lst = ''
- for cat in self.selectedFeatures: # build text string of categories for v.extract input
- lst += str(cat['Category']) + ','
- lst = lst[:-1]
- outMap = str(self.selectedFeatures[0]['Map']) + '_selection' + str(self._id_generator(3))
- ret, err = RunCommand('v.extract',
- input=self.selectedFeatures[0]['Map'],
- layer=self.selectedFeatures[0]['Layer'],
- output=outMap,
- cats=lst,
- getErrorMsg=True)
- if ret == 0:
- tree = self._giface.GetLayerTree()
- if tree:
- tree.AddLayer(ltype='vector', lname=outMap,
- lcmd=['d.vect', 'map=%s' % outMap],
- lchecked=True)
- #TODO colorize new map
- self.Reset()
- else:
- GMessage(_('Vector map <%s> was created') % outMap)
- self.Reset()
- else:
- GError(_("Unable to create a new vector map.\n\nReason: %s") % err)
-
- """
- def SetSelectedCat(self, cats):
- # allows setting selected vector categories by list of cats (per line)
- info = self.QuerySelectedMap()
- if 'Category' not in info:
- return
- for cat in cats.splitlines():
- tmpDict = {}
- tmpDict['Category'] = cat
- tmpDict['Map'] = info['Map']
- tmpDict['Layer'] = info['Layer']
- tmpDict['Type'] = '-'
- self.AddVecInfo(tmpDict)
- self._draw()
- """
-
- class VectorSelectHighlighter():
- """Class for highlighting selected features on display
- :param mapdisp: Map display frame
- """
- def __init__(self, mapdisp, giface):
- self.qlayer = None
- self.mapdisp = mapdisp
- self.giface = giface
- self.layerCat = {}
- self.data = {}
- self.data['Category'] = list()
- self.data['Map'] = None
- self.data['Layer']= None
- def SetMap(self, map):
- self.data['Map'] = map
- def SetLayer(self, layer):
- self.data['Layer'] = layer
- def SetCats(self, cats):
- self.data['Category'] = cats
- def Clear(self):
- self.data['Category'] = list()
- self.data['Map'] = None
- self.data['Layer']= None
- self.mapdisp.RemoveQueryLayer()
- self.giface.GetMapWindow().UpdateMap(render = False)
- def DrawSelected(self):
- """Highlight selected features"""
- self.layerCat[int(self.data['Layer'])] = self.data['Category']
-
- # add map layer with higlighted vector features
- self.AddQueryMapLayer() # -> self.qlayer
- self.qlayer.SetOpacity(0.7)
- self.giface.updateMap.emit(render=True, renderVector=True)
- def AddQueryMapLayer(self):
- """Redraw a map
- :return: True if map has been redrawn, False if no map is given
- """
- if self.mapdisp.GetMap().GetLayerIndex(self.qlayer) < 0:
- self.qlayer = None
- if self.qlayer:
- self.qlayer.SetCmd(self.mapdisp.AddTmpVectorMapLayer(self.data['Map'], self.layerCat, addLayer=False))
- else:
- self.qlayer = self.mapdisp.AddTmpVectorMapLayer(self.data['Map'], self.layerCat)
- return self.qlayer
|