[GRASS-SVN] r58576 - grass/trunk/gui/wxpython/animation

svn_grass at osgeo.org svn_grass at osgeo.org
Wed Jan 1 12:57:48 PST 2014


Author: annakrat
Date: 2014-01-01 12:57:48 -0800 (Wed, 01 Jan 2014)
New Revision: 58576

Modified:
   grass/trunk/gui/wxpython/animation/controller.py
   grass/trunk/gui/wxpython/animation/data.py
   grass/trunk/gui/wxpython/animation/dialogs.py
   grass/trunk/gui/wxpython/animation/provider.py
   grass/trunk/gui/wxpython/animation/utils.py
Log:
wxGUI/animation: add option to change region during animation (suggested by lucadelu and neteler)

Modified: grass/trunk/gui/wxpython/animation/controller.py
===================================================================
--- grass/trunk/gui/wxpython/animation/controller.py	2014-01-01 19:54:09 UTC (rev 58575)
+++ grass/trunk/gui/wxpython/animation/controller.py	2014-01-01 20:57:48 UTC (rev 58576)
@@ -327,7 +327,9 @@
                     self.animations[i].SetActive(False)
                     continue
                 anim = [anim for anim in self.animationData if anim.windowIndex == i][0]
-                self.animations[i].SetFrames([HashCmds(cmdList) for cmdList in anim.cmdMatrix])
+                regions = anim.GetRegions()
+                self.animations[i].SetFrames([HashCmds(cmdList, region)
+                                              for cmdList, region in zip(anim.cmdMatrix, regions)])
                 self.animations[i].SetActive(True)
         else:
             for i in range(len(self.animations)):
@@ -335,8 +337,10 @@
                     self.animations[i].SetActive(False)
                     continue
                 anim = [anim for anim in self.animationData if anim.windowIndex == i][0]
+                regions = anim.GetRegions()
                 identifiers = sampleCmdMatrixAndCreateNames(anim.cmdMatrix,
-                                                            mapNamesDict[anim.firstStdsNameType[0]])
+                                                            mapNamesDict[anim.firstStdsNameType[0]],
+                                                            regions)
                 self.animations[i].SetFrames(identifiers)
                 self.animations[i].SetActive(True)
 
@@ -367,7 +371,8 @@
 
     def _set2DData(self, animationData):
         opacities = [layer.opacity for layer in animationData.layerList if layer.active]
-        self.bitmapProvider.SetCmds(animationData.cmdMatrix, opacities)
+        regions = animationData.GetRegions()
+        self.bitmapProvider.SetCmds(animationData.cmdMatrix, opacities, regions)
 
     def _load3DData(self, animationData):
         nviz = animationData.GetNvizCommands()

