Преглед изворни кода

wxGUI/animation: adding support for multiple base layers/series; pep8 compliance

git-svn-id: https://svn.osgeo.org/grass/grass/trunk@58343 15284696-431f-4ddb-bdfa-cd5b030d7da7
Anna Petrášová пре 11 година
родитељ
комит
69b6346be3

BIN
gui/icons/grass/mapset-add.png


+ 1 - 1
gui/wxpython/animation/__init__.py

@@ -9,4 +9,4 @@ all = [
     'toolbars',
     'toolbars',
     'utils',
     'utils',
     'frame',
     'frame',
-    ]
+]

+ 11 - 12
gui/wxpython/animation/anim.py

@@ -6,18 +6,19 @@
 Classes:
 Classes:
  - anim::Animation
  - anim::Animation
 
 
-(C) 2012 by the GRASS Development Team
+(C) 2013 by the GRASS Development Team
 
 
 This program is free software under the GNU General Public License
 This program is free software under the GNU General Public License
 (>=v2). Read the file COPYING that comes with GRASS for details.
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
 
-@author Anna Kratochvilova <kratochanna gmail.com>
+@author Anna Petrasova <kratochanna gmail.com>
 """
 """
 
 
 import wx
 import wx
 from utils import Orientation, ReplayMode
 from utils import Orientation, ReplayMode
 from core.utils import _
 from core.utils import _
 
 
+
 class Animation(wx.EvtHandler):
 class Animation(wx.EvtHandler):
     """!Animation class specifies which frame to show at which instance."""
     """!Animation class specifies which frame to show at which instance."""
     def __init__(self):
     def __init__(self):
@@ -28,7 +29,7 @@ class Animation(wx.EvtHandler):
         # states
         # states
         self.orientation = Orientation.FORWARD
         self.orientation = Orientation.FORWARD
         self.replayMode = ReplayMode.ONESHOT
         self.replayMode = ReplayMode.ONESHOT
-        
+
         self.callbackUpdateFrame = None
         self.callbackUpdateFrame = None
         self.callbackEndAnimation = None
         self.callbackEndAnimation = None
         self.callbackOrientationChanged = None
         self.callbackOrientationChanged = None
@@ -59,7 +60,7 @@ class Animation(wx.EvtHandler):
         """!Get frame count."""
         """!Get frame count."""
         return len(self.frames)
         return len(self.frames)
 
 
-    count = property(fget = GetCount)
+    count = property(fget=GetCount)
 
 
     def GetReplayMode(self):
     def GetReplayMode(self):
         """!Returns replay mode (loop)."""
         """!Returns replay mode (loop)."""
@@ -68,7 +69,7 @@ class Animation(wx.EvtHandler):
     def SetReplayMode(self, mode):
     def SetReplayMode(self, mode):
         self._replayMode = mode
         self._replayMode = mode
 
 
-    replayMode = property(fset = SetReplayMode, fget = GetReplayMode)
+    replayMode = property(fset=SetReplayMode, fget=GetReplayMode)
 
 
     def GetOrientation(self):
     def GetOrientation(self):
         return self._orientation
         return self._orientation
@@ -76,7 +77,7 @@ class Animation(wx.EvtHandler):
     def SetOrientation(self, mode):
     def SetOrientation(self, mode):
         self._orientation = mode
         self._orientation = mode
 
 
-    orientation = property(fset = SetOrientation, fget = GetOrientation)
+    orientation = property(fset=SetOrientation, fget=GetOrientation)
 
 
     def SetCallbackUpdateFrame(self, callback):
     def SetCallbackUpdateFrame(self, callback):
         """!Sets function to be called when updating frame."""
         """!Sets function to be called when updating frame."""
@@ -92,7 +93,7 @@ class Animation(wx.EvtHandler):
 
 
     def Start(self):
     def Start(self):
         if not self.IsActive():
         if not self.IsActive():
-            return 
+            return
 
 
     def Pause(self, paused):
     def Pause(self, paused):
         if not self.IsActive():
         if not self.IsActive():
@@ -116,14 +117,14 @@ class Animation(wx.EvtHandler):
                 self.currentIndex = 0
                 self.currentIndex = 0
             elif self.replayMode == ReplayMode.REVERSE:
             elif self.replayMode == ReplayMode.REVERSE:
                 self.orientation = Orientation.BACKWARD
                 self.orientation = Orientation.BACKWARD
-                self.currentIndex = self.count - 2 # -1
+                self.currentIndex = self.count - 2  # -1
                 self.callbackOrientationChanged(Orientation.BACKWARD)
                 self.callbackOrientationChanged(Orientation.BACKWARD)
         else:
         else:
             if self.replayMode == ReplayMode.REPEAT:
             if self.replayMode == ReplayMode.REPEAT:
                 self.currentIndex = self.count - 1
                 self.currentIndex = self.count - 1
             elif self.replayMode == ReplayMode.REVERSE:
             elif self.replayMode == ReplayMode.REVERSE:
                 self.orientation = Orientation.FORWARD
                 self.orientation = Orientation.FORWARD
-                self.currentIndex = 1 # 0
+                self.currentIndex = 1  # 0
                 self.callbackOrientationChanged(Orientation.FORWARD)
                 self.callbackOrientationChanged(Orientation.FORWARD)
 
 
     def Update(self):
     def Update(self):
@@ -140,7 +141,7 @@ class Animation(wx.EvtHandler):
             self.currentIndex -= 1
             self.currentIndex -= 1
             if self.currentIndex == -1:
             if self.currentIndex == -1:
                 self._arrivedToEnd()
                 self._arrivedToEnd()
-                
+
     def FrameChangedFromOutside(self, index):
     def FrameChangedFromOutside(self, index):
         """!Let the animation know that frame was changed from outside."""
         """!Let the animation know that frame was changed from outside."""
         if not self.IsActive():
         if not self.IsActive():
@@ -188,5 +189,3 @@ class Animation(wx.EvtHandler):
 #
 #
 #if __name__ == '__main__':
 #if __name__ == '__main__':
 #    test()
 #    test()
-
-

+ 99 - 145
gui/wxpython/animation/controller.py

@@ -6,12 +6,12 @@
 Classes:
 Classes:
  - controller::AnimationController
  - controller::AnimationController
 
 
-(C) 2012 by the GRASS Development Team
+(C) 2013 by the GRASS Development Team
 
 
 This program is free software under the GNU General Public License
 This program is free software under the GNU General Public License
 (>=v2). Read the file COPYING that comes with GRASS for details.
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
 
-@author Anna Kratochvilova <kratochanna gmail.com>
+@author Anna Petrasova <kratochanna gmail.com>
 """
 """
 import os
 import os
 import wx
 import wx
@@ -20,12 +20,15 @@ from core.gcmd import GException, GError, GMessage
 from core.utils import _
 from core.utils import _
 from grass.imaging import writeAvi, writeGif, writeIms, writeSwf
 from grass.imaging import writeAvi, writeGif, writeIms, writeSwf
 
 
-from temporal_manager import TemporalManager
-from dialogs import InputDialog, EditDialog, AnimationData, ExportDialog
-from utils import TemporalMode, Orientation, RenderText, WxImageToPil
+from animation.temporal_manager import TemporalManager
+from animation.dialogs import InputDialog, EditDialog, ExportDialog
+from animation.utils import TemporalMode, Orientation, RenderText, WxImageToPil, \
+    sampleCmdMatrixAndCreateNames, layerListToCmdsMatrix, HashCmds
+from animation.data import AnimationData
+
 
 
 class AnimationController(wx.EvtHandler):
 class AnimationController(wx.EvtHandler):
-    def __init__(self, frame, sliders, animations, mapwindows, providers, bitmapPool):
+    def __init__(self, frame, sliders, animations, mapwindows, provider, bitmapPool, mapFilesPool):
         wx.EvtHandler.__init__(self)
         wx.EvtHandler.__init__(self)
 
 
         self.mapwindows = mapwindows
         self.mapwindows = mapwindows
@@ -38,14 +41,17 @@ class AnimationController(wx.EvtHandler):
         self.temporalMode = None
         self.temporalMode = None
         self.animationData = []
         self.animationData = []
 
 
-        self.timer = wx.Timer(self, id = wx.NewId())
+        self.timer = wx.Timer(self, id=wx.NewId())
 
 
         self.animations = animations
         self.animations = animations
         self.bitmapPool = bitmapPool
         self.bitmapPool = bitmapPool
-        self.bitmapProviders = providers
-        for anim, win, provider in zip(self.animations, self.mapwindows, self.bitmapProviders):
-            anim.SetCallbackUpdateFrame(lambda index, dataId, win = win, provider = provider : self.UpdateFrame(index, win, provider, dataId))
-            anim.SetCallbackEndAnimation(lambda index, dataId, win = win, provider = provider: self.UpdateFrameEnd(index, win, provider, dataId))
+        self.mapFilesPool = mapFilesPool
+        self.bitmapProvider = provider
+        for anim, win in zip(self.animations, self.mapwindows):
+            anim.SetCallbackUpdateFrame(
+                lambda index, dataId, win=win: self.UpdateFrame(index, win, dataId))
+            anim.SetCallbackEndAnimation(
+                lambda index, dataId, win=win: self.UpdateFrameEnd(index, win, dataId))
             anim.SetCallbackOrientationChanged(self.OrientationChangedInReverseMode)
             anim.SetCallbackOrientationChanged(self.OrientationChangedInReverseMode)
 
 
         for slider in self.sliders.values():
         for slider in self.sliders.values():
@@ -75,8 +81,8 @@ class AnimationController(wx.EvtHandler):
             self.timer.Start(self._timeTick)
             self.timer.Start(self._timeTick)
         self.DisableSliderIfNeeded()
         self.DisableSliderIfNeeded()
 
 
-    timeTick = property(fget = GetTimeTick, fset = SetTimeTick)
-        
+    timeTick = property(fget=GetTimeTick, fset=SetTimeTick)
+
     def OnTimerTick(self, event):
     def OnTimerTick(self, event):
         for anim in self.animations:
         for anim in self.animations:
             anim.Update()
             anim.Update()
@@ -102,7 +108,7 @@ class AnimationController(wx.EvtHandler):
             if not self.timer.IsRunning():
             if not self.timer.IsRunning():
                 self.timer.Start(self.timeTick)
                 self.timer.Start(self.timeTick)
                 self.DisableSliderIfNeeded()
                 self.DisableSliderIfNeeded()
-        
+
         for anim in self.animations:
         for anim in self.animations:
             anim.Pause(paused)
             anim.Pause(paused)
 
 
@@ -114,20 +120,20 @@ class AnimationController(wx.EvtHandler):
         for anim in self.animations:
         for anim in self.animations:
             anim.Stop()
             anim.Stop()
 
 
-    def UpdateFrameEnd(self, index, win, provider, dataId):
+    def UpdateFrameEnd(self, index, win, dataId):
         if self.timer.IsRunning():
         if self.timer.IsRunning():
             self.timer.Stop()
             self.timer.Stop()
             self.DisableSliderIfNeeded()
             self.DisableSliderIfNeeded()
 
 
         self.animationToolbar.Stop()
         self.animationToolbar.Stop()
-        
-        self.UpdateFrame(index, win, provider, dataId)
 
 
-    def UpdateFrame(self, index, win, provider, dataId):
-        bitmap = provider.GetBitmap(dataId)
+        self.UpdateFrame(index, win, dataId)
+
+    def UpdateFrame(self, index, win, dataId):
+        bitmap = self.bitmapProvider.GetBitmap(dataId)
         if dataId is None:
         if dataId is None:
             dataId = ''
             dataId = ''
-        win.DrawBitmap(bitmap, dataId)
+        win.DrawBitmap(bitmap)
         # self.frame.SetStatusText(dataId)
         # self.frame.SetStatusText(dataId)
         self.slider.UpdateFrame(index)
         self.slider.UpdateFrame(index)
 
 
@@ -151,7 +157,6 @@ class AnimationController(wx.EvtHandler):
             self.slider.EnableSlider(False)
             self.slider.EnableSlider(False)
         else:
         else:
             self.slider.EnableSlider(True)
             self.slider.EnableSlider(True)
-            
 
 
     def OrientationChangedInReverseMode(self, mode):
     def OrientationChangedInReverseMode(self, mode):
         if mode == Orientation.FORWARD:
         if mode == Orientation.FORWARD:
@@ -167,14 +172,13 @@ class AnimationController(wx.EvtHandler):
         for anim in self.animations:
         for anim in self.animations:
             anim.orientation = mode
             anim.orientation = mode
 
 
-
     def SetTemporalMode(self, mode):
     def SetTemporalMode(self, mode):
         self._temporalMode = mode
         self._temporalMode = mode
 
 
     def GetTemporalMode(self):
     def GetTemporalMode(self):
         return self._temporalMode
         return self._temporalMode
 
 
-    temporalMode = property(fget = GetTemporalMode, fset = SetTemporalMode)
+    temporalMode = property(fget=GetTemporalMode, fset=SetTemporalMode)
 
 
     def GetTimeGranularity(self):
     def GetTimeGranularity(self):
         if self.temporalMode == TemporalMode.TEMPORAL:
         if self.temporalMode == TemporalMode.TEMPORAL:
@@ -187,9 +191,8 @@ class AnimationController(wx.EvtHandler):
         # if self.timer.IsRunning():
         # if self.timer.IsRunning():
         #     running = True
         #     running = True
         self.EndAnimation()
         self.EndAnimation()
-
-        dlg = EditDialog(parent = self.frame, evalFunction = self.EvaluateInput,
-                          animationData = self.animationData, maxAnimations = len(self.animations))
+        dlg = EditDialog(parent=self.frame, evalFunction=self.EvaluateInput,
+                         animationData=self.animationData, maxAnimations=len(self.animations))
         if dlg.ShowModal() == wx.ID_CANCEL:
         if dlg.ShowModal() == wx.ID_CANCEL:
             dlg.Destroy()
             dlg.Destroy()
             return
             return
@@ -206,9 +209,10 @@ class AnimationController(wx.EvtHandler):
             if windowIndex not in indices:
             if windowIndex not in indices:
                 found = True
                 found = True
                 break
                 break
-                
+
         if not found:
         if not found:
-            GMessage(parent = self.frame, message = _("Maximum number of animations is %s.") % len(self.animations))
+            GMessage(parent=self.frame,
+                     message=_("Maximum number of animations is %s.") % len(self.animations))
             return
             return
 
 
         # running = False
         # running = False
@@ -221,20 +225,20 @@ class AnimationController(wx.EvtHandler):
         # number of active animations
         # number of active animations
         animationIndex = len([anim for anim in self.animations if anim.IsActive()])
         animationIndex = len([anim for anim in self.animations if anim.IsActive()])
         animData.SetDefaultValues(windowIndex, animationIndex)
         animData.SetDefaultValues(windowIndex, animationIndex)
-        dlg = InputDialog(parent = self.frame, mode = 'add', animationData = animData)
+        dlg = InputDialog(parent=self.frame, mode='add', animationData=animData)
         if dlg.ShowModal() == wx.ID_CANCEL:
         if dlg.ShowModal() == wx.ID_CANCEL:
             dlg.Destroy()
             dlg.Destroy()
             return
             return
         dlg.Destroy()
         dlg.Destroy()
         # check compatibility
         # check compatibility
         if animData.windowIndex in indices:
         if animData.windowIndex in indices:
-            GMessage(parent = self.frame, message = _("More animations are using one window."
-                                                      " Please select different window for each animation."))
+            GMessage(parent=self.frame, message=_("More animations are using one window."
+                                                  " Please select different window for each animation."))
             return
             return
         try:
         try:
             temporalMode, tempManager = self.EvaluateInput(self.animationData + [animData])
             temporalMode, tempManager = self.EvaluateInput(self.animationData + [animData])
         except GException, e:
         except GException, e:
-            GError(parent = self.frame, message = e.value, showTraceback = False)
+            GError(parent=self.frame, message=e.value, showTraceback=False)
             return
             return
         # if ok, set temporal mode
         # if ok, set temporal mode
         self.temporalMode = temporalMode
         self.temporalMode = temporalMode
@@ -244,41 +248,28 @@ class AnimationController(wx.EvtHandler):
         self.animationData.append(animData)
         self.animationData.append(animData)
         self._setAnimations()
         self._setAnimations()
 
 
-    def SetAnimations(self, inputs=None, dataType=None):
+    def SetAnimations(self, layerLists):
         """!Set animation data directly.
         """!Set animation data directly.
 
 
-        @param raster list of lists of raster maps or None
-        @param strds list of strds or None
-        @param inputs list of lists of raster maps or vector maps, 
-               or a space time raster or vector dataset
-        @param dataType The type of the input data must be one of 'rast', 'vect', 'strds' or 'strds'
+        @param layerLists list of layerLists
         """
         """
         try:
         try:
             animationData = []
             animationData = []
             for i in range(len(self.animations)):
             for i in range(len(self.animations)):
-                if inputs is not None and inputs[i]:
-                    if dataType == 'rast' or dataType == 'vect':
-                        if type(inputs[i]) == list:
-                            anim = AnimationData()
-                            anim.SetDefaultValues(i, i)
-                            anim.inputMapType = dataType
-                            anim.inputData = ','.join(inputs[i])
-                            animationData.append(anim)
-                    elif dataType == 'strds' or dataType == 'stvds':
-                        anim = AnimationData()
-                        anim.SetDefaultValues(i, i)
-                        anim.inputMapType = dataType
-                        anim.inputData = inputs[i]
-                        animationData.append(anim)
+                if layerLists[i]:
+                    anim = AnimationData()
+                    anim.SetDefaultValues(i, i)
+                    anim.SetLayerList(layerLists[i])
+                    animationData.append(anim)
 
 
         except (GException, ValueError, IOError) as e:
         except (GException, ValueError, IOError) as e:
-            GError(parent = self.frame, message = str(e),
-                   showTraceback = False, caption = _("Invalid input"))
+            GError(parent=self.frame, message=str(e),
+                   showTraceback=False, caption=_("Invalid input"))
             return
             return
         try:
         try:
             temporalMode, tempManager = self.EvaluateInput(animationData)
             temporalMode, tempManager = self.EvaluateInput(animationData)
         except GException, e:
         except GException, e:
-            GError(parent = self.frame, message = e.value, showTraceback = False)
+            GError(parent=self.frame, message=e.value, showTraceback=False)
             return
             return
         self.animationData = animationData
         self.animationData = animationData
         self.temporalManager = tempManager
         self.temporalManager = tempManager
@@ -288,15 +279,19 @@ class AnimationController(wx.EvtHandler):
     def _setAnimations(self):
     def _setAnimations(self):
         indices = [anim.windowIndex for anim in self.animationData]
         indices = [anim.windowIndex for anim in self.animationData]
 
 
-        self._updateWindows(activeIndices = indices)
+        self._updateWindows(activeIndices=indices)
 
 
         if self.temporalMode == TemporalMode.TEMPORAL:
         if self.temporalMode == TemporalMode.TEMPORAL:
             timeLabels, mapNamesDict = self.temporalManager.GetLabelsAndMaps()
             timeLabels, mapNamesDict = self.temporalManager.GetLabelsAndMaps()
         else:
         else:
             timeLabels, mapNamesDict = None, None
             timeLabels, mapNamesDict = None, None
-
-        self._updateSlider(timeLabels = timeLabels)
-        self._updateAnimations(activeIndices = indices, mapNamesDict = mapNamesDict)
+        for anim in self.animationData:
+            if anim.viewMode == '2d':
+                anim.cmdMatrix = layerListToCmdsMatrix(anim.layerList)
+            else:
+                anim.cmdMatrix = [(cmd,) for cmd in anim.GetNvizCommands()['commands']]
+        self._updateSlider(timeLabels=timeLabels)
+        self._updateAnimations(activeIndices=indices, mapNamesDict=mapNamesDict)
         wx.Yield()
         wx.Yield()
         self._updateBitmapData()
         self._updateBitmapData()
         # if running:
         # if running:
@@ -305,11 +300,11 @@ class AnimationController(wx.EvtHandler):
         # else:
         # else:
         self.EndAnimation()
         self.EndAnimation()
 
 
-    def _updateSlider(self, timeLabels = None):
+    def _updateSlider(self, timeLabels=None):
         if self.temporalMode == TemporalMode.NONTEMPORAL:
         if self.temporalMode == TemporalMode.NONTEMPORAL:
             self.frame.SetSlider('nontemporal')
             self.frame.SetSlider('nontemporal')
             self.slider = self.sliders['nontemporal']
             self.slider = self.sliders['nontemporal']
-            frameCount = len(self.animationData[0].mapData) # should be the same for all
+            frameCount = self.animationData[0].mapCount
             self.slider.SetFrames(frameCount)
             self.slider.SetFrames(frameCount)
         elif self.temporalMode == TemporalMode.TEMPORAL:
         elif self.temporalMode == TemporalMode.TEMPORAL:
             self.frame.SetSlider('temporal')
             self.frame.SetSlider('temporal')
@@ -320,14 +315,14 @@ class AnimationController(wx.EvtHandler):
             self.frame.SetSlider(None)
             self.frame.SetSlider(None)
             self.slider = None
             self.slider = None
 
 
-    def _updateAnimations(self, activeIndices, mapNamesDict = None):
+    def _updateAnimations(self, activeIndices, mapNamesDict=None):
         if self.temporalMode == TemporalMode.NONTEMPORAL:
         if self.temporalMode == TemporalMode.NONTEMPORAL:
             for i in range(len(self.animations)):
             for i in range(len(self.animations)):
                 if i not in activeIndices:
                 if i not in activeIndices:
                     self.animations[i].SetActive(False)
                     self.animations[i].SetActive(False)
                     continue
                     continue
                 anim = [anim for anim in self.animationData if anim.windowIndex == i][0]
                 anim = [anim for anim in self.animationData if anim.windowIndex == i][0]
