[GRASS-SVN] r58343 - in grass/trunk/gui: icons/grass wxpython/animation wxpython/core wxpython/gui_core wxpython/lmgr

svn_grass at osgeo.org svn_grass at osgeo.org
Sat Nov 30 19:46:14 PST 2013


Author: annakrat
Date: 2013-11-30 19:46:14 -0800 (Sat, 30 Nov 2013)
New Revision: 58343

Added:
   grass/trunk/gui/icons/grass/mapset-add.png
   grass/trunk/gui/wxpython/animation/data.py
   grass/trunk/gui/wxpython/animation/provider.py
Modified:
   grass/trunk/gui/wxpython/animation/__init__.py
   grass/trunk/gui/wxpython/animation/anim.py
   grass/trunk/gui/wxpython/animation/controller.py
   grass/trunk/gui/wxpython/animation/dialogs.py
   grass/trunk/gui/wxpython/animation/frame.py
   grass/trunk/gui/wxpython/animation/g.gui.animation.py
   grass/trunk/gui/wxpython/animation/mapwindow.py
   grass/trunk/gui/wxpython/animation/nviztask.py
   grass/trunk/gui/wxpython/animation/temporal_manager.py
   grass/trunk/gui/wxpython/animation/toolbars.py
   grass/trunk/gui/wxpython/animation/utils.py
   grass/trunk/gui/wxpython/core/layerlist.py
   grass/trunk/gui/wxpython/gui_core/simplelmgr.py
   grass/trunk/gui/wxpython/lmgr/frame.py
Log:
wxGUI/animation: adding support for multiple base layers/series; pep8 compliance

Added: grass/trunk/gui/icons/grass/mapset-add.png
===================================================================
(Binary files differ)


Property changes on: grass/trunk/gui/icons/grass/mapset-add.png
___________________________________________________________________
Added: svn:mime-type
   + image/png

Modified: grass/trunk/gui/wxpython/animation/__init__.py
===================================================================
--- grass/trunk/gui/wxpython/animation/__init__.py	2013-11-30 19:48:36 UTC (rev 58342)
+++ grass/trunk/gui/wxpython/animation/__init__.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -9,4 +9,4 @@
     'toolbars',
     'utils',
     'frame',
-    ]
+]

Modified: grass/trunk/gui/wxpython/animation/anim.py
===================================================================
--- grass/trunk/gui/wxpython/animation/anim.py	2013-11-30 19:48:36 UTC (rev 58342)
+++ grass/trunk/gui/wxpython/animation/anim.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -6,18 +6,19 @@
 Classes:
  - 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
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
- at author Anna Kratochvilova <kratochanna gmail.com>
+ at author Anna Petrasova <kratochanna gmail.com>
 """
 
 import wx
 from utils import Orientation, ReplayMode
 from core.utils import _
 
+
 class Animation(wx.EvtHandler):
     """!Animation class specifies which frame to show at which instance."""
     def __init__(self):
@@ -28,7 +29,7 @@
         # states
         self.orientation = Orientation.FORWARD
         self.replayMode = ReplayMode.ONESHOT
-        
+
         self.callbackUpdateFrame = None
         self.callbackEndAnimation = None
         self.callbackOrientationChanged = None
@@ -59,7 +60,7 @@
         """!Get frame count."""
         return len(self.frames)
 
-    count = property(fget = GetCount)
+    count = property(fget=GetCount)
 
     def GetReplayMode(self):
         """!Returns replay mode (loop)."""
@@ -68,7 +69,7 @@
     def SetReplayMode(self, mode):
         self._replayMode = mode
 
-    replayMode = property(fset = SetReplayMode, fget = GetReplayMode)
+    replayMode = property(fset=SetReplayMode, fget=GetReplayMode)
 
     def GetOrientation(self):
         return self._orientation
@@ -76,7 +77,7 @@
     def SetOrientation(self, mode):
         self._orientation = mode
 
-    orientation = property(fset = SetOrientation, fget = GetOrientation)
+    orientation = property(fset=SetOrientation, fget=GetOrientation)
 
     def SetCallbackUpdateFrame(self, callback):
         """!Sets function to be called when updating frame."""
@@ -92,7 +93,7 @@
 
     def Start(self):
         if not self.IsActive():
-            return 
+            return
 
     def Pause(self, paused):
         if not self.IsActive():
@@ -116,14 +117,14 @@
                 self.currentIndex = 0
             elif self.replayMode == ReplayMode.REVERSE:
                 self.orientation = Orientation.BACKWARD
-                self.currentIndex = self.count - 2 # -1
+                self.currentIndex = self.count - 2  # -1
                 self.callbackOrientationChanged(Orientation.BACKWARD)
         else:
             if self.replayMode == ReplayMode.REPEAT:
                 self.currentIndex = self.count - 1
             elif self.replayMode == ReplayMode.REVERSE:
                 self.orientation = Orientation.FORWARD
-                self.currentIndex = 1 # 0
+                self.currentIndex = 1  # 0
                 self.callbackOrientationChanged(Orientation.FORWARD)
 
     def Update(self):
@@ -140,7 +141,7 @@
             self.currentIndex -= 1
             if self.currentIndex == -1:
                 self._arrivedToEnd()
-                
+
     def FrameChangedFromOutside(self, index):
         """!Let the animation know that frame was changed from outside."""
         if not self.IsActive():
@@ -188,5 +189,3 @@
 #
 #if __name__ == '__main__':
 #    test()
-
-

Modified: grass/trunk/gui/wxpython/animation/controller.py
===================================================================
--- grass/trunk/gui/wxpython/animation/controller.py	2013-11-30 19:48:36 UTC (rev 58342)
+++ grass/trunk/gui/wxpython/animation/controller.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -6,12 +6,12 @@
 Classes:
  - 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
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
- at author Anna Kratochvilova <kratochanna gmail.com>
+ at author Anna Petrasova <kratochanna gmail.com>
 """
 import os
 import wx
@@ -20,12 +20,15 @@
 from core.utils import _
 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):
-    def __init__(self, frame, sliders, animations, mapwindows, providers, bitmapPool):
+    def __init__(self, frame, sliders, animations, mapwindows, provider, bitmapPool, mapFilesPool):
         wx.EvtHandler.__init__(self)
 
         self.mapwindows = mapwindows
@@ -38,14 +41,17 @@
         self.temporalMode = None
         self.animationData = []
 
-        self.timer = wx.Timer(self, id = wx.NewId())
+        self.timer = wx.Timer(self, id=wx.NewId())
 
         self.animations = animations
         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)
 
         for slider in self.sliders.values():
@@ -75,8 +81,8 @@
             self.timer.Start(self._timeTick)
         self.DisableSliderIfNeeded()
 
-    timeTick = property(fget = GetTimeTick, fset = SetTimeTick)
-        
+    timeTick = property(fget=GetTimeTick, fset=SetTimeTick)
+
     def OnTimerTick(self, event):
         for anim in self.animations:
             anim.Update()
@@ -102,7 +108,7 @@
             if not self.timer.IsRunning():
                 self.timer.Start(self.timeTick)
                 self.DisableSliderIfNeeded()
-        
+
         for anim in self.animations:
             anim.Pause(paused)
 
@@ -114,20 +120,20 @@
         for anim in self.animations:
             anim.Stop()
 
-    def UpdateFrameEnd(self, index, win, provider, dataId):
+    def UpdateFrameEnd(self, index, win, dataId):
         if self.timer.IsRunning():
             self.timer.Stop()
             self.DisableSliderIfNeeded()
 
         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:
             dataId = ''
-        win.DrawBitmap(bitmap, dataId)
+        win.DrawBitmap(bitmap)
         # self.frame.SetStatusText(dataId)
         self.slider.UpdateFrame(index)
 
@@ -151,7 +157,6 @@
             self.slider.EnableSlider(False)
         else:
             self.slider.EnableSlider(True)
-            
 
     def OrientationChangedInReverseMode(self, mode):
         if mode == Orientation.FORWARD:
@@ -167,14 +172,13 @@
         for anim in self.animations:
             anim.orientation = mode
 
-
     def SetTemporalMode(self, mode):
         self._temporalMode = mode
 
     def GetTemporalMode(self):
         return self._temporalMode
 
-    temporalMode = property(fget = GetTemporalMode, fset = SetTemporalMode)
+    temporalMode = property(fget=GetTemporalMode, fset=SetTemporalMode)
 
     def GetTimeGranularity(self):
         if self.temporalMode == TemporalMode.TEMPORAL:
@@ -187,9 +191,8 @@
         # if self.timer.IsRunning():
         #     running = True
         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:
             dlg.Destroy()
             return
@@ -206,9 +209,10 @@
             if windowIndex not in indices:
                 found = True
                 break
-                
+
         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
 
         # running = False
@@ -221,20 +225,20 @@
         # number of active animations
         animationIndex = len([anim for anim in self.animations if anim.IsActive()])
         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:
             dlg.Destroy()
             return
         dlg.Destroy()
         # check compatibility
         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
         try:
             temporalMode, tempManager = self.EvaluateInput(self.animationData + [animData])
         except GException, e:
-            GError(parent = self.frame, message = e.value, showTraceback = False)
+            GError(parent=self.frame, message=e.value, showTraceback=False)
             return
         # if ok, set temporal mode
         self.temporalMode = temporalMode
@@ -244,41 +248,28 @@
         self.animationData.append(animData)
         self._setAnimations()
 
-    def SetAnimations(self, inputs=None, dataType=None):
+    def SetAnimations(self, layerLists):
         """!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:
             animationData = []
             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:
-            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
         try:
             temporalMode, tempManager = self.EvaluateInput(animationData)
         except GException, e:
-            GError(parent = self.frame, message = e.value, showTraceback = False)
+            GError(parent=self.frame, message=e.value, showTraceback=False)
             return
         self.animationData = animationData
         self.temporalManager = tempManager
@@ -288,15 +279,19 @@
     def _setAnimations(self):
         indices = [anim.windowIndex for anim in self.animationData]
 
-        self._updateWindows(activeIndices = indices)
+        self._updateWindows(activeIndices=indices)
 
         if self.temporalMode == TemporalMode.TEMPORAL:
             timeLabels, mapNamesDict = self.temporalManager.GetLabelsAndMaps()
         else:
             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()
         self._updateBitmapData()
         # if running:
@@ -305,11 +300,11 @@
         # else:
         self.EndAnimation()
 
-    def _updateSlider(self, timeLabels = None):
+    def _updateSlider(self, timeLabels=None):
         if self.temporalMode == TemporalMode.NONTEMPORAL:
             self.frame.SetSlider('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)
         elif self.temporalMode == TemporalMode.TEMPORAL:
             self.frame.SetSlider('temporal')
@@ -320,14 +315,14 @@
             self.frame.SetSlider(None)
             self.slider = None
 
-    def _updateAnimations(self, activeIndices, mapNamesDict = None):
+    def _updateAnimations(self, activeIndices, mapNamesDict=None):
         if self.temporalMode == TemporalMode.NONTEMPORAL:
             for i in range(len(self.animations)):
                 if i not in activeIndices:
                     self.animations[i].SetActive(False)
                     continue
                 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)
         else:
             for i in range(len(self.animations)):
@@ -335,7 +330,9 @@
                     self.animations[i].SetActive(False)
                     continue
                 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)
 
     def _updateWindows(self, activeIndices):
@@ -347,48 +344,33 @@
                 self.frame.RemoveWindow(windowIndex)
 
     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:
             if animData.viewMode == '2d':
-                self._load2DData(animData)
+                self._set2DData(animData)
             else:
                 self._load3DData(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 _set2DData(self, animationData):
+        opacities = [layer.opacity for layer in animationData.layerList if layer.active]
+        self.bitmapProvider.SetCmds(animationData.cmdMatrix, opacities)
 
-    def _load2DData(self, animationData):
-        prov = self.bitmapProviders[animationData.windowIndex]
-        prov.SetData(datasource = animationData.mapData, dataType=animationData.inputMapType)
-
-        prov.Load()
-
     def _load3DData(self, animationData):
-        prov = self.bitmapProviders[animationData.windowIndex]
         nviz = animationData.GetNvizCommands()
-        prov.SetData(datasource = nviz['commands'], 
-                     dataNames = animationData.mapData, dataType = 'nviz',
-                     suffix = animationData.nvizParameter,
-                     nvizRegion = nviz['region'])
+        self.bitmapProvider.SetCmds3D(nviz['commands'], nviz['region'])
 
-        self.bitmapProviders[animationData.windowIndex].Load()
-
     def _loadLegend(self, animationData):
         if animationData.legendCmd:
-            prov = self.bitmapProviders[animationData.windowIndex]
             try:
-                bitmap = prov.LoadOverlay(animationData.legendCmd)
-                # place legend
+                bitmap = self.bitmapProvider.LoadOverlay(animationData.legendCmd)
                 try:
                     from PIL import Image
                     for param in animationData.legendCmd:
@@ -411,15 +393,15 @@
         tempManager = None
         windowIndex = []
         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)
 
-            if anim.inputMapType in ('rast', 'vect'):
-                maps += 1
-            elif anim.inputMapType in ('strds', 'stvds'):
-                stds += 1
-
         if maps and stds:
             temporalMode = TemporalMode.NONTEMPORAL
         elif maps:
@@ -436,27 +418,24 @@
             tempManager = TemporalManager()
             # these raise GException:
             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()
             if message:
-                GMessage(parent = self.frame, message = message)
+                GMessage(parent=self.frame, message=message)
 
         return temporalMode, tempManager
 
     def Reload(self):
         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()
 
     def Export(self):
         if not self.animationData:
-            GMessage(parent = self.frame, message = _("No animation to export."))
+            GMessage(parent=self.frame, message=_("No animation to export."))
             return
 
         if 'export' in self._dialogs:
@@ -468,14 +447,14 @@
             dlg.doExport.connect(self._export)
             self._dialogs['export'] = dlg
             dlg.Show()
-        
+
     def _export(self, exportInfo, decorations):
         size = self.frame.animationPanel.GetSize()
         if self.temporalMode == TemporalMode.TEMPORAL:
             timeLabels, mapNamesDict = self.temporalManager.GetLabelsAndMaps()
             frameCount = len(timeLabels)
         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 = []
         animWinPos = []
@@ -488,7 +467,7 @@
                 animWinPos.append(pos)
                 animWinSize.append(win.GetSize())
                 animWinIndex.append(i)
-        
+
         images = []
         busy = wx.BusyInfo(message=_("Preparing export, please wait..."), parent=self.frame)
         wx.Yield()
@@ -498,13 +477,13 @@
             # collect bitmaps of all windows and paste them into the one
             for i in animWinIndex:
                 frameId = self.animations[i].GetFrame(frameIndex)
-                bitmap = self.bitmapProviders[i].GetBitmap(frameId)
+                bitmap = self.bitmapProvider.GetBitmap(frameId)
                 im = wx.ImageFromBitmap(bitmap)
 
                 # add legend if used
                 legend = legends[i]
                 if legend:
-                    legendBitmap = self.bitmapProviders[i].LoadOverlay(legend)
+                    legendBitmap = self.bitmapProvider.LoadOverlay(legend)
                     x, y = self.mapwindows[i].GetOverlayPos()
                     legImage = wx.ImageFromBitmap(legendBitmap)
                     # not so nice result, can we handle the transparency otherwise?
@@ -525,11 +504,11 @@
                     timeLabel = timeLabels[frameIndex]
                     if timeLabel[1]:
                         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:
                         text = _("%(start)s %(unit)s") % \
-                                {'start': timeLabel[0], 'unit': timeLabel[2]}
-                    
+                            {'start': timeLabel[0], 'unit': timeLabel[2]}
+
                     decImage = RenderText(text, decoration['font']).ConvertToImage()
                 elif decoration['name'] == 'text':
                     text = decoration['text']
@@ -552,42 +531,17 @@
                 writeIms(filename=filename, images=pilImages)
             elif exportInfo['method'] == 'gif':
                 writeGif(filename=exportInfo['file'], images=pilImages,
-                            duration=self.timeTick / float(1000), repeat=True)
+                         duration=self.timeTick / float(1000), repeat=True)
             elif exportInfo['method'] == 'swf':
                 writeSwf(filename=exportInfo['file'], images=pilImages,
-                            duration=self.timeTick / float(1000), repeat=True)
+                         duration=self.timeTick / float(1000), repeat=True)
             elif exportInfo['method'] == 'avi':
                 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:
             del busy
             GError(parent=self.frame, message=str(e))
             return
         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()

Added: grass/trunk/gui/wxpython/animation/data.py
===================================================================
--- grass/trunk/gui/wxpython/animation/data.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/animation/data.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -0,0 +1,219 @@
+# -*- coding: utf-8 -*-
+"""!
+ at package animation.data
+
+ at 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.
+
+ at 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)


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