Modified: grass/trunk/gui/wxpython/animation/data.py
===================================================================
--- grass/trunk/gui/wxpython/animation/data.py	2014-01-01 19:54:09 UTC (rev 58575)
+++ grass/trunk/gui/wxpython/animation/data.py	2014-01-01 20:57:48 UTC (rev 58576)
@@ -17,6 +17,7 @@
 @author Anna Petrasova <kratochanna gmail.com>
 """
 import os
+import copy
 
 from grass.script import core as gcore
 
@@ -24,7 +25,7 @@
 from core.gcmd import GException
 from animation.nviztask import NvizTask
 from animation.utils import validateMapNames, getRegisteredMaps, \
-    checkSeriesCompatibility, validateTimeseriesName
+    checkSeriesCompatibility, validateTimeseriesName, interpolate
 from core.layerlist import LayerList, Layer
 import grass.temporal as tgis
 
@@ -50,6 +51,11 @@
         self.workspaceFile = None
         self.legendCmd = None
 
+        self._startRegion = None
+        self._endRegion = None
+        self._zoomRegionValue = None
+        self._regions = None
+
     def GetName(self):
         return self._name
 
@@ -180,6 +186,77 @@
 
         return {'commands': cmds, 'region': region}
 
+    def SetStartRegion(self, region):
+        self._startRegion = region
+
+    def GetStartRegion(self):
+        return self._startRegion
+
+    startRegion = property(fset=SetStartRegion, fget=GetStartRegion)
+
+    def SetEndRegion(self, region):
+        self._endRegion = region
+
+    def GetEndRegion(self):
+        return self._endRegion
+
+    endRegion = property(fset=SetEndRegion, fget=GetEndRegion)
+
+    def SetZoomRegionValue(self, value):
+        self._zoomRegionValue = value
+
+    def GetZoomRegionValue(self):
+        return self._zoomRegionValue
+
+    zoomRegionValue = property(fset=SetZoomRegionValue, fget=GetZoomRegionValue)
+
+    def GetRegions(self):
+        self._computeRegions(self._mapCount, self._startRegion,
+                             self._endRegion, self._zoomRegionValue)
+        return self._regions
+
+    def _computeRegions(self, count, startRegion, endRegion=None, zoomValue=None):
+        """Computes regions based on start region and end region or zoom value
+        for each of the animation frames."""
+        currRegion = dict(gcore.region())  # cast to dict, otherwise deepcopy error
+        del currRegion['cells']
+        del currRegion['cols']
+        del currRegion['rows']
+        regions = []
+        for i in range(self._mapCount):
+            if endRegion or zoomValue:
+                regions.append(copy.copy(currRegion))
+            else:
+                regions.append(None)
+        if not startRegion:
+            self._regions = regions
+            return
+
+        startRegionDict = gcore.parse_key_val(gcore.read_command('g.region', flags='gu',
+                                                                 region=startRegion),
+                                              val_type=float)
+        if endRegion:
+            endRegionDict = gcore.parse_key_val(gcore.read_command('g.region', flags='gu',
+                                                                   region=endRegion),
+                                                val_type=float)
+            for key in ('n', 's', 'e', 'w'):
+                values = interpolate(startRegionDict[key], endRegionDict[key], self._mapCount)
+                for value, region in zip(values, regions):
+                    region[key] = value
+
+        elif zoomValue:
+            for i in range(self._mapCount):
+                regions[i]['n'] -= zoomValue[0] * i
+                regions[i]['e'] -= zoomValue[1] * i
+                regions[i]['s'] += zoomValue[0] * i
+                regions[i]['w'] += zoomValue[1] * i
+
+                # handle cases when north < south and similarly EW
+                if regions[i]['n'] < regions[i]['s'] or \
+                   regions[i]['e'] < regions[i]['w']:
+                        regions[i] = regions[i - 1]
+        self._regions = regions
+
     def __repr__(self):
         return "%s(%r)" % (self.__class__, self.__dict__)
 

Modified: grass/trunk/gui/wxpython/animation/dialogs.py
===================================================================
--- grass/trunk/gui/wxpython/animation/dialogs.py	2014-01-01 19:54:09 UTC (rev 58575)
+++ grass/trunk/gui/wxpython/animation/dialogs.py	2014-01-01 20:57:48 UTC (rev 58576)
@@ -39,6 +39,7 @@
 from core.settings import UserSettings
 from core.utils import _
 from gui_core.gselect import Select
+from gui_core.widgets import FloatValidator
 
 from animation.utils import TemporalMode, getRegisteredMaps
 from animation.data import AnimationData, AnimLayer
@@ -283,16 +284,40 @@
         self.OnViewMode(event=None)
 
     def _layout(self):
+        self.notebook = wx.Notebook(parent=self, style=wx.BK_DEFAULT)
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        self.notebook.AddPage(self._createGeneralPage(self.notebook), _("General"))
+        self.notebook.AddPage(self._createAdvancedPage(self.notebook), _("Advanced"))
+        sizer.Add(self.notebook, proportion=1, flag=wx.ALL | wx.EXPAND, border=3)
+
+        # buttons
+        self.btnOk = wx.Button(self, wx.ID_OK)
+        self.btnCancel = wx.Button(self, wx.ID_CANCEL)
+        self.btnOk.SetDefault()
+        self.btnOk.Bind(wx.EVT_BUTTON, self.OnOk)
+        # button sizer
+        btnStdSizer = wx.StdDialogButtonSizer()
+        btnStdSizer.AddButton(self.btnOk)
+        btnStdSizer.AddButton(self.btnCancel)
+        btnStdSizer.Realize()
+
+        sizer.Add(item=btnStdSizer, proportion=0,
+                  flag=wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border=5)
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+
+    def _createGeneralPage(self, parent):
+        panel = wx.Panel(parent=parent)
         mainSizer = wx.BoxSizer(wx.VERTICAL)
 
-        self.windowChoice = wx.Choice(self, id=wx.ID_ANY,
+        self.windowChoice = wx.Choice(panel, id=wx.ID_ANY,
                                       choices=[_("top left"), _("top right"),
                                                _("bottom left"), _("bottom right")])
         self.windowChoice.SetSelection(self.animationData.windowIndex)
 
-        self.nameCtrl = wx.TextCtrl(self, id=wx.ID_ANY, value=self.animationData.name)
+        self.nameCtrl = wx.TextCtrl(panel, id=wx.ID_ANY, value=self.animationData.name)
 
-        self.nDChoice = wx.Choice(self, id=wx.ID_ANY)
+        self.nDChoice = wx.Choice(panel, id=wx.ID_ANY)
         mode = self.animationData.viewMode
         index = 0
         for i, (viewMode, viewModeName) in enumerate(self.animationData.viewModes):
@@ -305,13 +330,13 @@
         self.nDChoice.Bind(wx.EVT_CHOICE, self.OnViewMode)
 
         gridSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
-        gridSizer.Add(item=wx.StaticText(self, id=wx.ID_ANY, label=_("Name:")),
+        gridSizer.Add(item=wx.StaticText(panel, id=wx.ID_ANY, label=_("Name:")),
                       flag=wx.ALIGN_CENTER_VERTICAL)
         gridSizer.Add(item=self.nameCtrl, proportion=1, flag=wx.EXPAND)
-        gridSizer.Add(item=wx.StaticText(self, id=wx.ID_ANY, label=_("Window position:")),
+        gridSizer.Add(item=wx.StaticText(panel, id=wx.ID_ANY, label=_("Window position:")),
                       flag=wx.ALIGN_CENTER_VERTICAL)
         gridSizer.Add(item=self.windowChoice, proportion=1, flag=wx.ALIGN_RIGHT)
-        gridSizer.Add(item=wx.StaticText(self, id=wx.ID_ANY, label=_("View mode:")),
+        gridSizer.Add(item=wx.StaticText(panel, id=wx.ID_ANY, label=_("View mode:")),
                       flag=wx.ALIGN_CENTER_VERTICAL)
         gridSizer.Add(item=self.nDChoice, proportion=1, flag=wx.ALIGN_RIGHT)
         gridSizer.AddGrowableCol(0, 1)
@@ -319,40 +344,28 @@
         mainSizer.Add(item=gridSizer, proportion=0, flag=wx.ALL | wx.EXPAND, border=5)
         label = _("For 3D animation, please select only one space-time dataset\n"
                   "or one series of map layers.")
-        self.warning3DLayers = wx.StaticText(self, label=label)
+        self.warning3DLayers = wx.StaticText(panel, label=label)
         self.warning3DLayers.SetForegroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_GRAYTEXT))
         mainSizer.Add(item=self.warning3DLayers, proportion=0, flag=wx.EXPAND | wx.LEFT, border=5)
 
-        self.dataPanel = self._createDataPanel()
-        self.threeDPanel = self._create3DPanel()
+        self.dataPanel = self._createDataPanel(panel)
+        self.threeDPanel = self._create3DPanel(panel)
         mainSizer.Add(item=self.dataPanel, proportion=1, flag=wx.EXPAND | wx.ALL, border=3)
         mainSizer.Add(item=self.threeDPanel, proportion=0, flag=wx.EXPAND | wx.ALL, border=3)
 
-        # buttons
-        self.btnOk = wx.Button(self, wx.ID_OK)
-        self.btnCancel = wx.Button(self, wx.ID_CANCEL)
-        self.btnOk.SetDefault()
-        self.btnOk.Bind(wx.EVT_BUTTON, self.OnOk)
-        # button sizer
-        btnStdSizer = wx.StdDialogButtonSizer()
-        btnStdSizer.AddButton(self.btnOk)
-        btnStdSizer.AddButton(self.btnCancel)
-        btnStdSizer.Realize()
+        panel.SetSizer(mainSizer)
+        mainSizer.Fit(panel)
 
-        mainSizer.Add(item=btnStdSizer, proportion=0,
-                      flag=wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border=5)
+        return panel
 
-        self.SetSizer(mainSizer)
-        mainSizer.Fit(self)
-
-    def _createDataPanel(self):
-        panel = wx.Panel(self)
+    def _createDataPanel(self, parent):
+        panel = wx.Panel(parent)
         slmgrSizer = wx.BoxSizer(wx.VERTICAL)
         self._layerList = copy.deepcopy(self.animationData.layerList)
         self.simpleLmgr = AnimSimpleLayerManager(parent=panel,
                                                  layerList=self._layerList,
                                                  modal=True)
-        self.simpleLmgr.SetMinSize((globalvar.DIALOG_GSELECT_SIZE[0], 120))
+        self.simpleLmgr.SetMinSize((globalvar.DIALOG_GSELECT_SIZE[0], 80))
         slmgrSizer.Add(self.simpleLmgr, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
 
         self.legend = wx.CheckBox(panel, label=_("Show raster legend"))
@@ -371,8 +384,8 @@
 
         return panel
 
-    def _create3DPanel(self):
-        panel = wx.Panel(self, id=wx.ID_ANY)
+    def _create3DPanel(self, parent):
+        panel = wx.Panel(parent, id=wx.ID_ANY)
         dataStBox = wx.StaticBox(parent=panel, id=wx.ID_ANY,
                                  label=' %s ' % _("3D view parameters"))
         dataBoxSizer = wx.StaticBoxSizer(dataStBox, wx.VERTICAL)
@@ -407,16 +420,92 @@
 
         return panel
 
+    def _createAdvancedPage(self, parent):
+        panel = wx.Panel(parent=parent)
+
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+        box = wx.StaticBox(parent=panel, label=" %s " % _("Animate region change (2D view only)"))
+        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+
+        gridSizer = wx.GridBagSizer(hgap=3, vgap=3)
+        gridSizer.Add(wx.StaticText(panel, label=_("Start region:")),
+                      pos=(0, 0), flag=wx.ALIGN_CENTER_VERTICAL)
+        self.stRegion = Select(parent=panel, type='region', size=(200, -1))
+        if self.animationData.startRegion:
+            self.stRegion.SetValue(self.animationData.startRegion)
+        gridSizer.Add(self.stRegion, pos=(0, 1), flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
+
+        self.endRegRadio = wx.RadioButton(panel, label=_("End region:"), style=wx.RB_GROUP)
+        gridSizer.Add(self.endRegRadio, pos=(1, 0), flag=wx.EXPAND)
+        self.endRegion = Select(parent=panel, type='region', size=(200, -1))
+        gridSizer.Add(self.endRegion, pos=(1, 1), flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
+        self.zoomRadio = wx.RadioButton(panel, label=_("Zoom value:"))
+        self.zoomRadio.SetToolTipString(_("N-S/E-W distances in map units used to "
+                                          "gradually reduce region."))
+        gridSizer.Add(self.zoomRadio, pos=(2, 0), flag=wx.EXPAND)
+
+        zoomSizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.zoomNS = wx.TextCtrl(panel, validator=FloatValidator())
+        self.zoomEW = wx.TextCtrl(panel, validator=FloatValidator())
+        zoomSizer.Add(wx.StaticText(panel, label=_("N-S:")), proportion=0,
+                      flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=3)
+        zoomSizer.Add(self.zoomNS, proportion=1, flag=wx.LEFT, border=3)
+        zoomSizer.Add(wx.StaticText(panel, label=_("E-W:")), proportion=0,
+                      flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=3)
+        zoomSizer.Add(self.zoomEW, proportion=1, flag=wx.LEFT, border=3)
+        gridSizer.Add(zoomSizer, pos=(2, 1), flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
+        if self.animationData.endRegion:
+            self.endRegRadio.SetValue(True)
+            self.zoomRadio.SetValue(False)
+            self.endRegion.SetValue(self.animationData.endRegion)
+        if self.animationData.zoomRegionValue:
+            self.endRegRadio.SetValue(False)
+            self.zoomRadio.SetValue(True)
+            zoom = self.animationData.zoomRegionValue
+            self.zoomNS.SetValue(str(zoom[0]))
+            self.zoomEW.SetValue(str(zoom[1]))
+
+        self.endRegRadio.Bind(wx.EVT_RADIOBUTTON, lambda evt: self._enableRegionWidgets())
+        self.zoomRadio.Bind(wx.EVT_RADIOBUTTON, lambda evt: self._enableRegionWidgets())
+        self._enableRegionWidgets()
+
+        gridSizer.AddGrowableCol(1)
+        sizer.Add(gridSizer, proportion=0, flag=wx.EXPAND | wx.ALL, border=3)
+        mainSizer.Add(sizer, proportion=0, flag=wx.EXPAND | wx.ALL, border=3)
+
+        panel.SetSizer(mainSizer)
+        mainSizer.Fit(panel)
+
+        return panel
+
+    def _enableRegionWidgets(self):
+        """!Enables/disables region widgets
+        according to which radiobutton is active."""
+        endReg = self.endRegRadio.GetValue()
+        self.endRegion.Enable(endReg)
+        self.zoomNS.Enable(not endReg)
+        self.zoomEW.Enable(not endReg)
+
     def OnViewMode(self, event):
         mode = self.nDChoice.GetSelection()
         self.Freeze()
         self.simpleLmgr.Activate3D(mode == 1)
         self.warning3DLayers.Show(mode == 1)
+
+        # disable region widgets for 3d
+        regSizer = self.stRegion.GetContainingSizer()
+        for child in regSizer.GetChildren():
+            if child.IsSizer():
+                for child_ in child.GetSizer().GetChildren():
+                    child_.GetWindow().Enable(mode != 1)
+            elif child.IsWindow():
+                child.GetWindow().Enable(mode != 1)
+        self._enableRegionWidgets()
+
+        # update layout
         sizer = self.threeDPanel.GetContainingSizer()
         sizer.Show(self.threeDPanel, mode == 1, True)
         sizer.Layout()
-        self.Layout()
-        self.Fit()
         self.Thaw()
 
     def OnLegend(self, event):
@@ -478,6 +567,31 @@
             self.animationData.workspaceFile = self.fileSelector.GetValue()
         if self.threeDPanel.IsShown():
             self.animationData.nvizParameter = self.paramChoice.GetStringSelection()
+        # region (2d only)
+        if self.animationData.viewMode == '3d':
+            self.animationData.startRegion = None
+            self.animationData.endRegion = None
+            self.animationData.zoomRegionValue = None
+            return
+        isEnd = self.endRegRadio.GetValue() and self.endRegion.GetValue()
+        isZoom = self.zoomRadio.GetValue() and self.zoomNS.GetValue() and self.zoomEW.GetValue()
+        isStart = self.stRegion.GetValue()
+        condition = bool(isStart) + bool(isZoom) + bool(isEnd)
+        if condition == 1:
+            raise GException(_("Region information is not complete"))
+        elif condition == 2:
+            self.animationData.startRegion = isStart
+            if isEnd:
+                self.animationData.endRegion = self.endRegion.GetValue()
+                self.animationData.zoomRegionValue = None
+            else:
+                self.animationData.zoomRegionValue = (float(self.zoomNS.GetValue()),
+                                                      float(self.zoomEW.GetValue()))
+                self.animationData.endRegion = None
+        else:
+            self.animationData.startRegion = None
+            self.animationData.endRegion = None
+            self.animationData.zoomRegionValue = None
 
     def OnOk(self, event):
         try:

Modified: grass/trunk/gui/wxpython/animation/provider.py
===================================================================
--- grass/trunk/gui/wxpython/animation/provider.py	2014-01-01 19:54:09 UTC (rev 58575)
+++ grass/trunk/gui/wxpython/animation/provider.py	2014-01-01 20:57:48 UTC (rev 58576)
@@ -60,6 +60,8 @@
 
         self._cmds3D = []
         self._regionFor3D = None
+        self._regions = []
+        self._regionsForUniqueCmds = []
 
         self._renderer = BitmapRenderer(self._mapFilesPool, self._tempDir,
                                         self.imageWidth, self.imageHeight)
@@ -77,7 +79,7 @@
         self._renderer.renderingContinues.connect(self.renderingContinues)
         self._composer.compositionContinues.connect(self.compositionContinues)
 
-    def SetCmds(self, cmdsForComposition, opacities):
+    def SetCmds(self, cmdsForComposition, opacities, regions=None):
         """!Sets commands to be rendered with opacity levels.
         Applies to 2D mode.
 
