浏览代码

wxGUI/animations: possibility to add raster legend

git-svn-id: https://svn.osgeo.org/grass/grass/trunk@57593 15284696-431f-4ddb-bdfa-cd5b030d7da7
Anna Petrášová 11 年之前
父节点
当前提交
d89870e344

+ 7 - 1
gui/wxpython/animation/controller.py

@@ -379,7 +379,13 @@ class AnimationController(wx.EvtHandler):
         prov = self.bitmapProviders[animationData.windowIndex]
         prov = self.bitmapProviders[animationData.windowIndex]
         prov.SetData(datasource = animationData.mapData, dataType=animationData.inputMapType)
         prov.SetData(datasource = animationData.mapData, dataType=animationData.inputMapType)
 
 
-        self.bitmapProviders[animationData.windowIndex].Load()
+        prov.Load()
+        if animationData.legendCmd:
+            try:
+                bitmap = prov.LoadOverlay(animationData.legendCmd)
+                self.mapwindows[animationData.windowIndex].SetOverlay(bitmap)
+            except GException:
+                GError(message=_("Failed to display legend."))
 
 
     def _load3DData(self, animationData):
     def _load3DData(self, animationData):
         prov = self.bitmapProviders[animationData.windowIndex]
         prov = self.bitmapProviders[animationData.windowIndex]

+ 78 - 20
gui/wxpython/animation/dialogs.py

@@ -23,22 +23,20 @@ import sys
 import wx
 import wx
 import copy
 import copy
 import datetime
 import datetime
-from wx.lib.newevent import NewEvent
 import wx.lib.filebrowsebutton as filebrowse
 import wx.lib.filebrowsebutton as filebrowse
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
     sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
 
 
-import grass.temporal as tgis
-
 from core.gcmd import GMessage, GError, GException
 from core.gcmd import GMessage, GError, GException
 from core import globalvar
 from core import globalvar
 from gui_core import gselect
 from gui_core import gselect
 from gui_core.dialogs import MapLayersDialog, GetImageHandlers
 from gui_core.dialogs import MapLayersDialog, GetImageHandlers
+from gui_core.forms import GUI
 from core.settings import UserSettings
 from core.settings import UserSettings
 from core.utils import _
 from core.utils import _
 
 
-from utils import TemporalMode, validateTimeseriesName, validateMapNames
+from utils import TemporalMode, getRegisteredMaps, validateTimeseriesName, validateMapNames
 from nviztask import NvizTask
 from nviztask import NvizTask
 
 
 from grass.pydispatch.signal import Signal
 from grass.pydispatch.signal import Signal
@@ -271,6 +269,7 @@ class InputDialog(wx.Dialog):
             self.SetTitle(_("Edit animation"))
             self.SetTitle(_("Edit animation"))
 
 
         self.animationData = animationData
         self.animationData = animationData
+        self._tmpLegendCmd = None
 
 
         self._layout()
         self._layout()
         self.OnViewMode(event = None)
         self.OnViewMode(event = None)
@@ -357,6 +356,15 @@ class InputDialog(wx.Dialog):
         self.addManyMapsButton = wx.BitmapButton(panel, id = wx.ID_ANY, bitmap = bitmap)
         self.addManyMapsButton = wx.BitmapButton(panel, id = wx.ID_ANY, bitmap = bitmap)
         self.addManyMapsButton.Bind(wx.EVT_BUTTON, self.OnAddMaps)
         self.addManyMapsButton.Bind(wx.EVT_BUTTON, self.OnAddMaps)
 
 
+        self.legend = wx.CheckBox(panel, label=_("Show raster legend"))
+        self.legend.SetValue(bool(self.animationData.legendCmd))
+        self.legendBtn = wx.Button(panel, label=_("Set options"))
+        self.legendBtn.Bind(wx.EVT_BUTTON, self.OnLegend)
+        tooltip = _("By default, legend is created for the first raster map in case of multiple maps "
+                    "and for the first raster map of space time raster dataset.")
+        self.legend.SetToolTipString(tooltip) 
+        self.legendBtn.SetToolTipString(tooltip)
+
         self.OnDataType(None)
         self.OnDataType(None)
         if self.animationData.inputData is None:
         if self.animationData.inputData is None:
             self.dataSelect.SetValue('')
             self.dataSelect.SetValue('')