Modified: grass/trunk/gui/wxpython/animation/dialogs.py
===================================================================
--- grass/trunk/gui/wxpython/animation/dialogs.py	2013-11-30 19:48:36 UTC (rev 58342)
+++ grass/trunk/gui/wxpython/animation/dialogs.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -7,16 +7,17 @@
  - dialogs::SpeedDialog
  - dialogs::InputDialog
  - dialogs::EditDialog
- - dialogs::AnimationData
  - dialogs::ExportDialog
+ - dialogs::AnimSimpleLayerManager
+ - dialogs::AddTemporalLayerDialog
 
 
-(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
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
- at author Anna Kratochvilova <kratochanna gmail.com>
+ at author Anna Petrasova <kratochanna gmail.com>
 """
 import os
 import sys
@@ -30,25 +31,28 @@
 
 from core.gcmd import GMessage, GError, GException
 from core import globalvar
-from gui_core import gselect
 from gui_core.dialogs import MapLayersDialog, GetImageHandlers
 from gui_core.forms import GUI
 from core.settings import UserSettings
 from core.utils import _
+from gui_core.gselect import Select
 
-from utils import TemporalMode, getRegisteredMaps, validateTimeseriesName, validateMapNames
-from nviztask import NvizTask
+from animation.utils import TemporalMode, getRegisteredMaps
+from animation.data import AnimationData, AnimLayer
+from animation.toolbars import AnimSimpleLmgrToolbar, SIMPLE_LMGR_STDS
+from gui_core.simplelmgr import SimpleLayerManager, \
+    SIMPLE_LMGR_RASTER, SIMPLE_LMGR_VECTOR, SIMPLE_LMGR_TB_TOP
 
 from grass.pydispatch.signal import Signal
 import grass.script.core as gcore
 
 
 class SpeedDialog(wx.Dialog):
-    def __init__(self, parent, title = _("Adjust speed of animation"),
-                 temporalMode = None, minimumDuration = 20, timeGranularity = None,
-                 initialSpeed = 200):#, framesCount = None
-        wx.Dialog.__init__(self, parent = parent, id = wx.ID_ANY, title = title,
-                           style = wx.DEFAULT_DIALOG_STYLE)
+    def __init__(self, parent, title=_("Adjust speed of animation"),
+                 temporalMode=None, minimumDuration=20, timeGranularity=None,
+                 initialSpeed=200):
+        wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY, title=title,
+                           style=wx.DEFAULT_DIALOG_STYLE)
         # signal emitted when speed has changed; has attribute 'ms'
         self.speedChanged = Signal('SpeedDialog.speedChanged')
         self.minimumDuration = minimumDuration
@@ -71,7 +75,7 @@
     def GetTimeGranularity(self):
         return self._timeGranularity
 
-    timeGranularity = property(fset = SetTimeGranularity, fget = GetTimeGranularity)
+    timeGranularity = property(fset=SetTimeGranularity, fget=GetTimeGranularity)
 
     def SetTemporalMode(self, mode):
         self._temporalMode = mode
@@ -80,7 +84,7 @@
     def GetTemporalMode(self):
         return self._temporalMode
 
-    temporalMode = property(fset = SetTemporalMode, fget = GetTemporalMode)
+    temporalMode = property(fset=SetTemporalMode, fget=GetTemporalMode)
 
     def _layout(self):
         """!Layout window"""
@@ -88,53 +92,53 @@
         #
         # simple mode
         #
-        self.nontemporalBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
-                                           label = ' %s ' % _("Simple mode"))
+        self.nontemporalBox = wx.StaticBox(parent=self, id=wx.ID_ANY,
+                                           label=' %s ' % _("Simple mode"))
         box = wx.StaticBoxSizer(self.nontemporalBox, wx.VERTICAL)
-        gridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
+        gridSizer = wx.GridBagSizer(hgap=5, vgap=5)
 
-        labelDuration = wx.StaticText(self, id = wx.ID_ANY, label = _("Frame duration:"))
-        labelUnits = wx.StaticText(self, id = wx.ID_ANY, label = _("ms"))
-        self.spinDuration = wx.SpinCtrl(self, id = wx.ID_ANY, min = self.minimumDuration, 
-                                        max = 10000, initial = self.defaultSpeed)
+        labelDuration = wx.StaticText(self, id=wx.ID_ANY, label=_("Frame duration:"))
+        labelUnits = wx.StaticText(self, id=wx.ID_ANY, label=_("ms"))
+        self.spinDuration = wx.SpinCtrl(self, id=wx.ID_ANY, min=self.minimumDuration,
+                                        max=10000, initial=self.defaultSpeed)
         # TODO total time
 
-        gridSizer.Add(item = labelDuration, pos = (0, 0), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
-        gridSizer.Add(item = self.spinDuration, pos = (0, 1), flag = wx.ALIGN_CENTER)
-        gridSizer.Add(item = labelUnits, pos = (0, 2), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
+        gridSizer.Add(item=labelDuration, pos=(0, 0), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
+        gridSizer.Add(item=self.spinDuration, pos=(0, 1), flag = wx.ALIGN_CENTER)
+        gridSizer.Add(item=labelUnits, pos=(0, 2), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
         gridSizer.AddGrowableCol(0)
 
-        box.Add(item = gridSizer, proportion = 1, border = 5, flag = wx.ALL | wx.EXPAND)
+        box.Add(item=gridSizer, proportion=1, border=5, flag=wx.ALL | wx.EXPAND)
         self.nontemporalSizer = gridSizer
-        mainSizer.Add(item = box, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 5)
+        mainSizer.Add(item=box, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
         #
         # temporal mode
         #
-        self.temporalBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
-                                        label = ' %s ' % _("Temporal mode"))
+        self.temporalBox = wx.StaticBox(parent=self, id=wx.ID_ANY,
+                                        label=' %s ' % _("Temporal mode"))
         box = wx.StaticBoxSizer(self.temporalBox, wx.VERTICAL)
-        gridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
+        gridSizer = wx.GridBagSizer(hgap=5, vgap=5)
 
-        labelTimeUnit = wx.StaticText(self, id = wx.ID_ANY, label = _("Time unit:"))
-        labelDuration = wx.StaticText(self, id = wx.ID_ANY, label = _("Duration of time unit:"))
-        labelUnits = wx.StaticText(self, id = wx.ID_ANY, label = _("ms"))
-        self.spinDurationTemp = wx.SpinCtrl(self, id = wx.ID_ANY, min = self.minimumDuration,
-                                            max = 10000, initial = self.defaultSpeed)
-        self.choiceUnits = wx.Choice(self, id = wx.ID_ANY)
+        labelTimeUnit = wx.StaticText(self, id=wx.ID_ANY, label=_("Time unit:"))
+        labelDuration = wx.StaticText(self, id=wx.ID_ANY, label=_("Duration of time unit:"))
+        labelUnits = wx.StaticText(self, id=wx.ID_ANY, label=_("ms"))
+        self.spinDurationTemp = wx.SpinCtrl(self, id=wx.ID_ANY, min=self.minimumDuration,
+                                            max=10000, initial=self.defaultSpeed)
+        self.choiceUnits = wx.Choice(self, id=wx.ID_ANY)
 
         # TODO total time
 
-        gridSizer.Add(item = labelTimeUnit, pos = (0, 0), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
-        gridSizer.Add(item = self.choiceUnits, pos = (0, 1), flag = wx.ALIGN_CENTER | wx.EXPAND)
-        gridSizer.Add(item = labelDuration, pos = (1, 0), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
-        gridSizer.Add(item = self.spinDurationTemp, pos = (1, 1), flag = wx.ALIGN_CENTER | wx.EXPAND)
-        gridSizer.Add(item = labelUnits, pos = (1, 2), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
+        gridSizer.Add(item=labelTimeUnit, pos=(0, 0), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
+        gridSizer.Add(item=self.choiceUnits, pos=(0, 1), flag = wx.ALIGN_CENTER | wx.EXPAND)
+        gridSizer.Add(item=labelDuration, pos=(1, 0), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
+        gridSizer.Add(item=self.spinDurationTemp, pos=(1, 1), flag = wx.ALIGN_CENTER | wx.EXPAND)
+        gridSizer.Add(item=labelUnits, pos=(1, 2), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
         gridSizer.AddGrowableCol(1)
 
         self.temporalSizer = gridSizer
-        box.Add(item = gridSizer, proportion = 1, border = 5, flag = wx.ALL | wx.EXPAND)
-        mainSizer.Add(item = box, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 5)
-        
+        box.Add(item=gridSizer, proportion=1, border=5, flag=wx.ALL | wx.EXPAND)
+        mainSizer.Add(item=box, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
+
         self.btnOk = wx.Button(self, wx.ID_OK)
         self.btnApply = wx.Button(self, wx.ID_APPLY)
         self.btnCancel = wx.Button(self, wx.ID_CANCEL)
@@ -143,17 +147,17 @@
         self.btnOk.Bind(wx.EVT_BUTTON, self.OnOk)
         self.btnApply.Bind(wx.EVT_BUTTON, self.OnApply)
         self.btnCancel.Bind(wx.EVT_BUTTON, self.OnCancel)
-        self.Bind(wx.EVT_CLOSE,  self.OnCancel)
+        self.Bind(wx.EVT_CLOSE, self.OnCancel)
         # button sizer
         btnStdSizer = wx.StdDialogButtonSizer()
         btnStdSizer.AddButton(self.btnOk)
         btnStdSizer.AddButton(self.btnApply)
         btnStdSizer.AddButton(self.btnCancel)
         btnStdSizer.Realize()
-        
-        mainSizer.Add(item = btnStdSizer, proportion = 0,
-                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border = 5)
 
+        mainSizer.Add(item=btnStdSizer, proportion=0,
+                      flag=wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border=5)
+
         self.SetSizer(mainSizer)
         mainSizer.Fit(self)
 
@@ -199,12 +203,12 @@
         if self.temporalMode == TemporalMode.TEMPORAL:
             index = self.choiceUnits.GetSelection()
             unit = self.choiceUnits.GetClientData(index)
-            delta = self._timedelta(unit = unit, number = 1)
+            delta = self._timedelta(unit=unit, number=1)
             seconds1 = self._total_seconds(delta)
 
             number, unit = self.timeGranularity
             number = float(number)
-            delta = self._timedelta(unit = unit, number = number)
+            delta = self._timedelta(unit=unit, number=number)
             seconds2 = self._total_seconds(delta)
             value = timeTick
             ms = value * seconds1 / float(seconds2)
@@ -219,39 +223,38 @@
         elif self.temporalMode == TemporalMode.TEMPORAL:
             index = self.choiceUnits.GetSelection()
             unit = self.choiceUnits.GetClientData(index)
-            delta = self._timedelta(unit = unit, number = 1)
+            delta = self._timedelta(unit=unit, number=1)
             seconds1 = self._total_seconds(delta)
 
-
             number, unit = self.timeGranularity
             number = float(number)
-            delta = self._timedelta(unit = unit, number = number)
+            delta = self._timedelta(unit=unit, number=number)
             seconds2 = self._total_seconds(delta)
 
             value = self.spinDurationTemp.GetValue()
             ms = value * seconds2 / float(seconds1)
             if ms < self.minimumDuration:
-                GMessage(parent = self, message = _("Animation speed is too high."))
+                GMessage(parent=self, message=_("Animation speed is too high."))
                 return
             self.lastAppliedValueTemp = self.spinDurationTemp.GetValue()
         else:
             return
 
-        self.speedChanged.emit(ms = ms)
+        self.speedChanged.emit(ms=ms)
 
     def _timedelta(self, unit, number):
         if unit in "years":
-            delta = datetime.timedelta(days = 365.25 * number)
+            delta = datetime.timedelta(days=365.25 * number)
         elif unit in "months":
-            delta = datetime.timedelta(days = 30.4375 * number) # 365.25/12
+            delta = datetime.timedelta(days=30.4375 * number)  # 365.25/12
         elif unit in "days":
-            delta = datetime.timedelta(days = 1 * number)
+            delta = datetime.timedelta(days=1 * number)
         elif unit in "hours":
-            delta = datetime.timedelta(hours = 1 * number)
+            delta = datetime.timedelta(hours=1 * number)
         elif unit in "minutes":
-            delta = datetime.timedelta(minutes = 1 * number)
+            delta = datetime.timedelta(minutes=1 * number)
         elif unit in "seconds":
-            delta = datetime.timedelta(seconds = 1 * number)
+            delta = datetime.timedelta(seconds=1 * number)
 
         return delta
 
@@ -263,8 +266,8 @@
 
 class InputDialog(wx.Dialog):
     def __init__(self, parent, mode, animationData):
-        wx.Dialog.__init__(self, parent = parent, id = wx.ID_ANY,
-                           style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
+        wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY,
+                           style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
         if mode == 'add':
             self.SetTitle(_("Add new animation"))
         elif mode == 'edit':
@@ -274,49 +277,53 @@
         self._tmpLegendCmd = None
 
         self._layout()
-        self.OnViewMode(event = None)
+        self.OnViewMode(event=None)
 
     def _layout(self):
         mainSizer = wx.BoxSizer(wx.VERTICAL)
 
-        self.windowChoice = wx.Choice(self, id = wx.ID_ANY,
-                                      choices = [_("top left"), _("top right"),
-                                                 _("bottom left"), _("bottom right")])
+        self.windowChoice = wx.Choice(self, id=wx.ID_ANY,
+                                      choices=[_("top left"), _("top right"),
+                                               _("bottom left"), _("bottom right")])
         self.windowChoice.SetSelection(self.animationData.windowIndex)
 
-        self.nameCtrl = wx.TextCtrl(self, id = wx.ID_ANY, value = self.animationData.name)
+        self.nameCtrl = wx.TextCtrl(self, id=wx.ID_ANY, value=self.animationData.name)
 
-        self.nDChoice = wx.Choice(self, id = wx.ID_ANY)
+        self.nDChoice = wx.Choice(self, id=wx.ID_ANY)
         mode = self.animationData.viewMode
         index = 0
         for i, (viewMode, viewModeName) in enumerate(self.animationData.viewModes):
-            self.nDChoice.Append(viewModeName, clientData = viewMode)
+            self.nDChoice.Append(viewModeName, clientData=viewMode)
             if mode == viewMode:
                 index = i
 
         self.nDChoice.SetSelection(index)
-        # TODO
-        self.nDChoice.SetToolTipString(_(""))
+        self.nDChoice.SetToolTipString(_("Select 2D or 3D view"))
         self.nDChoice.Bind(wx.EVT_CHOICE, self.OnViewMode)
 
-        gridSizer = wx.FlexGridSizer(cols = 2, hgap = 5, vgap = 5)
-        gridSizer.Add(item = wx.StaticText(self, id = wx.ID_ANY, label = _("Name:")),
-                      flag = wx.ALIGN_CENTER_VERTICAL)
-        gridSizer.Add(item = self.nameCtrl, proportion = 1, flag = wx.EXPAND)
-        gridSizer.Add(item = wx.StaticText(self, id = wx.ID_ANY, label = _("Window position:")),
-                      flag = wx.ALIGN_CENTER_VERTICAL)
-        gridSizer.Add(item = self.windowChoice, proportion = 1, flag = wx.ALIGN_RIGHT)
-        gridSizer.Add(item = wx.StaticText(self, id = wx.ID_ANY, label = _("View mode:")),
-                      flag = wx.ALIGN_CENTER_VERTICAL)
-        gridSizer.Add(item = self.nDChoice, proportion = 1, flag = wx.ALIGN_RIGHT)
+        gridSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
+        gridSizer.Add(item=wx.StaticText(self, id=wx.ID_ANY, label=_("Name:")),
+                      flag=wx.ALIGN_CENTER_VERTICAL)
+        gridSizer.Add(item=self.nameCtrl, proportion=1, flag=wx.EXPAND)
+        gridSizer.Add(item=wx.StaticText(self, id=wx.ID_ANY, label=_("Window position:")),
+                      flag=wx.ALIGN_CENTER_VERTICAL)
+        gridSizer.Add(item=self.windowChoice, proportion=1, flag=wx.ALIGN_RIGHT)
+        gridSizer.Add(item=wx.StaticText(self, id=wx.ID_ANY, label=_("View mode:")),
+                      flag=wx.ALIGN_CENTER_VERTICAL)
+        gridSizer.Add(item=self.nDChoice, proportion=1, flag=wx.ALIGN_RIGHT)
         gridSizer.AddGrowableCol(0, 1)
         gridSizer.AddGrowableCol(1, 1)
-        mainSizer.Add(item = gridSizer, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
+        mainSizer.Add(item=gridSizer, proportion=0, flag=wx.ALL | wx.EXPAND, border=5)
+        label = _("For 3D animation, please select only one space-time dataset\n"
+                  "or one series of map layers.")
+        self.warning3DLayers = wx.StaticText(self, label=label)
+        self.warning3DLayers.SetForegroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_GRAYTEXT))
+        mainSizer.Add(item=self.warning3DLayers, proportion=0, flag=wx.EXPAND | wx.LEFT, border=5)
 
         self.dataPanel = self._createDataPanel()
         self.threeDPanel = self._create3DPanel()
-        mainSizer.Add(item = self.dataPanel, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
-        mainSizer.Add(item = self.threeDPanel, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
+        mainSizer.Add(item=self.dataPanel, proportion=1, flag=wx.EXPAND | wx.ALL, border=3)
+        mainSizer.Add(item=self.threeDPanel, proportion=0, flag=wx.EXPAND | wx.ALL, border=3)
 
         # buttons
         self.btnOk = wx.Button(self, wx.ID_OK)
@@ -328,166 +335,100 @@
         btnStdSizer.AddButton(self.btnOk)
         btnStdSizer.AddButton(self.btnCancel)
         btnStdSizer.Realize()
-        
-        mainSizer.Add(item = btnStdSizer, proportion = 0,
-                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border = 5)
 
+        mainSizer.Add(item=btnStdSizer, proportion=0,
+                      flag=wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border=5)
+
         self.SetSizer(mainSizer)
         mainSizer.Fit(self)
 
     def _createDataPanel(self):
-        panel = wx.Panel(self, id = wx.ID_ANY)
-        dataStBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
-                               label = ' %s ' % _("Data"))
-        dataBoxSizer = wx.StaticBoxSizer(dataStBox, wx.VERTICAL)
+        panel = wx.Panel(self)
+        slmgrSizer = wx.BoxSizer(wx.VERTICAL)
+        self._layerList = copy.deepcopy(self.animationData.layerList)
+        self.simpleLmgr = AnimSimpleLayerManager(parent=panel,
+                                                 layerList=self._layerList,
+                                                 modal=True)
+        self.simpleLmgr.SetMinSize((globalvar.DIALOG_GSELECT_SIZE[0], 120))
+        slmgrSizer.Add(self.simpleLmgr, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
 
-        self.dataChoice = wx.Choice(panel, id = wx.ID_ANY)
-        self._setMapTypes()
-        self.dataChoice.Bind(wx.EVT_CHOICE, self.OnDataType)
-
-        self.dataSelect = gselect.Select(parent = panel, id = wx.ID_ANY,
-                                           size = globalvar.DIALOG_GSELECT_SIZE)
-
-
-        iconTheme = UserSettings.Get(group = 'appearance', key = 'iconTheme', subkey = 'type')
-        bitmapPath = os.path.join(globalvar.ETCICONDIR, iconTheme, 'layer-open.png')
-        if os.path.isfile(bitmapPath) and os.path.getsize(bitmapPath):
-            bitmap = wx.Bitmap(name = bitmapPath)
-        else:
-            bitmap = wx.ArtProvider.GetBitmap(id = wx.ART_MISSING_IMAGE, client = wx.ART_TOOLBAR)
-        self.addManyMapsButton = wx.BitmapButton(panel, id = wx.ID_ANY, bitmap = bitmap)
-        self.addManyMapsButton.Bind(wx.EVT_BUTTON, self.OnAddMaps)
-
         self.legend = wx.CheckBox(panel, label=_("Show raster legend"))
         self.legend.SetValue(bool(self.animationData.legendCmd))
         self.legendBtn = wx.Button(panel, label=_("Set options"))
-        self.legendBtn.Bind(wx.EVT_BUTTON, self.OnLegend)
-        tooltip = _("By default, legend is created for the first raster map in case of multiple maps "
-                    "and for the first raster map of space time raster dataset.")
-        self.legend.SetToolTipString(tooltip) 
-        self.legendBtn.SetToolTipString(tooltip)
+        self.legend.Bind(wx.EVT_CHECKBOX, self.OnLegend)
+        self.legendBtn.Bind(wx.EVT_BUTTON, self.OnLegendProperties)
 
-        self.OnDataType(None)
-        if self.animationData.inputData is None:
-            self.dataSelect.SetValue('')
-        else:
-            self.dataSelect.SetValue(self.animationData.inputData)
-
         hbox = wx.BoxSizer(wx.HORIZONTAL)
-        hbox.Add(item = wx.StaticText(panel, wx.ID_ANY, label = _("Input data type:")),
-                proportion = 1, flag = wx.ALIGN_CENTER_VERTICAL)
-        hbox.Add(item = self.dataChoice, proportion = 1, flag = wx.EXPAND)
-        dataBoxSizer.Add(item = hbox, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
-
-        hbox = wx.BoxSizer(wx.HORIZONTAL)
-        # hbox.Add(item = wx.StaticText(panel, wx.ID_ANY, label = _("Input data:")),
-        #         proportion = 0, flag = wx.ALIGN_CENTER_VERTICAL)
-        hbox.Add(item = self.dataSelect, proportion = 1, flag = wx.ALIGN_CENTER)
-        hbox.Add(item = self.addManyMapsButton, proportion = 0, flag = wx.LEFT, border = 5)
-        dataBoxSizer.Add(item = hbox, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
-
-        hbox = wx.BoxSizer(wx.HORIZONTAL)
         hbox.Add(item=self.legend, proportion=1, flag=wx.ALIGN_CENTER_VERTICAL)
         hbox.Add(item=self.legendBtn, proportion=0, flag=wx.LEFT, border=5)
-        dataBoxSizer.Add(item=hbox, proportion=0, flag=wx.EXPAND | wx.ALL, border=3)
+        slmgrSizer.Add(item=hbox, proportion=0, flag=wx.EXPAND | wx.ALL, border=3)
 
-        panel.SetSizerAndFit(dataBoxSizer)
+        panel.SetSizerAndFit(slmgrSizer)
         panel.SetAutoLayout(True)
 
         return panel
 
     def _create3DPanel(self):
-        panel = wx.Panel(self, id = wx.ID_ANY)
-        dataStBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
-                                 label = ' %s ' % _("3D view parameters"))
+        panel = wx.Panel(self, id=wx.ID_ANY)
+        dataStBox = wx.StaticBox(parent=panel, id=wx.ID_ANY,
+                                 label=' %s ' % _("3D view parameters"))
         dataBoxSizer = wx.StaticBoxSizer(dataStBox, wx.VERTICAL)
 
         # workspace file
-        self.fileSelector = filebrowse.FileBrowseButton(parent = panel, id = wx.ID_ANY, 
-                                                    size = globalvar.DIALOG_GSELECT_SIZE,
-                                                    labelText = _("Workspace file:"),
-                                                    dialogTitle = _("Choose workspace file to import 3D view parameters"),
-                                                    buttonText = _('Browse'),
-                                                    startDirectory = os.getcwd(), fileMode = 0,
-                                                    fileMask = "GRASS Workspace File (*.gxw)|*.gxw")
+        self.fileSelector = \
+            filebrowse.FileBrowseButton(parent=panel, id=wx.ID_ANY,
+                                        size=globalvar.DIALOG_GSELECT_SIZE,
+                                        labelText=_("Workspace file:"),
+                                        dialogTitle=_("Choose workspace file to "
+                                                      "import 3D view parameters"),
+                                        buttonText=_('Browse'),
+                                        startDirectory=os.getcwd(), fileMode=0,
+                                        fileMask="GRASS Workspace File (*.gxw)|*.gxw")
         if self.animationData.workspaceFile:
             self.fileSelector.SetValue(self.animationData.workspaceFile)
-        self.paramLabel = wx.StaticText(panel, wx.ID_ANY, label = _("Parameter for animation:"))
-        self.paramChoice = wx.Choice(panel, id = wx.ID_ANY, choices = self.animationData.nvizParameters)
+        self.paramLabel = wx.StaticText(panel, wx.ID_ANY, label=_("Parameter for animation:"))
+        self.paramChoice = wx.Choice(panel, id=wx.ID_ANY, choices=self.animationData.nvizParameters)
         self.paramChoice.SetStringSelection(self.animationData.nvizParameter)
 
         hbox = wx.BoxSizer(wx.HORIZONTAL)
-        hbox.Add(item = self.fileSelector, proportion = 1, flag = wx.EXPAND | wx.ALIGN_CENTER)
-        dataBoxSizer.Add(item = hbox, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
+        hbox.Add(item=self.fileSelector, proportion=1, flag=wx.EXPAND | wx.ALIGN_CENTER)
+        dataBoxSizer.Add(item=hbox, proportion=0, flag=wx.EXPAND | wx.ALL, border=3)
 
         hbox = wx.BoxSizer(wx.HORIZONTAL)
-        hbox.Add(item = self.paramLabel, proportion = 1, flag = wx.ALIGN_CENTER_VERTICAL)
-        hbox.Add(item = self.paramChoice, proportion = 1, flag = wx.EXPAND)
-        dataBoxSizer.Add(item = hbox, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
-        
+        hbox.Add(item=self.paramLabel, proportion=1, flag=wx.ALIGN_CENTER_VERTICAL)
+        hbox.Add(item=self.paramChoice, proportion=1, flag=wx.EXPAND)
+        dataBoxSizer.Add(item=hbox, proportion=0, flag=wx.EXPAND | wx.ALL, border=3)
+
         panel.SetSizerAndFit(dataBoxSizer)
         panel.SetAutoLayout(True)
 
         return panel
 
-    def _setMapTypes(self, view2d = True):
-        index = 0
-
-        inputTypes = self.animationData.inputMapTypes
-        self.dataChoice.Clear()
-        for i, (itype, itypeName) in enumerate(inputTypes):
-            self.dataChoice.Append(itypeName, clientData = itype)
-            if itype == self.animationData.inputMapType:
-                index = i
-        self.dataChoice.SetSelection(index)
-
     def OnViewMode(self, event):
         mode = self.nDChoice.GetSelection()
         self.Freeze()
+        self.simpleLmgr.Activate3D(mode == 1)
+        self.warning3DLayers.Show(mode == 1)
         sizer = self.threeDPanel.GetContainingSizer()
-        sizer.Show(self.threeDPanel, mode != 0, True)
+        sizer.Show(self.threeDPanel, mode == 1, True)
         sizer.Layout()
-        self._setMapTypes(mode == 0)
         self.Layout()
         self.Fit()
         self.Thaw()
 
-    def OnDataType(self, event):
-        etype = self.dataChoice.GetClientData(self.dataChoice.GetSelection())
-        if etype in ('rast', 'vect'):
-            self.dataSelect.SetType(etype = etype, multiple = True)
-            self.addManyMapsButton.Enable(True)
-        else:
-            self.dataSelect.SetType(etype = etype, multiple = False)
-            self.addManyMapsButton.Enable(False)
+    def OnLegend(self, event):
+        if not self.legend.IsChecked():
+            return
+        if self._tmpLegendCmd or self.animationData.legendCmd:
+            return
+        cmd = ['d.legend', 'at=5,50,2,5']
+        GUI(parent=self, modal=True).ParseCommand(cmd=cmd,
+                                                  completed=(self.GetOptData, '', ''))
 
-        self.legend.Enable(etype in ('rast', 'strds'))
-        self.legendBtn.Enable(etype in ('rast', 'strds'))
-
-        self.dataSelect.SetValue('')
-
-    def OnAddMaps(self, event):
-        # TODO: fix dialog
-        etype = self.dataChoice.GetClientData(self.dataChoice.GetSelection())
-        index = 0
-        if etype == 'vect':
-            index = 2
-            
-        dlg = MapLayersDialog(self, title = _("Select raster maps for animation"))
-        dlg.applyAddingMapLayers.connect(lambda mapLayers:
-                                         self.dataSelect.SetValue(','.join(mapLayers)))
-        dlg.layerType.SetSelection(index)
-        dlg.LoadMapLayers(dlg.GetLayerType(cmd = True),
-                           dlg.mapset.GetStringSelection())
-        if dlg.ShowModal() == wx.ID_OK:
-            self.dataSelect.SetValue(','.join(dlg.GetMapLayers()))
-
-        dlg.Destroy()
-
-    def OnLegend(self, event):
+    def OnLegendProperties(self, event):
         """!Set options for legend"""
         if self._tmpLegendCmd:
-            cmd = self._tmpLegendCmd 
+            cmd = self._tmpLegendCmd
         elif self.animationData.legendCmd:
             cmd = self.animationData.legendCmd
         else:
@@ -500,47 +441,39 @@
         GUI(parent=self, modal=True).ParseCommand(cmd=cmd,
                                                   completed=(self.GetOptData, '', ''))
 
-    def _getLegendMapHint(self):
-        """!Determine probable map"""
-        inputData = self.dataSelect.GetValue()
-        etype = self.dataChoice.GetClientData(self.dataChoice.GetSelection())
-        if etype == 'strds':
-            timeseries = validateTimeseriesName(inputData, etype=etype)
-            timeseriesMaps = getRegisteredMaps(timeseries, etype)
-            if len(timeseriesMaps):
-                return timeseriesMaps[0]
-        else:  # multiple raster
-            maps = inputData.split(',')
-            if len(maps):
-                return maps[0]
-
-        return None
-
     def GetOptData(self, dcmd, layer, params, propwin):
         """!Process decoration layer data"""
-        self._tmpLegendCmd = dcmd
+        if dcmd:
+            self._tmpLegendCmd = dcmd
 
-        if dcmd and not self.legend.IsChecked():
-            self.legend.SetValue(True)
+            if not self.legend.IsChecked():
+                self.legend.SetValue(True)
+        else:
+            if not self._tmpLegendCmd and not self.animationData.legendCmd:
+                self.legend.SetValue(False)
 
     def _update(self):
+        if self.nDChoice.GetSelection() == 1 and len(self._layerList) > 1:
+            raise GException(_("Only one series or space-time "
+                               "dataset is accepted for 3D mode."))
+        hasSeries = False
+        for layer in self._layerList:
+            if layer.active and hasattr(layer, 'maps'):
+                hasSeries = True
+                break
+        if not hasSeries:
+            raise GException(_("No map series or space-time dataset added."))
+
+        self.animationData.layerList = self._layerList
         self.animationData.name = self.nameCtrl.GetValue()
         self.animationData.windowIndex = self.windowChoice.GetSelection()
 
-        sel = self.dataChoice.GetSelection()
-        self.animationData.inputMapType = self.dataChoice.GetClientData(sel)
-        self.animationData.inputData = self.dataSelect.GetValue()
         sel = self.nDChoice.GetSelection()
         self.animationData.viewMode = self.nDChoice.GetClientData(sel)
         self.animationData.legendCmd = None
         if self._tmpLegendCmd:
             if self.legend.IsChecked():
                 self.animationData.legendCmd = self._tmpLegendCmd
-        else:
-            if self.legend.IsChecked():
-                self.animationData.legendCmd = ['d.legend', 
-                                                'at=5,50,2,5',
-                                                'map=%s' % self._getLegendMapHint()]
 
         if self.threeDPanel.IsShown():
             self.animationData.workspaceFile = self.fileSelector.GetValue()
@@ -552,13 +485,13 @@
             self._update()
             self.EndModal(wx.ID_OK)
         except (GException, ValueError, IOError) as e:
-            GError(message = str(e), showTraceback = False, caption = _("Invalid input"))
+            GError(message=str(e), showTraceback=False, caption=_("Invalid input"))
 
 
 class EditDialog(wx.Dialog):
     def __init__(self, parent, evalFunction, animationData, maxAnimations):
-        wx.Dialog.__init__(self, parent = parent, id = wx.ID_ANY,
-                           style = wx.DEFAULT_DIALOG_STYLE)
+        wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY,
+                           style=wx.DEFAULT_DIALOG_STYLE)
         self.animationData = copy.deepcopy(animationData)
         self.eval = evalFunction
         self.SetTitle(_("Add, edit or remove animations"))
@@ -569,32 +502,36 @@
 
     def _layout(self):
         mainSizer = wx.BoxSizer(wx.VERTICAL)
-        box = wx.StaticBox (parent = self, id = wx.ID_ANY, label = " %s " % _("List of animations"))
+        box = wx.StaticBox (parent=self, id=wx.ID_ANY, label=" %s " % _("List of animations"))
         sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
-        gridBagSizer = wx.GridBagSizer (hgap = 5, vgap = 5)
+        gridBagSizer = wx.GridBagSizer (hgap=5, vgap=5)
         gridBagSizer.AddGrowableCol(0)
         # gridBagSizer.AddGrowableCol(1,1)
-        
-        self.listbox = wx.ListBox(self, id = wx.ID_ANY, choices = [], style = wx.LB_SINGLE|wx.LB_NEEDED_SB)
+
+        self.listbox = wx.ListBox(self, id=wx.ID_ANY, choices=[], style=wx.LB_SINGLE | wx.LB_NEEDED_SB)
         self.listbox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnEdit)
 
-        self.addButton = wx.Button(self, id = wx.ID_ANY, label = _("Add"))
+        self.addButton = wx.Button(self, id=wx.ID_ANY, label=_("Add"))
         self.addButton.Bind(wx.EVT_BUTTON, self.OnAdd)
-        self.editButton = wx.Button(self, id = wx.ID_ANY, label = _("Edit"))
+        self.editButton = wx.Button(self, id=wx.ID_ANY, label=_("Edit"))
         self.editButton.Bind(wx.EVT_BUTTON, self.OnEdit)
-        self.removeButton = wx.Button(self, id = wx.ID_ANY, label = _("Remove"))
+        self.removeButton = wx.Button(self, id=wx.ID_ANY, label=_("Remove"))
         self.removeButton.Bind(wx.EVT_BUTTON, self.OnRemove)
-        
+
         self._updateListBox()
-        
-        gridBagSizer.Add(self.listbox, pos = (0,0), span = (3, 1), flag = wx.ALIGN_CENTER_VERTICAL| wx.EXPAND, border = 0)
-        gridBagSizer.Add(self.addButton, pos = (0,1), flag = wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, border = 0)
-        gridBagSizer.Add(self.editButton, pos = (1,1), flag = wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, border = 0)
-        gridBagSizer.Add(self.removeButton, pos = (2,1), flag = wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, border = 0)
-        sizer.Add(gridBagSizer, proportion = 0, flag = wx.ALL | wx.EXPAND, border = 5)
-        mainSizer.Add(item = sizer, proportion = 0,
-                      flag = wx.EXPAND | wx.ALL, border = 5)
 
+        gridBagSizer.Add(self.listbox, pos=(0, 0), span = (3, 1),
+                         flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, border=0)
+        gridBagSizer.Add(self.addButton, pos=(0, 1),
+                         flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, border=0)
+        gridBagSizer.Add(self.editButton, pos=(1, 1),
+                         flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, border=0)
+        gridBagSizer.Add(self.removeButton, pos=(2, 1),
+                         flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, border=0)
+        sizer.Add(gridBagSizer, proportion=0, flag=wx.ALL | wx.EXPAND, border=5)
+        mainSizer.Add(item=sizer, proportion=0,
+                      flag=wx.EXPAND | wx.ALL, border=5)
+
         # buttons
         self.btnOk = wx.Button(self, wx.ID_OK)
         self.btnCancel = wx.Button(self, wx.ID_CANCEL)
@@ -605,17 +542,17 @@
         btnStdSizer.AddButton(self.btnOk)
         btnStdSizer.AddButton(self.btnCancel)
         btnStdSizer.Realize()
-        
-        mainSizer.Add(item = btnStdSizer, proportion = 0,
-                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border = 5)
 
+        mainSizer.Add(item=btnStdSizer, proportion=0,
+                      flag=wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border=5)
+
         self.SetSizer(mainSizer)
         mainSizer.Fit(self)
 
     def _updateListBox(self):
         self.listbox.Clear()
         for anim in self.animationData:
-            self.listbox.Append(anim.name, clientData = anim)
+            self.listbox.Append(anim.name, clientData=anim)
         if self.animationData:
             self.listbox.SetSelection(0)
 
@@ -629,29 +566,28 @@
     def OnAdd(self, event):
         windowIndex = self._getNextIndex()
         if windowIndex is None:
-            GMessage(self, message = _("Maximum number of animations is %d.") % self.maxAnimations)
+            GMessage(self, message=_("Maximum number of animations is %d.") % self.maxAnimations)
             return
         animData = AnimationData()
         # number of active animations
         animationIndex = len(self.animationData)
         animData.SetDefaultValues(windowIndex, animationIndex)
-        dlg = InputDialog(parent = self, mode = 'add', animationData = animData)
+        dlg = InputDialog(parent=self, mode='add', animationData=animData)
         if dlg.ShowModal() == wx.ID_CANCEL:
             dlg.Destroy()
             return
         dlg.Destroy()
         self.animationData.append(animData)
-        
+
         self._updateListBox()
 
-
     def OnEdit(self, event):
         index = self.listbox.GetSelection()
         if index == wx.NOT_FOUND:
             return
 
         animData = self.listbox.GetClientData(index)
-        dlg = InputDialog(parent = self, mode = 'edit', animationData = animData)
+        dlg = InputDialog(parent=self, mode='edit', animationData=animData)
         if dlg.ShowModal() == wx.ID_CANCEL:
             dlg.Destroy()
             return
@@ -666,7 +602,7 @@
 
         animData = self.listbox.GetClientData(index)
         self.animationData.remove(animData)
-        
+
         self._updateListBox()
 
     def GetResult(self):
@@ -675,182 +611,23 @@
     def OnOk(self, event):
         indices = set([anim.windowIndex for anim in self.animationData])
         if len(indices) != len(self.animationData):
-            GError(parent = self, message = _("More animations are using one window."
-                                                " Please select different window for each animation."))
+            GError(parent=self, message=_("More animations are using one window."
+                                          " Please select different window for each animation."))
             return
         try:
             temporalMode, tempManager = self.eval(self.animationData)
         except GException, e:
-            GError(parent = self, message = e.value, showTraceback = False)
+            GError(parent=self, message=e.value, showTraceback=False)
             return
         self.result = (self.animationData, temporalMode, tempManager)
 
         self.EndModal(wx.ID_OK)
 
 
-class AnimationData(object):
-    def __init__(self):
-        self._inputMapTypes = [('rast', _("Multiple raster maps")),
-                               ('vect', _("Multiple vector maps")),
-                               ('strds', _("Space time raster dataset")),
-                               ('stvds', _("Space time vector dataset"))]
-        self._inputMapType = 'rast'
-        self.inputData = None
-        self.mapData = 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 GetInputMapTypes(self):
-        return self._inputMapTypes
-
-    inputMapTypes = property(fget = GetInputMapTypes)
-
-    def GetInputMapType(self):
-        return self._inputMapType
-        
-    def SetInputMapType(self, itype):
-        if itype in [each[0] for each in self.inputMapTypes]:
-            self._inputMapType = itype
-        else:
-            raise ValueError("Bad input type.")
-
-    inputMapType = property(fget = GetInputMapType, fset = SetInputMapType)
-
-    def GetInputData(self):
-        return self._inputData
-
-    def SetInputData(self, data):
-        if data == '':
-            raise ValueError(_("No data selected."))
-        if data is None:
-            self._inputData = data
-            return
-
-        if self.inputMapType in ('rast', 'vect'):
-            maps = data.split(',')
-            newNames = validateMapNames(maps, self.inputMapType)
-            self._inputData = ','.join(newNames)
-            self.mapData = newNames
-
-        elif self.inputMapType in ('strds', 'stvds'):
-            timeseries = validateTimeseriesName(data, etype=self.inputMapType)
-            timeseriesMaps = getRegisteredMaps(timeseries, self.inputMapType)
-            self._inputData = timeseries
-            self.mapData = timeseriesMaps
-        else:
-            self._inputData = data
-
-    inputData = property(fget = GetInputData, fset = SetInputData)
-
-    def SetMapData(self, data):
-        self._mapData = data
-
-    def GetMapData(self):
-        return self._mapData
-
-    mapData = property(fget = GetMapData, fset = SetMapData)
-
-    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)
-
-    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.mapData:
-            return []
-
-        cmds = self.nvizTask.GetCommandSeries(series = self.mapData, paramName = self.nvizParameter)
-        region = self.nvizTask.GetRegion()
-
-        return {'commands': cmds, 'region': region}
-
-    def __repr__(self):
-        return "%s(%r)" % (self.__class__, self.__dict__)
-
-
-
 class ExportDialog(wx.Dialog):
     def __init__(self, parent, temporal, timeTick):
-        wx.Dialog.__init__(self, parent = parent, id = wx.ID_ANY, title = _("Export animation"),
-                           style = wx.DEFAULT_DIALOG_STYLE)
+        wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY, title=_("Export animation"),
+                           style=wx.DEFAULT_DIALOG_STYLE)
         self.decorations = []
 
         self.temporal = temporal
@@ -863,15 +640,14 @@
         wx.CallAfter(self._hideAll)
 
     def _layout(self):
-        notebook = wx.Notebook(self, id = wx.ID_ANY)
+        notebook = wx.Notebook(self, id=wx.ID_ANY)
         mainSizer = wx.BoxSizer(wx.VERTICAL)
 
-        notebook.AddPage(page = self._createExportFormatPanel(notebook), text = _("Format"))
-        notebook.AddPage(page = self._createDecorationsPanel(notebook), text = _("Decorations"))
-        mainSizer.Add(item = notebook, proportion = 0,
-                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border = 5)
+        notebook.AddPage(page=self._createExportFormatPanel(notebook), text=_("Format"))
+        notebook.AddPage(page=self._createDecorationsPanel(notebook), text=_("Decorations"))
+        mainSizer.Add(item=notebook, proportion=0,
+                      flag=wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border=5)
 
-
         self.btnExport = wx.Button(self, wx.ID_OK)
         self.btnExport.SetLabel(_("Export"))
         self.btnCancel = wx.Button(self, wx.ID_CANCEL)
@@ -884,9 +660,9 @@
         btnStdSizer.AddButton(self.btnExport)
         btnStdSizer.AddButton(self.btnCancel)
         btnStdSizer.Realize()
-        
-        mainSizer.Add(item = btnStdSizer, proportion = 0,
-                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border = 5)
+
+        mainSizer.Add(item=btnStdSizer, proportion=0,
+                      flag=wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border=5)
         self.SetSizer(mainSizer)
 
         # set the longest option to fit
@@ -898,24 +674,25 @@
         mainSizer.Fit(self)
 
     def _createDecorationsPanel(self, notebook):
-        panel = wx.Panel(notebook, id = wx.ID_ANY)
+        panel = wx.Panel(notebook, id=wx.ID_ANY)
         sizer = wx.BoxSizer(wx.VERTICAL)
-        sizer.Add(self._createDecorationsList(panel), proportion = 0, flag = wx.ALL | wx.EXPAND, border = 10)
-        sizer.Add(self._createDecorationsProperties(panel), proportion = 0, flag = wx.ALL | wx.EXPAND, border = 10)
+        sizer.Add(self._createDecorationsList(panel), proportion=0, flag=wx.ALL | wx.EXPAND, border=10)
+        sizer.Add(self._createDecorationsProperties(panel), proportion=0, flag=wx.ALL | wx.EXPAND, border=10)
         panel.SetSizer(sizer)
         sizer.Fit(panel)
         return panel
 
     def _createDecorationsList(self, panel):
-        gridBagSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
+        gridBagSizer = wx.GridBagSizer(hgap=5, vgap=5)
 
         gridBagSizer.AddGrowableCol(0)
-        
-        self.listbox = wx.ListBox(panel, id = wx.ID_ANY, choices = [], style = wx.LB_SINGLE|wx.LB_NEEDED_SB)
+
+        self.listbox = wx.ListBox(panel, id=wx.ID_ANY, choices=[],
+                                  style=wx.LB_SINGLE | wx.LB_NEEDED_SB)
         self.listbox.Bind(wx.EVT_LISTBOX, self.OnSelectionChanged)
 
-        gridBagSizer.Add(self.listbox, pos = (0, 0), span = (4, 1),
-                         flag = wx.ALIGN_CENTER_VERTICAL| wx.EXPAND, border = 0)
+        gridBagSizer.Add(self.listbox, pos=(0, 0), span=(4, 1),
+                         flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, border=0)
 
         buttonNames = ['time', 'image', 'text']
         buttonLabels = [_("Add time stamp"), _("Add image"), _("Add text")]
@@ -923,14 +700,14 @@
         for buttonName, buttonLabel in zip(buttonNames, buttonLabels):
             if buttonName == 'time' and self.temporal == TemporalMode.NONTEMPORAL:
                 continue
-            btn = wx.Button(panel, id = wx.ID_ANY, name = buttonName, label = buttonLabel)
-            btn.Bind(wx.EVT_BUTTON, lambda evt, temp = buttonName: self.OnAddDecoration(evt, temp))
-            gridBagSizer.Add(btn, pos = (i ,1), flag = wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, border = 0)
+            btn = wx.Button(panel, id=wx.ID_ANY, name=buttonName, label=buttonLabel)
+            btn.Bind(wx.EVT_BUTTON, lambda evt, temp=buttonName: self.OnAddDecoration(evt, temp))
+            gridBagSizer.Add(btn, pos=(i, 1), flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, border=0)
             i += 1
-        removeButton = wx.Button(panel, id = wx.ID_ANY, label = _("Remove"))
+        removeButton = wx.Button(panel, id=wx.ID_ANY, label=_("Remove"))
         removeButton.Bind(wx.EVT_BUTTON, self.OnRemove)
-        gridBagSizer.Add(removeButton, pos = (i, 1), flag = wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, border = 0)
-        
+        gridBagSizer.Add(removeButton, pos=(i, 1), flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, border=0)
+
         return gridBagSizer
 
     def _createDecorationsProperties(self, panel):
@@ -942,177 +719,177 @@
         else:
             label = _("Add image or text decoration by one of the buttons above.")
 
-        label = wx.StaticText(panel, id = wx.ID_ANY, label = label)
+        label = wx.StaticText(panel, id=wx.ID_ANY, label=label)
         label.Wrap(400)
-        self.informBox.Add(label, proportion = 1, flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border = 5)
-        self.hidevbox.Add(self.informBox, proportion = 0, flag = wx.EXPAND | wx.BOTTOM, border = 5)
-        
+        self.informBox.Add(label, proportion=1, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border=5)
+        self.hidevbox.Add(self.informBox, proportion=0, flag=wx.EXPAND | wx.BOTTOM, border=5)
+
         # font
         self.fontBox = wx.BoxSizer(wx.HORIZONTAL)
-        self.fontBox.Add(wx.StaticText(panel, id = wx.ID_ANY, label = _("Font settings:")),
-                         proportion = 0, flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border = 5)
-        self.sampleLabel = wx.StaticText(panel, id = wx.ID_ANY, label = _("Sample text"))
-        self.fontBox.Add(self.sampleLabel, proportion = 1,
-                         flag = wx.ALIGN_CENTER | wx.RIGHT | wx.LEFT, border = 5)
-        fontButton = wx.Button(panel, id = wx.ID_ANY, label = _("Set font"))
+        self.fontBox.Add(wx.StaticText(panel, id=wx.ID_ANY, label=_("Font settings:")),
+                         proportion=0, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border=5)
+        self.sampleLabel = wx.StaticText(panel, id=wx.ID_ANY, label=_("Sample text"))
+        self.fontBox.Add(self.sampleLabel, proportion=1,
+                         flag=wx.ALIGN_CENTER | wx.RIGHT | wx.LEFT, border=5)
+        fontButton = wx.Button(panel, id=wx.ID_ANY, label=_("Set font"))
         fontButton.Bind(wx.EVT_BUTTON, self.OnFont)
-        self.fontBox.Add(fontButton, proportion = 0, flag = wx.ALIGN_CENTER_VERTICAL)
-        self.hidevbox.Add(self.fontBox, proportion = 0, flag = wx.EXPAND | wx.BOTTOM, border = 5)
+        self.fontBox.Add(fontButton, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL)
+        self.hidevbox.Add(self.fontBox, proportion=0, flag=wx.EXPAND | wx.BOTTOM, border=5)
 
         # image
         self.imageBox = wx.BoxSizer(wx.HORIZONTAL)
         filetype, ltype = GetImageHandlers(wx.EmptyImage(10, 10))
-        self.browse = filebrowse.FileBrowseButton(parent = panel, id = wx.ID_ANY, fileMask = filetype,
-                                                  labelText = _("Image file:"),
-                                                  dialogTitle = _('Choose image file'),
-                                                  buttonText = _('Browse'),
-                                                  startDirectory = os.getcwd(), fileMode = wx.OPEN,
-                                                  changeCallback = self.OnSetImage)
-        self.imageBox.Add(self.browse, proportion = 1, flag = wx.EXPAND)
-        self.hidevbox.Add(self.imageBox, proportion = 0, flag = wx.EXPAND | wx.BOTTOM, border = 5)
+        self.browse = filebrowse.FileBrowseButton(parent=panel, id=wx.ID_ANY, fileMask=filetype,
+                                                  labelText=_("Image file:"),
+                                                  dialogTitle=_('Choose image file'),
+                                                  buttonText=_('Browse'),
+                                                  startDirectory=os.getcwd(), fileMode=wx.OPEN,
+                                                  changeCallback=self.OnSetImage)
+        self.imageBox.Add(self.browse, proportion=1, flag=wx.EXPAND)
+        self.hidevbox.Add(self.imageBox, proportion=0, flag=wx.EXPAND | wx.BOTTOM, border=5)
         # text
         self.textBox = wx.BoxSizer(wx.HORIZONTAL)
-        self.textBox.Add(wx.StaticText(panel, id = wx.ID_ANY, label = _("Text:")),
-                         proportion = 0, flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border = 5)
-        self.textCtrl = wx.TextCtrl(panel, id = wx.ID_ANY)
+        self.textBox.Add(wx.StaticText(panel, id=wx.ID_ANY, label=_("Text:")),
+                         proportion=0, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border=5)
+        self.textCtrl = wx.TextCtrl(panel, id=wx.ID_ANY)
         self.textCtrl.Bind(wx.EVT_TEXT, self.OnText)
-        self.textBox.Add(self.textCtrl, proportion = 1, flag = wx.EXPAND)
-        self.hidevbox.Add(self.textBox, proportion = 0, flag = wx.EXPAND)
+        self.textBox.Add(self.textCtrl, proportion=1, flag=wx.EXPAND)
+        self.hidevbox.Add(self.textBox, proportion=0, flag=wx.EXPAND)
 
         self.posBox = self._positionWidget(panel)
-        self.hidevbox.Add(self.posBox, proportion = 0, flag = wx.EXPAND | wx.TOP, border = 5)
+        self.hidevbox.Add(self.posBox, proportion=0, flag=wx.EXPAND | wx.TOP, border=5)
         return self.hidevbox
 
     def _positionWidget(self, panel):
-        grid = wx.GridBagSizer(vgap = 5, hgap = 5)
-        label = wx.StaticText(panel, id = wx.ID_ANY, label = _("Placement as percentage of"
+        grid = wx.GridBagSizer(vgap=5, hgap=5)
+        label = wx.StaticText(panel, id=wx.ID_ANY, label=_("Placement as percentage of"
                               " screen coordinates (X: 0, Y: 0 is top left):"))
         label.Wrap(400)
-        self.spinX = wx.SpinCtrl(panel, id = wx.ID_ANY, min = 0, max = 100, initial = 10)
-        self.spinY = wx.SpinCtrl(panel, id = wx.ID_ANY, min = 0, max = 100, initial = 10)
-        self.spinX.Bind(wx.EVT_SPINCTRL,  lambda evt, temp = 'X': self.OnPosition(evt, temp))
-        self.spinY.Bind(wx.EVT_SPINCTRL,  lambda evt, temp = 'Y': self.OnPosition(evt, temp))
-        
-        grid.Add(label, pos = (0, 0), span = (1, 4), flag = wx.EXPAND)
-        grid.Add(wx.StaticText(panel, id = wx.ID_ANY, label = _("X:")), pos = (1, 0),
+        self.spinX = wx.SpinCtrl(panel, id=wx.ID_ANY, min=0, max=100, initial=10)
+        self.spinY = wx.SpinCtrl(panel, id=wx.ID_ANY, min=0, max=100, initial=10)
+        self.spinX.Bind(wx.EVT_SPINCTRL, lambda evt, temp='X': self.OnPosition(evt, temp))
+        self.spinY.Bind(wx.EVT_SPINCTRL, lambda evt, temp='Y': self.OnPosition(evt, temp))
+
+        grid.Add(label, pos=(0, 0), span = (1, 4), flag = wx.EXPAND)
+        grid.Add(wx.StaticText(panel, id=wx.ID_ANY, label=_("X:")), pos=(1, 0),
                  flag = wx.ALIGN_CENTER_VERTICAL)
-        grid.Add(wx.StaticText(panel, id = wx.ID_ANY, label = _("Y:")), pos = (1, 2),
+        grid.Add(wx.StaticText(panel, id=wx.ID_ANY, label=_("Y:")), pos=(1, 2),
                  flag = wx.ALIGN_CENTER_VERTICAL)
-        grid.Add(self.spinX, pos = (1, 1))
-        grid.Add(self.spinY, pos = (1, 3))
+        grid.Add(self.spinX, pos=(1, 1))
+        grid.Add(self.spinY, pos=(1, 3))
 
         return grid
 
     def _createExportFormatPanel(self, notebook):
-        panel = wx.Panel(notebook, id = wx.ID_ANY)
+        panel = wx.Panel(notebook, id=wx.ID_ANY)
         borderSizer = wx.BoxSizer(wx.VERTICAL)
 
         hSizer = wx.BoxSizer(wx.HORIZONTAL)
         choices = [_("image sequence"), _("animated GIF"), _("SWF"), _("AVI")]
-        self.formatChoice = wx.Choice(parent = panel, id = wx.ID_ANY,
-                                      choices = choices)
+        self.formatChoice = wx.Choice(parent=panel, id=wx.ID_ANY,
+                                      choices=choices)
         self.formatChoice.Bind(wx.EVT_CHOICE, lambda event: self.ChangeFormat(event.GetSelection()))
-        hSizer.Add(item = wx.StaticText(panel, id = wx.ID_ANY, label = _("Export to:")),
-                   proportion = 0, flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL, border = 2)
-        hSizer.Add(item = self.formatChoice, proportion = 1,
-                   flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND | wx.ALL, border = 2)
-        borderSizer.Add(item = hSizer, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
+        hSizer.Add(item=wx.StaticText(panel, id=wx.ID_ANY, label=_("Export to:")),
+                   proportion=0, flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2)
+        hSizer.Add(item=self.formatChoice, proportion=1,
+                   flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND | wx.ALL, border=2)
+        borderSizer.Add(item=hSizer, proportion=0, flag=wx.EXPAND | wx.ALL, border=3)
 
         helpSizer = wx.BoxSizer(wx.HORIZONTAL)
         helpSizer.AddStretchSpacer(1)
         self.formatPanelSizer = wx.BoxSizer(wx.VERTICAL)
         helpSizer.Add(self.formatPanelSizer, proportion=5, flag=wx.EXPAND)
-        borderSizer.Add(helpSizer, proportion = 1, flag = wx.EXPAND)
+        borderSizer.Add(helpSizer, proportion=1, flag=wx.EXPAND)
         self.formatPanels = []
 
         # panel for image sequence
-        imSeqPanel = wx.Panel(parent = panel, id = wx.ID_ANY)
-        prefixLabel = wx.StaticText(imSeqPanel, id = wx.ID_ANY, label = _("File prefix:"))
-        self.prefixCtrl = wx.TextCtrl(imSeqPanel, id = wx.ID_ANY, value = _("animation_"))
-        formatLabel = wx.StaticText(imSeqPanel, id = wx.ID_ANY, label = _("File format:"))
+        imSeqPanel = wx.Panel(parent=panel, id=wx.ID_ANY)
+        prefixLabel = wx.StaticText(imSeqPanel, id=wx.ID_ANY, label=_("File prefix:"))
+        self.prefixCtrl = wx.TextCtrl(imSeqPanel, id=wx.ID_ANY, value=_("animation_"))
+        formatLabel = wx.StaticText(imSeqPanel, id=wx.ID_ANY, label=_("File format:"))
         imageTypes = ['PNG', 'JPEG', 'GIF', 'TIFF', 'PPM', 'BMP']
         self.imSeqFormatChoice = wx.Choice(imSeqPanel, choices=imageTypes)
         self.imSeqFormatChoice.SetSelection(0)
-        self.dirBrowse = filebrowse.DirBrowseButton(parent = imSeqPanel, id = wx.ID_ANY,
-                                                    labelText = _("Directory:"),
-                                                     dialogTitle = _("Choose directory for export"),
-                                                     buttonText = _("Browse"),
-                                                     startDirectory = os.getcwd())
+        self.dirBrowse = filebrowse.DirBrowseButton(parent=imSeqPanel, id=wx.ID_ANY,
+                                                    labelText=_("Directory:"),
+                                                    dialogTitle=_("Choose directory for export"),
+                                                    buttonText=_("Browse"),
+                                                    startDirectory=os.getcwd())
 
-        dirGridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
+        dirGridSizer = wx.GridBagSizer(hgap=5, vgap=5)
         dirGridSizer.AddGrowableCol(1)
-        dirGridSizer.Add(prefixLabel, pos = (0, 0), flag = wx.ALIGN_CENTER_VERTICAL)
-        dirGridSizer.Add(self.prefixCtrl, pos = (0, 1), flag = wx.EXPAND)
-        dirGridSizer.Add(formatLabel, pos = (1, 0), flag = wx.ALIGN_CENTER_VERTICAL)
-        dirGridSizer.Add(self.imSeqFormatChoice, pos = (1, 1), flag = wx.EXPAND)
-        dirGridSizer.Add(self.dirBrowse, pos = (2, 0), flag = wx.EXPAND, span = (1, 2))
+        dirGridSizer.Add(prefixLabel, pos=(0, 0), flag = wx.ALIGN_CENTER_VERTICAL)
+        dirGridSizer.Add(self.prefixCtrl, pos=(0, 1), flag = wx.EXPAND)
+        dirGridSizer.Add(formatLabel, pos=(1, 0), flag = wx.ALIGN_CENTER_VERTICAL)
+        dirGridSizer.Add(self.imSeqFormatChoice, pos=(1, 1), flag = wx.EXPAND)
+        dirGridSizer.Add(self.dirBrowse, pos=(2, 0), flag = wx.EXPAND, span = (1, 2))
         imSeqPanel.SetSizer(dirGridSizer)
         dirGridSizer.Fit(imSeqPanel)
 
-        self.formatPanelSizer.Add(item = imSeqPanel, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)
+        self.formatPanelSizer.Add(item=imSeqPanel, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
         self.formatPanels.append(imSeqPanel)
 
         # panel for gif
-        gifPanel = wx.Panel(parent = panel, id = wx.ID_ANY)
+        gifPanel = wx.Panel(parent=panel, id=wx.ID_ANY)
 
-        self.gifBrowse = filebrowse.FileBrowseButton(parent = gifPanel, id = wx.ID_ANY,
-                                                     fileMask = "GIF file (*.gif)|*.gif",
-                                                     labelText = _("GIF file:"),
-                                                     dialogTitle = _("Choose file to save animation"),
-                                                     buttonText = _("Browse"),
-                                                     startDirectory = os.getcwd(), fileMode = wx.SAVE)
-        gifGridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
+        self.gifBrowse = filebrowse.FileBrowseButton(parent=gifPanel, id=wx.ID_ANY,
+                                                     fileMask="GIF file (*.gif)|*.gif",
+                                                     labelText=_("GIF file:"),
+                                                     dialogTitle=_("Choose file to save animation"),
+                                                     buttonText=_("Browse"),
+                                                     startDirectory=os.getcwd(), fileMode=wx.SAVE)
+        gifGridSizer = wx.GridBagSizer(hgap=5, vgap=5)
         gifGridSizer.AddGrowableCol(0)
-        gifGridSizer.Add(self.gifBrowse, pos = (0, 0), flag = wx.EXPAND)
+        gifGridSizer.Add(self.gifBrowse, pos=(0, 0), flag = wx.EXPAND)
         gifPanel.SetSizer(gifGridSizer)
         gifGridSizer.Fit(gifPanel)
 
-        self.formatPanelSizer.Add(item = gifPanel, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)
+        self.formatPanelSizer.Add(item=gifPanel, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
         self.formatPanels.append(gifPanel)
 
         # panel for swf
-        swfPanel = wx.Panel(parent = panel, id = wx.ID_ANY)
-        self.swfBrowse = filebrowse.FileBrowseButton(parent = swfPanel, id = wx.ID_ANY,
-                                                     fileMask = "SWF file (*.swf)|*.swf",
-                                                     labelText = _("SWF file:"),
-                                                     dialogTitle = _("Choose file to save animation"),
-                                                     buttonText = _("Browse"),
-                                                     startDirectory = os.getcwd(), fileMode = wx.SAVE)
-        swfGridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
+        swfPanel = wx.Panel(parent=panel, id=wx.ID_ANY)
+        self.swfBrowse = filebrowse.FileBrowseButton(parent=swfPanel, id=wx.ID_ANY,
+                                                     fileMask="SWF file (*.swf)|*.swf",
+                                                     labelText=_("SWF file:"),
+                                                     dialogTitle=_("Choose file to save animation"),
+                                                     buttonText=_("Browse"),
+                                                     startDirectory=os.getcwd(), fileMode=wx.SAVE)
+        swfGridSizer = wx.GridBagSizer(hgap=5, vgap=5)
         swfGridSizer.AddGrowableCol(0)
-        swfGridSizer.Add(self.swfBrowse, pos = (0, 0), flag = wx.EXPAND)
+        swfGridSizer.Add(self.swfBrowse, pos=(0, 0), flag = wx.EXPAND)
         swfPanel.SetSizer(swfGridSizer)
         swfGridSizer.Fit(swfPanel)
 
-        self.formatPanelSizer.Add(item = swfPanel, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)
+        self.formatPanelSizer.Add(item=swfPanel, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
         self.formatPanels.append(swfPanel)
 
         # panel for avi
-        aviPanel = wx.Panel(parent = panel, id = wx.ID_ANY)
+        aviPanel = wx.Panel(parent=panel, id=wx.ID_ANY)
         ffmpeg = gcore.find_program('ffmpeg', '--help')
         if not ffmpeg:
             warning = _("Program 'ffmpeg' was not found.\nPlease install it first "
                         "and make sure\nit's in the PATH variable.")
             warningLabel = wx.StaticText(parent=aviPanel, label=warning)
             warningLabel.SetForegroundColour(wx.RED)
-        self.aviBrowse = filebrowse.FileBrowseButton(parent = aviPanel, id = wx.ID_ANY,
-                                                     fileMask = "AVI file (*.avi)|*.avi",
-                                                     labelText = _("AVI file:"),
-                                                     dialogTitle = _("Choose file to save animation"),
-                                                     buttonText = _("Browse"),
-                                                     startDirectory = os.getcwd(), fileMode = wx.SAVE)
-        encodingLabel = wx.StaticText(parent = aviPanel, id = wx.ID_ANY, label = _("Video codec:"))
-        self.encodingText = wx.TextCtrl(parent = aviPanel, id = wx.ID_ANY, value = 'mpeg4')
+        self.aviBrowse = filebrowse.FileBrowseButton(parent=aviPanel, id=wx.ID_ANY,
+                                                     fileMask="AVI file (*.avi)|*.avi",
+                                                     labelText=_("AVI file:"),
+                                                     dialogTitle=_("Choose file to save animation"),
+                                                     buttonText=_("Browse"),
+                                                     startDirectory=os.getcwd(), fileMode=wx.SAVE)
+        encodingLabel = wx.StaticText(parent=aviPanel, id=wx.ID_ANY, label=_("Video codec:"))
+        self.encodingText = wx.TextCtrl(parent=aviPanel, id=wx.ID_ANY, value='mpeg4')
         optionsLabel = wx.StaticText(parent=aviPanel, label=_("Additional options:"))
         self.optionsText = wx.TextCtrl(parent=aviPanel)
         self.optionsText.SetToolTipString(_("Consider adding '-sameq' or '-qscale 1' "
                                             "if not satisfied with video quality. "
                                             "Options depend on ffmpeg version."))
-        aviGridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
+        aviGridSizer = wx.GridBagSizer(hgap=5, vgap=5)
         aviGridSizer.AddGrowableCol(1)
-        aviGridSizer.Add(self.aviBrowse, pos = (0, 0), span = (1, 2), flag = wx.EXPAND)
-        aviGridSizer.Add(encodingLabel, pos = (1, 0), flag = wx.ALIGN_CENTER_VERTICAL)
-        aviGridSizer.Add(self.encodingText, pos = (1, 1), flag = wx.EXPAND)
+        aviGridSizer.Add(self.aviBrowse, pos=(0, 0), span = (1, 2), flag = wx.EXPAND)
+        aviGridSizer.Add(encodingLabel, pos=(1, 0), flag = wx.ALIGN_CENTER_VERTICAL)
+        aviGridSizer.Add(self.encodingText, pos=(1, 1), flag = wx.EXPAND)
         aviGridSizer.Add(optionsLabel, pos=(2, 0), flag=wx.ALIGN_CENTER_VERTICAL)
         aviGridSizer.Add(self.optionsText, pos=(2, 1), flag=wx.EXPAND)
         if not ffmpeg:
@@ -1122,24 +899,24 @@
         aviPanel.SetSizer(aviGridSizer)
         aviGridSizer.Fit(aviPanel)
 
-        self.formatPanelSizer.Add(item = aviPanel, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)
+        self.formatPanelSizer.Add(item=aviPanel, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
         self.formatPanels.append(aviPanel)
 
         fpsSizer = wx.BoxSizer(wx.HORIZONTAL)
         fps = 1000 / self.timeTick
-        fpsSizer.Add(wx.StaticText(panel, id = wx.ID_ANY, label = _("Current frame rate: %.2f fps") % fps),
-                     proportion = 1, flag = wx.EXPAND)
-        borderSizer.Add(fpsSizer, proportion = 0, flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL, border = 5)
+        fpsSizer.Add(wx.StaticText(panel, id=wx.ID_ANY, label=_("Current frame rate: %.2f fps") % fps),
+                     proportion=1, flag=wx.EXPAND)
+        borderSizer.Add(fpsSizer, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
 
         panel.SetSizer(borderSizer)
         borderSizer.Fit(panel)
-        self.ChangeFormat(index = 0)
+        self.ChangeFormat(index=0)
 
         return panel
 
     def ChangeFormat(self, index):
         for i, panel in enumerate(self.formatPanels):
-            self.formatPanelSizer.Show(item = panel, show = (i == index))
+            self.formatPanelSizer.Show(item=panel, show=(i == index))
         self.formatPanelSizer.Layout()
 
     def OnFont(self, event):
@@ -1149,14 +926,14 @@
             return
         cdata = self.listbox.GetClientData(index)
         font = cdata['font']
-        
+
         fontdata = wx.FontData()
         fontdata.EnableEffects(True)
         fontdata.SetColour('black')
         fontdata.SetInitialFont(font)
-        
+
         dlg = wx.FontDialog(self, fontdata)
-        
+
         if dlg.ShowModal() == wx.ID_OK:
             newfontdata = dlg.GetFontData()
             font = newfontdata.GetChosenFont()
@@ -1164,7 +941,6 @@
             cdata['font'] = font
             self.Layout()
 
-
     def OnPosition(self, event, coord):
         index = self.listbox.GetSelection()
         # should not happen
@@ -1194,7 +970,7 @@
 
         self._updateListBox()
         self.listbox.SetSelection(self.listbox.GetCount() - 1)
-        self.OnSelectionChanged(event = None)
+        self.OnSelectionChanged(event=None)
 
     def OnSelectionChanged(self, event):
         index = self.listbox.GetSelection()
@@ -1235,37 +1011,37 @@
 
         decData = self.listbox.GetClientData(index)
         self.decorations.remove(decData)
-        
+
         self._updateListBox()
         if self.listbox.GetCount():
             self.listbox.SetSelection(0)
-            self.OnSelectionChanged(event = None)
+            self.OnSelectionChanged(event=None)
 
     def OnExport(self, event):
         for decor in self.decorations:
             if decor['name'] == 'image':
                 if not os.path.exists(decor['file']):
                     if decor['file']:
-                        GError(parent = self, message = _("File %s not found.") % decor['file'])
+                        GError(parent=self, message=_("File %s not found.") % decor['file'])
                     else:
-                        GError(parent = self, message = _("Decoration image file is missing."))
+                        GError(parent=self, message=_("Decoration image file is missing."))
                     return
 
         if self.formatChoice.GetSelection() == 0:
             name = self.dirBrowse.GetValue()
             if not os.path.exists(name):
                 if name:
-                    GError(parent = self, message = _("Directory %s not found.") % name)
+                    GError(parent=self, message=_("Directory %s not found.") % name)
                 else:
-                    GError(parent = self, message = _("Export directory is missing."))
+                    GError(parent=self, message=_("Export directory is missing."))
                 return
         elif self.formatChoice.GetSelection() == 1:
             if not self.gifBrowse.GetValue():
-                GError(parent = self, message = _("Export file is missing."))
+                GError(parent=self, message=_("Export file is missing."))
                 return
         elif self.formatChoice.GetSelection() == 2:
             if not self.swfBrowse.GetValue():
-                GError(parent = self, message = _("Export file is missing."))
+                GError(parent=self, message=_("Export file is missing."))
                 return
 
         # hide only to keep previous values
@@ -1304,7 +1080,7 @@
         self.listbox.Clear()
         names = {'time': _("Time stamp"), 'image': _("Image"), 'text': _("Text")}
         for decor in self.decorations:
-            self.listbox.Append(names[decor['name']], clientData = decor)
+            self.listbox.Append(names[decor['name']], clientData=decor)
 
     def _hideAll(self):
         self.hidevbox.Show(self.fontBox, False)
@@ -1314,37 +1090,277 @@
         self.hidevbox.Show(self.informBox, True)
         self.hidevbox.Layout()
 
+
+class AnimSimpleLayerManager(SimpleLayerManager):
+    """!Simple layer manager for animation tool.
+    Allows to add space-time dataset or series of maps.
+    """
+    def __init__(self, parent, layerList,
+                 lmgrStyle=SIMPLE_LMGR_RASTER | SIMPLE_LMGR_VECTOR |
+                 SIMPLE_LMGR_TB_TOP | SIMPLE_LMGR_STDS,
+                 toolbarCls=AnimSimpleLmgrToolbar, modal=True):
+        SimpleLayerManager.__init__(self, parent, layerList, lmgrStyle, toolbarCls, modal)
+
+    def OnAddStds(self, event):
+        """!Opens dialog for specifying temporal dataset.
+        Dummy layer is added first."""
+        layer = AnimLayer()
+        layer.hidden = True
+        self._layerList.AddLayer(layer)
+        self.SetStdsProperties(layer)
+        event.Skip()
+
+    def SetStdsProperties(self, layer):
+        dlg = AddTemporalLayerDialog(parent=self, layer=layer)
+        # first get hidden property, it's altered afterwards
+        hidden = layer.hidden
+        if dlg.ShowModal() == wx.ID_OK:
+            layer = dlg.GetLayer()
+            if hidden:
+                signal = self.layerAdded
+            else:
+                signal = self.cmdChanged
+            signal.emit(index=self._layerList.GetLayerIndex(layer), layer=layer)
+        else:
+            if hidden:
+                self._layerList.RemoveLayer(layer)
+        dlg.Destroy()
+        self._update()
+        self.anyChange.emit()
+
+    def _layerChangeProperties(self, layer):
+        """!Opens new module dialog or recycles it."""
+        if layer in self._dialogs:
+            dlg = self._dialogs[layer]
+            if dlg.IsShown():
+                dlg.Raise()
+                dlg.SetFocus()
+            else:
+                dlg.Show()
+        else:
+            if not hasattr(layer, 'maps'):
+                GUI(parent=self, giface=None,
+                    modal=self._modal).ParseCommand(cmd=layer.cmd,
+                                                    completed=(self.GetOptData, layer, ''))
+            else:
+                self.SetStdsProperties(layer)
+
+    def Activate3D(self, activate=True):
+        """!Activates/deactivates certain tool depending on 2D/3D view."""
+        self._toolbar.EnableTools(['addRaster', 'addVector',
+                                   'opacity', 'up', 'down'], not activate)
+
+
+class AddTemporalLayerDialog(wx.Dialog):
+    """!Dialog for adding space-time dataset/ map series."""
+    def __init__(self, parent, layer, title=_("Add space-time dataset layer")):
+        wx.Dialog.__init__(self, parent=parent, title=title)
+
+        self.layer = layer
+        self._mapType = None
+        self._name = None
+        self._cmd = None
+
+        self.tselect = Select(parent=self, type='strds')
+        iconTheme = UserSettings.Get(group='appearance', key='iconTheme', subkey='type')
+        bitmapPath = os.path.join(globalvar.ETCICONDIR, iconTheme, 'layer-open.png')
+        if os.path.isfile(bitmapPath) and os.path.getsize(bitmapPath):
+            bitmap = wx.Bitmap(name=bitmapPath)
+        else:
+            bitmap = wx.ArtProvider.GetBitmap(id=wx.ART_MISSING_IMAGE, client=wx.ART_TOOLBAR)
+        self.addManyMapsButton = wx.BitmapButton(self, bitmap=bitmap)
+        self.addManyMapsButton.Bind(wx.EVT_BUTTON, self._onAddMaps)
+
+        types = [('rast', _("Multiple raster maps")),
+                 ('vect', _("Multiple vector maps")),
+                 ('strds', _("Space time raster dataset")),
+                 ('stvds', _("Space time vector dataset"))]
+        self._types = dict(types)
+
+        self.tchoice = wx.Choice(parent=self)
+        for type_, text in types:
+            self.tchoice.Append(text, clientData=type_)
+
+        self.editBtn = wx.Button(parent=self, label='Set properties')
+
+        self.okBtn = wx.Button(parent=self, id=wx.ID_OK)
+        self.cancelBtn = wx.Button(parent=self, id=wx.ID_CANCEL)
+
+        self.okBtn.Bind(wx.EVT_BUTTON, self._onOK)
+        self.editBtn.Bind(wx.EVT_BUTTON, self._onProperties)
+        self.tchoice.Bind(wx.EVT_CHOICE,
+                          lambda evt: self._setType())
+        self.tselect.Bind(wx.EVT_TEXT,
+                          lambda evt: self._datasetChanged())
+
+        if self.layer.mapType:
+            self._setType(self.layer.mapType)
+        else:
+            self._setType('rast')
+        if self.layer.name:
+            self.tselect.SetValue(self.layer.name)
+        if self.layer.cmd:
+            self._cmd = self.layer.cmd
+
+        self._layout()
+        self.SetSize(self.GetBestSize())
+
+    def _layout(self):
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+        bodySizer = wx.BoxSizer(wx.VERTICAL)
+        typeSizer = wx.BoxSizer(wx.HORIZONTAL)
+        selectSizer = wx.BoxSizer(wx.HORIZONTAL)
+        typeSizer.Add(wx.StaticText(self, label=_("Input data type:")),
+                      flag=wx.ALIGN_CENTER_VERTICAL)
+        typeSizer.AddStretchSpacer()
+        typeSizer.Add(self.tchoice)
+        bodySizer.Add(typeSizer, flag=wx.EXPAND | wx.BOTTOM, border=5)
+
+        selectSizer.Add(self.tselect, flag=wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, border=5)
+        selectSizer.Add(self.addManyMapsButton, flag=wx.EXPAND)
+        bodySizer.Add(selectSizer, flag=wx.BOTTOM, border=5)
+        bodySizer.Add(self.editBtn, flag=wx.BOTTOM, border=5)
+        mainSizer.Add(bodySizer, proportion=1, flag=wx.EXPAND | wx.ALL, border=10)
+
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(self.okBtn)
+        btnSizer.AddButton(self.cancelBtn)
+        btnSizer.Realize()
+
+        mainSizer.Add(btnSizer, proportion=0,
+                      flag=wx.EXPAND | wx.ALL, border=10)
+
+        self.SetSizer(mainSizer)
+        mainSizer.Fit(self)
+
+    def _datasetChanged(self):
+        if self._name != self.tselect.GetValue():
+            self._name = self.tselect.GetValue()
+            self._cmd = None
+
+    def _setType(self, typeName=None):
+        if typeName:
+            self.tchoice.SetStringSelection(self._types[typeName])
+            self.tselect.SetType(typeName)
+            if typeName in ('strds', 'stvds'):
+                self.tselect.SetType(typeName, multiple=False)
+                self.addManyMapsButton.Disable()
+            else:
+                self.tselect.SetType(typeName, multiple=True)
+                self.addManyMapsButton.Enable()
+            self._mapType = typeName
+            self.tselect.SetValue('')
+        else:
+            typeName = self.tchoice.GetClientData(self.tchoice.GetSelection())
+            if typeName in ('strds', 'stvds'):
+                self.tselect.SetType(typeName, multiple=False)
+                self.addManyMapsButton.Disable()
+            else:
+                self.tselect.SetType(typeName, multiple=True)
+                self.addManyMapsButton.Enable()
+            if typeName != self._mapType:
+                self._cmd = None
+                self._mapType = typeName
+                self.tselect.SetValue('')
+
+    def _createDefaultCommand(self):
+        cmd = []
+        if self._mapType in ('rast', 'strds'):
+            cmd.append('d.rast')
+        elif self._mapType in ('vect', 'stvds'):
+            cmd.append('d.vect')
+        if self._name:
+            if self._mapType in ('rast', 'vect'):
+                cmd.append('map={}'.format(self._name.split(',')[0]))
+            else:
+                try:
+                    maps = getRegisteredMaps(self._name, etype=self._mapType)
+                    if maps:
+                        cmd.append('map={}'.format(maps[0]))
+                except gcore.ScriptError, e:
+                    GError(parent=self, message=str(e), showTraceback=False)
+                    return None
+        return cmd
+
+    def _onAddMaps(self, event):
+        dlg = MapLayersDialog(self, title=_("Select raster/vector maps."))
+        dlg.applyAddingMapLayers.connect(lambda mapLayers:
+                                         self.tselect.SetValue(','.join(mapLayers)))
+        index = 0 if self._mapType == 'rast' else 1
+        dlg.layerType.SetSelection(index)
+        dlg.LoadMapLayers(dlg.GetLayerType(cmd=True),
+                          dlg.mapset.GetStringSelection())
+        if dlg.ShowModal() == wx.ID_OK:
+            self.tselect.SetValue(','.join(dlg.GetMapLayers()))
+
+        dlg.Destroy()
+
+    def _onProperties(self, event):
+        self._checkInput()
+        if self._cmd:
+            GUI(parent=self, show=True, modal=True).ParseCommand(cmd=self._cmd,
+                                                                 completed=(self._getOptData, '', ''))
+
+    def _checkInput(self):
+        if not self.tselect.GetValue():
+            GMessage(parent=self, message=_("Please select maps or dataset first."))
+            return
+
+        if not self._cmd:
+            self._cmd = self._createDefaultCommand()
+
+    def _getOptData(self, dcmd, layer, params, propwin):
+        if dcmd:
+            self._cmd = dcmd
+
+    def _onOK(self, event):
+        self._checkInput()
+        if self._cmd:
+            try:
+                self.layer.hidden = False
+                self.layer.mapType = self._mapType
+                self.layer.name = self._name
+                self.layer.cmd = self._cmd
+                event.Skip()
+            except (GException, gcore.ScriptError), e:
+                GError(parent=self, message=str(e))
+
+    def GetLayer(self):
+        return self.layer
+
+
 def test():
     import wx.lib.inspection
 
-    import grass.script as grass
-
     app = wx.PySimpleApp()
 
-    testExport()
+#    testTemporalLayer()
+#    testAnimLmgr()
+    testAnimInput()
     # wx.lib.inspection.InspectionTool().Show()
 
-    
-
     app.MainLoop()
 
+
 def testAnimInput():
     anim = AnimationData()
-    anim.SetDefaultValues(animationIndex = 0, windowIndex = 0)
+    anim.SetDefaultValues(animationIndex=0, windowIndex=0)
 
-    dlg = InputDialog(parent = None, mode = 'add', animationData = anim)
+    dlg = InputDialog(parent=None, mode='add', animationData=anim)
     dlg.Show()
 
+
 def testAnimEdit():
     anim = AnimationData()
-    anim.SetDefaultValues(animationIndex = 0, windowIndex = 0)
+    anim.SetDefaultValues(animationIndex=0, windowIndex=0)
 
-    dlg = EditDialog(parent = None, animationData = [anim])
+    dlg = EditDialog(parent=None, animationData=[anim])
     dlg.Show()
 
+
 def testExport():
-    dlg = ExportDialog(parent = None, temporal = TemporalMode.TEMPORAL,
-                       timeTick = 200, visvis = True)
+    dlg = ExportDialog(parent=None, temporal=TemporalMode.TEMPORAL,
+                       timeTick=200)
     if dlg.ShowModal() == wx.ID_OK:
         print dlg.GetDecorations()
         print dlg.GetExportInformation()
@@ -1353,6 +1369,28 @@
         dlg.Destroy()
 
 
+def testTemporalLayer():
+    frame = wx.Frame(None)
+    frame.Show()
+    layer = AnimLayer()
+    dlg = AddTemporalLayerDialog(parent=frame, layer=layer)
+    if dlg.ShowModal() == wx.ID_OK:
+        layer = dlg.GetLayer()
+        print layer.name, layer.cmd, layer.mapType
+        dlg.Destroy()
+    else:
+        dlg.Destroy()
+
+
+def testAnimLmgr():
+    from core.layerlist import LayerList
+
+    frame = wx.Frame(None)
+    mgr = AnimSimpleLayerManager(parent=frame, layerList=LayerList())
+    frame.mgr = mgr
+    frame.Show()
+
+
 if __name__ == '__main__':
-
-    test()
\ No newline at end of file
+    gcore.set_raise_on_error(True)
+    test()

Modified: grass/trunk/gui/wxpython/animation/frame.py
===================================================================
--- grass/trunk/gui/wxpython/animation/frame.py	2013-11-30 19:48:36 UTC (rev 58342)
+++ grass/trunk/gui/wxpython/animation/frame.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -4,46 +4,54 @@
 @brief Animation frame and different types of sliders
 
 Classes:
- - frame::SwipeMapDialog
  - frame::AnimationFrame
  - frame::AnimationsPanel
  - frame::AnimationSliderBase
  - frame::SimpleAnimationSlider
  - frame::TimeAnimationSlider
 
+(C) 2013 by the GRASS Development Team
 
-(C) 2012 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.
 
- at author Anna Kratochvilova <kratochanna gmail.com>
+ at author Anna Petrasova <kratochanna gmail.com>
 """
 import os
 import sys
 import wx
 import wx.aui
+import tempfile
 
 if __name__ == '__main__':
     sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
+import grass.script as gcore
 from core import globalvar
 from gui_core.widgets import IntegerValidator
 from core.gcmd import RunCommand
 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
+TMP_DIR = tempfile.mkdtemp()
 
+gcore.set_raise_on_error(True)
+
+
 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.iconsize = (16, 16)
@@ -52,22 +60,37 @@
 
         self.animations = [Animation() for i in range(MAX_COUNT)]
         self.windows = []
-        self.animationPanel = AnimationsPanel(self, self.windows, initialCount = MAX_COUNT)
+        self.animationPanel = AnimationsPanel(self, self.windows, initialCount=MAX_COUNT)
         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['nontemporal'] = SimpleAnimationSlider(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._mgr = wx.aui.AuiManager(self)
@@ -90,13 +113,14 @@
 
     def InitStatusbar(self):
         """!Init statusbar."""
-        self.CreateStatusBar(number = 1, style = 0)
+        self.CreateStatusBar(number=1, style=0)
 
     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():
             self._mgr.AddPane(slider, wx.aui.AuiPaneInfo().PaneBorder(False).Name('slider_' + name).
                               Layer(1).CaptionVisible(False).BestSize(slider.GetBestSize()).
@@ -105,7 +129,7 @@
 
     def _addToolbar(self, name):
         """!Add defined toolbar to the window
-        
+
         Currently known toolbars are:
          - 'mainToolbar'          - data management
          - 'animationToolbar'     - animation controls
@@ -143,15 +167,13 @@
                               CloseButton(False).Layer(2).Row(1).
                               BestSize((self.toolbars['miscToolbar'].GetBestSize())))
 
-    def SetAnimations(self, inputs=None, dataType=None):
+    def SetAnimations(self, layerLists):
         """!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):
         self.controller.AddAnimation()
 
@@ -166,7 +188,7 @@
 
     def OnEditAnimation(self, event):
         self.controller.EditAnimations()
-        
+
     def SetSlider(self, name):
         if name == 'nontemporal':
             self._mgr.GetPane('slider_nontemporal').Show()
@@ -189,7 +211,7 @@
         self.controller.StartAnimation()
 
     def OnPause(self, event):
-        self.controller.PauseAnimation(paused = event.IsChecked())
+        self.controller.PauseAnimation(paused=event.IsChecked())
 
     def OnStop(self, event):
         self.controller.EndAnimation()
@@ -218,10 +240,10 @@
                 win.SetFocus()
             else:
                 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
             win.speedChanged.connect(self.ChangeSpeed)
             win.Show()
@@ -232,40 +254,64 @@
     def Reload(self, event):
         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):
         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()
-                     
-                     
+
     def OnHelp(self, event):
         RunCommand('g.manual',
-                   quiet = True,
-                   entry = 'wxGUI.animation')
+                   quiet=True,
+                   entry='wxGUI.animation')
 
     def OnCloseWindow(self, event):