@@ -86,12 +88,15 @@
                  [['d.rast', 'map=elev_2002'], ['d.vect', 'map=points']],
                  ...]
         @param opacities list of opacity values
+        @param regions list of regions
         """
         Debug.msg(2, "BitmapProvider.SetCmds: {} lists".format(len(cmdsForComposition)))
         self._cmdsForComposition.extend(cmdsForComposition)
-        self._uniqueCmds = self._getUniqueCmds()
         self._opacities.extend(opacities)
+        self._regions.extend(regions)
 
+        self._getUniqueCmds()
+
     def SetCmds3D(self, cmds, region):
         """!Sets commands for 3D rendering.
 
@@ -103,12 +108,19 @@
         self._regionFor3D = region
 
     def _getUniqueCmds(self):
-        """!Returns list of unique commands."""
-        unique = set()
-        for cmdList in self._cmdsForComposition:
+        """!Returns list of unique commands.
+        Takes into account the region assigned."""
+        unique = list()
+        for cmdList, region in zip(self._cmdsForComposition, self._regions):
             for cmd in cmdList:
-                unique.add(tuple(cmd))
-        return list(unique)
+                if region:
+                    unique.append((tuple(cmd), tuple(sorted(region.items()))))
+                else:
+                    unique.append((tuple(cmd), None))
+        unique = list(set(unique))
+        self._uniqueCmds = [cmdAndRegion[0] for cmdAndRegion in unique]
+        self._regionsForUniqueCmds.extend([dict(cmdAndRegion[1]) if cmdAndRegion[1] else None
+                                           for cmdAndRegion in unique])
 
     def Unload(self):
         """!Unloads currently loaded data.
@@ -116,29 +128,32 @@
         """
         Debug.msg(2, "BitmapProvider.Unload")
         if self._cmdsForComposition:
