[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