+        CleanUp(TMP_DIR)()
         self.Destroy()
 
     def __del__(self):
         if hasattr(self, 'controller') and hasattr(self.controller, 'timer'):
             if self.controller.timer.IsRunning():
                 self.controller.timer.Stop()
+        CleanUp(TMP_DIR)()
 
 
 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.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):
             w = AnimationWindow(self)
             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(1)
@@ -278,7 +324,6 @@
             self.mainSizer.Hide(windows[i])
         self.Layout()
 
-
     def AddWindow(self, index):
         if len(self.shown) == self.count:
             return
@@ -299,12 +344,12 @@
 
 class AnimationSliderBase(wx.Panel):
     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.callbackSliderChanged = None
@@ -333,7 +378,7 @@
 
     def SetFrames(self, frames):
         self._setFrames(frames)
-        
+
     def _setFrames(self, frames):
         raise NotImplementedError
 
@@ -346,7 +391,7 @@
     def SetCallbackFrameIndexChanged(self, callback):
         self.callbackFrameIndexChanged = callback
 
-    def EnableSlider(self, enable = True):
+    def EnableSlider(self, enable=True):
         if enable and self.framesCount <= 1:
             enable = False  # we don't want to enable it
         self.enable = enable
@@ -394,11 +439,11 @@
 
     def _doLayout(self):
         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)
 
     def _setFrames(self, count):