-            for cmd in self._uniqueCmds:
-                del self._mapFilesPool[HashCmd(cmd)]
+            for cmd, region in zip(self._uniqueCmds, self._regionsForUniqueCmds):
+                del self._mapFilesPool[HashCmd(cmd, region)]
 
-            for cmdList in self._cmdsForComposition:
-                del self._bitmapPool[HashCmds(cmdList)]
+            for cmdList, region in zip(self._cmdsForComposition, self._regions):
+                del self._bitmapPool[HashCmds(cmdList, region)]
             self._uniqueCmds = []
             self._cmdsForComposition = []
             self._opacities = []
+            self._regions = []
+            self._regionsForUniqueCmds = []
         if self._cmds3D:
             self._cmds3D = []
             self._regionFor3D = None
 
-    def _dryRender(self, uniqueCmds, force):
+    def _dryRender(self, uniqueCmds, regions, force):
         """!Determines how many files will be rendered.
 
         @param uniqueCmds list of commands which are to be rendered
         @param force if forced rerendering
+        @param regions list of regions assigned to the commands
         """
         count = 0
-        for cmd in uniqueCmds:
-            filename = GetFileFromCmd(self._tempDir, cmd)
+        for cmd, region in zip(uniqueCmds, regions):
+            filename = GetFileFromCmd(self._tempDir, cmd, region)
             if not force and os.path.exists(filename) and \