-                self.animations[i].SetFrames(anim.mapData)
+                self.animations[i].SetFrames([HashCmds(cmdList) for cmdList in anim.cmdMatrix])
                 self.animations[i].SetActive(True)
                 self.animations[i].SetActive(True)
         else:
         else:
             for i in range(len(self.animations)):
             for i in range(len(self.animations)):
@@ -335,7 +330,9 @@ class AnimationController(wx.EvtHandler):
                     self.animations[i].SetActive(False)
                     self.animations[i].SetActive(False)
                     continue
                     continue
                 anim = [anim for anim in self.animationData if anim.windowIndex == i][0]
                 anim = [anim for anim in self.animationData if anim.windowIndex == i][0]
-                self.animations[i].SetFrames(mapNamesDict[anim.inputData])
+                identifiers = sampleCmdMatrixAndCreateNames(anim.cmdMatrix,
+                                                            mapNamesDict[anim.firstStdsNameType[0]])
+                self.animations[i].SetFrames(identifiers)
                 self.animations[i].SetActive(True)
                 self.animations[i].SetActive(True)
 
 
     def _updateWindows(self, activeIndices):
     def _updateWindows(self, activeIndices):
@@ -347,48 +344,33 @@ class AnimationController(wx.EvtHandler):
                 self.frame.RemoveWindow(windowIndex)
                 self.frame.RemoveWindow(windowIndex)
 
 
     def _updateBitmapData(self):
     def _updateBitmapData(self):
-        # unload data:
-        for prov in self.bitmapProviders:
-            prov.Unload()
+        # unload previous data
+        self.bitmapProvider.Unload()
 
 
-        # load data
+        # load new data
         for animData in self.animationData:
         for animData in self.animationData:
             if animData.viewMode == '2d':
             if animData.viewMode == '2d':
-                self._load2DData(animData)
+                self._set2DData(animData)
             else:
             else:
                 self._load3DData(animData)
                 self._load3DData(animData)
             self._loadLegend(animData)
             self._loadLegend(animData)
+        self.bitmapProvider.Load(nprocs=4)
+        # clear pools
+        self.bitmapPool.Clear()
+        self.mapFilesPool.Clear()
 
 
-        # clear bitmapPool
-        usedNames = []
-        for prov in self.bitmapProviders:
-            names = prov.GetDataNames()
-            if names:
-                usedNames.extend(names)
-        self.bitmapPool.Clear(usedNames)
-
-    def _load2DData(self, animationData):
-        prov = self.bitmapProviders[animationData.windowIndex]
-        prov.SetData(datasource = animationData.mapData, dataType=animationData.inputMapType)
-
-        prov.Load()
+    def _set2DData(self, animationData):
+        opacities = [layer.opacity for layer in animationData.layerList if layer.active]
+        self.bitmapProvider.SetCmds(animationData.cmdMatrix, opacities)
 
 
     def _load3DData(self, animationData):
     def _load3DData(self, animationData):
-        prov = self.bitmapProviders[animationData.windowIndex]
         nviz = animationData.GetNvizCommands()
         nviz = animationData.GetNvizCommands()
-        prov.SetData(datasource = nviz['commands'], 
-                     dataNames = animationData.mapData, dataType = 'nviz',
-                     suffix = animationData.nvizParameter,
-                     nvizRegion = nviz['region'])
-
-        self.bitmapProviders[animationData.windowIndex].Load()
+        self.bitmapProvider.SetCmds3D(nviz['commands'], nviz['region'])
 
 
     def _loadLegend(self, animationData):
     def _loadLegend(self, animationData):
         if animationData.legendCmd:
         if animationData.legendCmd:
-            prov = self.bitmapProviders[animationData.windowIndex]
             try:
             try:
-                bitmap = prov.LoadOverlay(animationData.legendCmd)
-                # place legend
+                bitmap = self.bitmapProvider.LoadOverlay(animationData.legendCmd)
                 try:
                 try:
                     from PIL import Image
                     from PIL import Image
                     for param in animationData.legendCmd:
                     for param in animationData.legendCmd:
@@ -411,15 +393,15 @@ class AnimationController(wx.EvtHandler):
         tempManager = None
         tempManager = None
         windowIndex = []
         windowIndex = []
         for anim in animationData:
         for anim in animationData:
-
-            mapCount.add(len(anim.mapData))
+            for layer in anim.layerList:
+                if layer.active and hasattr(layer, 'maps'):
+                    if layer.mapType in ('strds', 'stvds'):
+                        stds += 1
+                    else:
+                        maps += 1
+                    mapCount.add(len(layer.maps))
             windowIndex.append(anim.windowIndex)
             windowIndex.append(anim.windowIndex)
 
 
-            if anim.inputMapType in ('rast', 'vect'):
-                maps += 1
-            elif anim.inputMapType in ('strds', 'stvds'):
-                stds += 1
-
         if maps and stds:
         if maps and stds:
             temporalMode = TemporalMode.NONTEMPORAL
             temporalMode = TemporalMode.NONTEMPORAL
         elif maps:
         elif maps:
@@ -436,27 +418,24 @@ class AnimationController(wx.EvtHandler):
             tempManager = TemporalManager()
             tempManager = TemporalManager()
             # these raise GException:
             # these raise GException:
             for anim in animationData:
             for anim in animationData:
-                if anim.inputMapType not in ('strds', 'stvds'):
-                    continue
-                tempManager.AddTimeSeries(anim.inputData, anim.inputMapType)
+                tempManager.AddTimeSeries(*anim.firstStdsNameType)
+
             message = tempManager.EvaluateInputData()
             message = tempManager.EvaluateInputData()
             if message:
             if message:
-                GMessage(parent = self.frame, message = message)
+                GMessage(parent=self.frame, message=message)
 
 
         return temporalMode, tempManager
         return temporalMode, tempManager
 
 
     def Reload(self):
     def Reload(self):
         self.EndAnimation()
         self.EndAnimation()
 
 
-        activeIndices = [anim.windowIndex for anim in self.animationData]
-        for index in activeIndices:
-            self.bitmapProviders[index].Load(force = True)
+        self.bitmapProvider.Load(force=True)
 
 
         self.EndAnimation()
         self.EndAnimation()
 
 
     def Export(self):
     def Export(self):
         if not self.animationData:
         if not self.animationData:
-            GMessage(parent = self.frame, message = _("No animation to export."))
+            GMessage(parent=self.frame, message=_("No animation to export."))
             return
             return
 
 
         if 'export' in self._dialogs:
         if 'export' in self._dialogs:
@@ -468,14 +447,14 @@ class AnimationController(wx.EvtHandler):
             dlg.doExport.connect(self._export)
             dlg.doExport.connect(self._export)
             self._dialogs['export'] = dlg
             self._dialogs['export'] = dlg
             dlg.Show()
             dlg.Show()
-        
+
     def _export(self, exportInfo, decorations):
     def _export(self, exportInfo, decorations):
         size = self.frame.animationPanel.GetSize()
         size = self.frame.animationPanel.GetSize()
         if self.temporalMode == TemporalMode.TEMPORAL:
         if self.temporalMode == TemporalMode.TEMPORAL:
             timeLabels, mapNamesDict = self.temporalManager.GetLabelsAndMaps()
             timeLabels, mapNamesDict = self.temporalManager.GetLabelsAndMaps()
             frameCount = len(timeLabels)
             frameCount = len(timeLabels)
         else:
         else:
-            frameCount = len(self.animationData[0].mapData) # should be the same for all
+            frameCount = self.animationData[0].mapCount  # should be the same for all
 
 
         animWinSize = []
         animWinSize = []
         animWinPos = []
         animWinPos = []
@@ -488,7 +467,7 @@ class AnimationController(wx.EvtHandler):
                 animWinPos.append(pos)
                 animWinPos.append(pos)
                 animWinSize.append(win.GetSize())
                 animWinSize.append(win.GetSize())
                 animWinIndex.append(i)
                 animWinIndex.append(i)
-        
+
         images = []
         images = []
         busy = wx.BusyInfo(message=_("Preparing export, please wait..."), parent=self.frame)
         busy = wx.BusyInfo(message=_("Preparing export, please wait..."), parent=self.frame)
         wx.Yield()
         wx.Yield()
@@ -498,13 +477,13 @@ class AnimationController(wx.EvtHandler):
             # collect bitmaps of all windows and paste them into the one
             # collect bitmaps of all windows and paste them into the one
             for i in animWinIndex:
             for i in animWinIndex:
                 frameId = self.animations[i].GetFrame(frameIndex)
                 frameId = self.animations[i].GetFrame(frameIndex)
-                bitmap = self.bitmapProviders[i].GetBitmap(frameId)
+                bitmap = self.bitmapProvider.GetBitmap(frameId)
                 im = wx.ImageFromBitmap(bitmap)
                 im = wx.ImageFromBitmap(bitmap)
 
 
                 # add legend if used
                 # add legend if used
                 legend = legends[i]
                 legend = legends[i]
                 if legend:
                 if legend:
-                    legendBitmap = self.bitmapProviders[i].LoadOverlay(legend)
+                    legendBitmap = self.bitmapProvider.LoadOverlay(legend)
                     x, y = self.mapwindows[i].GetOverlayPos()
                     x, y = self.mapwindows[i].GetOverlayPos()
                     legImage = wx.ImageFromBitmap(legendBitmap)
                     legImage = wx.ImageFromBitmap(legendBitmap)
                     # not so nice result, can we handle the transparency otherwise?
                     # not so nice result, can we handle the transparency otherwise?
@@ -525,11 +504,11 @@ class AnimationController(wx.EvtHandler):
                     timeLabel = timeLabels[frameIndex]
                     timeLabel = timeLabels[frameIndex]
                     if timeLabel[1]:
                     if timeLabel[1]:
                         text = _("%(from)s %(dash)s %(to)s") % \
                         text = _("%(from)s %(dash)s %(to)s") % \
-                                {'from': timeLabel[0], 'dash': u"\u2013", 'to': timeLabel[1]}
+                            {'from': timeLabel[0], 'dash': u"\u2013", 'to': timeLabel[1]}
                     else:
                     else:
                         text = _("%(start)s %(unit)s") % \
                         text = _("%(start)s %(unit)s") % \
-                                {'start': timeLabel[0], 'unit': timeLabel[2]}
-                    
+                            {'start': timeLabel[0], 'unit': timeLabel[2]}
+
                     decImage = RenderText(text, decoration['font']).ConvertToImage()
                     decImage = RenderText(text, decoration['font']).ConvertToImage()
                 elif decoration['name'] == 'text':
                 elif decoration['name'] == 'text':
                     text = decoration['text']
                     text = decoration['text']
@@ -552,42 +531,17 @@ class AnimationController(wx.EvtHandler):
                 writeIms(filename=filename, images=pilImages)
                 writeIms(filename=filename, images=pilImages)
             elif exportInfo['method'] == 'gif':
             elif exportInfo['method'] == 'gif':
                 writeGif(filename=exportInfo['file'], images=pilImages,
                 writeGif(filename=exportInfo['file'], images=pilImages,
-                            duration=self.timeTick / float(1000), repeat=True)
+                         duration=self.timeTick / float(1000), repeat=True)
             elif exportInfo['method'] == 'swf':
             elif exportInfo['method'] == 'swf':
                 writeSwf(filename=exportInfo['file'], images=pilImages,
                 writeSwf(filename=exportInfo['file'], images=pilImages,
-                            duration=self.timeTick / float(1000), repeat=True)
+                         duration=self.timeTick / float(1000), repeat=True)
             elif exportInfo['method'] == 'avi':
             elif exportInfo['method'] == 'avi':
                 writeAvi(filename=exportInfo['file'], images=pilImages,
                 writeAvi(filename=exportInfo['file'], images=pilImages,
-                            duration=self.timeTick / float(1000),
-                            encoding=exportInfo['encoding'],
-                            inputOptions=exportInfo['options'])
+                         duration=self.timeTick / float(1000),
+                         encoding=exportInfo['encoding'],
+                         inputOptions=exportInfo['options'])
         except Exception, e:
         except Exception, e:
             del busy
             del busy
             GError(parent=self.frame, message=str(e))
             GError(parent=self.frame, message=str(e))
             return
             return
         del busy
         del busy
-
-#def test():
-#    import grass.script as grass
-#    import wx
-#    app = wx.PySimpleApp()
-#    wx.InitAllImageHandlers()
-#    # app.MainLoop()
-#
-#    bitmaps = {}
-#    rasters = ['elevation.dem']
-#    # rasters = ['streams']
-#    # rasters = grass.read_command("g.mlist", type = 'rast', fs = ',', quiet = True).strip().split(',')
-#
-#    # print nrows, ncols
-#    size = (300,100)
-#    newSize, scale = ComputeScale(size)
-#    # print scale
-#    LoadRasters(rasters = rasters, bitmaps = bitmaps, scale = scale, size = newSize)
-#
-#    for b in bitmaps.keys():
-#        bitmaps[b].SaveFile('/home/anna/testy/ctypes/' + b + '.png', wx.BITMAP_TYPE_PNG)
-#
-#if __name__ == '__main__':
-#
-#    test()

+ 219 - 0
gui/wxpython/animation/data.py

@@ -0,0 +1,219 @@
+# -*- coding: utf-8 -*-
+"""!
+@package animation.data
+
+@brief animation data structures
+
+Classes:
+ - data::AnimationData
+ - data::AnimationLayer
+
+
+(C) 2013 by the GRASS Development Team
+
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+@author Anna Petrasova <kratochanna gmail.com>
+"""
+import os
+
+from grass.script import core as gcore
+
+from core.utils import _
+from core.gcmd import GException
+from animation.nviztask import NvizTask
+from animation.utils import validateMapNames, getRegisteredMaps, \
+    checkSeriesCompatibility, validateTimeseriesName
+from core.layerlist import LayerList, Layer
+import grass.temporal as tgis
+
+
+class AnimationData(object):
+    def __init__(self):
+        self._name = None
+        self._windowIndex = 0
+        self._layerList = None
+        # only this stds is taken into account for time computations
+        # if there are any stds at all
+        self._firstStdsNameType = None
+        self._mapCount = None
+        self._cmdMatrix = None
+        self._viewModes = [('2d', _("2D view")),
+                           ('3d', _("3D view"))]
+        self.viewMode = '2d'
+
+        self.nvizTask = NvizTask()
+        self._nvizParameters = self.nvizTask.ListMapParameters()
+        self.nvizParameter = self._nvizParameters[0]
+
+        self.workspaceFile = None
+        self.legendCmd = None
+
+    def GetName(self):
+        return self._name
+
+    def SetName(self, name):
+        if name == '':
+            raise ValueError(_("No animation name selected."))
+        self._name = name
+
+    name = property(fget=GetName, fset=SetName)
+
+    def GetWindowIndex(self):
+        return self._windowIndex
+
+    def SetWindowIndex(self, windowIndex):
+        self._windowIndex = windowIndex
+
+    windowIndex = property(fget=GetWindowIndex, fset=SetWindowIndex)
+
+    def SetLayerList(self, layerList):
+        """!
+        Throws GException if layer list's combination of stds is not valid.
+        """
+        mapSeriesList = []
+        timeseriesList = []
+        for layer in layerList:
+            if layer.active and hasattr(layer, 'maps'):
+                if layer.mapType in ('strds', 'stvds'):
+                    timeseriesList.append((layer.name, layer.mapType))
+                    self._firstStdsNameType = layer.name, layer.mapType
+                else:
+                    mapSeriesList.append((layer.maps))
+        if not timeseriesList:
+            self._firstStdsNameType = None, None
+        # this throws GException
+        count = checkSeriesCompatibility(mapSeriesList=mapSeriesList,
+                                         timeseriesList=timeseriesList)
+        self._mapCount = count
+        self._layerList = layerList
+
+    def GetLayerList(self):
+        return self._layerList
+
+    layerList = property(fget=GetLayerList, fset=SetLayerList)
+
+    def GetFirstStdsNameType(self):
+        return self._firstStdsNameType
+
+    firstStdsNameType = property(fget=GetFirstStdsNameType)
+
+    def GetMapCount(self):
+        return self._mapCount
+
+    mapCount = property(fget=GetMapCount)
+
+    def GetCmdMatrix(self):
+        return self._cmdMatrix
+
+    def SetCmdMatrix(self, cmdMatrix):
+        self._cmdMatrix = cmdMatrix
+
+    cmdMatrix = property(fget=GetCmdMatrix, fset=SetCmdMatrix)
+
+    def GetWorkspaceFile(self):
+        return self._workspaceFile
+
+    def SetWorkspaceFile(self, fileName):
+        if fileName is None:
+            self._workspaceFile = None
+            return
+
+        if fileName == '':
+            raise ValueError(_("No workspace file selected."))
+
+        if not os.path.exists(fileName):
+            raise IOError(_("File %s not found") % fileName)
+        self._workspaceFile = fileName
+
+        self.nvizTask.Load(self.workspaceFile)
+
+    workspaceFile = property(fget=GetWorkspaceFile, fset=SetWorkspaceFile)
+
+    def SetDefaultValues(self, windowIndex, animationIndex):
+        self.windowIndex = windowIndex
+        self.name = _("Animation %d") % (animationIndex + 1)
+        self.layerList = LayerList()
+
+    def GetNvizParameters(self):
+        return self._nvizParameters
+
+    nvizParameters = property(fget=GetNvizParameters)
+
+    def GetNvizParameter(self):
+        return self._nvizParameter
+
+    def SetNvizParameter(self, param):
+        self._nvizParameter = param
+
+    nvizParameter = property(fget=GetNvizParameter, fset=SetNvizParameter)
+
+    def GetViewMode(self):
+        return self._viewMode
+
+    def SetViewMode(self, mode):
+        self._viewMode = mode
+
+    viewMode = property(fget=GetViewMode, fset=SetViewMode)
+
+    def GetViewModes(self):
+        return self._viewModes
+
+    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):
+        if not self.workspaceFile or not self._layerList:
+            return []
+
+        cmds = self.nvizTask.GetCommandSeries(layerList=self._layerList,
+                                              paramName=self.nvizParameter)
+        region = self.nvizTask.GetRegion()
+
+        return {'commands': cmds, 'region': region}
+
+    def __repr__(self):
+        return "%s(%r)" % (self.__class__, self.__dict__)
+
+
+class AnimLayer(Layer):
+    """!Animation layer allows to add either space-time dataset
+    or series of maps."""
+    def __init__(self):
+        Layer.__init__(self)
+        self._mapTypes.extend(['strds', 'stvds'])
+        self._maps = []
+        tgis.init()
+
+    def SetName(self, name):
+        if not self.hidden:
+            if self._mapType is None:
+                raise ValueError("To set layer name, the type of layer must be specified.")
+            if self._mapType in ('strds', 'stvds'):
+                try:
+                    name = validateTimeseriesName(name, self._mapType)
+                    self._maps = getRegisteredMaps(name, self._mapType)
+                except (GException, gcore.ScriptError), e:
+                    raise ValueError(str(e))
+            else:
+                self._maps = validateMapNames(name.split(','), self._internalTypes[self._mapType])
+        self._name = name
+        self.label = name
+
+    def GetName(self):
+        return self._name
+
+    name = property(fget=GetName, fset=SetName)
+
+    def GetMaps(self):
+        return self._maps
+
+    maps = property(fget=GetMaps)

Разлика између датотеке није приказан због своје велике величине
+ 608 - 570
gui/wxpython/animation/dialogs.py


+ 130 - 101
gui/wxpython/animation/frame.py

@@ -4,46 +4,54 @@
 @brief Animation frame and different types of sliders
 @brief Animation frame and different types of sliders
 
 
 Classes:
 Classes:
- - frame::SwipeMapDialog
  - frame::AnimationFrame
  - frame::AnimationFrame
  - frame::AnimationsPanel
  - frame::AnimationsPanel
  - frame::AnimationSliderBase
  - frame::AnimationSliderBase
  - frame::SimpleAnimationSlider
  - frame::SimpleAnimationSlider
  - frame::TimeAnimationSlider
  - frame::TimeAnimationSlider
 
 
-
-(C) 2012 by the GRASS Development Team
+(C) 2013 by the GRASS Development Team
 
 
 This program is free software under the GNU General Public License
 This program is free software under the GNU General Public License
 (>=v2). Read the file COPYING that comes with GRASS for details.
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
 
-@author Anna Kratochvilova <kratochanna gmail.com>
+@author Anna Petrasova <kratochanna gmail.com>
 """
 """
 import os
 import os
 import sys
 import sys
 import wx
 import wx
 import wx.aui
 import wx.aui
+import tempfile
 
 
 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.script as gcore
 from core import globalvar
 from core import globalvar
 from gui_core.widgets import IntegerValidator
 from gui_core.widgets import IntegerValidator
 from core.gcmd import RunCommand
 from core.gcmd import RunCommand
 from core.utils import _
 from core.utils import _
 
 
-from mapwindow import AnimationWindow, BitmapProvider, BitmapPool
-from controller import AnimationController
-from anim import Animation
-from toolbars import MainToolbar, AnimationToolbar, MiscToolbar
-from dialogs import SpeedDialog
-from utils import Orientation, ReplayMode, TemporalType
+from animation.mapwindow import AnimationWindow
+from animation.provider import BitmapProvider, BitmapPool, \
+    MapFilesPool, CleanUp
+from animation.controller import AnimationController
+from animation.anim import Animation
+from animation.toolbars import MainToolbar, AnimationToolbar, MiscToolbar
+from animation.dialogs import SpeedDialog
+from animation.utils import Orientation, ReplayMode, TemporalType
 
 
 
 
 MAX_COUNT = 4
 MAX_COUNT = 4
+TMP_DIR = tempfile.mkdtemp()
+
+gcore.set_raise_on_error(True)
+
 
 
 class AnimationFrame(wx.Frame):
 class AnimationFrame(wx.Frame):