@@ -423,8 +468,8 @@
     def __init__(self, parent):
         AnimationSliderBase.__init__(self, parent)
         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.temporalType = TemporalType.RELATIVE
 
@@ -434,21 +479,21 @@
     def _doLayout(self):
         vbox = wx.BoxSizer(wx.VERTICAL)
         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.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.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.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.SetSizerAndFit(vbox)
@@ -507,9 +552,10 @@
 
     def _updateFrameIndex(self, index):
         start = self.timeLabels[index][0]
-        if self.timeLabels[index][1]: # interval
+        if self.timeLabels[index][1]:  # interval
             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:
                 label = _("to %(to)s") % {'to': self.timeLabels[index][1]}
         else:
@@ -523,20 +569,3 @@
         if len(label) != self.label2Length:
             self.label2Length = len(label)
             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()

Modified: grass/trunk/gui/wxpython/animation/g.gui.animation.py
===================================================================
--- grass/trunk/gui/wxpython/animation/g.gui.animation.py	2013-11-30 19:48:36 UTC (rev 58342)
+++ grass/trunk/gui/wxpython/animation/g.gui.animation.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -60,22 +60,21 @@
 if __name__ == '__main__':
     sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
 
-from core.settings import UserSettings
 from core.globalvar import CheckWxVersion