-               self._mapFilesPool.GetSize(HashCmd(cmd)) == (self.imageWidth, self.imageHeight):
+               self._mapFilesPool.GetSize(HashCmd(cmd, region)) == (self.imageWidth, self.imageHeight):
                 continue
             count += 1
 
@@ -146,18 +161,19 @@
 
         return count
 
-    def _dryCompose(self, cmdLists, force):
+    def _dryCompose(self, cmdLists, regions, force):
         """!Determines how many lists of (commands) files
         will be composed (with g.pnmcomp).
 
         @param cmdLists list of commands lists which are to be composed
+        @param regions list of regions assigned to the commands
         @param force if forced rerendering
         """
         count = 0
-        for cmdList in cmdLists:
-            if not force and HashCmds(cmdList) in self._bitmapPool and \
-                self._bitmapPool[HashCmds(cmdList)].GetSize() == (self.imageWidth,
-                                                                  self.imageHeight):
+        for cmdList, region in zip(cmdLists, regions):
+            if not force and HashCmds(cmdList, region) in self._bitmapPool and \
+                self._bitmapPool[HashCmds(cmdList, region)].GetSize() == (self.imageWidth,
+                                                                          self.imageHeight):
                 continue
             count += 1
 
@@ -176,34 +192,37 @@
         Debug.msg(2, "BitmapProvider.Load: "
                      "force={}, bgcolor={}, nprocs={}".format(force, bgcolor, nprocs))
         cmds = []