-    def __init__(self, parent = None, id = wx.ID_ANY, title = _("Animation tool"), rasters = None, timeseries = None):
-        wx.Frame.__init__(self, parent, id, title = title, style = wx.DEFAULT_FRAME_STYLE, size = (800, 600))
+    def __init__(self, parent=None, title=_("Animation tool"),
+                 rasters=None, timeseries=None):
+        wx.Frame.__init__(self, parent, title=title,
+                          style=wx.DEFAULT_FRAME_STYLE, size=(800, 600))
 
 
         self.SetClientSize(self.GetSize())
         self.SetClientSize(self.GetSize())
         self.iconsize = (16, 16)
         self.iconsize = (16, 16)
@@ -52,22 +60,37 @@ class AnimationFrame(wx.Frame):
 
 
         self.animations = [Animation() for i in range(MAX_COUNT)]
         self.animations = [Animation() for i in range(MAX_COUNT)]
         self.windows = []
         self.windows = []
-        self.animationPanel = AnimationsPanel(self, self.windows, initialCount = MAX_COUNT)
+        self.animationPanel = AnimationsPanel(self, self.windows, initialCount=MAX_COUNT)
         bitmapPool = BitmapPool()
         bitmapPool = BitmapPool()
-        self.providers = [BitmapProvider(frame = self, bitmapPool = bitmapPool) for i in range(MAX_COUNT)]
+        mapFilesPool = MapFilesPool()
+        # create temporal directory and ensure it's deleted after programs ends
+#        tempDir = tempfile.mkdtemp()
+#        self.cleanUp = CleanUp(tempDir)
+
+        self._progressDlg = None
+        self._progressDlgMax = None
+
+        self.provider = BitmapProvider(bitmapPool=bitmapPool,
+                                       mapFilesPool=mapFilesPool, tempDir=TMP_DIR)
         self.animationSliders = {}
         self.animationSliders = {}
         self.animationSliders['nontemporal'] = SimpleAnimationSlider(self)
         self.animationSliders['nontemporal'] = SimpleAnimationSlider(self)
         self.animationSliders['temporal'] = TimeAnimationSlider(self)
         self.animationSliders['temporal'] = TimeAnimationSlider(self)
-        self.controller = AnimationController(frame = self, 
-                                              sliders = self.animationSliders,
-                                              animations = self.animations,
-                                              mapwindows = self.windows,
-                                              providers = self.providers,
-                                              bitmapPool = bitmapPool)
-        for win, provider in zip(self.windows, self.providers):
-            win.Bind(wx.EVT_SIZE, lambda event, provider=provider,
-                     sizeMethod=win.GetClientSize: self.FrameSizeChanged(event, provider, sizeMethod))
-            provider.mapsLoaded.connect(lambda: self.SetStatusText(''))
+        self.controller = AnimationController(frame=self,
+                                              sliders=self.animationSliders,
+                                              animations=self.animations,
+                                              mapwindows=self.windows,
+                                              provider=self.provider,
+                                              bitmapPool=bitmapPool,
+                                              mapFilesPool=mapFilesPool)
+        for win in self.windows:
+            win.Bind(wx.EVT_SIZE, self.FrameSizeChanged)
+            self.provider.mapsLoaded.connect(lambda: self.SetStatusText(''))
+            self.provider.renderingStarted.connect(self._showRenderingProgress)
+            self.provider.renderingContinues.connect(self._updateProgress)
+            self.provider.renderingFinished.connect(self._closeProgress)
+            self.provider.compositionStarted.connect(self._showRenderingProgress)
+            self.provider.compositionContinues.connect(self._updateProgress)
+            self.provider.compositionFinished.connect(self._closeProgress)
 
 
         self.InitStatusbar()
         self.InitStatusbar()
         self._mgr = wx.aui.AuiManager(self)
         self._mgr = wx.aui.AuiManager(self)
@@ -90,13 +113,14 @@ class AnimationFrame(wx.Frame):
 
 
     def InitStatusbar(self):
     def InitStatusbar(self):
         """!Init statusbar."""
         """!Init statusbar."""
-        self.CreateStatusBar(number = 1, style = 0)
+        self.CreateStatusBar(number=1, style=0)
 
 
     def _addPanes(self):
     def _addPanes(self):
-        self._mgr.AddPane(self.animationPanel, wx.aui.AuiPaneInfo().CentrePane().
-                  Name('animPanel').CentrePane().CaptionVisible(False).PaneBorder(False).
-                  Floatable(False).BestSize((-1,-1)).
-                  CloseButton(False).DestroyOnClose(True).Layer(0))
+        self._mgr.AddPane(self.animationPanel,
+                          wx.aui.AuiPaneInfo().CentrePane().
+                          Name('animPanel').CentrePane().CaptionVisible(False).PaneBorder(False).
+                          Floatable(False).BestSize((-1, -1)).
+                          CloseButton(False).DestroyOnClose(True).Layer(0))
         for name, slider in self.animationSliders.iteritems():
         for name, slider in self.animationSliders.iteritems():
             self._mgr.AddPane(slider, wx.aui.AuiPaneInfo().PaneBorder(False).Name('slider_' + name).
             self._mgr.AddPane(slider, wx.aui.AuiPaneInfo().PaneBorder(False).Name('slider_' + name).
                               Layer(1).CaptionVisible(False).BestSize(slider.GetBestSize()).
                               Layer(1).CaptionVisible(False).BestSize(slider.GetBestSize()).
@@ -105,7 +129,7 @@ class AnimationFrame(wx.Frame):
 
 
     def _addToolbar(self, name):
     def _addToolbar(self, name):
         """!Add defined toolbar to the window
         """!Add defined toolbar to the window
-        
+
         Currently known toolbars are:
         Currently known toolbars are:
          - 'mainToolbar'          - data management
          - 'mainToolbar'          - data management
          - 'animationToolbar'     - animation controls
          - 'animationToolbar'     - animation controls
@@ -143,14 +167,12 @@ class AnimationFrame(wx.Frame):
                               CloseButton(False).Layer(2).Row(1).
                               CloseButton(False).Layer(2).Row(1).
                               BestSize((self.toolbars['miscToolbar'].GetBestSize())))
                               BestSize((self.toolbars['miscToolbar'].GetBestSize())))
 
 
-    def SetAnimations(self, inputs=None, dataType=None):
+    def SetAnimations(self, layerLists):
         """!Set animation data
         """!Set animation data
-        
-        @param inputs list of lists of raster maps or vector maps,  
-               or a space time raster or vector dataset 
-  	  @param dataType The type of the input data must be one of 'rast', 'vect', 'strds' or 'strds' 
-        """ 
-        self.controller.SetAnimations(inputs, dataType) 
+
+        @param layerLists list of layerLists
+        """
+        self.controller.SetAnimations(layerLists)
 
 
     def OnAddAnimation(self, event):
     def OnAddAnimation(self, event):
         self.controller.AddAnimation()
         self.controller.AddAnimation()
@@ -166,7 +188,7 @@ class AnimationFrame(wx.Frame):
 
 
     def OnEditAnimation(self, event):
     def OnEditAnimation(self, event):
         self.controller.EditAnimations()
         self.controller.EditAnimations()
-        
+
     def SetSlider(self, name):
     def SetSlider(self, name):
         if name == 'nontemporal':
         if name == 'nontemporal':
             self._mgr.GetPane('slider_nontemporal').Show()
             self._mgr.GetPane('slider_nontemporal').Show()
@@ -189,7 +211,7 @@ class AnimationFrame(wx.Frame):
         self.controller.StartAnimation()
         self.controller.StartAnimation()
 
 
     def OnPause(self, event):
     def OnPause(self, event):
-        self.controller.PauseAnimation(paused = event.IsChecked())
+        self.controller.PauseAnimation(paused=event.IsChecked())
 
 
     def OnStop(self, event):
     def OnStop(self, event):
         self.controller.EndAnimation()
         self.controller.EndAnimation()
@@ -218,10 +240,10 @@ class AnimationFrame(wx.Frame):
                 win.SetFocus()
                 win.SetFocus()
             else:
             else:
                 win.Show()
                 win.Show()
-        else: # start
-            win = SpeedDialog(self, temporalMode = self.controller.GetTemporalMode(),
-                              timeGranularity = self.controller.GetTimeGranularity(),
-                              initialSpeed = self.controller.timeTick)
+        else:  # start
+            win = SpeedDialog(self, temporalMode=self.controller.GetTemporalMode(),
+                              timeGranularity=self.controller.GetTimeGranularity(),
+                              initialSpeed=self.controller.timeTick)
             self.dialogs['speed'] = win
             self.dialogs['speed'] = win
             win.speedChanged.connect(self.ChangeSpeed)
             win.speedChanged.connect(self.ChangeSpeed)
             win.Show()
             win.Show()
@@ -232,40 +254,64 @@ class AnimationFrame(wx.Frame):
     def Reload(self, event):
     def Reload(self, event):
         self.controller.Reload()
         self.controller.Reload()
 
 
+    def _showRenderingProgress(self, count):
+        # the message is not really visible, it's there for the initial dlg size
+        self._progressDlg = wx.ProgressDialog(title=_("Loading data"),
+                                              message="Loading data started, please be patient.",
+                                              maximum=count,
+                                              parent=self,
+                                              style=wx.PD_CAN_ABORT | wx.PD_APP_MODAL |
+                                              wx.PD_AUTO_HIDE | wx.PD_SMOOTH)
+        self._progressDlgMax = count
+
+    def _updateProgress(self, current, text):
+        text += _(" ({} out of {})").format(current, self._progressDlgMax)
+        keepGoing, skip = self._progressDlg.Update(current, text)
+        if not keepGoing:
+            self.provider.RequestStopRendering()
+
+    def _closeProgress(self):
+        self._progressDlg.Destroy()
+        self._progressDlg = None
+
     def OnExportAnimation(self, event):
     def OnExportAnimation(self, event):
         self.controller.Export()
         self.controller.Export()
 
 
-    def FrameSizeChanged(self, event, provider, sizeMethod):
-        provider.WindowSizeChanged(*sizeMethod())
-        if self.animationPanel.shown:
-            self.SetStatusText(_("Window size has changed, rerender maps if needed"))
+    def FrameSizeChanged(self, event):
+        maxWidth = maxHeight = 0
+        for win in self.windows:
+            w, h = win.GetClientSize()
+            if w >= maxWidth and h >= maxHeight:
+                maxWidth, maxHeight = w, h
+        self.provider.WindowSizeChanged(maxWidth, maxHeight)
         event.Skip()
         event.Skip()
-                     
-                     
+
     def OnHelp(self, event):
     def OnHelp(self, event):
         RunCommand('g.manual',
         RunCommand('g.manual',
-                   quiet = True,
-                   entry = 'wxGUI.animation')
+                   quiet=True,
+                   entry='wxGUI.animation')
 
 
     def OnCloseWindow(self, event):
     def OnCloseWindow(self, event):
+        CleanUp(TMP_DIR)()
         self.Destroy()
         self.Destroy()
 
 
     def __del__(self):
     def __del__(self):
         if hasattr(self, 'controller') and hasattr(self.controller, 'timer'):
         if hasattr(self, 'controller') and hasattr(self.controller, 'timer'):
             if self.controller.timer.IsRunning():
             if self.controller.timer.IsRunning():
                 self.controller.timer.Stop()
                 self.controller.timer.Stop()
+        CleanUp(TMP_DIR)()
 
 
 
 
 class AnimationsPanel(wx.Panel):
 class AnimationsPanel(wx.Panel):
-    def __init__(self, parent, windows, initialCount = 4):
-        wx.Panel.__init__(self, parent, id = wx.ID_ANY, style = wx.NO_BORDER)
+    def __init__(self, parent, windows, initialCount=4):
+        wx.Panel.__init__(self, parent, id=wx.ID_ANY, style=wx.NO_BORDER)
         self.shown = []
         self.shown = []
         self.count = initialCount
         self.count = initialCount
-        self.mainSizer = wx.FlexGridSizer(rows = 2, hgap = 0, vgap = 0)
+        self.mainSizer = wx.FlexGridSizer(rows=2, hgap=0, vgap=0)
         for i in range(initialCount):
         for i in range(initialCount):
             w = AnimationWindow(self)
             w = AnimationWindow(self)
             windows.append(w)
             windows.append(w)
-            self.mainSizer.Add(item = w, proportion = 1, flag = wx.EXPAND)
+            self.mainSizer.Add(item=w, proportion=1, flag=wx.EXPAND)
 
 
         self.mainSizer.AddGrowableCol(0)
         self.mainSizer.AddGrowableCol(0)
         self.mainSizer.AddGrowableCol(1)
         self.mainSizer.AddGrowableCol(1)
@@ -278,7 +324,6 @@ class AnimationsPanel(wx.Panel):
             self.mainSizer.Hide(windows[i])
             self.mainSizer.Hide(windows[i])
         self.Layout()
         self.Layout()
 
 
-
     def AddWindow(self, index):
     def AddWindow(self, index):
         if len(self.shown) == self.count:
         if len(self.shown) == self.count:
             return
             return
@@ -299,12 +344,12 @@ class AnimationsPanel(wx.Panel):
 
 
 class AnimationSliderBase(wx.Panel):
 class AnimationSliderBase(wx.Panel):
     def __init__(self, parent):
     def __init__(self, parent):
-        wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY)
-        self.label1 = wx.StaticText(self, id = wx.ID_ANY)
-        self.slider = wx.Slider(self, id = wx.ID_ANY, style = wx.SL_HORIZONTAL)
-        self.indexField = wx.TextCtrl(self, id = wx.ID_ANY, size = (40, -1),
-                                      style = wx.TE_PROCESS_ENTER | wx.TE_RIGHT,
-                                      validator = IntegerValidator())
+        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
+        self.label1 = wx.StaticText(self, id=wx.ID_ANY)
+        self.slider = wx.Slider(self, id=wx.ID_ANY, style=wx.SL_HORIZONTAL)
+        self.indexField = wx.TextCtrl(self, id=wx.ID_ANY, size=(40, -1),
+                                      style=wx.TE_PROCESS_ENTER | wx.TE_RIGHT,
+                                      validator=IntegerValidator())
 
 
         self.callbackSliderChanging = None
         self.callbackSliderChanging = None
         self.callbackSliderChanged = None
         self.callbackSliderChanged = None
@@ -333,7 +378,7 @@ class AnimationSliderBase(wx.Panel):
 
 
     def SetFrames(self, frames):
     def SetFrames(self, frames):
         self._setFrames(frames)
         self._setFrames(frames)
-        
+
     def _setFrames(self, frames):
     def _setFrames(self, frames):
         raise NotImplementedError
         raise NotImplementedError
 
 
@@ -346,7 +391,7 @@ class AnimationSliderBase(wx.Panel):
     def SetCallbackFrameIndexChanged(self, callback):
     def SetCallbackFrameIndexChanged(self, callback):
         self.callbackFrameIndexChanged = callback
         self.callbackFrameIndexChanged = callback
 
 
-    def EnableSlider(self, enable = True):
+    def EnableSlider(self, enable=True):
         if enable and self.framesCount <= 1:
         if enable and self.framesCount <= 1:
             enable = False  # we don't want to enable it
             enable = False  # we don't want to enable it
         self.enable = enable
         self.enable = enable
@@ -394,11 +439,11 @@ class SimpleAnimationSlider(AnimationSliderBase):
 
 
     def _doLayout(self):
     def _doLayout(self):
         hbox = wx.BoxSizer(wx.HORIZONTAL)
         hbox = wx.BoxSizer(wx.HORIZONTAL)
-        hbox.Add(item = self.indexField, proportion = 0,
-                 flag = wx.ALIGN_CENTER | wx.LEFT, border = 5)
-        hbox.Add(item = self.label1, proportion = 0, 
-                 flag = wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, border = 5)
-        hbox.Add(item = self.slider, proportion = 1, flag = wx.ALIGN_CENTER| wx.EXPAND, border = 0)
+        hbox.Add(item=self.indexField, proportion=0,
+                 flag=wx.ALIGN_CENTER | wx.LEFT, border=5)
+        hbox.Add(item=self.label1, proportion=0,
+                 flag=wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, border=5)
+        hbox.Add(item=self.slider, proportion=1, flag=wx.ALIGN_CENTER | wx.EXPAND, border=0)
         self.SetSizerAndFit(hbox)
         self.SetSizerAndFit(hbox)
 
 
     def _setFrames(self, count):
     def _setFrames(self, count):
@@ -423,8 +468,8 @@ class TimeAnimationSlider(AnimationSliderBase):
     def __init__(self, parent):
     def __init__(self, parent):
         AnimationSliderBase.__init__(self, parent)
         AnimationSliderBase.__init__(self, parent)
         self.timeLabels = []
         self.timeLabels = []
-        self.label2 = wx.StaticText(self, id = wx.ID_ANY)
-        self.label3 = wx.StaticText(self, id = wx.ID_ANY)
+        self.label2 = wx.StaticText(self, id=wx.ID_ANY)
+        self.label3 = wx.StaticText(self, id=wx.ID_ANY)
         self.label2Length = 0
         self.label2Length = 0
         self.temporalType = TemporalType.RELATIVE
         self.temporalType = TemporalType.RELATIVE
 
 
@@ -434,21 +479,21 @@ class TimeAnimationSlider(AnimationSliderBase):
     def _doLayout(self):
     def _doLayout(self):
         vbox = wx.BoxSizer(wx.VERTICAL)
         vbox = wx.BoxSizer(wx.VERTICAL)
         hbox = wx.BoxSizer(wx.HORIZONTAL)
         hbox = wx.BoxSizer(wx.HORIZONTAL)