-from core.giface import StandaloneGrassInterface
 from core.utils import _, GuiModuleMain
+from core.layerlist import LayerList
 from animation.frame import AnimationFrame, MAX_COUNT
+from animation.data import AnimLayer
 
+
 def main():
     rast = options['rast']
     vect = options['vect']
     strds = options['strds']
     stvds = options['stvds']
-    
-    dataType=None
-    inputs=None
-    numInputs=0
-    
+
+    numInputs = 0
+
     if rast:
         numInputs += 1
     if vect:
@@ -85,33 +84,47 @@
     if stvds:
         numInputs += 1
 
-    if  numInputs > 1:
+    if numInputs > 1:
         grass.fatal(_("Options 'rast', 'vect', 'strds' and 'stvds' are mutually exclusive."))
 
+    layerList = LayerList()
     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:
-        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:
-        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:
-        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()
     if not CheckWxVersion([2, 9]):
         wx.InitAllImageHandlers()
 
-    frame = AnimationFrame(parent = None)
+    frame = AnimationFrame(parent=None)
     frame.CentreOnScreen()
     frame.Show()
-    frame.SetAnimations(inputs = inputs, dataType = dataType)
+    if len(layerList) >= 1:
+        frame.SetAnimations([layerList] + [None] * (MAX_COUNT - 1))
     app.MainLoop()
 
 if __name__ == '__main__':
     options, flags = grass.parser()