+        regions = []
         if self._uniqueCmds:
             cmds.extend(self._uniqueCmds)
+            regions.extend(self._regionsForUniqueCmds)
         if self._cmds3D:
             cmds.extend(self._cmds3D)
+            regions.extend([None] * len(self._cmds3D))
 
-        count = self._dryRender(cmds, force=force)
+        count = self._dryRender(cmds, regions, force=force)
         self.renderingStarted.emit(count=count)
 
         # create no data bitmap
         if None not in self._bitmapPool or force:
             self._bitmapPool[None] = createNoDataBitmap(self.imageWidth, self.imageHeight)
 
-        ok = self._renderer.Render(cmds, regionFor3D=self._regionFor3D,
+        ok = self._renderer.Render(cmds, regions, regionFor3D=self._regionFor3D,
                                    bgcolor=bgcolor, force=force, nprocs=nprocs)
         self.renderingFinished.emit()
         if not ok:
             self.mapsLoaded.emit()  # what to do here?
             return
         if self._cmdsForComposition:
-            count = self._dryCompose(self._cmdsForComposition, force=force)
+            count = self._dryCompose(self._cmdsForComposition, self._regions, force=force)
             self.compositionStarted.emit(count=count)
-            self._composer.Compose(self._cmdsForComposition, self._opacities,
+            self._composer.Compose(self._cmdsForComposition, self._regions, self._opacities,
                                    bgcolor=bgcolor, force=force, nprocs=nprocs)
             self.compositionFinished.emit()
         if self._cmds3D:
             for cmd in self._cmds3D:
-                self._bitmapPool[HashCmds([cmd])] = \
-                    wx.Bitmap(GetFileFromCmd(self._tempDir, cmd))
+                self._bitmapPool[HashCmds([cmd], None)] = \
+                    wx.Bitmap(GetFileFromCmd(self._tempDir, cmd, None))
 
         self.mapsLoaded.emit()
 
@@ -273,10 +292,11 @@
         self._stopRendering = False
         self._isRendering = False
 
-    def Render(self, cmdList, regionFor3D, bgcolor, force, nprocs):
+    def Render(self, cmdList, regions, regionFor3D, bgcolor, force, nprocs):
         """!Renders all maps and stores files.
 
         @param cmdList list of rendering commands to run
+        @param regions regions for 2D rendering assigned to commands
         @param regionFor3D region for setting 3D view
         @param bgcolor background color as a tuple of 3 values 0 to 255
         @param force if True reload all data, otherwise only missing data
@@ -292,19 +312,19 @@
         cmd_list = []
 
         filteredCmdList = []
-        for cmd in cmdList:
-            filename = GetFileFromCmd(self._tempDir, cmd)
+        for cmd, region in zip(cmdList, regions):
+            filename = GetFileFromCmd(self._tempDir, cmd, region)
             if not force and os.path.exists(filename) and \
-               self._mapFilesPool.GetSize(HashCmd(cmd)) == (self.imageWidth, self.imageHeight):
+               self._mapFilesPool.GetSize(HashCmd(cmd, region)) == (self.imageWidth, self.imageHeight):
                 # for reference counting
-                self._mapFilesPool[HashCmd(cmd)] = filename
+                self._mapFilesPool[HashCmd(cmd, region)] = filename
                 continue
-            filteredCmdList.append(cmd)
+            filteredCmdList.append((cmd, region))
 
         mapNum = len(filteredCmdList)
         stopped = False
         self._isRendering = True
-        for cmd in filteredCmdList:
+        for cmd, region in filteredCmdList:
             count += 1
 
             # Queue object for interprocess communication
@@ -316,12 +336,13 @@
                                   cmd, regionFor3D, bgcolor, q))
             else:
                 p = Process(target=RenderProcess2D,
-                            args=(self.imageWidth, self.imageHeight, self._tempDir, cmd, bgcolor, q))
+                            args=(self.imageWidth, self.imageHeight, self._tempDir,
+                                  cmd, region, bgcolor, q))
             p.start()
 
             queue_list.append(q)
             proc_list.append(p)
-            cmd_list.append(cmd)
+            cmd_list.append((cmd, region))
 
             proc_count += 1
             # Wait for all running processes and read/store the created images
@@ -329,8 +350,8 @@
                 for i in range(len(cmd_list)):
                     proc_list[i].join()
                     filename = queue_list[i].get()
-                    self._mapFilesPool[HashCmd(cmd_list[i])] = filename
-                    self._mapFilesPool.SetSize(HashCmd(cmd_list[i]),
+                    self._mapFilesPool[HashCmd(cmd_list[i][0], cmd_list[i][1])] = filename
+                    self._mapFilesPool.SetSize(HashCmd(cmd_list[i][0], cmd_list[i][1]),
                                                (self.imageWidth, self.imageHeight))
 
                 proc_count = 0
@@ -367,10 +388,11 @@
         self._stopComposing = False
         self._isComposing = False
 
-    def Compose(self, cmdLists, opacityList, bgcolor, force, nprocs):
+    def Compose(self, cmdLists, regions, opacityList, bgcolor, force, nprocs):
         """!Performs the composition of ppm/pgm files.
 
         @param cmdLisst lists of rendering commands lists to compose
+        @param regions regions for 2D rendering assigned to commands
         @param opacityList list of lists of opacity values
         @param bgcolor background color as a tuple of 3 values 0 to 255
         @param force if True reload all data, otherwise only missing data
@@ -387,31 +409,31 @@
         cmd_lists = []
 
         filteredCmdLists = []
-        for cmdList in cmdLists:
-            if not force and HashCmds(cmdList) in self._bitmapPool and \
-                self._bitmapPool[HashCmds(cmdList)].GetSize() == (self.imageWidth,
-                                                                  self.imageHeight):
+        for cmdList, region in zip(cmdLists, regions):
+            if not force and HashCmds(cmdList, region) in self._bitmapPool and \
+                self._bitmapPool[HashCmds(cmdList, region)].GetSize() == (self.imageWidth,
+                                                                          self.imageHeight):
                 # TODO: find a better way than to assign the same to increase the reference
-                self._bitmapPool[HashCmds(cmdList)] = self._bitmapPool[HashCmds(cmdList)]
+                self._bitmapPool[HashCmds(cmdList, region)] = self._bitmapPool[HashCmds(cmdList, region)]
                 continue
-            filteredCmdLists.append(cmdList)
+            filteredCmdLists.append((cmdList, region))
 
         num = len(filteredCmdLists)
 
         self._isComposing = True
-        for cmdList in filteredCmdLists:
+        for cmdList, region in filteredCmdLists:
             count += 1
             # Queue object for interprocess communication
             q = Queue()
             # The separate render process
             p = Process(target=CompositeProcess,
                         args=(self.imageWidth, self.imageHeight, self._tempDir,
-                              cmdList, opacityList, bgcolor, q))
+                              cmdList, region, opacityList, bgcolor, q))
             p.start()
 
             queue_list.append(q)
             proc_list.append(p)
