[GRASS-SVN] r64460 - in grass/trunk/gui/wxpython: gui_core lmgr mapdisp

svn_grass at osgeo.org svn_grass at osgeo.org
Wed Feb 4 05:13:23 PST 2015


Author: martinl
Date: 2015-02-04 05:13:23 -0800 (Wed, 04 Feb 2015)
New Revision: 64460

Added:
   grass/trunk/gui/wxpython/gui_core/vselect.py
Modified:
   grass/trunk/gui/wxpython/lmgr/layertree.py
   grass/trunk/gui/wxpython/mapdisp/frame.py
   grass/trunk/gui/wxpython/mapdisp/toolbars.py
Log:
wxGUI: add functionality to interactively select vector features in map display
       currently only selection by clicking is supported

       Original author: Matej Krejci, OSGeoREL, CTU in Prague
       
       todo:
        - select by bbox
        - highlight selected features also in dbmgr


Added: grass/trunk/gui/wxpython/gui_core/vselect.py
===================================================================
--- grass/trunk/gui/wxpython/gui_core/vselect.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gui_core/vselect.py	2015-02-04 13:13:23 UTC (rev 64460)
@@ -0,0 +1,403 @@
+"""
+ at package gui_core.vselect
+
+ at brief wxGUI classes for interactive selection of vector
+features. Allows to create 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.
+
+ at 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 to select vector features from map display and to export
+    them as a new vector map. Current version allows to select
+    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 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()
+        self._draw()
+
+    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()
+            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 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)
+        except grass.ScriptError:
+            GError(parent=self,
+                   message=_("Failed to query vector map(s) <%s>.") % self.map)
+            return None
+
+        return query[0]
+
+    def getSelectedFeatures(self):
+        return self.selectedFeatures
+
+    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)
+                #colorize new map
+                ret, err =RunCommand('d.vect',
+                            map=outMap,
+                            color='red',getErrorMsg=True)
+                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 to set 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['Layer']=1
+        self.data['Map'] = None
+        self.giface.updateMap.emit(render=True, renderVector=True)
+
+    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
+
+
+
+
+


Property changes on: grass/trunk/gui/wxpython/gui_core/vselect.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Modified: grass/trunk/gui/wxpython/lmgr/layertree.py
===================================================================
--- grass/trunk/gui/wxpython/lmgr/layertree.py	2015-02-04 12:25:43 UTC (rev 64459)
+++ grass/trunk/gui/wxpython/lmgr/layertree.py	2015-02-04 13:13:23 UTC (rev 64460)
@@ -1391,7 +1391,12 @@
         # redraw map if auto-rendering is enabled
         self.rerender = True
         self.Map.SetLayers(self.GetVisibleLayers())
-        
+
+        # if interactive vector feature selection is open -> reset
+        vselect = self._giface.GetMapDisplay().GetDialog('vselect')
+        if vselect:
+            vselect.Reset()
+
     def OnCmdChanged(self, event):
         """Change command string"""
         ctrl = event.GetEventObject().GetId()
@@ -1492,6 +1497,11 @@
                     self.lmgr.nviz.UpdatePage('volume')
                     self.lmgr.nviz.SetPage('volume')
 
+        # if interactive vector feature selection is open -> reset
+        vselect = self._giface.GetMapDisplay().GetDialog('vselect')
+        if vselect:
+            vselect.Reset()
+
     def OnEndDrag(self, event):
         self.StopDragging()
         dropTarget = event.GetItem()

Modified: grass/trunk/gui/wxpython/mapdisp/frame.py
===================================================================
--- grass/trunk/gui/wxpython/mapdisp/frame.py	2015-02-04 12:25:43 UTC (rev 64459)
+++ grass/trunk/gui/wxpython/mapdisp/frame.py	2015-02-04 13:13:23 UTC (rev 64460)
@@ -54,6 +54,7 @@
     MeasureAreaController
 from gui_core.forms import GUI
 from core.giface import Notification
+from gui_core.vselect import VectorSelectBase
 
 from mapdisp import statusbar as sb
 
@@ -198,6 +199,7 @@
         self.dialogs['category'] = None
         self.dialogs['vnet'] = None
         self.dialogs['query'] = None
+        self.dialogs['vselect'] = None
 
         # initialize layers to query (d.what.vect/rast)
         self._vectQueryLayers = []
@@ -543,6 +545,10 @@
             self.MapWindow.UpdateMap(render = True, renderVector = True)
         else:
             self.MapWindow.UpdateMap(render = True)
+
+        # reset dialog with selected features
+        if self.dialogs['vselect']:
+            self.dialogs['vselect'].Reset()
         
         # update statusbar
         self.StatusbarUpdate()
@@ -556,6 +562,23 @@
             self.toolbars['vdigit'].action['id'] = -1
             self.toolbars['vdigit'].action['desc']=''
 
+    def OnSelect(self, event):
+        """Vector feature selection button clicked
+        """
+        layerList = self._giface.GetLayerList()
+        layerSelected = layerList.GetSelectedLayer()
+        if not self.dialogs['vselect']:
+            if layerSelected is None:
+                GMessage(_("No map layer selected. Operation canceled."))
+                return
+
+            self.dialogs['vselect'] = VectorSelectBase(self.parent, self._giface)
+            self.dialogs['vselect'].CreateDialog(createButton=True)
+            self.dialogs['vselect'].onCloseDialog.connect(self._onCloseVectorSelectDialog)
+            
+    def _onCloseVectorSelectDialog(self):
+        self.dialogs['vselect'] = None
+
     def OnRotate(self, event):
         """Rotate 3D view
         """
@@ -986,13 +1009,19 @@
         icon = ''
         size = 0
         # here we know that there is one selected layer and it is vector
-        vparam = self._giface.GetLayerList().GetSelectedLayers()[0].cmd
+        layerSelected = self._giface.GetLayerList().GetSelectedLayer()
+        if not layerSelected:
+            return None
+        
+        vparam = layerSelected.cmd
         for p in vparam:
             if '=' in p:
-                parg,pval = p.split('=', 1)
-                if parg == 'icon': icon = pval
-                elif parg == 'size': size = float(pval)
-
+                parg, pval = p.split('=', 1)
+                if parg == 'icon':
+                    icon = pval
+                elif parg == 'size':
+                    size = float(pval)
+        
         pattern = ["d.vect",
                    "map=%s" % name,
                    "color=%s" % colorStr,
@@ -1428,7 +1457,11 @@
     def GetToolbarNames(self):
         """Return toolbar names"""
         return self.toolbars.keys()
-    
+
+    def GetDialog(self, name):
+        """Get selected dialog if exist"""
+        return self.dialogs.get(name, None)
+
     def OnVNet(self, event):
         """Dialog for v.net* modules 
         """

Modified: grass/trunk/gui/wxpython/mapdisp/toolbars.py
===================================================================
--- grass/trunk/gui/wxpython/mapdisp/toolbars.py	2015-02-04 12:25:43 UTC (rev 64459)
+++ grass/trunk/gui/wxpython/mapdisp/toolbars.py	2015-02-04 13:13:23 UTC (rev 64460)
@@ -28,6 +28,9 @@
     'query'      : MetaIcon(img = 'info',
                             label = _('Query raster/vector map(s)'),
                             desc = _('Query selected raster/vector map(s)')),
+    'select'     : MetaIcon(img = 'select',
+                            label = _('Select vector feature(s)'),
+                            desc = _('Select features interactively from vector map')),
     'addBarscale': MetaIcon(img = 'scalebar-add',
                             label = _('Show/hide scale bar')),
     'addLegend'  : MetaIcon(img = 'legend-add',
@@ -131,7 +134,7 @@
         self.combo.Hide()
         self.combo.Show()
         
-        for tool in (self.pointer, self.query, self.pan, self.zoomIn, self.zoomOut):
+        for tool in (self.pointer, self.select, self.query, self.pan, self.zoomIn, self.zoomOut):
             self.toolSwitcher.AddToolToGroup(group='mouseUse', toolbar=self, tool=tool)
         
         self.EnableTool(self.zoomBack, False)
@@ -150,6 +153,9 @@
                                      ('pointer', BaseIcons['pointer'],
                                       self.parent.OnPointer,
                                       wx.ITEM_CHECK),
+                                     ('select', MapIcons['select'],
+                                      self.parent.OnSelect,
+                                      wx.ITEM_CHECK),
                                      ('query', MapIcons['query'],
                                       self.parent.OnQuery,
                                       wx.ITEM_CHECK),



More information about the grass-commit mailing list