-    
+
     GuiModuleMain(main)

Modified: grass/trunk/gui/wxpython/animation/mapwindow.py
===================================================================
--- grass/trunk/gui/wxpython/animation/mapwindow.py	2013-11-30 19:48:36 UTC (rev 58342)
+++ grass/trunk/gui/wxpython/animation/mapwindow.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -1,32 +1,23 @@
 """!
 @package animation.mapwindow
 
- at brief Animation window and bitmaps management
+ at brief Animation window
 
 Classes:
  - mapwindow::BufferedWindow
  - 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
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
- at author Anna Kratochvilova <kratochanna gmail.com>
+ at author Anna Petrasova <kratochanna gmail.com>
 """
-import os
+
 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.settings import UserSettings
-from core.utils import _, CmdToTuple, autoCropImageFromFile
 
-from grass.pydispatch.signal import Signal
 
 class BufferedWindow(wx.Window):
     """
@@ -45,7 +36,8 @@
     """
     def __init__(self, *args, **kwargs):
         # 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)
 
         Debug.msg(2, "BufferedWindow.__init__()")
@@ -71,7 +63,7 @@
         # The Buffer init is done here, to make sure the buffer is always
         # the same size as the Window
         #Size  = self.GetClientSizeTuple()
-        size  = self.GetClientSize()
+        size = self.GetClientSize()
 
         # Make new offscreen bitmap: this bitmap will always have the
         # current drawing in it, so it can be used to save the image to