-            cmd_lists.append(cmdList)
+            cmd_lists.append((cmdList, region))
 
             proc_count += 1
 
@@ -421,11 +443,11 @@
                     proc_list[i].join()
                     filename = queue_list[i].get()
                     if filename is None:
-                        self._bitmapPool[HashCmds(cmd_lists[i])] = \
+                        self._bitmapPool[HashCmds(cmd_lists[i][0], cmd_lists[i][1])] = \
                             createNoDataBitmap(self.imageWidth, self.imageHeight,
                                                text="Failed to render")
                     else:
-                        self._bitmapPool[HashCmds(cmd_lists[i])] = \
+                        self._bitmapPool[HashCmds(cmd_lists[i][0], cmd_lists[i][1])] = \
                             wx.BitmapFromImage(wx.Image(filename))
                         os.remove(filename)
                 proc_count = 0
@@ -446,7 +468,7 @@
             self._stopComposing = True
 
 
-def RenderProcess2D(imageWidth, imageHeight, tempDir, cmd, bgcolor, fileQueue):
+def RenderProcess2D(imageWidth, imageHeight, tempDir, cmd, region, bgcolor, fileQueue):
     """!Render raster or vector files as ppm image and write the
        resulting ppm filename in the provided file queue
 
@@ -454,26 +476,32 @@
     @param imageHeight image height
     @param tempDir directory for rendering
     @param cmd d.rast/d.vect command as a list
+    @param region region as a dict or None
     @param bgcolor background color as a tuple of 3 values 0 to 255
     @param fileQueue the inter process communication queue
     storing the file name of the image
     """
 
-    filename = GetFileFromCmd(tempDir, cmd)
+    filename = GetFileFromCmd(tempDir, cmd, region)
     transparency = True
 
     # Set the environment variables for this process
     _setEnvironment(imageWidth, imageHeight, filename,
                     transparent=transparency, bgcolor=bgcolor)
-
+    if region:
+        os.environ['GRASS_REGION'] = gcore.region_env(**region)
     cmdTuple = CmdToTuple(cmd)
     returncode, stdout, messages = read2_command(cmdTuple[0], **cmdTuple[1])
     if returncode != 0:
         gcore.warning("Rendering failed:\n" + messages)
         fileQueue.put(None)
+        if region:
+            os.environ.pop('GRASS_REGION')
         os.remove(filename)
         return
 
+    if region:
+        os.environ.pop('GRASS_REGION')
     fileQueue.put(filename)
 
 
@@ -485,12 +513,13 @@
     @param imageHeight image height
     @param tempDir directory for rendering
     @param cmd m.nviz.image command as a list