-        hbox.Add(item = self.label1, proportion = 0, 
-                 flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
+        hbox.Add(item=self.label1, proportion=0,
+                 flag=wx.ALIGN_CENTER_VERTICAL, border=0)
         hbox.AddStretchSpacer()
         hbox.AddStretchSpacer()
-        hbox.Add(item = self.indexField, proportion = 0, 
-                 flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
-        hbox.Add(item = self.label2, proportion = 0, 
-                 flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border = 3)
+        hbox.Add(item=self.indexField, proportion=0,
+                 flag=wx.ALIGN_CENTER_VERTICAL, border=0)
+        hbox.Add(item=self.label2, proportion=0,
+                 flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=3)
         hbox.AddStretchSpacer()
         hbox.AddStretchSpacer()
-        hbox.Add(item = self.label3, proportion = 0, 
-                 flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
-        vbox.Add(item = hbox, proportion = 0, flag = wx.EXPAND, border = 0)
+        hbox.Add(item=self.label3, proportion=0,
+                 flag=wx.ALIGN_CENTER_VERTICAL, border=0)
+        vbox.Add(item=hbox, proportion=0, flag=wx.EXPAND, border=0)
 
 
         hbox = wx.BoxSizer(wx.HORIZONTAL)
         hbox = wx.BoxSizer(wx.HORIZONTAL)
-        hbox.Add(item = self.slider, proportion = 1, flag = wx.ALIGN_CENTER | wx.EXPAND, border = 0)
-        vbox.Add(item = hbox, proportion = 0, flag = wx.EXPAND, border = 0)
+        hbox.Add(item=self.slider, proportion=1, flag=wx.ALIGN_CENTER | wx.EXPAND, border=0)
+        vbox.Add(item=hbox, proportion=0, flag=wx.EXPAND, border=0)
 
 
         self._setTemporalType()
         self._setTemporalType()
         self.SetSizerAndFit(vbox)
         self.SetSizerAndFit(vbox)
@@ -507,9 +552,10 @@ class TimeAnimationSlider(AnimationSliderBase):
 
 
     def _updateFrameIndex(self, index):
     def _updateFrameIndex(self, index):
         start = self.timeLabels[index][0]
         start = self.timeLabels[index][0]
-        if self.timeLabels[index][1]: # interval
+        if self.timeLabels[index][1]:  # interval
             if self.temporalType == TemporalType.ABSOLUTE:
             if self.temporalType == TemporalType.ABSOLUTE:
-                label = _("%(from)s %(dash)s %(to)s") % {'from': start, 'dash': u"\u2013", 'to': self.timeLabels[index][1]}
+                label = _("%(from)s %(dash)s %(to)s") % \
+                    {'from': start, 'dash': u"\u2013", 'to': self.timeLabels[index][1]}
             else:
             else:
                 label = _("to %(to)s") % {'to': self.timeLabels[index][1]}
                 label = _("to %(to)s") % {'to': self.timeLabels[index][1]}
         else:
         else:
@@ -523,20 +569,3 @@ class TimeAnimationSlider(AnimationSliderBase):
         if len(label) != self.label2Length:
         if len(label) != self.label2Length:
             self.label2Length = len(label)
             self.label2Length = len(label)
             self.Layout()
             self.Layout()
-
-def test():
-
-    import grass.script as grass
-
-    app = wx.PySimpleApp()
-    wx.InitAllImageHandlers()
-
-    frame = AnimationFrame(parent=None)
-    frame.SetAnimations(inputs=None, dataType=None)
-
-    frame.Show()
-    app.MainLoop()
-
-if __name__ == '__main__':
-
-    test()

+ 32 - 19
gui/wxpython/animation/g.gui.animation.py

@@ -60,22 +60,21 @@ import grass.script as grass
 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"))
 
 
-from core.settings import UserSettings
 from core.globalvar import CheckWxVersion
 from core.globalvar import CheckWxVersion
-from core.giface import StandaloneGrassInterface
 from core.utils import _, GuiModuleMain
 from core.utils import _, GuiModuleMain
+from core.layerlist import LayerList
 from animation.frame import AnimationFrame, MAX_COUNT
 from animation.frame import AnimationFrame, MAX_COUNT
+from animation.data import AnimLayer
+
 
 
 def main():
 def main():
     rast = options['rast']
     rast = options['rast']
     vect = options['vect']
     vect = options['vect']
     strds = options['strds']
     strds = options['strds']
     stvds = options['stvds']
     stvds = options['stvds']
-    
-    dataType=None
-    inputs=None
-    numInputs=0
-    
+
+    numInputs = 0
+
     if rast:
     if rast:
         numInputs += 1
         numInputs += 1
     if vect:
     if vect:
@@ -85,33 +84,47 @@ def main():
     if stvds:
     if stvds:
         numInputs += 1
         numInputs += 1
 
 
-    if  numInputs > 1:
+    if numInputs > 1:
         grass.fatal(_("Options 'rast', 'vect', 'strds' and 'stvds' are mutually exclusive."))
         grass.fatal(_("Options 'rast', 'vect', 'strds' and 'stvds' are mutually exclusive."))
 
 
+    layerList = LayerList()
     if rast:
     if rast:
-        inputs = [rast.split(',')] + [None] * (MAX_COUNT - 1)
-        dataType='rast'
+        layer = AnimLayer()
+        layer.mapType = 'rast'
+        layer.name = rast
+        layer.cmd = ['d.rast', 'map={}'.format(rast.split(',')[0])]
+        layerList.AddLayer(layer)
     if vect:
     if vect:
-        inputs = [vect.split(',')] + [None] * (MAX_COUNT - 1)
-        dataType='vect'
+        layer = AnimLayer()
+        layer.mapType = 'vect'
+        layer.name = vect
+        layer.cmd = ['d.vect', 'map={}'.format(rast.split(',')[0])]
+        layerList.AddLayer(layer)
     if strds:
     if strds:
-        inputs = [strds] + [None] * (MAX_COUNT - 1)
-        dataType='strds'
+        layer = AnimLayer()
+        layer.mapType = 'strds'
+        layer.name = strds
+        layer.cmd = ['d.rast', 'map=']
+        layerList.AddLayer(layer)
     if stvds:
     if stvds:
-        inputs = [stvds] + [None] * (MAX_COUNT - 1)
-        dataType='stvds'
+        layer = AnimLayer()
+        layer.mapType = 'stvds'
+        layer.name = stvds
+        layer.cmd = ['d.vect', 'map=']
+        layerList.AddLayer(layer)
 
 
     app = wx.PySimpleApp()
     app = wx.PySimpleApp()
     if not CheckWxVersion([2, 9]):
     if not CheckWxVersion([2, 9]):
         wx.InitAllImageHandlers()
         wx.InitAllImageHandlers()
 
 
-    frame = AnimationFrame(parent = None)
+    frame = AnimationFrame(parent=None)
     frame.CentreOnScreen()
     frame.CentreOnScreen()
     frame.Show()
     frame.Show()
-    frame.SetAnimations(inputs = inputs, dataType = dataType)
+    if len(layerList) >= 1:
+        frame.SetAnimations([layerList] + [None] * (MAX_COUNT - 1))
     app.MainLoop()
     app.MainLoop()
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     options, flags = grass.parser()
     options, flags = grass.parser()
-    
+
     GuiModuleMain(main)
     GuiModuleMain(main)

+ 15 - 394
gui/wxpython/animation/mapwindow.py

@@ -1,32 +1,23 @@
 """!
 """!
 @package animation.mapwindow
 @package animation.mapwindow
 
 
-@brief Animation window and bitmaps management
+@brief Animation window
 
 
 Classes:
 Classes:
  - mapwindow::BufferedWindow
  - mapwindow::BufferedWindow
  - mapwindow::AnimationWindow
  - mapwindow::AnimationWindow
- - mapwindow::BitmapProvider
- - mapwindow::BitmapPool
 
 
-(C) 2012 by the GRASS Development Team
+(C) 2013 by the GRASS Development Team
 
 
 This program is free software under the GNU General Public License
 This program is free software under the GNU General Public License
 (>=v2). Read the file COPYING that comes with GRASS for details.
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
 
-@author Anna Kratochvilova <kratochanna gmail.com>
+@author Anna Petrasova <kratochanna gmail.com>
 """
 """
-import os
+
 import wx
 import wx
-from multiprocessing import Process, Queue
-import tempfile
-import grass.script as grass
-from core.gcmd import RunCommand, GException
 from core.debug import Debug
 from core.debug import Debug
-from core.settings import UserSettings
-from core.utils import _, CmdToTuple, autoCropImageFromFile
 
 
-from grass.pydispatch.signal import Signal
 
 
 class BufferedWindow(wx.Window):
 class BufferedWindow(wx.Window):
     """
     """
@@ -45,7 +36,8 @@ class BufferedWindow(wx.Window):
     """
     """
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         # make sure the NO_FULL_REPAINT_ON_RESIZE style flag is set.
         # make sure the NO_FULL_REPAINT_ON_RESIZE style flag is set.
-        kwargs['style'] = kwargs.setdefault('style', wx.NO_FULL_REPAINT_ON_RESIZE) | wx.NO_FULL_REPAINT_ON_RESIZE
+        kwargs['style'] = kwargs.setdefault('style', wx.NO_FULL_REPAINT_ON_RESIZE) | \
+            wx.NO_FULL_REPAINT_ON_RESIZE
         wx.Window.__init__(self, *args, **kwargs)
         wx.Window.__init__(self, *args, **kwargs)
 
 
         Debug.msg(2, "BufferedWindow.__init__()")
         Debug.msg(2, "BufferedWindow.__init__()")
@@ -71,7 +63,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.GetClientSize()
+        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
@@ -82,7 +74,7 @@ class BufferedWindow(wx.Window):
 
 
     def SaveToFile(self, FileName, FileType=wx.BITMAP_TYPE_PNG):
     def SaveToFile(self, FileName, FileType=wx.BITMAP_TYPE_PNG):
         ## This will save the contents of the buffer
         ## This will save the contents of the buffer
-        ## to the specified file. See the wxWindows docs for 
+        ## to the specified file. See the wxWindows docs for
         ## wx.Bitmap::SaveFile for the details
         ## wx.Bitmap::SaveFile for the details
         self._Buffer.SaveFile(FileName, FileType)
         self._Buffer.SaveFile(FileName, FileType)
 
 
@@ -99,18 +91,18 @@ class BufferedWindow(wx.Window):
         dc = wx.MemoryDC()
         dc = wx.MemoryDC()
         dc.SelectObject(self._Buffer)
         dc.SelectObject(self._Buffer)
         self.Draw(dc)
         self.Draw(dc)
-        del dc # need to get rid of the MemoryDC before Update() is called.
+        del dc  # need to get rid of the MemoryDC before Update() is called.
         self.Refresh()
         self.Refresh()
         self.Update()
         self.Update()
 
 
 
 
 class AnimationWindow(BufferedWindow):
 class AnimationWindow(BufferedWindow):
     def __init__(self, parent, id=wx.ID_ANY,
     def __init__(self, parent, id=wx.ID_ANY,
-                 style = wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE | wx.BORDER_RAISED):
+                 style=wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE |
+                 wx.BORDER_RAISED):
         Debug.msg(2, "AnimationWindow.__init__()")
         Debug.msg(2, "AnimationWindow.__init__()")
 
 
         self.bitmap = wx.EmptyBitmap(1, 1)
         self.bitmap = wx.EmptyBitmap(1, 1)
-        self.text = ''
         self.parent = parent
         self.parent = parent
         self._pdc = wx.PseudoDC()
         self._pdc = wx.PseudoDC()
         self._overlay = None
         self._overlay = None
@@ -126,20 +118,19 @@ class AnimationWindow(BufferedWindow):
         """!Draws bitmap."""
         """!Draws bitmap."""
         Debug.msg(5, "AnimationWindow.Draw()")
         Debug.msg(5, "AnimationWindow.Draw()")
 
 
-        dc.Clear() # make sure you clear the bitmap!
+        dc.Clear()  # make sure you clear the bitmap!
         dc.DrawBitmap(self.bitmap, x=0, y=0)
         dc.DrawBitmap(self.bitmap, x=0, y=0)
-        dc.DrawText(self.text, 0, 0)
 
 
     def OnSize(self, event):
     def OnSize(self, event):
         Debug.msg(5, "AnimationWindow.OnSize()")
         Debug.msg(5, "AnimationWindow.OnSize()")
 
 
-        self.DrawBitmap(self.bitmap, self.text)
-        
+        self.DrawBitmap(self.bitmap)
+
         BufferedWindow.OnSize(self, event)
         BufferedWindow.OnSize(self, event)
         if event:
         if event:
             event.Skip()
             event.Skip()
 
 
-    def DrawBitmap(self, bitmap, text):
+    def DrawBitmap(self, bitmap):
         """!Draws bitmap.
         """!Draws bitmap.
         Does not draw the bitmap if it is the same one as last time.
         Does not draw the bitmap if it is the same one as last time.
         """
         """
@@ -147,7 +138,6 @@ class AnimationWindow(BufferedWindow):
             return
             return
 
 
         self.bitmap = bitmap
         self.bitmap = bitmap
-        self.text = text
         self.UpdateDrawing()
         self.UpdateDrawing()
 
 
     def DrawOverlay(self, x, y):
     def DrawOverlay(self, x, y):
@@ -193,7 +183,6 @@ class AnimationWindow(BufferedWindow):
         if self._overlay:
         if self._overlay:
             self._pdc.DrawToDC(dc)
             self._pdc.DrawToDC(dc)
 
 
-
     def OnMouseEvents(self, event):
     def OnMouseEvents(self, event):
         """!Handle mouse events."""
         """!Handle mouse events."""
         # If it grows larger, split it.
         # If it grows larger, split it.
@@ -223,371 +212,3 @@ class AnimationWindow(BufferedWindow):
         """!Returns x, y position in pixels"""
         """!Returns x, y position in pixels"""
         rect = self._pdc.GetIdBounds(1)
         rect = self._pdc.GetIdBounds(1)
         return rect.GetX(), rect.GetY()
         return rect.GetX(), rect.GetY()
-
-
-class BitmapProvider(object):
-    """!Class responsible for loading data and providing bitmaps"""
-    def __init__(self, frame, bitmapPool, imageWidth=640, imageHeight=480, nprocs=4):
-
-        self.datasource = None
-        self.dataNames = None
-        self.dataType = None
-        self.bitmapPool = bitmapPool
-        self.frame = frame
-        self.imageWidth = imageWidth # width of the image to render with d.rast or d.vect
-        self.imageHeight = imageHeight # height of the image to render with d.rast or d.vect
-        self.nprocs = nprocs # Number of procs to be used for rendering
-
-        self.suffix = ''
-        self.nvizRegion = None
-        
-        self.mapsLoaded = Signal('mapsLoaded')
-
-    def GetDataNames(self):
-        return self.dataNames
-
-    def SetData(self, datasource, dataNames = None, dataType = 'rast',
-                suffix = '', nvizRegion = None):
-        """!Sets data.
-
-        @param datasource data to load (raster maps, vector maps, m.nviz.image commands)
-        @param dataNames data labels (keys)
-        @param dataType 'rast', 'vect', 'nviz'
-        @param nvizRegion region which must be set for m.nviz.image
-        """
-        self.datasource = datasource
-        self.dataType = dataType
-        self.suffix = suffix
-        self.nvizRegion = nvizRegion
-        
-        if dataNames:
-            self.dataNames = dataNames
-        else:
-            self.dataNames = datasource
-
-        self.dataNames = [name + self.suffix for name in self.dataNames]
-
-    def GetBitmap(self, dataId):
-        """!Returns bitmap with given key
-        or 'no data' bitmap if no such key exists.
-
-        @param dataId name of bitmap
-        """
-        if dataId:
-            dataId += self.suffix
-        try:
-            bitmap = self.bitmapPool[dataId]
-        except KeyError:
-            bitmap = self.bitmapPool[None]
-        return bitmap
-
-    def WindowSizeChanged(self, width, height):
-        """!Sets size when size of related window changes."""
-        self.imageWidth, self.imageHeight = width, height
-
-    def _createNoDataBitmap(self, width, height, text="No data"):
-        """!Creates 'no data' bitmap.
-
-        Used when requested bitmap is not available (loading data was not successful) or 
-        we want to show 'no data' bitmap.
-        """
-        bitmap = wx.EmptyBitmap(width, height)
-        dc = wx.MemoryDC()
-        dc.SelectObject(bitmap)
-        dc.Clear()
-        text = _(text)
-        dc.SetFont(wx.Font(pointSize = 40, family = wx.FONTFAMILY_SCRIPT,
-                           style = wx.FONTSTYLE_NORMAL, weight = wx.FONTWEIGHT_BOLD))
-        tw, th = dc.GetTextExtent(text)
-        dc.DrawText(text, (width-tw)/2,  (height-th)/2)
-        dc.SelectObject(wx.NullBitmap)
-        return bitmap
-
-    def Load(self, force = False, nprocs=4):
-        """!Loads data.
-
-        Shows progress dialog.
-
-        @param force if True reload all data, otherwise only missing data
-        @param imageWidth width of the image to render with d.rast or d.vect
-        @param imageHeight height of the image to render with d.rast or d.vect
-        @param nprocs number of procs to be used for rendering
-        """
-        if nprocs <= 0:
-            nprocs = 1
-
-        count, maxLength = self._dryLoad(rasters = self.datasource,
-                                         names = self.dataNames, force = force)
-        progress = None
-        if self.dataType in ('rast', 'vect', 'strds', 'stvds') and count > 5 or \
-            self.dataType == 'nviz':
-            progress = wx.ProgressDialog(title = "Loading data",
-                                         message = " " * (maxLength + 20), # ?
-                                         maximum = count,
-                                         parent = self.frame,
-                                         style = wx.PD_CAN_ABORT | wx.PD_APP_MODAL |
-                                                 wx.PD_AUTO_HIDE | wx.PD_SMOOTH)
-            updateFunction = progress.Update
-        else:
-            updateFunction = None
-
-        if self.dataType in ('rast', 'vect', 'strds', 'stvds'):
-            self._loadMaps(mapType=self.dataType, maps = self.datasource, names = self.dataNames,
-                           force = force, updateFunction = updateFunction,
-                           imageWidth=self.imageWidth, imageHeight=self.imageHeight, nprocs=nprocs)
-        elif self.dataType == 'nviz':
-            self._load3D(commands = self.datasource, region = self.nvizRegion, names = self.dataNames,
-                         force = force, updateFunction = updateFunction)
-        if progress:
-            progress.Destroy()
-
-        self.mapsLoaded.emit()
-
-    def Unload(self):
-        self.datasource = None
-        self.dataNames = None
-        self.dataType = None
-
-    def _dryLoad(self, rasters, names, force):
-        """!Tries how many bitmaps will be loaded.
-        Used for progress dialog.
-
-        @param rasters raster maps to be loaded
-        @param names names used as keys for bitmaps
-        @param force load everything even though it is already there
-        """
-        count = 0
-        maxLength = 0
-        for raster, name in zip(rasters, names):
-            if not force and name in self.bitmapPool and \
-               self.bitmapPool[name].GetSize() == (self.imageWidth, self.imageHeight):
-                continue
-            count += 1
-            if len(raster) > maxLength:
-                maxLength = len(raster)
-
-        return count, maxLength
-
-    
-    def _loadMaps(self, mapType, maps, names, force, updateFunction,
-                  imageWidth, imageHeight, nprocs):
-        """!Loads rasters/vectors (also from temporal dataset).
-
-        Uses d.rast/d.vect and multiprocessing for parallel rendering
-
-        @param mapType Must be "rast" or "vect"
-        @param maps raster or vector maps to be loaded
-        @param names names used as keys for bitmaps
-        @param force load everything even though it is already there
-        @param updateFunction function called for updating progress dialog
-        @param imageWidth width of the image to render with d.rast or d.vect
-        @param imageHeight height of the image to render with d.rast or d.vect
-        @param nprocs number of procs to be used for rendering
-        """
-
-        count = 0
-
-        # Variables for parallel rendering
-        proc_count = 0
-        proc_list = []
-        queue_list = []
-        name_list = []
-
-        mapNum = len(maps)
-
-        # create no data bitmap
-        if None not in self.bitmapPool or force:
-            self.bitmapPool[None] = self._createNoDataBitmap(imageWidth, imageHeight)
-
-        for mapname, name in zip(maps, names):
-            count += 1
-
-            if not force and name in self.bitmapPool and \
-               self.bitmapPool[name].GetSize() == (self.imageWidth, self.imageHeight):
-                continue
-
-            # Queue object for interprocess communication
-            q = Queue()
-            # The separate render process
-            p = Process(target=mapRenderProcess, args=(mapType, mapname, imageWidth, imageHeight, q))
-            p.start()
-
-            queue_list.append(q)
-            proc_list.append(p)
-            name_list.append(name)
-
-            proc_count += 1
-
-            # Wait for all running processes and read/store the created images
-            if proc_count == nprocs or count == mapNum:
-                for i in range(len(name_list)):
-                    proc_list[i].join()
-                    filename = queue_list[i].get()
-
-                    # Unfortunately the png files must be read here, 
-                    # since the swig wx objects can not be serialized by the Queue object :(
-                    if filename == None:
-                        self.bitmapPool[name_list[i]] = self._createNoDataBitmap(imageWidth, imageHeight,
-                                                                                 text="Failed to render")
-                    else:
-                        self.bitmapPool[name_list[i]] = wx.BitmapFromImage(wx.Image(filename))
-                        os.remove(filename)
-
-                proc_count = 0
-                proc_list = []
-                queue_list = []
-                name_list = []
-
-            if updateFunction:
-                keepGoing, skip = updateFunction(count, mapname)
-                if not keepGoing:
-                    break
-
-    def _load3D(self, commands, region, names, force, updateFunction):
-        """!Load 3D view images using m.nviz.image.
-
-        @param commands 
-        @param region 
-        @param names names used as keys for bitmaps
-        @param force load everything even though it is already there
-        @param updateFunction function called for updating progress dialog
-        """
-        ncols, nrows = self.imageWidth, self.imageHeight
-        count = 0
-        format = 'ppm'
-        tempFile = grass.tempfile(False)
-        tempFileFormat = tempFile + '.' + format
-
-        os.environ['GRASS_REGION'] = grass.region_env(**region)
-        # create no data bitmap
-        if None not in self.bitmapPool or force:
-            self.bitmapPool[None] = self._createNoDataBitmap(ncols, nrows)
-        for command, name in zip(commands, names):
-            if name in self.bitmapPool and force is False:
-                continue
-            count += 1
-            # set temporary file
-            command[1]['output'] = tempFile
-            # set size
-            command[1]['size'] = '%d,%d' % (ncols, nrows)
-            # set format
-            command[1]['format'] = format
-
-            returncode, messages = RunCommand(getErrorMsg = True, prog = command[0], **command[1])
-            if returncode != 0:
-                self.bitmapPool[name] = wx.EmptyBitmap(ncols, nrows)
-                continue
-
-            self.bitmapPool[name] = wx.Bitmap(tempFileFormat)
-
-            if updateFunction:
-                keepGoing, skip = updateFunction(count, name)
-                if not keepGoing:
-                    break
-        grass.try_remove(tempFileFormat)
-        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:
-            return wx.BitmapFromImage(autoCropImageFromFile(filename))
-        else:
-            os.remove(filename)
-            raise GException(messages)
-
-
-def mapRenderProcess(mapType, mapname, width, height, fileQueue):
-    """!Render raster or vector files as png image and write the 
-       resulting png filename in the provided file queue
-    
-    @param mapType Must be "rast" or "vect"
-    @param mapname raster or vector map name to be rendered
-    @param width Width of the resulting image
-    @param height Height of the resulting image
-    @param fileQueue The inter process communication queue storing the file name of the image
-    """
-    
-    # temporary file, we use python here to avoid calling g.tempfile for each render process
-    fileHandler, filename = tempfile.mkstemp(suffix=".png")
-    os.close(fileHandler)
-    
-    # Set the environment variables for this process
-    _setEnvironment(width, height, filename, transparent=False)
-
-    if mapType in ('rast', 'strds'):
-        Debug.msg(1, "Render raster image " + str(filename))
-        returncode, stdout, messages = read2_command('d.rast', map = mapname)
-    elif mapType in ('vect', 'stvds'):
-        Debug.msg(1, "Render vector image " + str(filename))
-        returncode, stdout, messages = read2_command('d.vect', map = mapname)
-    else:
-        returncode = 1
-        return
-
-    if returncode != 0:
-        grass.warning("Rendering failed:\n" + messages)
-        fileQueue.put(None)
-        os.remove(filename)
-        return
-
-    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 storing bitmaps (emulates dictionary)"""
-    def __init__(self):
-        self.bitmaps = {}
-
-    def __getitem__(self, key):
-        return self.bitmaps[key]
-
-    def __setitem__(self, key, bitmap):
-        self.bitmaps[key] = bitmap
-
-    def __contains__(self, key):
-        return key in self.bitmaps
-
-    def Clear(self, usedKeys):
-        """!Removes all bitmaps which are currently not used.
-
-        @param usedKeys keys which are currently used
-        """
-        for key in self.bitmaps.keys():
-            if key not in usedKeys and key is not None:
-                del self.bitmaps[key]
-
-
-def read2_command(*args, **kwargs):
-    kwargs['stdout'] = grass.PIPE
-    kwargs['stderr'] = grass.PIPE
-    ps = grass.start_command(*args, **kwargs)
-    stdout, stderr = ps.communicate()
-    return ps.returncode, stdout, stderr

+ 30 - 30
gui/wxpython/animation/nviztask.py

@@ -6,12 +6,12 @@
 Classes:
 Classes:
  - nviztask::NvizTask
  - nviztask::NvizTask
 
 
-(C) 2012 by the GRASS Development Team
+(C) 2013 by the GRASS Development Team
 
 
 This program is free software under the GNU General Public License
 This program is free software under the GNU General Public License
 (>=v2). Read the file COPYING that comes with GRASS for details.
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
 
-@author Anna Kratochvilova <kratochanna gmail.com>
+@author Anna Petrasova <kratochanna gmail.com>
 """
 """
 
 
 import os
 import os
@@ -19,8 +19,7 @@ import sys
 try:
 try:
     import xml.etree.ElementTree as etree
     import xml.etree.ElementTree as etree
 except ImportError:
 except ImportError:
-    import elementtree.ElementTree as etree # Python <= 2.4
-from pprint import pprint
+    import elementtree.ElementTree as etree  # Python <= 2.4
 
 
 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"))
@@ -29,7 +28,7 @@ from core.workspace import ProcessWorkspaceFile
 from core.gcmd import RunCommand, GException
 from core.gcmd import RunCommand, GException
 from core.utils import GetLayerNameFromCmd, CmdToTuple, _
 from core.utils import GetLayerNameFromCmd, CmdToTuple, _
 from grass.script import task as gtask
 from grass.script import task as gtask
-from grass.script import core as grass
+
 
 
 class NvizTask:
 class NvizTask:
     def __init__(self):
     def __init__(self):
@@ -42,7 +41,7 @@ class NvizTask:
         self.filename = filename
         self.filename = filename
         try:
         try:
             gxwXml = ProcessWorkspaceFile(etree.parse(self.filename))
             gxwXml = ProcessWorkspaceFile(etree.parse(self.filename))
-        except Exception, e:
+        except Exception:
             raise GException(_("Reading workspace file <%s> failed.\n"
             raise GException(_("Reading workspace file <%s> failed.\n"
                                "Invalid file, unable to parse XML document.") % filename)
                                "Invalid file, unable to parse XML document.") % filename)
         # for display in gxwXml.displays:
         # for display in gxwXml.displays:
@@ -71,20 +70,19 @@ class NvizTask:
 
 
             if not layer['nviz']:
             if not layer['nviz']:
                 continue
                 continue
-            layerName, found = GetLayerNameFromCmd(layer['cmd'], fullyQualified = False,
-                                                   param = 'map')
+            layerName, found = GetLayerNameFromCmd(layer['cmd'], fullyQualified=False,
+                                                   param='map')
             if not found:
             if not found:
                 continue
                 continue
 
 
             if 'surface' in layer['nviz']:
             if 'surface' in layer['nviz']:
-                self._processSurface(layer['nviz']['surface'], mapName = layerName)
-
+                self._processSurface(layer['nviz']['surface'], mapName=layerName)
 
 
     def _processSurface(self, surface, mapName):
     def _processSurface(self, surface, mapName):
         self._setMultiTaskParam('elevation_map', mapName)
         self._setMultiTaskParam('elevation_map', mapName)
 
 
         # attributes like color, shine, transparency
         # attributes like color, shine, transparency
-        attributes = ('color', 'shine', 'transp') # mask missing
+        attributes = ('color', 'shine', 'transp')  # mask missing
         parameters = (('color_map', 'color'),
         parameters = (('color_map', 'color'),
                      ('shininess_map', 'shininess_value'),
                      ('shininess_map', 'shininess_value'),
                      ('transparency_map', 'transparency_value'))
                      ('transparency_map', 'transparency_value'))
@@ -128,7 +126,7 @@ class NvizTask:
 
 
     def _processState(self, state):
     def _processState(self, state):
         color = state['view']['background']['color']
         color = state['view']['background']['color']
-        self.task.set_param('bgcolor', self._join(color, delim = ':'))
+        self.task.set_param('bgcolor', self._join(color, delim=':'))
         self.task.set_param('position', self._join((state['view']['position']['x'],
         self.task.set_param('position', self._join((state['view']['position']['x'],
                                                     state['view']['position']['y'])))
                                                     state['view']['position']['y'])))
         self.task.set_param('height', state['iview']['height']['value'])
         self.task.set_param('height', state['iview']['height']['value'])
@@ -141,20 +139,17 @@ class NvizTask:
                                                  state['iview']['focus']['z'])))
                                                  state['iview']['focus']['z'])))
         self.task.set_param('light_position', self._join((state['light']['position']['x'],
         self.task.set_param('light_position', self._join((state['light']['position']['x'],
                                                           state['light']['position']['y'],
                                                           state['light']['position']['y'],
-                                                          state['light']['position']['z']/100.)))
+                                                          state['light']['position']['z'] / 100.)))
         color = state['light']['color'][:3]
         color = state['light']['color'][:3]
-        self.task.set_param('light_color', self._join(color, delim = ':'))
+        self.task.set_param('light_color', self._join(color, delim=':'))
         self.task.set_param('light_brightness', int(state['light']['bright']))
         self.task.set_param('light_brightness', int(state['light']['bright']))
         self.task.set_param('light_ambient', state['light']['ambient'])
         self.task.set_param('light_ambient', state['light']['ambient'])
 
 
-
-        
-
     def _setMultiTaskParam(self, param, value):
     def _setMultiTaskParam(self, param, value):
         last = self.task.get_param(param)['value']
         last = self.task.get_param(param)['value']
         self.task.set_param(param, self._join((last, value)))
         self.task.set_param(param, self._join((last, value)))
 
 
-    def _join(self, toJoin, delim = ','):
+    def _join(self, toJoin, delim=','):
         toJoin = filter(self._ignore, toJoin)
         toJoin = filter(self._ignore, toJoin)
         return delim.join(map(str, toJoin))
         return delim.join(map(str, toJoin))
 
 
@@ -168,13 +163,22 @@ class NvizTask:
         # params = self.task.get_list_params()
         # params = self.task.get_list_params()
         # parameter with 'map' name
         # parameter with 'map' name
         # params = filter(lambda x: 'map' in x, params)
         # params = filter(lambda x: 'map' in x, params)
-        return ('elevation_map', 'color_map', 'vline','vpoint')
+        return ('elevation_map', 'color_map', 'vline', 'vpoint')
 
 
-    def GetCommandSeries(self, series, paramName):
+    def GetCommandSeries(self, layerList, paramName):
         commands = []
         commands = []
         if not self.task:
         if not self.task:
             return commands
             return commands
 
 
+        if len(layerList) > 1:
+            raise GException(_("Please add only one layer in the list."))
+            return
+        layer = layerList[0]
+        if hasattr(layer, 'maps'):
+            series = layer.maps
+        else:
+            raise GException(_("No map series nor space-time dataset is added."))
+
         for value in series:
         for value in series:
             self.task.set_param(paramName, value)
             self.task.set_param(paramName, value)
             # FIXME: we assume we want always default color map
             # FIXME: we assume we want always default color map
@@ -182,8 +186,8 @@ class NvizTask:
                 self.task.set_param('color_map', '')
                 self.task.set_param('color_map', '')
             self.task.set_flag('overwrite', True)
             self.task.set_flag('overwrite', True)
             self.task.set_param('output', 'tobechanged')
             self.task.set_param('output', 'tobechanged')
-            cmd = self.task.get_cmd(ignoreErrors = False, ignoreRequired = False, ignoreDefault = True)
-            commands.append(CmdToTuple(cmd))
+            cmd = self.task.get_cmd(ignoreErrors=False, ignoreRequired=False, ignoreDefault=True)
+            commands.append(cmd)
 
 
         return commands
         return commands
 
 
@@ -192,29 +196,25 @@ class NvizTask:
             return None
             return None
         self.task.set_flag('overwrite', True)
         self.task.set_flag('overwrite', True)
         self.task.set_param('output', 'tobechanged')
         self.task.set_param('output', 'tobechanged')
-        cmd = self.task.get_cmd(ignoreErrors = False, ignoreRequired = False, ignoreDefault = True)
+        cmd = self.task.get_cmd(ignoreErrors=False, ignoreRequired=False, ignoreDefault=True)
         return CmdToTuple(cmd)
         return CmdToTuple(cmd)
 
 
     def GetRegion(self):
     def GetRegion(self):
         return self.region
         return self.region
 
 
 
 
-
 def test():
 def test():
-
     nviz = NvizTask('/home/anna/testy/nviz/t12.gxw')
     nviz = NvizTask('/home/anna/testy/nviz/t12.gxw')
     # nviz = NvizState('/home/anna/testy/nviz/t3.gxw')
     # nviz = NvizState('/home/anna/testy/nviz/t3.gxw')
-    
+
     # cmd = nviz.GetCommand()
     # cmd = nviz.GetCommand()
-    cmds = nviz.GetCommandSeries(['aspect','elevation'], 'color_map')
+    cmds = nviz.GetCommandSeries(['aspect', 'elevation'], 'color_map')
     for cmd in cmds:
     for cmd in cmds:
         print cmd
         print cmd
-        returncode, message = RunCommand(getErrorMsg = True, prog = cmd[0], **cmd[1])
+        returncode, message = RunCommand(getErrorMsg=True, prog=cmd[0], **cmd[1])
         print returncode, message
         print returncode, message
 
 
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
 
 
     test()
     test()
-
-

+ 764 - 0
gui/wxpython/animation/provider.py

@@ -0,0 +1,764 @@
+# -*- coding: utf-8 -*-
+"""!
+@package animation.provider
+
+@brief Animation files and bitmaps management
+
+Classes:
+ - mapwindow::BitmapProvider
+ - mapwindow::BitmapRenderer
+ - mapwindow::BitmapComposer
+ - mapwindow::DictRefCounter
+ - mapwindow::MapFilesPool
+ - mapwindow::BitmapPool
+ - mapwindow::CleanUp
+
+(C) 2013 by the GRASS Development Team
+
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+@author Anna Petrasova <kratochanna gmail.com>
+"""
+import os
+import sys
+import wx
+import tempfile
+from multiprocessing import Process, Queue
+if __name__ == '__main__':
+    sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
+
+from core.gcmd import RunCommand, GException
+from core.settings import UserSettings
+from core.debug import Debug
+from core.utils import _, CmdToTuple, autoCropImageFromFile
+
+from animation.utils import HashCmd, HashCmds, GetFileFromCmd, GetFileFromCmds
+
+import grass.script.core as gcore
+from grass.pydispatch.signal import Signal
+
+
+class BitmapProvider:
+    """!Class for management of image files and bitmaps.
+
+    There is one instance of this class in the application.
+    It handles both 2D and 3D animations.
+    """
+    def __init__(self, bitmapPool, mapFilesPool, tempDir,
+                 imageWidth=640, imageHeight=480):
+
+        self._bitmapPool = bitmapPool
+        self._mapFilesPool = mapFilesPool
+        self.imageWidth = imageWidth  # width of the image to render with d.rast or d.vect
+        self.imageHeight = imageHeight  # height of the image to render with d.rast or d.vect
+        self._tempDir = tempDir
+
+        self._uniqueCmds = []
+        self._cmdsForComposition = []
+        self._opacities = []
+
+        self._cmds3D = []
+        self._regionFor3D = None
+
+        self._renderer = BitmapRenderer(self._mapFilesPool, self._tempDir,
+                                        self.imageWidth, self.imageHeight)
+        self._composer = BitmapComposer(self._tempDir, self._mapFilesPool,
+                                        self._bitmapPool, self.imageWidth,
+                                        self.imageHeight)
+        self.renderingStarted = Signal('BitmapProvider.renderingStarted')
+        self.compositionStarted = Signal('BitmapProvider.compositionStarted')
+        self.renderingContinues = Signal('BitmapProvider.renderingContinues')
+        self.compositionContinues = Signal('BitmapProvider.compositionContinues')
+        self.renderingFinished = Signal('BitmapProvider.renderingFinished')
+        self.compositionFinished = Signal('BitmapProvider.compositionFinished')
+        self.mapsLoaded = Signal('BitmapProvider.mapsLoaded')
+
+        self._renderer.renderingContinues.connect(self.renderingContinues)
+        self._composer.compositionContinues.connect(self.compositionContinues)
+
+    def SetCmds(self, cmdsForComposition, opacities):
+        """!Sets commands to be rendered with opacity levels.
+        Applies to 2D mode.
+
+        @param cmdsForComposition list of lists of command lists
+                [[['d.rast', 'map=elev_2001'], ['d.vect', 'map=points']], # g.pnmcomp
+                 [['d.rast', 'map=elev_2002'], ['d.vect', 'map=points']],
+                 ...]
+        @param opacities list of opacity values
+        """
+        Debug.msg(2, "BitmapProvider.SetCmds: {} lists".format(len(cmdsForComposition)))
+        self._cmdsForComposition.extend(cmdsForComposition)
+        self._uniqueCmds = self._getUniqueCmds()
+        self._opacities.extend(opacities)
+
+    def SetCmds3D(self, cmds, region):
+        """!Sets commands for 3D rendering.
+
+        @param cmds list of commands m.nviz.image (cmd as a list)
+        @param region for 3D rendering
+        """
+        Debug.msg(2, "BitmapProvider.SetCmds3D: {} commands".format(len(cmds)))
+        self._cmds3D = cmds
+        self._regionFor3D = region
+
+    def _getUniqueCmds(self):
+        """!Returns list of unique commands."""
+        unique = set()
+        for cmdList in self._cmdsForComposition:
+            for cmd in cmdList:
+                unique.add(tuple(cmd))
+        return list(unique)
+
+    def Unload(self):
+        """!Unloads currently loaded data.
+        Needs to be called before setting new data.
+        """
+        Debug.msg(2, "BitmapProvider.Unload")
+        if self._cmdsForComposition:
+            for cmd in self._uniqueCmds:
+                del self._mapFilesPool[HashCmd(cmd)]
+
+            for cmdList in self._cmdsForComposition:
+                del self._bitmapPool[HashCmds(cmdList)]
+            self._uniqueCmds = []
+            self._cmdsForComposition = []
+            self._opacities = []
+        if self._cmds3D:
+            self._cmds3D = []
+            self._regionFor3D = None
+
+    def _dryRender(self, uniqueCmds, force):
+        """!Determines how many files will be rendered.
+
+        @param uniqueCmds list of commands which are to be rendered
+        @param force if forced rerendering
+        """
+        count = 0
+        for cmd in uniqueCmds:
+            filename = GetFileFromCmd(self._tempDir, cmd)
+            if not force and os.path.exists(filename) and \
+               self._mapFilesPool.GetSize(HashCmd(cmd)) == (self.imageWidth, self.imageHeight):
+                continue
+            count += 1
+
+        Debug.msg(3, "BitmapProvider._dryRender: {} files to be rendered".format(count))
+
+        return count
+
+    def _dryCompose(self, cmdLists, 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 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):
+                continue
+            count += 1
+
+        Debug.msg(2, "BitmapProvider._dryCompose: {} files to be composed".format(count))
+
+        return count
+
+    def Load(self, force=False, bgcolor=(255, 255, 255), nprocs=4):
+        """!Loads data, both 2D and 3D. In case of 2D, it creates composites,
+        even when there is only 1 layer to compose (to be changed for speedup)
+
+        @param force if True reload all data, otherwise only missing data
+        @param bgcolor background color as a tuple of 3 values 0 to 255
+        @param nprocs number of procs to be used for rendering
+        """
+        Debug.msg(2, "BitmapProvider.Load: "
+                     "force={}, bgcolor={}, nprocs={}".format(force, bgcolor, nprocs))
+        cmds = []
+        if self._uniqueCmds:
+            cmds.extend(self._uniqueCmds)
+        if self._cmds3D:
+            cmds.extend(self._cmds3D)
+
+        count = self._dryRender(cmds, 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,
+                                   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)
+            self.compositionStarted.emit(count=count)
+            self._composer.Compose(self._cmdsForComposition, self._opacities,
+                                   bgcolor=bgcolor, force=force, nprocs=nprocs)
+            self.compositionFinished.emit()
+        if self._cmds3D:
+            for cmd in self._cmds3D:
+                self._bitmapPool[HashCmd(cmd)] = \
+                    wx.Bitmap(GetFileFromCmd(self._tempDir, cmd))
+
+        self.mapsLoaded.emit()
+
+    def RequestStopRendering(self):
+        """!Requests to stop rendering/composition"""
+        Debug.msg(2, "BitmapProvider.RequestStopRendering")
+        self._renderer.RequestStopRendering()
+        self._composer.RequestStopComposing()
+
+    def GetBitmap(self, dataId):
+        """!Returns bitmap with given key
+        or 'no data' bitmap if no such key exists.
+
+        @param dataId name of bitmap
+        """
+        try:
+            bitmap = self._bitmapPool[dataId]
+        except KeyError:
+            bitmap = self._bitmapPool[None]
+        return bitmap
+
+    def WindowSizeChanged(self, width, height):
+        """!Sets size when size of related window changes."""
+        Debug.msg(5, "BitmapProvider.WindowSizeChanged: w={}, h={}".format(width, height))
+
+        self.imageWidth, self.imageHeight = width, height
+
+        self._composer.imageWidth = self._renderer.imageWidth = width
+        self._composer.imageHeight = self._renderer.imageHeight = height
+
+    def LoadOverlay(self, cmd):
+        """!Creates raster legend with d.legend
+
+        @param cmd d.legend command as a list
+
+        @return bitmap with legend
+        """
+        Debug.msg(5, "BitmapProvider.LoadOverlay: cmd={}".format(cmd))
+
+        fileHandler, filename = tempfile.mkstemp(suffix=".png")
+        os.close(fileHandler)
+        # Set the environment variables for this process
+        _setEnvironment(self.imageWidth, self.imageHeight, filename,
+                        transparent=True, bgcolor=(0, 0, 0))
+
+        Debug.msg(1, "Render raster legend " + str(filename))
+        cmdTuple = CmdToTuple(cmd)
+        returncode, stdout, messages = read2_command(cmdTuple[0], **cmdTuple[1])
+
+        if returncode == 0:
+            return wx.BitmapFromImage(autoCropImageFromFile(filename))
+        else:
+            os.remove(filename)
+            raise GException(messages)
+
+
+class BitmapRenderer:
+    """!Class which renderes 2D and 3D images to files."""
+    def __init__(self, mapFilesPool, tempDir,
+                 imageWidth, imageHeight):
+        self._mapFilesPool = mapFilesPool
+        self._tempDir = tempDir
+        self.imageWidth = imageWidth
+        self.imageHeight = imageHeight
+
+        self.renderingContinues = Signal('BitmapRenderer.renderingContinues')
+        self._stopRendering = False
+        self._isRendering = False
+
+    def Render(self, cmdList, regionFor3D, bgcolor, force, nprocs):
+        """!Renders all maps and stores files.
+
+        @param cmdList list of rendering commands to run
+        @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
+        @param nprocs number of procs to be used for rendering
+        """
+        Debug.msg(3, "BitmapRenderer.Render")
+        count = 0
+
+        # Variables for parallel rendering
+        proc_count = 0
+        proc_list = []
+        queue_list = []
+        cmd_list = []
+
+        filteredCmdList = []
+        for cmd in cmdList:
+            filename = GetFileFromCmd(self._tempDir, cmd)
+            if not force and os.path.exists(filename) and \
+               self._mapFilesPool.GetSize(HashCmd(cmd)) == (self.imageWidth, self.imageHeight):
+                # for reference counting
+                self._mapFilesPool[HashCmd(cmd)] = filename
+                continue
+            filteredCmdList.append(cmd)
+
+        mapNum = len(filteredCmdList)
+        stopped = False
+        self._isRendering = True
+        for cmd in filteredCmdList:
+            count += 1
+
+            # Queue object for interprocess communication
+            q = Queue()
+            # The separate render process
+            if cmd[0] == 'm.nviz.image':
+                p = Process(target=self.RenderProcess3D, args=(cmd, regionFor3D, bgcolor, q))
+            else:
+                p = Process(target=self.RenderProcess2D, args=(cmd, bgcolor, q))
+            p.start()
+
+            queue_list.append(q)
+            proc_list.append(p)
+            cmd_list.append(cmd)
+
+            proc_count += 1
+            # Wait for all running processes and read/store the created images
+            if proc_count == nprocs or count == mapNum:
+                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.imageWidth, self.imageHeight))
+
+                proc_count = 0
+                proc_list = []
+                queue_list = []
+                cmd_list = []
+
+            self.renderingContinues.emit(current=count, text=_("Rendering map layers"))
+            if self._stopRendering:
+                self._stopRendering = False
+                stopped = True
+                break
+
+        self._isRendering = False
+        return not stopped
+
+    def RenderProcess2D(self, cmd, bgcolor, fileQueue):
+        """!Render raster or vector files as ppm image and write the
+           resulting ppm filename in the provided file queue
+
+        @param cmd d.rast/d.vect command as a list
+        @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
+        """
+        Debug.msg(3, "BitmapRenderer.RenderProcess2D: cmd={}".format(cmd))
+
+        filename = GetFileFromCmd(self._tempDir, cmd)
+        transparency = True
+
+        # Set the environment variables for this process
+        _setEnvironment(self.imageWidth, self.imageHeight, filename,
+                        transparent=transparency, bgcolor=bgcolor)
+
+        Debug.msg(1, "Render image to file " + str(filename))
+        cmdTuple = CmdToTuple(cmd)
+        returncode, stdout, messages = read2_command(cmdTuple[0], **cmdTuple[1])
+        if returncode != 0:
+            gcore.warning("Rendering failed:\n" + messages)
+            fileQueue.put(None)
+            os.remove(filename)
+            return
+
+        fileQueue.put(filename)
+
+    def RenderProcess3D(self, cmd, region, bgcolor, fileQueue):
+        """!Renders image with m.nviz.image and writes the
+           resulting ppm filename in the provided file queue
+
+        @param cmd m.nviz.image command as a list
+        @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
+        """
+        Debug.msg(3, "BitmapRenderer.RenderProcess3D: cmd={}".format(cmd))
+
+        filename = GetFileFromCmd(self._tempDir, cmd)
+        os.environ['GRASS_REGION'] = gcore.region_env(**region)
+
+        Debug.msg(1, "Render image to file " + str(filename))
+        cmdTuple = CmdToTuple(cmd)
+        cmdTuple[1]['output'] = os.path.splitext(filename)[0]
+        # set size
+        cmdTuple[1]['size'] = '%d,%d' % (self.imageWidth, self.imageHeight)
+        # set format
+        cmdTuple[1]['format'] = 'ppm'
+        cmdTuple[1]['bgcolor'] = bgcolor = ':'.join([str(part) for part in bgcolor])
+        returncode, stdout, messages = read2_command(cmdTuple[0], **cmdTuple[1])
+        if returncode != 0:
+            gcore.warning("Rendering failed:\n" + messages)
+            fileQueue.put(None)
+            os.environ.pop('GRASS_REGION')
+            return
+
+        os.environ.pop('GRASS_REGION')
+        fileQueue.put(filename)
+
+    def RequestStopRendering(self):
+        """!Requests to stop rendering."""
+        if self._isRendering:
+            self._stopRendering = True
+
+
+class BitmapComposer:
+    """!Class which handles the composition of image files with g.pnmcomp."""
+    def __init__(self, tmpDir, mapFilesPool, bitmapPool,
+                 imageWidth, imageHeight):
+        self._mapFilesPool = mapFilesPool
+        self._bitmapPool = bitmapPool
+        self._tmpDir = tmpDir
+        self.imageWidth = imageWidth
+        self.imageHeight = imageHeight
+
+        self.compositionContinues = Signal('BitmapComposer.composingContinues')
+        self._stopComposing = False
+        self._isComposing = False
+
+    def Compose(self, cmdLists, opacityList, bgcolor, force, nprocs):
+        """!Performs the composition of ppm/pgm files.
+
+        @param cmdLisst lists of rendering commands lists to compose
+        @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
+        @param nprocs number of procs to be used for rendering
+        """
+        Debug.msg(3, "BitmapComposer.Compose")
+
+        count = 0
+
+        # Variables for parallel rendering
+        proc_count = 0
+        proc_list = []
+        queue_list = []
+        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):
+                # TODO: find a better way than to assign the same to increase the reference
+                self._bitmapPool[HashCmds(cmdList)] = self._bitmapPool[HashCmds(cmdList)]
+                continue
+            filteredCmdLists.append(cmdList)
+
+        num = len(filteredCmdLists)
+
+        self._isComposing = True
+        for cmdList in filteredCmdLists:
+            count += 1
+            # Queue object for interprocess communication
+            q = Queue()
+            # The separate render process
+            p = Process(target=self.CompositeProcess,
+                        args=(cmdList, opacityList, bgcolor, q))
+            p.start()
+
+            queue_list.append(q)
+            proc_list.append(p)
+            cmd_lists.append(cmdList)
+
+            proc_count += 1
+
+            # Wait for all running processes and read/store the created images
+            if proc_count == nprocs or count == num:
+                for i in range(len(cmd_lists)):
+                    proc_list[i].join()
+                    filename = queue_list[i].get()
+                    if filename is None:
+                        self._bitmapPool[HashCmds(cmd_lists[i])] = \
+                            createNoDataBitmap(self.imageWidth, self.imageHeight,
+                                               text="Failed to render")
+                    else:
+                        self._bitmapPool[HashCmds(cmd_lists[i])] = \
+                            wx.BitmapFromImage(wx.Image(filename))
+                        os.remove(filename)
+                proc_count = 0
+                proc_list = []
+                queue_list = []
+                cmd_lists = []
+
+            self.compositionContinues.emit(current=count, text=_("Overlaying map layers"))
+            if self._stopComposing:
+                self._stopComposing = False
+                break
+
+        self._isComposing = False
+
+    def CompositeProcess(self, cmdList, opacities, bgcolor, fileQueue):
+        """!Performs the composition of image ppm files and writes the
+           resulting ppm filename in the provided file queue
+
+        @param cmdList list of d.rast/d.vect commands
+        @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
+        storing the file name of the image
+        """
+        Debug.msg(3, "BitmapComposer.CompositeProcess")
+
+        maps = []
+        masks = []
+        for cmd in cmdList:
+            maps.append(GetFileFromCmd(self._tmpDir, cmd))
+            masks.append(GetFileFromCmd(self._tmpDir, cmd, 'pgm'))
+        filename = GetFileFromCmds(self._tmpDir, cmdList)
+        # Set the environment variables for this process
+        _setEnvironment(self.imageWidth, self.imageHeight, filename,
+                        transparent=False, bgcolor=bgcolor)
+
+        opacities = [str(op / 100.) for op in opacities]
+        bgcolor = ':'.join([str(part) for part in bgcolor])
+        returncode, messages = RunCommand('g.pnmcomp',
+                                          getErrorMsg=True,
+                                          overwrite=True,
+                                          input='%s' % ",".join(reversed(maps)),
+                                          mask='%s' % ",".join(reversed(masks)),
+                                          opacity='%s' % ",".join(reversed(opacities)),
+                                          bgcolor=bgcolor,
+                                          width=self.imageWidth,
+                                          height=self.imageHeight,
+                                          output=filename)
+
+        if returncode != 0:
+            gcore.warning("Rendering composite failed:\n" + messages)
+            fileQueue.put(None)
+            os.remove(filename)
+            return
+
+        fileQueue.put(filename)
+
+    def RequestStopComposing(self):
+        """!Requests to stop the composition."""
+        if self._isComposing:
+            self._stopComposing = True
+
+
+class DictRefCounter:
+    """!Base class storing map files/bitmaps (emulates dictionary).
+        Counts the references to know which files/bitmaps to delete."""
+    def __init__(self):
+        self.dictionary = {}
+        self.referenceCount = {}
+
+    def __getitem__(self, key):
+        return self.dictionary[key]
+
+    def __setitem__(self, key, value):
+        self.dictionary[key] = value
+        if key not in self.referenceCount:
+            self.referenceCount[key] = 1
+        else:
+            self.referenceCount[key] += 1
+        Debug.msg(5, 'DictRefCounter.__setitem__: +1 for key {}'.format(key))
+
+    def __contains__(self, key):
+        return key in self.dictionary
+
+    def __delitem__(self, key):
+        self.referenceCount[key] -= 1
+        Debug.msg(5, 'DictRefCounter.__delitem__: -1 for key {}'.format(key))
+
+    def keys(self):
+        return self.dictionary.keys()
+
+    def Clear(self):
+        """!Clears items which are not needed any more."""
+        Debug.msg(4, 'DictRefCounter.Clear')
+        for key in self.dictionary.keys():
+            if key is not None:
+                if self.referenceCount[key] <= 0:
+                    del self.dictionary[key]
+                    del self.referenceCount[key]
+
+
+class MapFilesPool(DictRefCounter):
+    """!Stores rendered images as files."""
+    def __init__(self):
+        DictRefCounter.__init__(self)
+        self.size = {}
+
+    def SetSize(self, key, size):
+        self.size[key] = size
+
+    def GetSize(self, key):
+        return self.size[key]
+
+    def Clear(self):
+        """!Removes files which are not needed anymore.
+        Removes both ppm and pgm.
+        """
+        Debug.msg(4, 'MapFilesPool.Clear')
+
+        for key in self.dictionary.keys():
+            if self.referenceCount[key] <= 0:
+                name, ext = os.path.splitext(self.dictionary[key])
+                os.remove(self.dictionary[key])
+                if ext == '.ppm':
+                    os.remove(name + '.pgm')
+                del self.dictionary[key]
+                del self.referenceCount[key]
+                del self.size[key]
+
+
+class BitmapPool(DictRefCounter):
+    """!Class storing bitmaps (emulates dictionary)"""
+    def __init__(self):
+        DictRefCounter.__init__(self)
+
+
+class CleanUp:
+    """!Responsible for cleaning up the files."""
+    def __init__(self, tempDir):
+        self._tempDir = tempDir
+
+    def __call__(self):
+        import shutil
+        if os.path.exists(self._tempDir):
+            try:
+                shutil.rmtree(self._tempDir)
+                Debug.msg(5, 'CleanUp: removed directory {}'.format(self._tempDir))
+            except OSError:
+                gcore.warning(_("Directory {} not removed.").format(self._tempDir))
+
+
+def _setEnvironment(width, height, filename, transparent, bgcolor):
+    """!Sets environmental variables for 2D rendering.
+
+    @param width rendering width
+    @param height rendering height
+    @param filename file name
+    @param transparent use transparency
+    @param bgcolor background color as a tuple of 3 values 0 to 255
+    """
+    Debug.msg(5, "_setEnvironment: width={}, height={}, "
+                 "filename={}, transparent={}, bgcolor={}".format(width, height, filename,
+                                                                  transparent, bgcolor))
+
+    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'] = '{:02x}{:02x}{:02x}'.format(*bgcolor)
+    os.environ['GRASS_TRUECOLOR'] = "TRUE"
+    if transparent:
+        os.environ['GRASS_TRANSPARENT'] = "TRUE"
+    else:
+        os.environ['GRASS_TRANSPARENT'] = "FALSE"
+    os.environ['GRASS_PNGFILE'] = str(filename)
+
+
+def createNoDataBitmap(imageWidth, imageHeight, text="No data"):
+    """!Creates 'no data' bitmap.
+
+    Used when requested bitmap is not available (loading data was not successful) or
+    we want to show 'no data' bitmap.
+
+    @param imageWidth image width
+    @param imageHeight image height
+    """
+    Debug.msg(4, "createNoDataBitmap: w={}, h={}, text={}".format(imageWidth,
+                                                                  imageHeight, text))
+    bitmap = wx.EmptyBitmap(imageWidth, imageHeight)
+    dc = wx.MemoryDC()
+    dc.SelectObject(bitmap)
+    dc.Clear()
+    text = _(text)
+    dc.SetFont(wx.Font(pointSize=40, family=wx.FONTFAMILY_SCRIPT,
+                       style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_BOLD))
+    tw, th = dc.GetTextExtent(text)
+    dc.DrawText(text, (imageWidth - tw) / 2, (imageHeight - th) / 2)
+    dc.SelectObject(wx.NullBitmap)
+    return bitmap
+
+
+def read2_command(*args, **kwargs):
+    kwargs['stdout'] = gcore.PIPE
+    kwargs['stderr'] = gcore.PIPE
+    ps = gcore.start_command(*args, **kwargs)
+    stdout, stderr = ps.communicate()
+    return ps.returncode, stdout, stderr
+
+
+def test():
+    import shutil
+
+    from core.layerlist import LayerList, Layer
+    from animation.data import AnimLayer
+    from animation.utils import layerListToCmdsMatrix
+
+    layerList = LayerList()
+    layer = AnimLayer()
+    layer.mapType = 'strds'
+    layer.name = 'JR'
+    layer.cmd = ['d.rast', 'map=elev_2007_1m']
+    layerList.AddLayer(layer)
+
+    layer = Layer()
+    layer.mapType = 'vect'
+    layer.name = 'buildings_2009_approx'
+    layer.cmd = ['d.vect', 'map=buildings_2009_approx',
+                 'color=grey']
+    layer.opacity = 50
+    layerList.AddLayer(layer)
+
+    bPool = BitmapPool()
+    mapFilesPool = MapFilesPool()
+
+    tempDir = '/tmp/test'
+    if os.path.exists(tempDir):
+        shutil.rmtree(tempDir)
+    os.mkdir(tempDir)
+    # comment this line to keep the directory after prgm ends
+#    cleanUp = CleanUp(tempDir)
+#    import atexit
+#    atexit.register(cleanUp)
+
+    prov = BitmapProvider(bPool, mapFilesPool, tempDir,
+                          imageWidth=640, imageHeight=480)
+    prov.renderingStarted.connect(
+        lambda count: sys.stdout.write("Total number of maps: {}\n".format(count)))
+    prov.renderingContinues.connect(
+        lambda current, text: sys.stdout.write("Current number: {}\n".format(current)))
+    prov.compositionStarted.connect(
+        lambda count: sys.stdout.write("Composition: total number of maps: {}\n".format(count)))
+    prov.compositionContinues.connect(
+        lambda current, text: sys.stdout.write("Composition: Current number: {}\n".format(current)))
+    prov.mapsLoaded.connect(
+        lambda: sys.stdout.write("Maps loading finished\n"))
+    cmdMatrix = layerListToCmdsMatrix(layerList)
+    prov.SetCmds(cmdMatrix, [l.opacity for l in layerList])
+    app = wx.App()
+
+    prov.Load(bgcolor=(13, 156, 230), nprocs=4)
+
+    for key in bPool.keys():
+        if key is not None:
+            bPool[key].SaveFile(os.path.join(tempDir, key + '.png'), wx.BITMAP_TYPE_PNG)
+#    prov.Unload()
+#    prov.SetCmds(cmdMatrix, [l.opacity for l in layerList])
+#    prov.Load(bgcolor=(13, 156, 230))
+#    prov.Unload()
+#    newList = LayerList()
+#    prov.SetCmds(layerListToCmdsMatrix(newList), [l.opacity for l in newList])
+#    prov.Load()
+#    prov.Unload()
+#    mapFilesPool.Clear()
+#    bPool.Clear()
+#    print bPool.keys(), mapFilesPool.keys()
+
+
+if __name__ == '__main__':
+    test()

+ 144 - 131
gui/wxpython/animation/temporal_manager.py

@@ -27,13 +27,14 @@ import grass.script as grass
 import grass.temporal as tgis
 import grass.temporal as tgis
 from core.gcmd import GException
 from core.gcmd import GException
 from core.utils import _
 from core.utils import _
-from utils import validateTimeseriesName, TemporalType
+from animation.utils import validateTimeseriesName, TemporalType
 
 
 
 
 class DataMode:
 class DataMode:
     SIMPLE = 1
     SIMPLE = 1
     MULTIPLE = 2
     MULTIPLE = 2
 
 
+
 class GranularityMode:
 class GranularityMode:
     ONE_UNIT = 1
     ONE_UNIT = 1
     ORIGINAL = 2
     ORIGINAL = 2
@@ -60,7 +61,7 @@ class TemporalManager(object):
     def SetTemporalType(self, ttype):
     def SetTemporalType(self, ttype):
         self._temporalType = ttype
         self._temporalType = ttype
 
 
-    temporalType = property(fget = GetTemporalType, fset = SetTemporalType)
+    temporalType = property(fget=GetTemporalType, fset=SetTemporalType)
 
 
     def AddTimeSeries(self, timeseries, etype):
     def AddTimeSeries(self, timeseries, etype):
         """!Add space time dataset
         """!Add space time dataset
@@ -90,13 +91,11 @@ class TemporalManager(object):
         ret, message = self._setTemporalState()
         ret, message = self._setTemporalState()
         if not ret:
         if not ret:
             raise GException(message)
             raise GException(message)
-        if message: # warning
+        if message:  # warning
             return message
             return message
 
 
         return None
         return None
 
 
-
-
     def _setTemporalState(self):
     def _setTemporalState(self):
         # check for absolute x relative
         # check for absolute x relative
         absolute, relative = 0, 0
         absolute, relative = 0, 0
@@ -106,13 +105,14 @@ class TemporalManager(object):
             else:
             else:
                 relative += 1
                 relative += 1
         if bool(absolute) == bool(relative):
         if bool(absolute) == bool(relative):
-            message = _("It is not allowed to display data with different temporal types (absolute and relative).")
+            message = _("It is not allowed to display data with different "
+                        "temporal types (absolute and relative).")
             return False, message
             return False, message
         if absolute:
         if absolute:
             self.temporalType = TemporalType.ABSOLUTE
             self.temporalType = TemporalType.ABSOLUTE
         else:
         else:
             self.temporalType = TemporalType.RELATIVE
             self.temporalType = TemporalType.RELATIVE
-            
+
         # check for units for relative type
         # check for units for relative type
         if relative:
         if relative:
             units = set()
             units = set()
@@ -130,9 +130,10 @@ class TemporalManager(object):
             else:
             else:
                 point += 1
                 point += 1
         if bool(interval) == bool(point):
         if bool(interval) == bool(point):
-            message = _("You are going to display data with different temporal types of maps (interval and point)."
+            message = _("You are going to display data with different "
+                        "temporal types of maps (interval and point)."
                         " It is recommended to use data of one temporal type to avoid confusion.")
                         " It is recommended to use data of one temporal type to avoid confusion.")
-            return True, message # warning
+            return True, message  # warning
 
 
         return True, None
         return True, None
 
 
@@ -140,14 +141,14 @@ class TemporalManager(object):
         """!Returns temporal granularity of currently loaded timeseries."""
         """!Returns temporal granularity of currently loaded timeseries."""
         if self.dataMode == DataMode.SIMPLE:
         if self.dataMode == DataMode.SIMPLE:
             gran = self.timeseriesInfo[self.timeseriesList[0]]['granularity']
             gran = self.timeseriesInfo[self.timeseriesList[0]]['granularity']
-            if 'unit' in self.timeseriesInfo[self.timeseriesList[0]]: # relative:
+            if 'unit' in self.timeseriesInfo[self.timeseriesList[0]]:  # relative:
                 granNum = gran
                 granNum = gran
                 unit = self.timeseriesInfo[self.timeseriesList[0]]['unit']
                 unit = self.timeseriesInfo[self.timeseriesList[0]]['unit']
                 if self.granularityMode == GranularityMode.ONE_UNIT:
                 if self.granularityMode == GranularityMode.ONE_UNIT:
                     granNum = 1
                     granNum = 1
             else:  # absolute
             else:  # absolute
                 granNum, unit = gran.split()
                 granNum, unit = gran.split()
-                if self.granularityMode == GranularityMode.ONE_UNIT: 
+                if self.granularityMode == GranularityMode.ONE_UNIT:
                     granNum = 1
                     granNum = 1
 
 
             return (int(granNum), unit)
             return (int(granNum), unit)
@@ -174,25 +175,34 @@ class TemporalManager(object):
                 granNum = 1
                 granNum = 1
             return (granNum, unit)
             return (granNum, unit)
 
 
-
     def GetLabelsAndMaps(self):
     def GetLabelsAndMaps(self):
         """!Returns time labels and map names.
         """!Returns time labels and map names.
         """
         """
         mapLists = []
         mapLists = []
         labelLists = []
         labelLists = []
+        labelListSet = set()
         for dataset in self.timeseriesList:
         for dataset in self.timeseriesList:
             grassLabels, listOfMaps = self._getLabelsAndMaps(dataset)
             grassLabels, listOfMaps = self._getLabelsAndMaps(dataset)
             mapLists.append(listOfMaps)
             mapLists.append(listOfMaps)
-            labelLists.append(grassLabels)
+            labelLists.append(tuple(grassLabels))
+            labelListSet.update(grassLabels)
+        # combine all timeLabels and fill missing maps with None
+        # BUT this does not work properly if the datasets have
+        # no temporal overlap! We would need to sample all datasets
+        # by a temporary dataset, I don't know how it would work with point data
+        if self.temporalType == TemporalType.ABSOLUTE:
+            # ('1996-01-01 00:00:00', '1997-01-01 00:00:00', 'year'),
+            timestamps = sorted(list(labelListSet), key=lambda x: x[0])
+        else:
+            # ('15', '16', u'years'),
+            timestamps = sorted(list(labelListSet), key=lambda x: float(x[0]))
 
 
-        # choose longest time interval and fill missing maps with None
-        timestamps = max(labelLists, key = len)
         newMapLists = []
         newMapLists = []
         for mapList, labelList in zip(mapLists, labelLists):
         for mapList, labelList in zip(mapLists, labelLists):
             newMapList = [None] * len(timestamps)
             newMapList = [None] * len(timestamps)
             i = 0
             i = 0
             # compare start time
             # compare start time
-            while timestamps[i][0] != labelList[0][0]: # compare
+            while timestamps[i][0] != labelList[0][0]:  # compare
                 i += 1
                 i += 1
             newMapList[i:i + len(mapList)] = mapList
             newMapList[i:i + len(mapList)] = mapList
             newMapLists.append(newMapList)
             newMapLists.append(newMapList)
@@ -208,7 +218,7 @@ class TemporalManager(object):
         for both interval and point data.
         for both interval and point data.
         """
         """
         sp = tgis.dataset_factory(self.timeseriesInfo[timeseries]['etype'], timeseries)
         sp = tgis.dataset_factory(self.timeseriesInfo[timeseries]['etype'], timeseries)
-        if sp.is_in_db() == False:
+        if sp.is_in_db() is False:
             raise GException(_("Space time dataset <%s> not found.") % timeseries)
             raise GException(_("Space time dataset <%s> not found.") % timeseries)
         sp.select()
         sp.select()
 
 
@@ -231,11 +241,11 @@ class TemporalManager(object):
         # after instance, there can be a gap or an interval
         # after instance, there can be a gap or an interval
         # if it is a gap we remove it and put there the previous instance instead
         # if it is a gap we remove it and put there the previous instance instead
         # however the first gap must be removed to avoid duplication
         # however the first gap must be removed to avoid duplication
-        maps = sp.get_registered_maps_as_objects_by_granularity(gran = gran)
+        maps = sp.get_registered_maps_as_objects_by_granularity(gran=gran)
         if maps and len(maps) > 0:
         if maps and len(maps) > 0:
             lastTimeseries = None
             lastTimeseries = None
-            followsPoint = False # indicates that we are just after finding a point
-            afterPoint = False # indicates that we are after finding a point
+            followsPoint = False  # indicates that we are just after finding a point
+            afterPoint = False  # indicates that we are after finding a point
             for mymap in maps:
             for mymap in maps:
                 if isinstance(mymap, list):
                 if isinstance(mymap, list):
                     if len(mymap) > 0:
                     if len(mymap) > 0:
@@ -276,9 +286,6 @@ class TemporalManager(object):
                             listOfMaps.append(series)
                             listOfMaps.append(series)
                 timeLabels.append((str(start), end, unit))
                 timeLabels.append((str(start), end, unit))
 
 
-        if self.temporalType == TemporalType.ABSOLUTE:
-            timeLabels = self._pretifyTimeLabels(timeLabels)
-
         return timeLabels, listOfMaps
         return timeLabels, listOfMaps
 
 
     def _pretifyTimeLabels(self, labels):
     def _pretifyTimeLabels(self, labels):
@@ -312,7 +319,7 @@ class TemporalManager(object):
         sp.select()
         sp.select()
         # Get ordered map list
         # Get ordered map list
         maps = sp.get_registered_maps_as_objects()
         maps = sp.get_registered_maps_as_objects()
-        
+
         if not sp.check_temporal_topology(maps):
         if not sp.check_temporal_topology(maps):
             raise GException(_("Topology of Space time dataset %s is invalid." % id))
             raise GException(_("Topology of Space time dataset %s is invalid." % id))
 
 
@@ -326,6 +333,7 @@ class TemporalManager(object):
         infoDict[id]['map_time'] = sp.get_map_time()
         infoDict[id]['map_time'] = sp.get_map_time()
         infoDict[id]['maps'] = maps
         infoDict[id]['maps'] = maps
 
 
+
 def test():
 def test():
     from pprint import pprint
     from pprint import pprint
 
 
@@ -344,84 +352,87 @@ def test():
     except GException, e:
     except GException, e:
         print e
         print e
         return
         return
-    
+
     print '///////////////////////////'
     print '///////////////////////////'
     gran = temp.GetGranularity()
     gran = temp.GetGranularity()
     print "granularity: " + str(gran)
     print "granularity: " + str(gran)
     pprint (temp.GetLabelsAndMaps())
     pprint (temp.GetLabelsAndMaps())
 
 
 
 
-
 def createAbsoluteInterval():
 def createAbsoluteInterval():
-    grass.run_command('g.region', s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10, flags = 'p3', quiet = True)
-
-    grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite = True)
-    grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite = True)
-    grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite = True)
-    grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite = True)
-    grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite = True)
-    grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite = True)
-
-    grass.mapcalc(exp="temp_1 = rand(0, 550)", overwrite = True)
-    grass.mapcalc(exp="temp_2 = rand(0, 450)", overwrite = True)
-    grass.mapcalc(exp="temp_3 = rand(0, 320)", overwrite = True)
-    grass.mapcalc(exp="temp_4 = rand(0, 510)", overwrite = True)
-    grass.mapcalc(exp="temp_5 = rand(0, 300)", overwrite = True)
-    grass.mapcalc(exp="temp_6 = rand(0, 650)", overwrite = True)
-
-    n1 = grass.read_command("g.tempfile", pid = 1, flags = 'd').strip()
+    grass.run_command('g.region', s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10,
+                      flags='p3', quiet=True)
+
+    grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite=True)
+    grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite=True)
+    grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite=True)
+    grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite=True)
+    grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite=True)
+    grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite=True)
+
+    grass.mapcalc(exp="temp_1 = rand(0, 550)", overwrite=True)
+    grass.mapcalc(exp="temp_2 = rand(0, 450)", overwrite=True)
+    grass.mapcalc(exp="temp_3 = rand(0, 320)", overwrite=True)
+    grass.mapcalc(exp="temp_4 = rand(0, 510)", overwrite=True)
+    grass.mapcalc(exp="temp_5 = rand(0, 300)", overwrite=True)
+    grass.mapcalc(exp="temp_6 = rand(0, 650)", overwrite=True)
+
+    n1 = grass.read_command("g.tempfile", pid=1, flags='d').strip()
     fd = open(n1, 'w')
     fd = open(n1, 'w')
     fd.write(
     fd.write(
-       "prec_1|2001-01-01|2001-02-01\n"
-       "prec_2|2001-04-01|2001-05-01\n"
-       "prec_3|2001-05-01|2001-09-01\n"
-       "prec_4|2001-09-01|2002-01-01\n"
-       "prec_5|2002-01-01|2002-05-01\n"
-       "prec_6|2002-05-01|2002-07-01\n"
-       )
+        "prec_1|2001-01-01|2001-02-01\n"
+        "prec_2|2001-04-01|2001-05-01\n"
+        "prec_3|2001-05-01|2001-09-01\n"
+        "prec_4|2001-09-01|2002-01-01\n"
+        "prec_5|2002-01-01|2002-05-01\n"
+        "prec_6|2002-05-01|2002-07-01\n"
+    )
     fd.close()
     fd.close()
 
 
-    n2 = grass.read_command("g.tempfile", pid = 2, flags = 'd').strip()
+    n2 = grass.read_command("g.tempfile", pid=2, flags='d').strip()
     fd = open(n2, 'w')
     fd = open(n2, 'w')
     fd.write(
     fd.write(
-       "temp_1|2000-10-01|2001-01-01\n"
-       "temp_2|2001-04-01|2001-05-01\n"
-       "temp_3|2001-05-01|2001-09-01\n"
-       "temp_4|2001-09-01|2002-01-01\n"
-       "temp_5|2002-01-01|2002-05-01\n"
-       "temp_6|2002-05-01|2002-07-01\n"
-       )
+        "temp_1|2000-10-01|2001-01-01\n"
+        "temp_2|2001-04-01|2001-05-01\n"
+        "temp_3|2001-05-01|2001-09-01\n"
+        "temp_4|2001-09-01|2002-01-01\n"
+        "temp_5|2002-01-01|2002-05-01\n"
+        "temp_6|2002-05-01|2002-07-01\n"
+    )
     fd.close()
     fd.close()
     name1 = 'absinterval1'
     name1 = 'absinterval1'
     name2 = 'absinterval2'
     name2 = 'absinterval2'
-    grass.run_command('t.unregister', type = 'rast',
-                       maps='prec_1,prec_2,prec_3,prec_4,prec_5,prec_6,temp_1,temp_2,temp_3,temp_4,temp_5,temp_6')
+    grass.run_command('t.unregister', type='rast',
+                      maps='prec_1,prec_2,prec_3,prec_4,prec_5,prec_6,'
+                      'temp_1,temp_2,temp_3,temp_4,temp_5,temp_6')
     for name, fname in zip((name1, name2), (n1, n2)):
     for name, fname in zip((name1, name2), (n1, n2)):
-        grass.run_command('t.create', overwrite = True, type='strds',
-                          temporaltype='absolute', output=name, 
+        grass.run_command('t.create', overwrite=True, type='strds',
+                          temporaltype='absolute', output=name,
                           title="A test with input files", descr="A test with input files")
                           title="A test with input files", descr="A test with input files")
-        grass.run_command('t.register', flags = 'i', input=name, file=fname, overwrite = True)
+        grass.run_command('t.register', flags='i', input=name, file=fname, overwrite=True)
 
 
     return name1, name2
     return name1, name2
 
 
+
 def createRelativeInterval():
 def createRelativeInterval():
-    grass.run_command('g.region', s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10, flags = 'p3', quiet = True)
-
-    grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite = True)
-    grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite = True)
-    grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite = True)
-    grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite = True)
-    grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite = True)
-    grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite = True)
-
-    grass.mapcalc(exp="temp_1 = rand(0, 550)", overwrite = True)
-    grass.mapcalc(exp="temp_2 = rand(0, 450)", overwrite = True)
-    grass.mapcalc(exp="temp_3 = rand(0, 320)", overwrite = True)
-    grass.mapcalc(exp="temp_4 = rand(0, 510)", overwrite = True)
-    grass.mapcalc(exp="temp_5 = rand(0, 300)", overwrite = True)
-    grass.mapcalc(exp="temp_6 = rand(0, 650)", overwrite = True)
-
-    n1 = grass.read_command("g.tempfile", pid = 1, flags = 'd').strip()
+    grass.run_command('g.region', s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10,
+                      flags='p3', quiet=True)
+
+    grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite=True)
+    grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite=True)
+    grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite=True)
+    grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite=True)
+    grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite=True)
+    grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite=True)
+
+    grass.mapcalc(exp="temp_1 = rand(0, 550)", overwrite=True)
+    grass.mapcalc(exp="temp_2 = rand(0, 450)", overwrite=True)
+    grass.mapcalc(exp="temp_3 = rand(0, 320)", overwrite=True)
+    grass.mapcalc(exp="temp_4 = rand(0, 510)", overwrite=True)
+    grass.mapcalc(exp="temp_5 = rand(0, 300)", overwrite=True)
+    grass.mapcalc(exp="temp_6 = rand(0, 650)", overwrite=True)
+
+    n1 = grass.read_command("g.tempfile", pid=1, flags='d').strip()
     fd = open(n1, 'w')
     fd = open(n1, 'w')
     fd.write(
     fd.write(
         "prec_1|1|4\n"
         "prec_1|1|4\n"
@@ -430,92 +441,94 @@ def createRelativeInterval():
         "prec_4|10|11\n"
         "prec_4|10|11\n"
         "prec_5|11|14\n"
         "prec_5|11|14\n"
         "prec_6|14|17\n"
         "prec_6|14|17\n"
-       )
+    )
     fd.close()
     fd.close()
 
 
-    n2 = grass.read_command("g.tempfile", pid = 2, flags = 'd').strip()
+    n2 = grass.read_command("g.tempfile", pid=2, flags='d').strip()
     fd = open(n2, 'w')
     fd = open(n2, 'w')
     fd.write(
     fd.write(
-        "temp_1|1|4\n"
-        "temp_2|4|7\n"
+        "temp_1|5|6\n"
+        "temp_2|6|7\n"
         "temp_3|7|10\n"
         "temp_3|7|10\n"
         "temp_4|10|11\n"
         "temp_4|10|11\n"
-        "temp_5|11|14\n"
-        "temp_6|14|17\n"
-       )
+        "temp_5|11|18\n"
+        "temp_6|19|22\n"
+    )
     fd.close()
     fd.close()
     name1 = 'relinterval1'
     name1 = 'relinterval1'
     name2 = 'relinterval2'
     name2 = 'relinterval2'
-    grass.run_command('t.unregister', type = 'rast',
-                       maps='prec_1,prec_2,prec_3,prec_4,prec_5,prec_6,temp_1,temp_2,temp_3,temp_4,temp_5,temp_6')
+    grass.run_command('t.unregister', type='rast',
+                      maps='prec_1,prec_2,prec_3,prec_4,prec_5,prec_6,'
+                      'temp_1,temp_2,temp_3,temp_4,temp_5,temp_6')
     for name, fname in zip((name1, name2), (n1, n2)):
     for name, fname in zip((name1, name2), (n1, n2)):
-        grass.run_command('t.create', overwrite = True, type='strds',
-                          temporaltype='relative', output=name, 
+        grass.run_command('t.create', overwrite=True, type='strds',
+                          temporaltype='relative', output=name,
                           title="A test with input files", descr="A test with input files")
                           title="A test with input files", descr="A test with input files")
-        grass.run_command('t.register', flags = 'i', input = name, file = fname, unit = "years", overwrite = True)
+        grass.run_command('t.register', flags='i', input=name, file=fname, unit="years", overwrite=True)
     return name1, name2
     return name1, name2
-        
+
+
 def createAbsolutePoint():
 def createAbsolutePoint():
-    grass.run_command('g.region', s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10, flags = 'p3', quiet = True)
+    grass.run_command('g.region', s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10,
+                      flags='p3', quiet=True)
 
 
-    grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite = True)
-    grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite = True)
-    grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite = True)
-    grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite = True)
-    grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite = True)
-    grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite = True)
+    grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite=True)
+    grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite=True)
+    grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite=True)
+    grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite=True)
+    grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite=True)
+    grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite=True)
 
 
-    n1 = grass.read_command("g.tempfile", pid = 1, flags = 'd').strip()
+    n1 = grass.read_command("g.tempfile", pid=1, flags='d').strip()
     fd = open(n1, 'w')
     fd = open(n1, 'w')
     fd.write(
     fd.write(
-       "prec_1|2001-01-01\n"
-       "prec_2|2001-03-01\n"
-       "prec_3|2001-04-01\n"
-       "prec_4|2001-05-01\n"
-       "prec_5|2001-08-01\n"
-       "prec_6|2001-09-01\n"
-       )
+        "prec_1|2001-01-01\n"
+        "prec_2|2001-03-01\n"
+        "prec_3|2001-04-01\n"
+        "prec_4|2001-05-01\n"
+        "prec_5|2001-08-01\n"
+        "prec_6|2001-09-01\n"
+    )
     fd.close()
     fd.close()
     name = 'abspoint'
     name = 'abspoint'
-    grass.run_command('t.create', overwrite = True, type='strds',
-                      temporaltype='absolute', output=name, 
+    grass.run_command('t.create', overwrite=True, type='strds',
+                      temporaltype='absolute', output=name,
                       title="A test with input files", descr="A test with input files")
                       title="A test with input files", descr="A test with input files")
 
 
-    grass.run_command('t.register', flags = 'i', input=name, file=n1, overwrite = True)
+    grass.run_command('t.register', flags='i', input=name, file=n1, overwrite=True)
     return name
     return name
 
 
+
 def createRelativePoint():
 def createRelativePoint():
-    grass.run_command('g.region', s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10, flags = 'p3', quiet = True)
+    grass.run_command('g.region', s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10,
+                      flags='p3', quiet=True)
 
 
-    grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite = True)
-    grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite = True)
-    grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite = True)
-    grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite = True)
-    grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite = True)
-    grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite = True)
+    grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite=True)
+    grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite=True)
+    grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite=True)
+    grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite=True)
+    grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite=True)
+    grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite=True)
 
 
-    n1 = grass.read_command("g.tempfile", pid = 1, flags = 'd').strip()
+    n1 = grass.read_command("g.tempfile", pid=1, flags='d').strip()
     fd = open(n1, 'w')
     fd = open(n1, 'w')
     fd.write(
     fd.write(
-       "prec_1|1\n"
-       "prec_2|3\n"
-       "prec_3|5\n"
-       "prec_4|7\n"
-       "prec_5|11\n"
-       "prec_6|13\n"
-       )
+        "prec_1|1\n"
+        "prec_2|3\n"
+        "prec_3|5\n"
+        "prec_4|7\n"
+        "prec_5|11\n"
+        "prec_6|13\n"
+    )
     fd.close()
     fd.close()
     name = 'relpoint'
     name = 'relpoint'
-    grass.run_command('t.create', overwrite = True, type='strds',
+    grass.run_command('t.create', overwrite=True, type='strds',
                       temporaltype='relative', output=name,
                       temporaltype='relative', output=name,
                       title="A test with input files", descr="A test with input files")
                       title="A test with input files", descr="A test with input files")
 
 
-    grass.run_command('t.register', unit="day", input=name, file=n1, overwrite = True)
+    grass.run_command('t.register', unit="day", input=name, file=n1, overwrite=True)
     return name
     return name
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
 
 
     test()
     test()
-
-
-

+ 69 - 36
gui/wxpython/animation/toolbars.py

@@ -4,41 +4,52 @@
 @brief Animation toolbars
 @brief Animation toolbars
 
 
 Classes:
 Classes:
- - toolbars::MainToolbar(BaseToolbar):
- - toolbars::AnimationToolbar(BaseToolbar):
- - toolbars::MiscToolbar(BaseToolbar):
+ - toolbars::MainToolbar(BaseToolbar)
+ - toolbars::AnimationToolbar(BaseToolbar)
+ - toolbars::MiscToolbar(BaseToolbar)
+ - toolbars::AnimSimpleLmgrToolbar
 
 
 
 
-(C) 2012 by the GRASS Development Team
+(C) 2013 by the GRASS Development Team
 
 
 This program is free software under the GNU General Public License
 This program is free software under the GNU General Public License
 (>=v2). Read the file COPYING that comes with GRASS for details.
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
 
-@author Anna Kratochvilova <kratochanna gmail.com>
+@author Anna Petrasova <kratochanna gmail.com>
 """
 """
 import wx
 import wx
 from gui_core.toolbars import BaseToolbar, BaseIcons
 from gui_core.toolbars import BaseToolbar, BaseIcons
 from icons.icon import MetaIcon
 from icons.icon import MetaIcon
-
-from anim import ReplayMode
 from core.utils import _
 from core.utils import _
+from gui_core.simplelmgr import SimpleLmgrToolbar
+from animation.anim import ReplayMode
 
 
 ganimIcons = {
 ganimIcons = {
-        'speed': MetaIcon(img = 'settings', label = _("Change animation speed")),
-        'playForward': MetaIcon(img = 'execute', label = _("Play forward")),
-        'playBack': MetaIcon(img = 'player-back', label = _("Play back")),
-        'stop': MetaIcon(img = 'player-stop', label = _("Stop")),
-        'pause': MetaIcon(img = 'player-pause', label = _("Pause")),
-        'oneDirectionReplay': MetaIcon(img = 'redraw', label = _("Repeat")),
-        'bothDirectionReplay': MetaIcon(img = 'player-repeat-back-forward',
-                                        label = _("Play back and forward")),
-        'addAnimation': MetaIcon(img = 'layer-add', label = _("Add new animation"),
-                                 desc = _("Add new animation")),
-        'editAnimation': MetaIcon(img = 'layer-more', label = _("Add, edit or remove animation"),
-                                  desc = _("Add, edit or remove animation")),
-        'exportAnimation': MetaIcon(img = 'layer-export', label = _("Export animation"),
-                                    desc = _("Export animation"))
-        }
+    'speed': MetaIcon(img='settings', label=_("Change animation speed")),
+    'playForward': MetaIcon(img='execute', label=_("Play forward")),
+    'playBack': MetaIcon(img='player-back', label=_("Play back")),
+    'stop': MetaIcon(img='player-stop', label=_("Stop")),
+    'pause': MetaIcon(img='player-pause', label=_("Pause")),
+    'oneDirectionReplay': MetaIcon(img='redraw', label=_("Repeat")),
+    'bothDirectionReplay': MetaIcon(img='player-repeat-back-forward',
+                                    label=_("Play back and forward")),
+    'addAnimation': MetaIcon(img='layer-add', label=_("Add new animation"),
+                             desc=_("Add new animation")),
+    'editAnimation': MetaIcon(img='layer-more', label=_("Add, edit or remove animation"),
+                              desc=_("Add, edit or remove animation")),
+    'exportAnimation': MetaIcon(img='layer-export', label=_("Export animation"),
+                                desc=_("Export animation"))
+}
+
+SIMPLE_LMGR_STDS = 128
+
+
+simpleLmgrIcons = {
+    'addSeries': MetaIcon(img='mapset-add',
+                          label=_("Add space-time dataset or series of map layers"),
+                          desc=_("Add space-time dataset or series of map layers for animation")),
+}
+
 
 
 class MainToolbar(BaseToolbar):
 class MainToolbar(BaseToolbar):
     """!Main toolbar (data management)
     """!Main toolbar (data management)
@@ -47,12 +58,12 @@ class MainToolbar(BaseToolbar):
         """!Main toolbar constructor
         """!Main toolbar constructor
         """
         """
         BaseToolbar.__init__(self, parent)
         BaseToolbar.__init__(self, parent)
-        
+
         self.InitToolbar(self._toolbarData())
         self.InitToolbar(self._toolbarData())
 
 
         # realize the toolbar
         # realize the toolbar
         self.Realize()
         self.Realize()
-        
+
     def _toolbarData(self):
     def _toolbarData(self):
         """!Returns toolbar data (name, icon, handler)"""
         """!Returns toolbar data (name, icon, handler)"""
         # BaseIcons are a set of often used icons. It is possible
         # BaseIcons are a set of often used icons. It is possible
@@ -65,8 +76,10 @@ class MainToolbar(BaseToolbar):
                                      ("reload", BaseIcons["render"],
                                      ("reload", BaseIcons["render"],
                                       self.parent.Reload),
                                       self.parent.Reload),
                                      ("exportAnimation", icons["exportAnimation"],
                                      ("exportAnimation", icons["exportAnimation"],
-                                      self.parent.OnExportAnimation),
-                                    ))
+                                      self.parent.OnExportAnimation)
+                                     ))
+
+
 class AnimationToolbar(BaseToolbar):
 class AnimationToolbar(BaseToolbar):
     """!Animation toolbar (to control animation)
     """!Animation toolbar (to control animation)
     """
     """
@@ -74,7 +87,7 @@ class AnimationToolbar(BaseToolbar):
         """!Animation toolbar constructor
         """!Animation toolbar constructor
         """
         """
         BaseToolbar.__init__(self, parent)
         BaseToolbar.__init__(self, parent)
-        
+
         self.InitToolbar(self._toolbarData())
         self.InitToolbar(self._toolbarData())
 
 
         # realize the toolbar
         # realize the toolbar
@@ -82,8 +95,7 @@ class AnimationToolbar(BaseToolbar):
 
 
         self.isPlayingForward = True
         self.isPlayingForward = True
         self.EnableAnimTools(False)
         self.EnableAnimTools(False)
-        
-        
+
     def _toolbarData(self):
     def _toolbarData(self):
         """!Returns toolbar data (name, icon, handler)"""
         """!Returns toolbar data (name, icon, handler)"""
         # BaseIcons are a set of often used icons. It is possible
         # BaseIcons are a set of often used icons. It is possible
@@ -107,8 +119,9 @@ class AnimationToolbar(BaseToolbar):
                                       wx.ITEM_CHECK),
                                       wx.ITEM_CHECK),
                                      (None, ),
                                      (None, ),
                                      ("adjustSpeed", icons['speed'],
                                      ("adjustSpeed", icons['speed'],
-                                       self.parent.OnAdjustSpeed)
-                                    ))
+                                      self.parent.OnAdjustSpeed)
+                                     ))
+
     def OnPlayForward(self, event):
     def OnPlayForward(self, event):
         self.PlayForward()
         self.PlayForward()
         self.parent.OnPlayForward(event)
         self.parent.OnPlayForward(event)
@@ -132,7 +145,7 @@ class AnimationToolbar(BaseToolbar):
         self.EnableTool(self.stop, True)
         self.EnableTool(self.stop, True)
         self.ToggleTool(self.pause, False)
         self.ToggleTool(self.pause, False)
         self.isPlayingForward = False
         self.isPlayingForward = False
-        
+
     def OnPause(self, event):
     def OnPause(self, event):
         self.Pause()
         self.Pause()
         self.parent.OnPause(event)
         self.parent.OnPause(event)
@@ -187,6 +200,7 @@ class AnimationToolbar(BaseToolbar):
         self.EnableTool(self.pause, enable)
         self.EnableTool(self.pause, enable)
         self.EnableTool(self.stop, enable)
         self.EnableTool(self.stop, enable)
 
 
+
 class MiscToolbar(BaseToolbar):
 class MiscToolbar(BaseToolbar):
     """!Toolbar with miscellaneous tools related to app
     """!Toolbar with miscellaneous tools related to app
     """
     """
@@ -194,15 +208,34 @@ class MiscToolbar(BaseToolbar):
         """!Toolbar constructor
         """!Toolbar constructor
         """
         """
         BaseToolbar.__init__(self, parent)
         BaseToolbar.__init__(self, parent)
-        
+
         self.InitToolbar(self._toolbarData())
         self.InitToolbar(self._toolbarData())
         # realize the toolbar
         # realize the toolbar
         self.Realize()
         self.Realize()
-        
+
     def _toolbarData(self):
     def _toolbarData(self):
         """!Toolbar data"""
         """!Toolbar data"""
         return self._getToolbarData((("help", BaseIcons['help'],
         return self._getToolbarData((("help", BaseIcons['help'],
                                       self.parent.OnHelp),
                                       self.parent.OnHelp),
-                                    ("quit", BaseIcons['quit'],
+                                     ("quit", BaseIcons['quit'],
                                       self.parent.OnCloseWindow),
                                       self.parent.OnCloseWindow),
-                                     ))
+                                     ))
+
+
+class AnimSimpleLmgrToolbar(SimpleLmgrToolbar):
+    """!Simple layer manager toolbar for animation tool.
+    Allows to add space-time dataset or series of maps.
+    """
+    def __init__(self, parent, lmgrStyle):
+        SimpleLmgrToolbar.__init__(self, parent, lmgrStyle)
+
+    def _toolbarData(self):
+        data = SimpleLmgrToolbar._toolbarData(self)
+        if self._style & SIMPLE_LMGR_STDS:
+            data.insert(0, ('addSeries', simpleLmgrIcons['addSeries'],
+                            self.parent.OnAddStds))
+        return data
+
+    def EnableTools(self, tools, enable=True):
+        for tool in tools:
+            self.EnableTool(getattr(self, tool), enable)

+ 163 - 19
gui/wxpython/animation/utils.py

@@ -10,14 +10,16 @@ Classes:
  - utils::ReplayMode
  - utils::ReplayMode
 
 
 
 
-(C) 2012 by the GRASS Development Team
+(C) 2013 by the GRASS Development Team
 
 
 This program is free software under the GNU General Public License
 This program is free software under the GNU General Public License
 (>=v2). Read the file COPYING that comes with GRASS for details.
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
 
-@author Anna Kratochvilova <kratochanna gmail.com>
+@author Anna Perasova <kratochanna gmail.com>
 """
 """
+import os
 import wx
 import wx
+import hashlib
 try:
 try:
     from PIL import Image
     from PIL import Image
     hasPIL = True
     hasPIL = True
@@ -30,24 +32,29 @@ import grass.script as grass
 from core.gcmd import GException
 from core.gcmd import GException
 from core.utils import _
 from core.utils import _
 
 
+
 class TemporalMode:
 class TemporalMode:
     TEMPORAL = 1
     TEMPORAL = 1
     NONTEMPORAL = 2
     NONTEMPORAL = 2
 
 
+
 class TemporalType:
 class TemporalType:
     ABSOLUTE = 1
     ABSOLUTE = 1
     RELATIVE = 2
     RELATIVE = 2
 
 
+
 class Orientation:
 class Orientation:
     FORWARD = 1
     FORWARD = 1
     BACKWARD = 2
     BACKWARD = 2
 
 
+
 class ReplayMode:
 class ReplayMode:
     ONESHOT = 1
     ONESHOT = 1
     REVERSE = 2
     REVERSE = 2
     REPEAT = 3
     REPEAT = 3
 
 
-def validateTimeseriesName(timeseries, etype = 'strds'):
+
+def validateTimeseriesName(timeseries, etype='strds'):
     """!Checks if space time dataset exists and completes missing mapset.
     """!Checks if space time dataset exists and completes missing mapset.
 
 
     Raises GException if dataset doesn't exist.
     Raises GException if dataset doesn't exist.
@@ -60,13 +67,13 @@ def validateTimeseriesName(timeseries, etype = 'strds'):
         else:
         else:
             raise GException(_("Space time dataset <%s> not found.") % timeseries)
             raise GException(_("Space time dataset <%s> not found.") % timeseries)
 
 
-
     for mapset, names in trastDict.iteritems():
     for mapset, names in trastDict.iteritems():
         if timeseries in names:
         if timeseries in names:
             return timeseries + "@" + mapset
             return timeseries + "@" + mapset
 
 
     raise GException(_("Space time dataset <%s> not found.") % timeseries)
     raise GException(_("Space time dataset <%s> not found.") % timeseries)
 
 
+
 def validateMapNames(names, etype):
 def validateMapNames(names, etype):
     """!Checks if maps exist and completes missing mapset.
     """!Checks if maps exist and completes missing mapset.
 
 
@@ -95,18 +102,13 @@ def validateMapNames(names, etype):
 
 
 
 
 def getRegisteredMaps(timeseries, etype):
 def getRegisteredMaps(timeseries, etype):
-    """!Returns list of maps registered in dataset"""
+    """!Returns list of maps registered in dataset.
+    Can throw ScriptError if the dataset doesn't exist.
+    """
     timeseriesMaps = []
     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)
+    sp = tgis.open_old_space_time_dataset(timeseries, etype)
+
+    rows = sp.get_registered_maps(columns="id", where=None, order="start_time")
     timeseriesMaps = []
     timeseriesMaps = []
     if rows:
     if rows:
         for row in rows:
         for row in rows:
@@ -114,15 +116,84 @@ def getRegisteredMaps(timeseries, etype):
     return timeseriesMaps
     return timeseriesMaps
 
 
 
 
+def checkSeriesCompatibility(mapSeriesList=None, timeseriesList=None):
+    """!Checks whether time series (map series and stds) are compatible,
+        which means they have equal number of maps ad times (in case of stds).
+        This is needed within layer list, not within the entire animation tool.
+        Throws GException if these are incompatible.
+
+        @return number of maps for animation
+    """
+    timeseriesInfo = {'count': set(), 'temporalType': set(), 'mapType': set(),
+                      'mapTimes': set()}
+
+    if timeseriesList:
+        for stds, etype in timeseriesList:
+            sp = tgis.open_old_space_time_dataset(stds, etype)
+            mapType = sp.get_map_time()  # interval, ...
+            tempType = sp.get_initial_values()[0]  # absolute
+            timeseriesInfo['mapType'].add(mapType)
+            timeseriesInfo['temporalType'].add(tempType)
+            rows = sp.get_registered_maps_as_objects(where=None,
+                                                     order="start_time")
+
+            if rows:
+                times = []
+                timeseriesInfo['count'].add(len(rows))
+                for row in rows:
+                    if tempType == 'absolute':
+                        time = row.get_absolute_time()
+                    else:
+                        time = row.get_relative_time()
+                    times.append(time)
+                timeseriesInfo['mapTimes'].add(tuple(times))
+            else:
+                timeseriesInfo['mapTimes'].add(None)
+                timeseriesInfo['count'].add(None)
+
+    if len(timeseriesInfo['count']) > 1:
+        raise GException(_("The number of maps in space-time datasets "
+                           "has to be the same."))
+
+    if len(timeseriesInfo['temporalType']) > 1:
+        raise GException(_("The temporal type (absolute/relative) of space-time datasets "
+                           "has to be the same."))
+
+    if len(timeseriesInfo['mapType']) > 1:
+        raise GException(_("The map type (point/interval) of space-time datasets "
+                           "has to be the same."))
+
+    if len(timeseriesInfo['mapTimes']) > 1:
+        raise GException(_("The temporal extents of maps in space-time datasets "
+                           "have to be the same."))
+
+    if mapSeriesList:
+        count = set()
+        for mapSeries in mapSeriesList:
+            count.add(len(mapSeries))
+        if len(count) > 1:
+            raise GException(_("The number of maps to animate has to be "
+                               "the same for each map series."))
+
+        if timeseriesList and list(count)[0] != list(timeseriesInfo['count'])[0]:
+            raise GException(_("The number of maps to animate has to be "
+                               "the same as the number of maps in temporal dataset."))
+
+    if mapSeriesList:
+        return list(count)[0]
+    if timeseriesList:
+        return list(timeseriesInfo['count'])[0]
+
+
 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.
 
 
     @code
     @code
-   
+
     >>> ComputeScaledRect(sourceSize = (10, 40), destSize = (100, 50))
     >>> ComputeScaledRect(sourceSize = (10, 40), destSize = (100, 50))
     {'height': 50, 'scale': 1.25, 'width': 13, 'x': 44, 'y': 0}
     {'height': 50, 'scale': 1.25, 'width': 13, 'x': 44, 'y': 0}
-    
+
     @endcode
     @endcode
 
 
     @param sourceSize size of source rectangle
     @param sourceSize size of source rectangle
@@ -145,6 +216,7 @@ def ComputeScaledRect(sourceSize, destSize):
 
 
     return {'width': width, 'height': height, 'x': x, 'y': y, 'scale': scale}
     return {'width': width, 'height': height, 'x': x, 'y': y, 'scale': scale}
 
 
+
 def RenderText(text, font):
 def RenderText(text, font):
     """!Renderes text with given font to bitmap."""
     """!Renderes text with given font to bitmap."""
     dc = wx.MemoryDC()
     dc = wx.MemoryDC()
@@ -160,8 +232,80 @@ def RenderText(text, font):
 
 
     return bmp
     return bmp
 
 
+
 def WxImageToPil(image):
 def WxImageToPil(image):
     """!Converts wx.Image to PIL image"""
     """!Converts wx.Image to PIL image"""
-    pilImage = Image.new( 'RGB', (image.GetWidth(), image.GetHeight()) )
-    pilImage.fromstring( image.GetData() )
+    pilImage = Image.new('RGB', (image.GetWidth(), image.GetHeight()))
+    pilImage.fromstring(image.GetData())
     return pilImage
     return pilImage
+
+
+def HashCmd(cmd):
+    """!Returns a hash from command given as a list."""
+    name = '_'.join(cmd)
+    return hashlib.sha1(name).hexdigest()
+
+
+def HashCmds(cmds):
+    """!Returns a hash from list of commands."""
+    name = '_'.join([item for sublist in cmds for item in sublist])
+    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 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 layerListToCmdsMatrix(layerList):
+    """!Goes thru layerList and create matrix of commands
+    for the composition of map series.:
+
+    @returns matrix of cmds for composition
+    """
+    count = 0
+    for layer in layerList:
+        if layer.active and hasattr(layer, 'maps'):
+            # assuming the consistency of map number is checked already
+            count = len(layer.maps)
+            break
+    cmdsForComposition = []
+    for layer in layerList:
+        if not layer.active:
+            continue
+        if hasattr(layer, 'maps'):
+            for i, part in enumerate(layer.cmd):
+                if part.startswith('map='):
+                    cmd = layer.cmd[:]
+                    cmds = []
+                    for map_ in layer.maps:
+                        cmd[i] = 'map={}'.format(map_)
+                        cmds.append(cmd[:])
+                    cmdsForComposition.append(cmds)
+        else:
+            cmdsForComposition.append([layer.cmd] * count)
+
+    return zip(*cmdsForComposition)
+
+
+def sampleCmdMatrixAndCreateNames(cmdMatrix, sampledSeries):
+    """!Applies information from temporal sampling
+    to the command matrix."""
+    namesList = []
+    j = -1
+    lastName = ''
+    for name in sampledSeries:
+        if name is not None:
+            if lastName != name:
+                lastName = name
+                j += 1
+            namesList.append(HashCmds(cmdMatrix[j]))
+        else:
+            namesList.append(None)
+    assert(j == len(cmdMatrix) - 1)
+    return namesList

+ 1 - 1
gui/wxpython/core/layerlist.py

@@ -191,7 +191,7 @@ class Layer(object):
 
 
         self._mapTypes = ['rast', 'vect', 'rast3d']
         self._mapTypes = ['rast', 'vect', 'rast3d']
         self._internalTypes = {'rast': 'cell',
         self._internalTypes = {'rast': 'cell',
-                               'vect': 'vect',
+                               'vect': 'vector',
                                'rast3d': 'grid3'}
                                'rast3d': 'grid3'}
 
 
     def GetName(self):
     def GetName(self):

+ 20 - 12
gui/wxpython/gui_core/simplelmgr.py

@@ -49,7 +49,8 @@ class SimpleLayerManager(wx.Panel):
     """!Simple layer manager class provides similar functionality to
     """!Simple layer manager class provides similar functionality to
     Layertree, but it's just list, not tree."""
     Layertree, but it's just list, not tree."""
     def __init__(self, parent, layerList,
     def __init__(self, parent, layerList,
-                 lmgrStyle=SIMPLE_LMGR_RASTER | SIMPLE_LMGR_VECTOR | SIMPLE_LMGR_TB_LEFT):
+                 lmgrStyle=SIMPLE_LMGR_RASTER | SIMPLE_LMGR_VECTOR | SIMPLE_LMGR_TB_LEFT,
+                 toolbarCls=None, modal=False):
         wx.Panel.__init__(self, parent=parent, name='SimpleLayerManager')
         wx.Panel.__init__(self, parent=parent, name='SimpleLayerManager')
 
 
         self._style = lmgrStyle
         self._style = lmgrStyle
@@ -57,9 +58,14 @@ class SimpleLayerManager(wx.Panel):
         self._checkList = wx.CheckListBox(self, style=wx.LB_EXTENDED)
         self._checkList = wx.CheckListBox(self, style=wx.LB_EXTENDED)
         # dialog windows held separately
         # dialog windows held separately
         self._dialogs = {}
         self._dialogs = {}
-        self._toolbar = SimpleLmgrToolbar(self, lmgrStyle=self._style)
+        if not toolbarCls:
+            toolbarCls = SimpleLmgrToolbar
+        self._toolbar = toolbarCls(self, lmgrStyle=self._style)
 
 
         self._auimgr = wx.aui.AuiManager(self)
         self._auimgr = wx.aui.AuiManager(self)
+        
+        self._modal = modal
+        
 
 
         # needed in order not to change selection when moving layers
         # needed in order not to change selection when moving layers
         self._blockSelectionChanged = False
         self._blockSelectionChanged = False
@@ -81,6 +87,7 @@ class SimpleLayerManager(wx.Panel):
 
 
         self._layout()
         self._layout()
         self.SetMinSize((200, -1))
         self.SetMinSize((200, -1))
+        self._update()
 
 
     def _layout(self):
     def _layout(self):
         self._auimgr.AddPane(self._checkList,
         self._auimgr.AddPane(self._checkList,
@@ -132,16 +139,17 @@ class SimpleLayerManager(wx.Panel):
         Dummy layer is added first."""
         Dummy layer is added first."""
         cmd = ['d.rast']
         cmd = ['d.rast']
         layer = self.AddRaster(name='', cmd=cmd, hidden=True, dialog=None)
         layer = self.AddRaster(name='', cmd=cmd, hidden=True, dialog=None)
-        GUI(parent=self, giface=None, modal=True).ParseCommand(cmd=cmd,
-                                                   completed=(self.GetOptData, layer, ''))
+        GUI(parent=self, giface=None, modal=self._modal).ParseCommand(cmd=cmd,
+                                                    completed=(self.GetOptData, layer, ''))
         event.Skip()
         event.Skip()
 
 
     def OnAddVector(self, event):
     def OnAddVector(self, event):
         """!Opens d.vect dialog and adds layer.
         """!Opens d.vect dialog and adds layer.
         Dummy layer is added first."""
         Dummy layer is added first."""
         cmd = ['d.vect']
         cmd = ['d.vect']
+
         layer = self.AddVector(name='', cmd=cmd, hidden=True, dialog=None)
         layer = self.AddVector(name='', cmd=cmd, hidden=True, dialog=None)
-        GUI(parent=self, giface=None, modal=True).ParseCommand(cmd=cmd,
+        GUI(parent=self, giface=None, modal=self._modal).ParseCommand(cmd=cmd,
                                                    completed=(self.GetOptData, layer, ''))
                                                    completed=(self.GetOptData, layer, ''))
         event.Skip()
         event.Skip()
 
 
@@ -150,7 +158,7 @@ class SimpleLayerManager(wx.Panel):
         Dummy layer is added first."""
         Dummy layer is added first."""
         cmd = ['d.rast3d']
         cmd = ['d.rast3d']
         layer = self.AddRast3d(name='', cmd=cmd, hidden=True, dialog=None)
         layer = self.AddRast3d(name='', cmd=cmd, hidden=True, dialog=None)
-        GUI(parent=self, giface=None, modal=True).ParseCommand(cmd=cmd,
+        GUI(parent=self, giface=None, modal=self._modal).ParseCommand(cmd=cmd,
                                                    completed=(self.GetOptData, layer, ''))
                                                    completed=(self.GetOptData, layer, ''))
         event.Skip()
         event.Skip()
 
 
@@ -160,7 +168,7 @@ class SimpleLayerManager(wx.Panel):
         for layer in layers:
         for layer in layers:
             self.layerRemoved.emit(index=self._layerList.GetLayerIndex(layer), layer=layer)
             self.layerRemoved.emit(index=self._layerList.GetLayerIndex(layer), layer=layer)
             self._layerList.RemoveLayer(layer)
             self._layerList.RemoveLayer(layer)
-            if self._dialogs[layer]:
+            if layer in self._dialogs and self._dialogs[layer]:
                 self._dialogs[layer].Destroy()
                 self._dialogs[layer].Destroy()
         self._update()
         self._update()
         self.anyChange.emit()
         self.anyChange.emit()
@@ -208,8 +216,8 @@ class SimpleLayerManager(wx.Panel):
 
 
     def _layerChangeProperties(self, layer):
     def _layerChangeProperties(self, layer):
         """!Opens new module dialog or recycles it."""
         """!Opens new module dialog or recycles it."""
-        dlg = self._dialogs[layer]
-        if dlg is not None:
+        if layer in self._dialogs:
+            dlg = self._dialogs[layer]
             if dlg.IsShown():
             if dlg.IsShown():
                 dlg.Raise()
                 dlg.Raise()
                 dlg.SetFocus()
                 dlg.SetFocus()
@@ -217,7 +225,7 @@ class SimpleLayerManager(wx.Panel):
                 dlg.Show()
                 dlg.Show()
         else:
         else:
             GUI(parent=self, giface=None,
             GUI(parent=self, giface=None,
-                modal=False).ParseCommand(cmd=layer.cmd,
+                modal=self._modal).ParseCommand(cmd=layer.cmd,
                                           completed=(self.GetOptData, layer, ''))
                                           completed=(self.GetOptData, layer, ''))
 
 
     def OnLayerChangeOpacity(self, event):
     def OnLayerChangeOpacity(self, event):
@@ -351,7 +359,7 @@ class SimpleLmgrToolbar(BaseToolbar):
             direction = wx.TB_HORIZONTAL
             direction = wx.TB_HORIZONTAL
         BaseToolbar.__init__(self, parent, style=wx.NO_BORDER | direction)
         BaseToolbar.__init__(self, parent, style=wx.NO_BORDER | direction)
 
 
-        self.InitToolbar(self._toolbarData())
+        self.InitToolbar(self._getToolbarData(self._toolbarData()))
 
 
         # realize the toolbar
         # realize the toolbar
         self.Realize()
         self.Realize()
@@ -381,7 +389,7 @@ class SimpleLmgrToolbar(BaseToolbar):
             data.insert(0, ('addRaster', BaseIcons['addRast'],
             data.insert(0, ('addRaster', BaseIcons['addRast'],
                             self.parent.OnAddRaster))
                             self.parent.OnAddRaster))
 
 
-        return self._getToolbarData(data)
+        return data
 
 
 
 
 icons = {
 icons = {

+ 9 - 2
gui/wxpython/lmgr/frame.py

@@ -1468,8 +1468,15 @@ class GMFrame(wx.Frame):
                 if tree.GetLayerInfo(layer, key = 'type') == 'raster':
                 if tree.GetLayerInfo(layer, key = 'type') == 'raster':
                     rasters.append(tree.GetLayerInfo(layer, key = 'maplayer').GetName())
                     rasters.append(tree.GetLayerInfo(layer, key = 'maplayer').GetName())
             if len(rasters) >= 2:
             if len(rasters) >= 2:
-                frame.SetAnimations(raster = [rasters, None, None, None])
-
+                from core.layerlist import LayerList
+                from animation.data import AnimLayer
+                layerList = LayerList()
+                layer = AnimLayer()
+                layer.mapType = 'rast'
+                layer.name = ','.join(rasters)
+                layer.cmd = ['d.rast', 'map=']
+                layerList.AddLayer(layer)
+                frame.SetAnimations([layerList, None, None, None])
 
 
     def OnHistogram(self, event):
     def OnHistogram(self, event):
         """!Init histogram display canvas and tools
         """!Init histogram display canvas and tools