@@ -82,7 +74,7 @@
 
     def SaveToFile(self, FileName, FileType=wx.BITMAP_TYPE_PNG):
         ## 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
         self._Buffer.SaveFile(FileName, FileType)
 
@@ -99,18 +91,18 @@
         dc = wx.MemoryDC()
         dc.SelectObject(self._Buffer)
         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.Update()
 
 
 class AnimationWindow(BufferedWindow):
     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__()")
 
         self.bitmap = wx.EmptyBitmap(1, 1)
-        self.text = ''
         self.parent = parent
         self._pdc = wx.PseudoDC()
         self._overlay = None
@@ -126,20 +118,19 @@
         """!Draws bitmap."""
         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.DrawText(self.text, 0, 0)
 
     def OnSize(self, event):
         Debug.msg(5, "AnimationWindow.OnSize()")
 
-        self.DrawBitmap(self.bitmap, self.text)
-        
+        self.DrawBitmap(self.bitmap)
+
         BufferedWindow.OnSize(self, event)
         if event:
             event.Skip()
 
-    def DrawBitmap(self, bitmap, text):
+    def DrawBitmap(self, bitmap):
         """!Draws bitmap.
         Does not draw the bitmap if it is the same one as last time.
         """
@@ -147,7 +138,6 @@
             return
 
         self.bitmap = bitmap
-        self.text = text
         self.UpdateDrawing()
 
     def DrawOverlay(self, x, y):
@@ -193,7 +183,6 @@
         if self._overlay:
             self._pdc.DrawToDC(dc)
 
-
     def OnMouseEvents(self, event):
         """!Handle mouse events."""
         # If it grows larger, split it.
@@ -223,371 +212,3 @@
         """!Returns x, y position in pixels"""
         rect = self._pdc.GetIdBounds(1)
         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

Modified: grass/trunk/gui/wxpython/animation/nviztask.py
===================================================================
--- grass/trunk/gui/wxpython/animation/nviztask.py	2013-11-30 19:48:36 UTC (rev 58342)
+++ grass/trunk/gui/wxpython/animation/nviztask.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -6,12 +6,12 @@
 Classes:
  - 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
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
- at author Anna Kratochvilova <kratochanna gmail.com>
+ at author Anna Petrasova <kratochanna gmail.com>
 """
 
 import os
@@ -19,8 +19,7 @@
 try:
     import xml.etree.ElementTree as etree
 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__':
     sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
@@ -29,8 +28,8 @@
 from core.gcmd import RunCommand, GException
 from core.utils import GetLayerNameFromCmd, CmdToTuple, _
 from grass.script import task as gtask
-from grass.script import core as grass
 
+
 class NvizTask:
     def __init__(self):
         self.task = None
@@ -42,7 +41,7 @@
         self.filename = filename
         try:
             gxwXml = ProcessWorkspaceFile(etree.parse(self.filename))
-        except Exception, e:
+        except Exception:
             raise GException(_("Reading workspace file <%s> failed.\n"
                                "Invalid file, unable to parse XML document.") % filename)
         # for display in gxwXml.displays:
@@ -71,20 +70,19 @@
 
             if not layer['nviz']:
                 continue
-            layerName, found = GetLayerNameFromCmd(layer['cmd'], fullyQualified = False,
-                                                   param = 'map')
+            layerName, found = GetLayerNameFromCmd(layer['cmd'], fullyQualified=False,
+                                                   param='map')
             if not found:
                 continue
 
             if 'surface' in layer['nviz']:
-                self._processSurface(layer['nviz']['surface'], mapName = layerName)
+                self._processSurface(layer['nviz']['surface'], mapName=layerName)
 
-
     def _processSurface(self, surface, mapName):
         self._setMultiTaskParam('elevation_map', mapName)
 
         # attributes like color, shine, transparency
-        attributes = ('color', 'shine', 'transp') # mask missing
+        attributes = ('color', 'shine', 'transp')  # mask missing
         parameters = (('color_map', 'color'),
                      ('shininess_map', 'shininess_value'),
                      ('transparency_map', 'transparency_value'))
@@ -128,7 +126,7 @@
 
     def _processState(self, state):
         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'],
                                                     state['view']['position']['y'])))
         self.task.set_param('height', state['iview']['height']['value'])
@@ -141,20 +139,17 @@
                                                  state['iview']['focus']['z'])))
         self.task.set_param('light_position', self._join((state['light']['position']['x'],
                                                           state['light']['position']['y'],
-                                                          state['light']['position']['z']/100.)))
+                                                          state['light']['position']['z'] / 100.)))
         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_ambient', state['light']['ambient'])
 
-
-        
-
     def _setMultiTaskParam(self, param, value):
         last = self.task.get_param(param)['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)
         return delim.join(map(str, toJoin))
 
@@ -168,13 +163,22 @@
         # params = self.task.get_list_params()
         # parameter with 'map' name
         # 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 = []
         if not self.task:
             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:
             self.task.set_param(paramName, value)
             # FIXME: we assume we want always default color map
@@ -182,8 +186,8 @@
                 self.task.set_param('color_map', '')
             self.task.set_flag('overwrite', True)
             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
 
@@ -192,29 +196,25 @@
             return None
         self.task.set_flag('overwrite', True)
         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)
 
     def GetRegion(self):
         return self.region
 
 
-
 def test():
-
     nviz = NvizTask('/home/anna/testy/nviz/t12.gxw')
     # nviz = NvizState('/home/anna/testy/nviz/t3.gxw')
-    
+
     # cmd = nviz.GetCommand()
-    cmds = nviz.GetCommandSeries(['aspect','elevation'], 'color_map')
+    cmds = nviz.GetCommandSeries(['aspect', 'elevation'], 'color_map')
     for cmd in cmds:
         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
 
 
 if __name__ == '__main__':
 
     test()
-
-

Added: grass/trunk/gui/wxpython/animation/provider.py
===================================================================
--- grass/trunk/gui/wxpython/animation/provider.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/animation/provider.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -0,0 +1,764 @@
+# -*- coding: utf-8 -*-
+"""!
+ at package animation.provider
+
+ at 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.
+
+ at 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()


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