+    @param region region as a dict
     @param bgcolor background color as a tuple of 3 values 0 to 255
     @param fileQueue the inter process communication queue
     storing the file name of the image
     """
 
-    filename = GetFileFromCmd(tempDir, cmd)
+    filename = GetFileFromCmd(tempDir, cmd, None)
     os.environ['GRASS_REGION'] = gcore.region_env(region3d=True, **region)
     Debug.msg(1, "Render image to file " + str(filename))
 
@@ -512,7 +541,7 @@
     fileQueue.put(filename)
 
 
-def CompositeProcess(imageWidth, imageHeight, tempDir, cmdList, opacities, bgcolor, fileQueue):
+def CompositeProcess(imageWidth, imageHeight, tempDir, cmdList, region, opacities, bgcolor, fileQueue):
     """!Performs the composition of image ppm files and writes the
        resulting ppm filename in the provided file queue
 
@@ -520,6 +549,7 @@
     @param imageHeight image height
     @param tempDir directory for rendering
     @param cmdList list of d.rast/d.vect commands
+    @param region region as a dict or None
     @param opacities list of opacities
     @param bgcolor background color as a tuple of 3 values 0 to 255
     @param fileQueue the inter process communication queue
@@ -529,9 +559,9 @@
     maps = []
     masks = []
     for cmd in cmdList:
-        maps.append(GetFileFromCmd(tempDir, cmd))
-        masks.append(GetFileFromCmd(tempDir, cmd, 'pgm'))
-    filename = GetFileFromCmds(tempDir, cmdList)
+        maps.append(GetFileFromCmd(tempDir, cmd, region))
+        masks.append(GetFileFromCmd(tempDir, cmd, region, 'pgm'))
+    filename = GetFileFromCmds(tempDir, cmdList, region)
     # Set the environment variables for this process
     _setEnvironment(imageWidth, imageHeight, filename,
                     transparent=False, bgcolor=bgcolor)

Modified: grass/trunk/gui/wxpython/animation/utils.py
===================================================================
--- grass/trunk/gui/wxpython/animation/utils.py	2014-01-01 19:54:09 UTC (rev 58575)
+++ grass/trunk/gui/wxpython/animation/utils.py	2014-01-01 20:57:48 UTC (rev 58576)
@@ -242,26 +242,30 @@
     return pilImage
 
 
-def HashCmd(cmd):
-    """!Returns a hash from command given as a list."""
+def HashCmd(cmd, region):
+    """!Returns a hash from command given as a list and a region as a dict."""
     name = '_'.join(cmd)
+    if region:
+        name += str(sorted(region.items()))
     return hashlib.sha1(name).hexdigest()
 
 
-def HashCmds(cmds):
-    """!Returns a hash from list of commands."""
+def HashCmds(cmds, region):
+    """!Returns a hash from list of commands and regions as dicts."""
     name = ';'.join([item for sublist in cmds for item in sublist])
+    if region:
+        name += str(sorted(region.items()))
     return hashlib.sha1(name).hexdigest()
 
 
-def GetFileFromCmd(dirname, cmd, extension='ppm'):
-    """!Returns file path created as a hash from command."""
-    return os.path.join(dirname, HashCmd(cmd) + '.' + extension)
+def GetFileFromCmd(dirname, cmd, region, extension='ppm'):
+    """!Returns file path created as a hash from command and region."""
+    return os.path.join(dirname, HashCmd(cmd, region) + '.' + extension)
 
 
-def GetFileFromCmds(dirname, cmds, extension='ppm'):
-    """!Returns file path created as a hash from list of commands."""
-    return os.path.join(dirname, HashCmds(cmds) + '.' + extension)
+def GetFileFromCmds(dirname, cmds, region, extension='ppm'):
+    """!Returns file path created as a hash from list of commands and regions."""
+    return os.path.join(dirname, HashCmds(cmds, region) + '.' + extension)
 
 
 def layerListToCmdsMatrix(layerList):
@@ -295,7 +299,7 @@
     return zip(*cmdsForComposition)
 
 
-def sampleCmdMatrixAndCreateNames(cmdMatrix, sampledSeries):
+def sampleCmdMatrixAndCreateNames(cmdMatrix, sampledSeries, regions):
     """!Applies information from temporal sampling
     to the command matrix."""
     namesList = []
@@ -306,7 +310,7 @@
             if lastName != name:
                 lastName = name
                 j += 1
-            namesList.append(HashCmds(cmdMatrix[j]))
+            namesList.append(HashCmds(cmdMatrix[j], regions[j]))
         else:
             namesList.append(None)
     assert(j == len(cmdMatrix) - 1)
@@ -321,3 +325,29 @@
         return cpu_count()
     except NotImplementedError:
         return 4
+
+
+def interpolate(start, end, count):
+    """!Interpolates values between start and end.
+
+    @param start start value (float)
+    @param end end value (float)
+    @param count total number of values including start and end
+
+    >>> interpolate(0, 10, 5)
+    [0, 2.5, 5.0, 7.5, 10]
+    >>> interpolate(10, 0, 5)
+    [10, 7.5, 5.0, 2.5, 0]
+    """
+    step = (end - start) / float(count - 1)
+    values = []
+    if start < end:
+        while start < end:
+            values.append(start)
+            start += step
+    else:
+        while end < start:
+            values.append(start)
+            start += step
+    values.append(end)
+    return values



More information about the grass-commit mailing list