@@ -375,7 +383,12 @@ class InputDialog(wx.Dialog):
         hbox.Add(item = self.dataSelect, proportion = 1, flag = wx.ALIGN_CENTER)
         hbox.Add(item = self.dataSelect, proportion = 1, flag = wx.ALIGN_CENTER)
         hbox.Add(item = self.addManyMapsButton, proportion = 0, flag = wx.LEFT, border = 5)
         hbox.Add(item = self.addManyMapsButton, proportion = 0, flag = wx.LEFT, border = 5)
         dataBoxSizer.Add(item = hbox, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
         dataBoxSizer.Add(item = hbox, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
-        
+
+        hbox = wx.BoxSizer(wx.HORIZONTAL)
+        hbox.Add(item=self.legend, proportion=1, flag=wx.ALIGN_CENTER_VERTICAL)
+        hbox.Add(item=self.legendBtn, proportion=0, flag=wx.LEFT, border=5)
+        dataBoxSizer.Add(item=hbox, proportion=0, flag=wx.EXPAND | wx.ALL, border=3)
+
         panel.SetSizerAndFit(dataBoxSizer)
         panel.SetSizerAndFit(dataBoxSizer)
         panel.SetAutoLayout(True)
         panel.SetAutoLayout(True)
 
 
@@ -446,6 +459,9 @@ class InputDialog(wx.Dialog):
             self.dataSelect.SetType(etype = etype, multiple = False)
             self.dataSelect.SetType(etype = etype, multiple = False)
             self.addManyMapsButton.Enable(False)
             self.addManyMapsButton.Enable(False)
 
 
+        self.legend.Enable(etype in ('rast', 'strds'))
+        self.legendBtn.Enable(etype in ('rast', 'strds'))
+
         self.dataSelect.SetValue('')
         self.dataSelect.SetValue('')
 
 
     def OnAddMaps(self, event):
     def OnAddMaps(self, event):
@@ -466,6 +482,45 @@ class InputDialog(wx.Dialog):
 
 
         dlg.Destroy()
         dlg.Destroy()
 
 
+    def OnLegend(self, event):
+        """!Set options for legend"""
+        if self._tmpLegendCmd:
+            cmd = self._tmpLegendCmd 
+        elif self.animationData.legendCmd:
+            cmd = self.animationData.legendCmd
+        else:
+            cmd = ['d.legend', 'at=5,50,2,5']
+
+            mapName = self._getLegendMapHint()
+            if mapName:
+                cmd.append("map=%s" % mapName)
+
+        GUI(parent=self, modal=True).ParseCommand(cmd=cmd,
+                                                  completed=(self.GetOptData, '', ''))
+
+    def _getLegendMapHint(self):
+        """!Determine probable map"""
+        inputData = self.dataSelect.GetValue()
+        etype = self.dataChoice.GetClientData(self.dataChoice.GetSelection())
+        if etype == 'strds':
+            timeseries = validateTimeseriesName(inputData, etype=etype)
+            timeseriesMaps = getRegisteredMaps(timeseries, etype)
+            if len(timeseriesMaps):
+                return timeseriesMaps[0]
+        else:  # multiple raster
+            maps = inputData.split(',')
+            if len(maps):
+                return maps[0]
+
+        return None
+
+    def GetOptData(self, dcmd, layer, params, propwin):
+        """!Process decoration layer data"""
+        self._tmpLegendCmd = dcmd
+
+        if dcmd and not self.legend.IsChecked():
+            self.legend.SetValue(True)
+
     def _update(self):
     def _update(self):
         self.animationData.name = self.nameCtrl.GetValue()
         self.animationData.name = self.nameCtrl.GetValue()
         self.animationData.windowIndex = self.windowChoice.GetSelection()
         self.animationData.windowIndex = self.windowChoice.GetSelection()
@@ -475,6 +530,13 @@ class InputDialog(wx.Dialog):
         self.animationData.inputData = self.dataSelect.GetValue()
         self.animationData.inputData = self.dataSelect.GetValue()
         sel = self.nDChoice.GetSelection()
         sel = self.nDChoice.GetSelection()
         self.animationData.viewMode = self.nDChoice.GetClientData(sel)
         self.animationData.viewMode = self.nDChoice.GetClientData(sel)
+        if self._tmpLegendCmd:
+            if self.legend.IsChecked():
+                self.animationData.legendCmd = self._tmpLegendCmd
+        else:
+            if self.legend.IsChecked():
+                self.animationData.legendCmd = ['d.legend', 'at=5,50,2,5', 
+                                                'map=%s' % self._getLegendMapHint()]
 
 
         if self.threeDPanel.IsShown():
         if self.threeDPanel.IsShown():
             self.animationData.workspaceFile = self.fileSelector.GetValue()
             self.animationData.workspaceFile = self.fileSelector.GetValue()
@@ -639,6 +701,7 @@ class AnimationData(object):
         self.nvizParameter = self._nvizParameters[0]
         self.nvizParameter = self._nvizParameters[0]
 
 
         self.workspaceFile = None
         self.workspaceFile = None
+        self.legendCmd = None
 
 
     def GetName(self):
     def GetName(self):
         return self._name
         return self._name
@@ -691,21 +754,8 @@ class AnimationData(object):
             self.mapData = newNames
             self.mapData = newNames
 
 
         elif self.inputMapType in ('strds', 'stvds'):
         elif self.inputMapType in ('strds', 'stvds'):
-            timeseries = validateTimeseriesName(data, etype = self.inputMapType)
-            if self.inputMapType == 'strds':
-                sp = tgis.SpaceTimeRasterDataset(ident = timeseries)
-            elif self.inputMapType == 'stvds':
-                sp = tgis.SpaceTimeVectorDataset(ident = timeseries)
-                
-            if sp.is_in_db() == False:
-                raise GException(_("Space time dataset <%s> not found.") % timeseries)
-        
-            sp.select()
-            rows = sp.get_registered_maps(columns = "id", where = None, order = "start_time", dbif = None)
-            timeseriesMaps = []
-            if rows:
-                for row in rows:
-                    timeseriesMaps.append(row["id"])
+            timeseries = validateTimeseriesName(data, etype=self.inputMapType)
+            timeseriesMaps = getRegisteredMaps(timeseries, self.inputMapType)
             self._inputData = timeseries
             self._inputData = timeseries
             self.mapData = timeseriesMaps
             self.mapData = timeseriesMaps
         else:
         else:
@@ -769,6 +819,14 @@ class AnimationData(object):
         return self._viewModes
         return self._viewModes
 
 
     viewModes = property(fget = GetViewModes)
     viewModes = property(fget = GetViewModes)
+    
+    def SetLegendCmd(self, cmd):
+        self._legendCmd = cmd
+
+    def GetLegendCmd(self):
+        return self._legendCmd
+
+    legendCmd = property(fget=GetLegendCmd, fset=SetLegendCmd)
 
 
     def GetNvizCommands(self):
     def GetNvizCommands(self):
         if not self.workspaceFile or not self.mapData:
         if not self.workspaceFile or not self.mapData:

+ 72 - 10
gui/wxpython/animation/mapwindow.py

@@ -21,10 +21,10 @@ import wx
 from multiprocessing import Process, Queue
 from multiprocessing import Process, Queue
 import tempfile
 import tempfile
 import grass.script as grass
 import grass.script as grass
-from core.gcmd import RunCommand
+from core.gcmd import RunCommand, GException
 from core.debug import Debug
 from core.debug import Debug
 from core.settings import UserSettings
 from core.settings import UserSettings
-from core.utils import _
+from core.utils import _, CmdToTuple
 
 
 from grass.pydispatch.signal import Signal
 from grass.pydispatch.signal import Signal
 
 
@@ -71,7 +71,7 @@ class BufferedWindow(wx.Window):
         # The Buffer init is done here, to make sure the buffer is always
         # The Buffer init is done here, to make sure the buffer is always
         # the same size as the Window
         # the same size as the Window
         #Size  = self.GetClientSizeTuple()
         #Size  = self.GetClientSizeTuple()
-        size  = self.ClientSize
+        size  = self.GetClientSize()
 
 
         # Make new offscreen bitmap: this bitmap will always have the
         # Make new offscreen bitmap: this bitmap will always have the
         # current drawing in it, so it can be used to save the image to
         # current drawing in it, so it can be used to save the image to
@@ -112,6 +112,8 @@ class AnimationWindow(BufferedWindow):
         self.bitmap = wx.EmptyBitmap(1, 1)
         self.bitmap = wx.EmptyBitmap(1, 1)
         self.text = ''
         self.text = ''
         self.parent = parent
         self.parent = parent
+        self._pdc = wx.PseudoDC()
+        self._overlay = None
 
 
         BufferedWindow.__init__(self, parent=parent, id=id, style=style)
         BufferedWindow.__init__(self, parent=parent, id=id, style=style)
         self.SetBackgroundColour(wx.BLACK)
         self.SetBackgroundColour(wx.BLACK)
@@ -147,6 +149,33 @@ class AnimationWindow(BufferedWindow):
         self.text = text
         self.text = text
         self.UpdateDrawing()
         self.UpdateDrawing()
 
 
+    def DrawOverlay(self):
+        self._pdc.BeginDrawing()
+        self._pdc.DrawBitmap(bmp=self._overlay, x=0, y=0)
+        self._pdc.EndDrawing()
+
+    def SetOverlay(self, bitmap):
+        """!Sets overlay bitmap (legend)"""
+        Debug.msg(3, "AnimationWindow.SetOverlay()")
+        if bitmap:
+            if self._overlay:
+                self._pdc.RemoveAll()
+            self._overlay = bitmap
+            self._pdc.BeginDrawing()
+            self._pdc.DrawBitmap(bmp=bitmap, x=0, y=0)
+            self._pdc.EndDrawing()
+        else:
+            self._overlay = None
+            self._pdc.RemoveAll()
+        self.UpdateDrawing()
+
+    def OnPaint(self, event):
+        Debug.msg(5, "AnimationWindow.OnPaint()")
+        # All that is needed here is to draw the buffer to screen
+        dc = wx.BufferedPaintDC(self, self._Buffer)
+        if self._overlay:
+            self._pdc.DrawToDC(dc)
+
 class BitmapProvider(object):
 class BitmapProvider(object):
     """!Class responsible for loading data and providing bitmaps"""
     """!Class responsible for loading data and providing bitmaps"""
     def __init__(self, frame, bitmapPool, imageWidth=640, imageHeight=480, nprocs=4):
     def __init__(self, frame, bitmapPool, imageWidth=640, imageHeight=480, nprocs=4):
@@ -405,6 +434,30 @@ class BitmapProvider(object):
         grass.try_remove(tempFileFormat)
         grass.try_remove(tempFileFormat)
         os.environ.pop('GRASS_REGION')
         os.environ.pop('GRASS_REGION')
 
 
+    def LoadOverlay(self, cmd):
+        """!Creates raster legend with d.legend
+
+        @param cmd d.legend command as a list
+
+        @return bitmap with legend
+        """
+        fileHandler, filename = tempfile.mkstemp(suffix=".png")
+        os.close(fileHandler)
+        # Set the environment variables for this process
+        _setEnvironment(self.imageWidth, self.imageHeight, filename, transparent=True)
+
+        Debug.msg(1, "Render raster legend " + str(filename))
+        cmdTuple = CmdToTuple(cmd)
+        returncode, stdout, messages = read2_command(cmdTuple[0], **cmdTuple[1])
+
+        if returncode == 0:
+            bitmap = wx.Bitmap(filename, wx.BITMAP_TYPE_PNG)
+            return bitmap
+        else:
+            os.remove(filename)
+            raise GException(messages)
+
+
 def mapRenderProcess(mapType, mapname, width, height, fileQueue):
 def mapRenderProcess(mapType, mapname, width, height, fileQueue):
     """!Render raster or vector files as png image and write the 
     """!Render raster or vector files as png image and write the 
        resulting png filename in the provided file queue
        resulting png filename in the provided file queue
@@ -421,13 +474,7 @@ def mapRenderProcess(mapType, mapname, width, height, fileQueue):
     os.close(fileHandler)
     os.close(fileHandler)
     
     
     # Set the environment variables for this process
     # Set the environment variables for this process
-    os.environ['GRASS_WIDTH'] = str(width) 
-    os.environ['GRASS_HEIGHT'] = str(height)
-    driver = UserSettings.Get(group = 'display', key = 'driver', subkey = 'type')
-    os.environ['GRASS_RENDER_IMMEDIATE'] = driver
-    os.environ['GRASS_TRUECOLOR'] = "1" 
-    os.environ['GRASS_TRANSPARENT'] = "1"
-    os.environ['GRASS_PNGFILE'] = str(filename)
+    _setEnvironment(width, height, filename, transparent=False)
 
 
     if mapType in ('rast', 'strds'):
     if mapType in ('rast', 'strds'):
         Debug.msg(1, "Render raster image " + str(filename))
         Debug.msg(1, "Render raster image " + str(filename))
@@ -446,6 +493,21 @@ def mapRenderProcess(mapType, mapname, width, height, fileQueue):
 
 
     fileQueue.put(filename)
     fileQueue.put(filename)
     
     
+
+def _setEnvironment(width, height, filename, transparent):
+    os.environ['GRASS_WIDTH'] = str(width)
+    os.environ['GRASS_HEIGHT'] = str(height)
+    driver = UserSettings.Get(group='display', key='driver', subkey='type')
+    os.environ['GRASS_RENDER_IMMEDIATE'] = driver
+    os.environ['GRASS_BACKGROUNDCOLOR'] = 'ffffff'
+    os.environ['GRASS_TRUECOLOR'] = "TRUE"
+    if transparent:
+        os.environ['GRASS_TRANSPARENT'] = "TRUE"
+    else:
+        os.environ['GRASS_TRANSPARENT'] = "FALSE"
+    os.environ['GRASS_PNGFILE'] = str(filename)
+
+
 class BitmapPool():
 class BitmapPool():
     """!Class storing bitmaps (emulates dictionary)"""
     """!Class storing bitmaps (emulates dictionary)"""
     def __init__(self):
     def __init__(self):

+ 21 - 0
gui/wxpython/animation/utils.py

@@ -93,6 +93,27 @@ def validateMapNames(names, etype):
                 raise GException(_("Map <%s> not found.") % name)
                 raise GException(_("Map <%s> not found.") % name)
     return newNames
     return newNames
 
 
+
+def getRegisteredMaps(timeseries, etype):
+    """!Returns list of maps registered in dataset"""
+    timeseriesMaps = []
+    if etype == 'strds':
+        sp = tgis.SpaceTimeRasterDataset(ident=timeseries)
+    elif etype == 'stvds':
+        sp = tgis.SpaceTimeVectorDataset(ident=timeseries)
+
+    if sp.is_in_db() == False:
+        raise GException(_("Space time dataset <%s> not found.") % timeseries)
+        
+    sp.select()
+    rows = sp.get_registered_maps(columns="id", where=None, order="start_time", dbif=None)
+    timeseriesMaps = []
+    if rows:
+        for row in rows:
+            timeseriesMaps.append(row["id"])
+    return timeseriesMaps
+
+
 def ComputeScaledRect(sourceSize, destSize):
 def ComputeScaledRect(sourceSize, destSize):
     """!Fits source rectangle into destination rectangle
     """!Fits source rectangle into destination rectangle
     by scaling and centering.
     by scaling and centering.