Modified: grass/trunk/gui/wxpython/animation/temporal_manager.py
===================================================================
--- grass/trunk/gui/wxpython/animation/temporal_manager.py	2013-11-30 19:48:36 UTC (rev 58342)
+++ grass/trunk/gui/wxpython/animation/temporal_manager.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -27,13 +27,14 @@
 import grass.temporal as tgis
 from core.gcmd import GException
 from core.utils import _
-from utils import validateTimeseriesName, TemporalType
+from animation.utils import validateTimeseriesName, TemporalType
 
 
 class DataMode:
     SIMPLE = 1
     MULTIPLE = 2
 
+
 class GranularityMode:
     ONE_UNIT = 1
     ORIGINAL = 2
@@ -60,7 +61,7 @@
     def SetTemporalType(self, ttype):
         self._temporalType = ttype
 
-    temporalType = property(fget = GetTemporalType, fset = SetTemporalType)
+    temporalType = property(fget=GetTemporalType, fset=SetTemporalType)
 
     def AddTimeSeries(self, timeseries, etype):
         """!Add space time dataset
@@ -90,13 +91,11 @@
         ret, message = self._setTemporalState()
         if not ret:
             raise GException(message)
-        if message: # warning
+        if message:  # warning
             return message
 
         return None
 
-
-
     def _setTemporalState(self):
         # check for absolute x relative
         absolute, relative = 0, 0
@@ -106,13 +105,14 @@
             else:
                 relative += 1
         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
         if absolute:
             self.temporalType = TemporalType.ABSOLUTE
         else:
             self.temporalType = TemporalType.RELATIVE
-            
+
         # check for units for relative type
         if relative:
             units = set()
@@ -130,9 +130,10 @@
             else:
                 point += 1
         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.")
-            return True, message # warning
+            return True, message  # warning
 
         return True, None
 
@@ -140,14 +141,14 @@
         """!Returns temporal granularity of currently loaded timeseries."""
         if self.dataMode == DataMode.SIMPLE:
             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
                 unit = self.timeseriesInfo[self.timeseriesList[0]]['unit']
                 if self.granularityMode == GranularityMode.ONE_UNIT:
                     granNum = 1
             else:  # absolute
                 granNum, unit = gran.split()
-                if self.granularityMode == GranularityMode.ONE_UNIT: 
+                if self.granularityMode == GranularityMode.ONE_UNIT:
                     granNum = 1
 
             return (int(granNum), unit)
@@ -174,25 +175,34 @@
                 granNum = 1
             return (granNum, unit)
 
-
     def GetLabelsAndMaps(self):
         """!Returns time labels and map names.
         """
         mapLists = []
         labelLists = []
+        labelListSet = set()
         for dataset in self.timeseriesList:
             grassLabels, listOfMaps = self._getLabelsAndMaps(dataset)
             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 = []
         for mapList, labelList in zip(mapLists, labelLists):
             newMapList = [None] * len(timestamps)
             i = 0
             # compare start time
-            while timestamps[i][0] != labelList[0][0]: # compare
+            while timestamps[i][0] != labelList[0][0]:  # compare
                 i += 1
             newMapList[i:i + len(mapList)] = mapList
             newMapLists.append(newMapList)
@@ -208,7 +218,7 @@
         for both interval and point data.
         """
         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)
         sp.select()
 
@@ -231,11 +241,11 @@
         # 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
         # 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:
             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:
                 if isinstance(mymap, list):
                     if len(mymap) > 0:
@@ -276,9 +286,6 @@
                             listOfMaps.append(series)
                 timeLabels.append((str(start), end, unit))
 
-        if self.temporalType == TemporalType.ABSOLUTE:
-            timeLabels = self._pretifyTimeLabels(timeLabels)
-
         return timeLabels, listOfMaps
 
     def _pretifyTimeLabels(self, labels):
@@ -312,7 +319,7 @@
         sp.select()
         # Get ordered map list
         maps = sp.get_registered_maps_as_objects()
-        
+
         if not sp.check_temporal_topology(maps):
             raise GException(_("Topology of Space time dataset %s is invalid." % id))
 
@@ -326,6 +333,7 @@
         infoDict[id]['map_time'] = sp.get_map_time()
         infoDict[id]['maps'] = maps
 
+
 def test():
     from pprint import pprint
 
@@ -344,84 +352,87 @@
     except GException, e:
         print e
         return
-    
+
     print '///////////////////////////'
     gran = temp.GetGranularity()
     print "granularity: " + str(gran)
     pprint (temp.GetLabelsAndMaps())
 
 
-
 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.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)
 
-    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)
+    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()
+    n1 = grass.read_command("g.tempfile", pid=1, flags='d').strip()
     fd = open(n1, 'w')
     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()
 
-    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.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()
     name1 = 'absinterval1'
     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)):
-        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")
-        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
 
+
 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.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)
 
-    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)
+    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()
+    n1 = grass.read_command("g.tempfile", pid=1, flags='d').strip()
     fd = open(n1, 'w')
     fd.write(
         "prec_1|1|4\n"
@@ -430,92 +441,94 @@
         "prec_4|10|11\n"
         "prec_5|11|14\n"
         "prec_6|14|17\n"
-       )
+    )
     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.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_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()
     name1 = 'relinterval1'
     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)):
-        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")
-        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
-        
+
+
 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.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()
     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")
 
-    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
 
+
 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.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()
     name = 'relpoint'
-    grass.run_command('t.create', overwrite = True, type='strds',
+    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")
 
-    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
 
 if __name__ == '__main__':
 
     test()
-
-
-

Modified: grass/trunk/gui/wxpython/animation/toolbars.py
===================================================================
--- grass/trunk/gui/wxpython/animation/toolbars.py	2013-11-30 19:48:36 UTC (rev 58342)
+++ grass/trunk/gui/wxpython/animation/toolbars.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -4,42 +4,53 @@
 @brief Animation toolbars
 
 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
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
- at author Anna Kratochvilova <kratochanna gmail.com>
+ at author Anna Petrasova <kratochanna gmail.com>
 """
 import wx
 from gui_core.toolbars import BaseToolbar, BaseIcons
 from icons.icon import MetaIcon
-
-from anim import ReplayMode
 from core.utils import _
+from gui_core.simplelmgr import SimpleLmgrToolbar
+from animation.anim import ReplayMode
 
 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):
     """!Main toolbar (data management)
     """
@@ -47,12 +58,12 @@
         """!Main toolbar constructor
         """
         BaseToolbar.__init__(self, parent)
-        
+
         self.InitToolbar(self._toolbarData())
 
         # realize the toolbar
         self.Realize()
-        
+
     def _toolbarData(self):
         """!Returns toolbar data (name, icon, handler)"""
         # BaseIcons are a set of often used icons. It is possible
@@ -65,8 +76,10 @@
                                      ("reload", BaseIcons["render"],
                                       self.parent.Reload),
                                      ("exportAnimation", icons["exportAnimation"],
-                                      self.parent.OnExportAnimation),
-                                    ))
+                                      self.parent.OnExportAnimation)
+                                     ))
+
+
 class AnimationToolbar(BaseToolbar):
     """!Animation toolbar (to control animation)
     """
@@ -74,7 +87,7 @@
         """!Animation toolbar constructor
         """
         BaseToolbar.__init__(self, parent)
-        
+
         self.InitToolbar(self._toolbarData())
 
         # realize the toolbar
@@ -82,8 +95,7 @@
 
         self.isPlayingForward = True
         self.EnableAnimTools(False)
-        
-        
+
     def _toolbarData(self):
         """!Returns toolbar data (name, icon, handler)"""
         # BaseIcons are a set of often used icons. It is possible
@@ -107,8 +119,9 @@
                                       wx.ITEM_CHECK),
                                      (None, ),
                                      ("adjustSpeed", icons['speed'],
-                                       self.parent.OnAdjustSpeed)
-                                    ))
+                                      self.parent.OnAdjustSpeed)
+                                     ))
+
     def OnPlayForward(self, event):
         self.PlayForward()
         self.parent.OnPlayForward(event)
@@ -132,7 +145,7 @@
         self.EnableTool(self.stop, True)
         self.ToggleTool(self.pause, False)
         self.isPlayingForward = False
-        
+
     def OnPause(self, event):
         self.Pause()
         self.parent.OnPause(event)
@@ -187,6 +200,7 @@
         self.EnableTool(self.pause, enable)
         self.EnableTool(self.stop, enable)
 
+
 class MiscToolbar(BaseToolbar):
     """!Toolbar with miscellaneous tools related to app
     """
@@ -194,15 +208,34 @@
         """!Toolbar constructor
         """
         BaseToolbar.__init__(self, parent)
-        
+
         self.InitToolbar(self._toolbarData())
         # realize the toolbar
         self.Realize()
-        
+
     def _toolbarData(self):
         """!Toolbar data"""
         return self._getToolbarData((("help", BaseIcons['help'],
                                       self.parent.OnHelp),
-                                    ("quit", BaseIcons['quit'],
+                                     ("quit", BaseIcons['quit'],
                                       self.parent.OnCloseWindow),
-                                     ))
\ No newline at end of file
+                                     ))
+
+
+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)

Modified: grass/trunk/gui/wxpython/animation/utils.py
===================================================================
--- grass/trunk/gui/wxpython/animation/utils.py	2013-11-30 19:48:36 UTC (rev 58342)
+++ grass/trunk/gui/wxpython/animation/utils.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -10,14 +10,16 @@
  - 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
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
- at author Anna Kratochvilova <kratochanna gmail.com>
+ at author Anna Perasova <kratochanna gmail.com>
 """
+import os
 import wx
+import hashlib
 try:
     from PIL import Image
     hasPIL = True
@@ -30,24 +32,29 @@
 from core.gcmd import GException
 from core.utils import _
 
+
 class TemporalMode:
     TEMPORAL = 1
     NONTEMPORAL = 2
 
+
 class TemporalType:
     ABSOLUTE = 1
     RELATIVE = 2
 
+
 class Orientation:
     FORWARD = 1
     BACKWARD = 2
 
+
 class ReplayMode:
     ONESHOT = 1
     REVERSE = 2
     REPEAT = 3
 
-def validateTimeseriesName(timeseries, etype = 'strds'):
+
+def validateTimeseriesName(timeseries, etype='strds'):
     """!Checks if space time dataset exists and completes missing mapset.
 
     Raises GException if dataset doesn't exist.
@@ -60,13 +67,13 @@
         else:
             raise GException(_("Space time dataset <%s> not found.") % timeseries)
 
-
     for mapset, names in trastDict.iteritems():
         if timeseries in names:
             return timeseries + "@" + mapset
 
     raise GException(_("Space time dataset <%s> not found.") % timeseries)
 
+
 def validateMapNames(names, etype):
     """!Checks if maps exist and completes missing mapset.
 
@@ -95,18 +102,13 @@
 
 
 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 = []
-    if etype == 'strds':
-        sp = tgis.SpaceTimeRasterDataset(ident=timeseries)
-    elif etype == 'stvds':
-        sp = tgis.SpaceTimeVectorDataset(ident=timeseries)
+    sp = tgis.open_old_space_time_dataset(timeseries, etype)
 
-    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)
+    rows = sp.get_registered_maps(columns="id", where=None, order="start_time")
     timeseriesMaps = []
     if rows:
         for row in rows:
@@ -114,15 +116,84 @@
     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):
     """!Fits source rectangle into destination rectangle
     by scaling and centering.
 
     @code
-   
+
     >>> ComputeScaledRect(sourceSize = (10, 40), destSize = (100, 50))
     {'height': 50, 'scale': 1.25, 'width': 13, 'x': 44, 'y': 0}
-    
+
     @endcode
 
     @param sourceSize size of source rectangle
@@ -145,6 +216,7 @@
 
     return {'width': width, 'height': height, 'x': x, 'y': y, 'scale': scale}
 
+
 def RenderText(text, font):
     """!Renderes text with given font to bitmap."""
     dc = wx.MemoryDC()
@@ -160,8 +232,80 @@
 
     return bmp
 
+
 def WxImageToPil(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
+
+
+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

Modified: grass/trunk/gui/wxpython/core/layerlist.py
===================================================================
--- grass/trunk/gui/wxpython/core/layerlist.py	2013-11-30 19:48:36 UTC (rev 58342)
+++ grass/trunk/gui/wxpython/core/layerlist.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -191,7 +191,7 @@
 
         self._mapTypes = ['rast', 'vect', 'rast3d']
         self._internalTypes = {'rast': 'cell',
-                               'vect': 'vect',
+                               'vect': 'vector',
                                'rast3d': 'grid3'}
 
     def GetName(self):

Modified: grass/trunk/gui/wxpython/gui_core/simplelmgr.py
===================================================================
--- grass/trunk/gui/wxpython/gui_core/simplelmgr.py	2013-11-30 19:48:36 UTC (rev 58342)
+++ grass/trunk/gui/wxpython/gui_core/simplelmgr.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -49,7 +49,8 @@
     """!Simple layer manager class provides similar functionality to
     Layertree, but it's just list, not tree."""
     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')
 
         self._style = lmgrStyle
@@ -57,9 +58,14 @@
         self._checkList = wx.CheckListBox(self, style=wx.LB_EXTENDED)
         # dialog windows held separately
         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._modal = modal
+        
 
         # needed in order not to change selection when moving layers
         self._blockSelectionChanged = False
@@ -81,6 +87,7 @@
 
         self._layout()
         self.SetMinSize((200, -1))
+        self._update()
 
     def _layout(self):
         self._auimgr.AddPane(self._checkList,
@@ -132,16 +139,17 @@
         Dummy layer is added first."""
         cmd = ['d.rast']
         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()
 
     def OnAddVector(self, event):
         """!Opens d.vect dialog and adds layer.
         Dummy layer is added first."""
         cmd = ['d.vect']
+
         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, ''))
         event.Skip()
 
@@ -150,7 +158,7 @@
         Dummy layer is added first."""
         cmd = ['d.rast3d']
         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, ''))
         event.Skip()
 
@@ -160,7 +168,7 @@
         for layer in layers:
             self.layerRemoved.emit(index=self._layerList.GetLayerIndex(layer), layer=layer)
             self._layerList.RemoveLayer(layer)
-            if self._dialogs[layer]:
+            if layer in self._dialogs and self._dialogs[layer]:
                 self._dialogs[layer].Destroy()
         self._update()
         self.anyChange.emit()
@@ -208,8 +216,8 @@
 
     def _layerChangeProperties(self, layer):
         """!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():
                 dlg.Raise()
                 dlg.SetFocus()
@@ -217,7 +225,7 @@
                 dlg.Show()
         else:
             GUI(parent=self, giface=None,
-                modal=False).ParseCommand(cmd=layer.cmd,
+                modal=self._modal).ParseCommand(cmd=layer.cmd,
                                           completed=(self.GetOptData, layer, ''))
 
     def OnLayerChangeOpacity(self, event):
@@ -351,7 +359,7 @@
             direction = wx.TB_HORIZONTAL
         BaseToolbar.__init__(self, parent, style=wx.NO_BORDER | direction)
 
-        self.InitToolbar(self._toolbarData())
+        self.InitToolbar(self._getToolbarData(self._toolbarData()))
 
         # realize the toolbar
         self.Realize()
@@ -381,7 +389,7 @@
             data.insert(0, ('addRaster', BaseIcons['addRast'],
                             self.parent.OnAddRaster))
 
-        return self._getToolbarData(data)
+        return data
 
 
 icons = {

Modified: grass/trunk/gui/wxpython/lmgr/frame.py
===================================================================
--- grass/trunk/gui/wxpython/lmgr/frame.py	2013-11-30 19:48:36 UTC (rev 58342)
+++ grass/trunk/gui/wxpython/lmgr/frame.py	2013-12-01 03:46:14 UTC (rev 58343)
@@ -1468,9 +1468,16 @@
                 if tree.GetLayerInfo(layer, key = 'type') == 'raster':
                     rasters.append(tree.GetLayerInfo(layer, key = 'maplayer').GetName())
             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):
         """!Init histogram display canvas and tools
         """



More information about the grass-commit mailing list