Browse Source

wxGUI: add support for WMS layers (work in progress)
author: Stepan Turek


git-svn-id: https://svn.osgeo.org/grass/grass/trunk@54454 15284696-431f-4ddb-bdfa-cd5b030d7da7

Martin Landa 12 years ago
parent
commit
07b54c7f78

+ 208 - 0
gui/scripts/d.wms.py

@@ -0,0 +1,208 @@
+#!/usr/bin/env python
+#
+############################################################################
+#
+# MODULE:       d.wms
+#
+# AUTHOR(S):    Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
+#
+# PURPOSE:      Wrapper for wxGUI to add WMS into layer tree
+#
+# COPYRIGHT:    (C) 2012 by Stepan Turek, and the GRASS Development Team
+#
+#   This program is free software under the GNU General Public License
+#   (>=v2). Read the file COPYING that comes with GRASS for details.
+#
+#############################################################################
+
+#%module
+#% description: Downloads and displays data from WMS server.
+#% keywords: raster
+#% keywords: import
+#% keywords: wms
+#%end
+
+#%option
+#% key: url
+#% type: string
+#% description: URL of WMS server 
+#% required: yes
+#%end
+
+#%option
+#% key: layers
+#% type: string
+#% description: Layers to request from WMS server
+#% multiple: yes
+#% required: yes
+#%end
+
+#%option
+#% key: map
+#% type: string
+#% description: Name for output WMS layer in the layer tree
+#% required : yes
+#%end
+
+#%option
+#% key: srs
+#% type: integer
+#% description: EPSG number of source projection for request 
+#% answer:4326 
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: wms_version
+#% type: string
+#% description: WMS standard
+#% options: 1.1.1,1.3.0
+#% answer: 1.1.1
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: format
+#% type: string
+#% description: Image format requested from the server
+#% options: geotiff,tiff,jpeg,gif,png
+#% answer: geotiff
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: method
+#% type: string
+#% description: Reprojection method to use
+#% options:near,bilinear,cubic,cubicspline
+#% answer:near
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: maxcols
+#% type:integer
+#% description: Maximum columns to request at a time
+#% answer:400
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: maxrows
+#% type: integer
+#% description: Maximum rows to request at a time
+#% answer: 300
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: urlparams
+#% type:string
+#% description: Additional query parameters for server
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: username
+#% type:string
+#% description: Username for server connection
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: password
+#% type:string
+#% description: Password for server connection
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: styles
+#% type: string
+#% description: Styles to request from map server
+#% multiple: yes
+#% guisection: Map style
+#%end
+
+#%option
+#% key: bgcolor
+#% type: string
+#% description: Color of map background
+#% guisection: Map style
+#%end
+
+#%flag
+#% key: o
+#% description: Don't request transparent data
+#% guisection: Map style
+#%end
+
+#%option
+#% key: driver
+#% type:string
+#% description: Driver for communication with server
+#% descriptions: WMS_GDAL;Download data using GDAL WMS driver;WMS_GRASS;Download data using native GRASS-WMS driver;WMTS_GRASS;Download data using native GRASS-WMTS driver;OnEarth_GRASS;Download data using native GRASS-OnEarth driver;
+#% options:WMS_GDAL, WMS_GRASS, WMTS_GRASS, OnEarth_GRASS
+#% answer:WMS_GRASS
+#%end
+
+#%option G_OPT_F_INPUT
+#% key: capfile
+#% required: no
+#% gisprompt: old,file,bin_input
+#% description: Capabilities file to load
+
+import os
+import sys
+
+from grass.script import core as grass
+
+sys.path.append(os.path.join(os.getenv("GISBASE"), "etc", "r.in.wms"))
+                
+def GetRegion():
+    """!Parse region from GRASS_REGION env var.
+    """
+    region = os.environ["GRASS_REGION"]
+    conv_reg_vals = {'east' : 'e',
+                     'north' : 'n',
+                     'west' : 'w',
+                     'south' : 's',
+                     'rows' : 'rows',
+                     'cols' : 'cols',
+                     'e-w resol' : 'ewres',
+                     'n-s resol' : 'nsres'}
+
+    keys_to_convert = conv_reg_vals.keys()
+
+    conv_region = {}
+    region = region.split(';')
+
+    for r in region:
+        r = r.split(':')
+        r[0] = r[0].strip()
+        
+        if r[0] in keys_to_convert:
+            conv_region[conv_reg_vals[r[0]]] = float(r[1])
+
+    return conv_region
+
+def main():
+    options['region'] = GetRegion()
+
+    if 'GRASS' in options['driver']:
+        grass.debug("Using GRASS driver")
+        from wms_drv import WMSDrv
+        wms = WMSDrv()
+    elif 'GDAL' in options['driver']:
+        grass.debug("Using GDAL WMS driver")
+        from wms_gdal_drv import WMSGdalDrv
+        wms = WMSGdalDrv()
+    
+    temp_map = wms.GetMap(options, flags) 
+    os.rename(temp_map, os.environ["GRASS_PNGFILE"])
+
+    return 0
+
+if __name__ == "__main__":
+    options, flags = grass.parser()
+    sys.exit(main())

+ 9 - 3
gui/wxpython/core/gconsole.py

@@ -79,6 +79,8 @@ class CmdThread(threading.Thread):
 
 
         self.setDaemon(True)
         self.setDaemon(True)
 
 
+        self.requestCmd = None
+
         self.receiver = receiver
         self.receiver = receiver
         self._want_abort_all = False
         self._want_abort_all = False
 
 
@@ -99,6 +101,10 @@ class CmdThread(threading.Thread):
 
 
         return CmdThread.requestId
         return CmdThread.requestId
 
 
+    def GetId(self):
+         """!Get id for next command"""
+         return CmdThread.requestId + 1
+
     def SetId(self, id):
     def SetId(self, id):
         """!Set starting id"""
         """!Set starting id"""
         CmdThread.requestId = id
         CmdThread.requestId = id
@@ -135,7 +141,7 @@ class CmdThread(threading.Thread):
 
 
             time.sleep(.1)
             time.sleep(.1)
             self.requestCmd = vars()['callable'](*args, **kwds)
             self.requestCmd = vars()['callable'](*args, **kwds)
-            if self._want_abort_all:
+            if self._want_abort_all and self.requestCmd is not None:
                 self.requestCmd.abort()
                 self.requestCmd.abort()
                 if self.requestQ.empty():
                 if self.requestQ.empty():
                     self._want_abort_all = False
                     self._want_abort_all = False
@@ -197,11 +203,11 @@ class CmdThread(threading.Thread):
         """!Abort command(s)"""
         """!Abort command(s)"""
         if abortall:
         if abortall:
             self._want_abort_all = True
             self._want_abort_all = True
-        self.requestCmd.abort()
+        if self.requestCmd is not None:
+            self.requestCmd.abort()
         if self.requestQ.empty():
         if self.requestQ.empty():
             self._want_abort_all = False
             self._want_abort_all = False
 
 
-
 class GStdout:
 class GStdout:
     """!GConsole standard output
     """!GConsole standard output
 
 

+ 116 - 52
gui/wxpython/core/render.py

@@ -3,6 +3,9 @@
 
 
 @brief Rendering map layers and overlays into map composition image.
 @brief Rendering map layers and overlays into map composition image.
 
 
+@todo Implement RenderManager also for other layers (see WMS
+implementation for details)
+
 Classes:
 Classes:
  - render::Layer
  - render::Layer
  - render::MapLayer
  - render::MapLayer
@@ -33,6 +36,7 @@ from wx.lib.newevent import NewEvent
 from grass.script import core as grass
 from grass.script import core as grass
 
 
 from core          import utils
 from core          import utils
+from core.ws       import RenderWMSMgr
 from core.gcmd     import GException, GError, RunCommand
 from core.gcmd     import GException, GError, RunCommand
 from core.debug    import Debug
 from core.debug    import Debug
 from core.settings import UserSettings
 from core.settings import UserSettings
@@ -47,8 +51,10 @@ class Layer(object):
     
     
     - For map layer use MapLayer class.
     - For map layer use MapLayer class.
     - For overlays use Overlay class.
     - For overlays use Overlay class.
+
+    @todo needs refactoring (avoid parent)
     """
     """
-    def __init__(self, type, cmd, name = None,
+    def __init__(self, type, cmd, parent, name = None,
                  active = True, hidden = False, opacity = 1.0):
                  active = True, hidden = False, opacity = 1.0):
         """!Create new instance
         """!Create new instance
         
         
@@ -62,7 +68,24 @@ class Layer(object):
         @param hidden layer is hidden, won't be listed in Layer Manager if True
         @param hidden layer is hidden, won't be listed in Layer Manager if True
         @param opacity layer opacity <0;1>
         @param opacity layer opacity <0;1>
         """
         """
-        self.type  = type
+        self.parent = parent
+        
+        # generated file for each layer
+        if USE_GPNMCOMP or type == 'overlay':
+            tmpfile = tempfile.mkstemp()[1]
+            self.maskfile = tmpfile + '.pgm'
+            if type == 'overlay':
+                self.mapfile  = tmpfile + '.png'
+            else:
+                self.mapfile  = tmpfile + '.ppm'
+            grass.try_remove(tmpfile)
+        else:
+            self.mapfile = self.maskfile = None
+        
+        # stores class which manages rendering instead of simple command - e. g. wms
+        self.renderMgr = None
+        
+        self.SetType(type)
         self.name  = name
         self.name  = name
         
         
         if self.type == 'command':
         if self.type == 'command':
@@ -76,25 +99,13 @@ class Layer(object):
         self.hidden  = hidden
         self.hidden  = hidden
         self.opacity = opacity
         self.opacity = opacity
         
         
-        self.force_render = True
+        self.forceRender = True
         
         
         Debug.msg (3, "Layer.__init__(): type=%s, cmd='%s', name=%s, " \
         Debug.msg (3, "Layer.__init__(): type=%s, cmd='%s', name=%s, " \
                        "active=%d, opacity=%d, hidden=%d" % \
                        "active=%d, opacity=%d, hidden=%d" % \
                        (self.type, self.GetCmd(string = True), self.name, self.active,
                        (self.type, self.GetCmd(string = True), self.name, self.active,
                         self.opacity, self.hidden))
                         self.opacity, self.hidden))
-        
-        # generated file for each layer
-        if USE_GPNMCOMP or self.type == 'overlay':
-            tmpfile = tempfile.mkstemp()[1]
-            self.maskfile = tmpfile + '.pgm'
-            if self.type == 'overlay':
-                self.mapfile  = tmpfile + '.png'
-            else:
-                self.mapfile  = tmpfile + '.ppm'
-            grass.try_remove(tmpfile)
-        else:
-            self.mapfile = self.maskfile = None
-        
+                
     def __del__(self):
     def __del__(self):
         Debug.msg (3, "Layer.__del__(): layer=%s, cmd='%s'" %
         Debug.msg (3, "Layer.__del__(): layer=%s, cmd='%s'" %
                    (self.name, self.GetCmd(string = True)))
                    (self.name, self.GetCmd(string = True)))
@@ -120,13 +131,12 @@ class Layer(object):
                       'vector','thememap','themechart',
                       'vector','thememap','themechart',
                       'grid', 'geodesic', 'rhumb', 'labels',
                       'grid', 'geodesic', 'rhumb', 'labels',
                       'command', 'rastleg','maplegend',
                       'command', 'rastleg','maplegend',
-                      'overlay')
+                      'overlay', 'wms')
         
         
         if self.type not in layertypes:
         if self.type not in layertypes:
             raise GException(_("<%(name)s>: layer type <%(type)s> is not supported") % \
             raise GException(_("<%(name)s>: layer type <%(type)s> is not supported") % \
                                  {'type' : self.type, 'name' : self.name})
                                  {'type' : self.type, 'name' : self.name})
         
         
-        # start monitor
         if self.mapfile:
         if self.mapfile:
             os.environ["GRASS_PNGFILE"] = self.mapfile
             os.environ["GRASS_PNGFILE"] = self.mapfile
         
         
@@ -135,10 +145,7 @@ class Layer(object):
             if self.type == 'command':
             if self.type == 'command':
                 read = False
                 read = False
                 for c in self.cmd:
                 for c in self.cmd:
-                    ret, msg = RunCommand(c[0],
-                                          getErrorMsg = True,
-                                          quiet = True,
-                                          **c[1])
+                    ret, msg = self._runCommand(c)
                     if ret != 0:
                     if ret != 0:
                         break
                         break
                     if not read:
                     if not read:
@@ -146,11 +153,7 @@ class Layer(object):
                 
                 
                 os.environ["GRASS_PNG_READ"] = "FALSE"
                 os.environ["GRASS_PNG_READ"] = "FALSE"
             else:
             else:
-                ret, msg = RunCommand(self.cmd[0],
-                                      getErrorMsg = True,
-                                      quiet = True,
-                                      **self.cmd[1])
-                
+                ret, msg = self._runCommand(self.cmd)
             if ret != 0:
             if ret != 0:
                 sys.stderr.write(_("Command '%s' failed\n") % self.GetCmd(string = True))
                 sys.stderr.write(_("Command '%s' failed\n") % self.GetCmd(string = True))
                 if msg:
                 if msg:
@@ -169,10 +172,27 @@ class Layer(object):
         if self.mapfile and "GRASS_PNGFILE" in os.environ:
         if self.mapfile and "GRASS_PNGFILE" in os.environ:
             del os.environ["GRASS_PNGFILE"]
             del os.environ["GRASS_PNGFILE"]
         
         
-        self.force_render = False
+        self.forceRender = False
         
         
         return self.mapfile
         return self.mapfile
     
     
+    def _runCommand(self, cmd):
+        """!Run command to render data
+
+        @todo catch error for wms (?)
+        """ 
+        if self.type == 'wms':
+            ret = 0
+            msg = ''
+            self.renderMgr.Render(cmd)
+        else:
+            ret, msg = RunCommand(self.cmd[0],
+                                  getErrorMsg = True,
+                                  quiet = True,
+                                  **self.cmd[1])
+        
+        return ret, msg
+    
     def GetCmd(self, string = False):
     def GetCmd(self, string = False):
         """!Get GRASS command as list of string.
         """!Get GRASS command as list of string.
         
         
@@ -238,16 +258,21 @@ class Layer(object):
         """!Check if layer is activated for rendering"""
         """!Check if layer is activated for rendering"""
         return self.active
         return self.active
     
     
-    def SetType(self, type):
+    def SetType(self, ltype):
         """!Set layer type"""
         """!Set layer type"""
-        if type not in ('raster', '3d-raster', 'vector',
+        if ltype not in ('raster', '3d-raster', 'vector',
                         'overlay', 'command',
                         'overlay', 'command',
                         'shaded', 'rgb', 'his', 'rastarrow', 'rastnum','maplegend',
                         'shaded', 'rgb', 'his', 'rastarrow', 'rastnum','maplegend',
                         'thememap', 'themechart', 'grid', 'labels',
                         'thememap', 'themechart', 'grid', 'labels',
-                        'geodesic','rhumb'):
-            raise GException(_("Unsupported map layer type '%s'") % type)
+                        'geodesic','rhumb', 'wms'):
+            raise GException(_("Unsupported map layer type '%s'") % ltype)
+        
+        if ltype == 'wms' and not isinstance(self.renderMgr, RenderWMSMgr):
+            self.renderMgr = RenderWMSMgr(self, self.mapfile, self.maskfile)
+        elif self.type == 'wms' and ltype != 'wms':
+            self.renderMgr = None
         
         
-        self.type = type
+        self.type = ltype
 
 
     def SetName(self, name):
     def SetName(self, name):
         """!Set layer name"""
         """!Set layer name"""
@@ -281,10 +306,24 @@ class Layer(object):
         Debug.msg(3, "Layer.SetCmd(): cmd='%s'" % self.GetCmd(string = True))
         Debug.msg(3, "Layer.SetCmd(): cmd='%s'" % self.GetCmd(string = True))
         
         
         # for re-rendering
         # for re-rendering
-        self.force_render = True
-        
+        self.forceRender = True
+
+    def IsDownloading(self):
+        """!Is data downloading from web server e. g. wms"""
+        if self.renderMgr is None:
+            return False
+        else:
+            return self.renderMgr.IsDownloading()
+
+    def AbortDownload(self):
+        """!Abort downloading data"""
+        if self.renderMgr is None:
+            return
+        else:
+            self.renderMgr.Abort()
+
 class MapLayer(Layer):
 class MapLayer(Layer):
-    def __init__(self, type, cmd, name = None,
+    def __init__(self, type, cmd, parent, name = None,
                  active = True, hidden = False, opacity = 1.0): 
                  active = True, hidden = False, opacity = 1.0): 
         """!Represents map layer in the map canvas
         """!Represents map layer in the map canvas
         
         
@@ -296,7 +335,7 @@ class MapLayer(Layer):
         @param hidden layer is hidden, won't be listed in Layer Manager if True
         @param hidden layer is hidden, won't be listed in Layer Manager if True
         @param opacity layer opacity <0;1>
         @param opacity layer opacity <0;1>
         """
         """
-        Layer.__init__(self, type, cmd, name,
+        Layer.__init__(self, type, cmd, parent, name,
                        active, hidden, opacity)
                        active, hidden, opacity)
         
         
     def GetMapset(self):
     def GetMapset(self):
@@ -314,7 +353,7 @@ class MapLayer(Layer):
             return self.name
             return self.name
         
         
 class Overlay(Layer):
 class Overlay(Layer):
-    def __init__(self, id, type, cmd,
+    def __init__(self, id, type, cmd, parent,
                  active = True, hidden = True, opacity = 1.0):
                  active = True, hidden = True, opacity = 1.0):
         """!Represents overlay displayed in map canvas
         """!Represents overlay displayed in map canvas
         
         
@@ -326,9 +365,8 @@ class Overlay(Layer):
         @param hidden layer is hidden, won't be listed in Layer Manager if True
         @param hidden layer is hidden, won't be listed in Layer Manager if True
         @param opacity layer opacity <0;1>
         @param opacity layer opacity <0;1>
         """
         """
-        Layer.__init__(self, 'overlay', cmd, type,
+        Layer.__init__(self, 'overlay', cmd, parent, type,
                        active, hidden, opacity)
                        active, hidden, opacity)
-        
         self.id = id
         self.id = id
         
         
 class Map(object):
 class Map(object):
@@ -362,6 +400,9 @@ class Map(object):
         self._initGisEnv() # g.gisenv
         self._initGisEnv() # g.gisenv
         self.GetWindow()
         self.GetWindow()
 
 
+        # reference to mapWindow, which contains this Map instance
+        self.mapWin = None
+
         # GRASS environment variable (for rendering)
         # GRASS environment variable (for rendering)
         self.env = {"GRASS_BACKGROUNDCOLOR" : "FFFFFF",
         self.env = {"GRASS_BACKGROUNDCOLOR" : "FFFFFF",
                "GRASS_COMPRESSION"     : "0",
                "GRASS_COMPRESSION"     : "0",
@@ -376,6 +417,9 @@ class Map(object):
         # projection info
         # projection info
         self.projinfo = self._projInfo()
         self.projinfo = self._projInfo()
 
 
+        # is some layer being downloaded?
+        self.downloading = False
+
     def _runCommand(self, cmd, **kwargs):
     def _runCommand(self, cmd, **kwargs):
         """!Run command in environment defined by self.gisrc if
         """!Run command in environment defined by self.gisrc if
         defined"""
         defined"""
@@ -834,28 +878,37 @@ class Map(object):
         masks = list()
         masks = list()
         opacities = list()
         opacities = list()
         # render map layers
         # render map layers
-        ilayer = 1
         if overlaysOnly:
         if overlaysOnly:
             layers = self.overlays
             layers = self.overlays
         else:
         else:
             layers = self.layers + self.overlays
             layers = self.layers + self.overlays
         
         
+        self.downloading = False
+        #event = wxUpdateProgressBar(layer = None, map = self)
+        # When using Event for progress update, the event is handled after 
+        # rendering. Maybe there exists some better solution than calling 
+        # the method directly.
+        mapWindow.GetProgressBar().UpdateProgress(None, self)
+        #wx.PostEvent(mapWindow, event)
         for layer in layers:
         for layer in layers:
             # skip non-active map layers
             # skip non-active map layers
             if not layer or not layer.active:
             if not layer or not layer.active:
                 continue
                 continue
             
             
             # render
             # render
-            if force or layer.force_render:
+            if force or layer.forceRender:
                 if not layer.Render():
                 if not layer.Render():
                     continue
                     continue
-            
+
+            if layer.IsDownloading():
+                self.downloading = True     
             if mapWindow:
             if mapWindow:
                 # update progress bar
                 # update progress bar
                 ### wx.SafeYield(mapWindow)
                 ### wx.SafeYield(mapWindow)
-                event = wxUpdateProgressBar(value = ilayer)
-                wx.PostEvent(mapWindow, event)
-            
+                mapWindow.GetProgressBar().UpdateProgress(layer, self)
+                #event = wxUpdateProgressBar(layer = layer, map = self)
+                #wx.PostEvent(mapWindow, event)
+
             # skip map layers when rendering fails
             # skip map layers when rendering fails
             if not os.path.exists(layer.mapfile):
             if not os.path.exists(layer.mapfile):
                 continue
                 continue
@@ -867,7 +920,6 @@ class Map(object):
                 opacities.append(str(layer.opacity))
                 opacities.append(str(layer.opacity))
             
             
             Debug.msg(3, "Map.Render() type=%s, layer=%s " % (layer.type, layer.name))
             Debug.msg(3, "Map.Render() type=%s, layer=%s " % (layer.type, layer.name))
-            ilayer += 1
         
         
         return maps, masks, opacities
         return maps, masks, opacities
         
         
@@ -985,7 +1037,7 @@ class Map(object):
             l_opacity = 0
             l_opacity = 0
         elif l_opacity > 1:
         elif l_opacity > 1:
             l_opacity = 1
             l_opacity = 1
-        layer = MapLayer(type = type, name = name, cmd = command,
+        layer = MapLayer(type = type, name = name, cmd = command, parent = self,
                          active = l_active, hidden = l_hidden, opacity = l_opacity)
                          active = l_active, hidden = l_hidden, opacity = l_opacity)
         
         
         # add maplayer to the list of layers
         # add maplayer to the list of layers
@@ -1052,8 +1104,8 @@ class Map(object):
         
         
         layerNameList = ""
         layerNameList = ""
         for layer in self.layers:
         for layer in self.layers:
-            if layer.name:
-                layerNameList += layer.name + ','
+            if layer.GetName():
+                layerNameList += layer.GetName() + ','
         Debug.msg (4, "Map.ReoderLayers(): layers=%s" % \
         Debug.msg (4, "Map.ReoderLayers(): layers=%s" % \
                    (layerNameList))
                    (layerNameList))
         
         
@@ -1192,7 +1244,7 @@ class Map(object):
         @return None on failure
         @return None on failure
         """
         """
         Debug.msg (2, "Map.AddOverlay(): cmd=%s, render=%d" % (command, l_render))
         Debug.msg (2, "Map.AddOverlay(): cmd=%s, render=%d" % (command, l_render))
-        overlay = Overlay(id = id, type = type, cmd = command,
+        overlay = Overlay(id = id, type = type, cmd = command, parent = self,
                           active = l_active, hidden = l_hidden, opacity = l_opacity)
                           active = l_active, hidden = l_hidden, opacity = l_opacity)
         
         
         # add maplayer to the list of layers
         # add maplayer to the list of layers
@@ -1300,5 +1352,17 @@ class Map(object):
     def RenderOverlays(self, force):
     def RenderOverlays(self, force):
         """!Render overlays only (for nviz)"""
         """!Render overlays only (for nviz)"""
         for layer in self.overlays:
         for layer in self.overlays:
-            if force or layer.force_render:
+            if force or layer.forceRender:
                 layer.Render()
                 layer.Render()
+
+    def SetParentMapWindow(self, mapWin):
+        """!Set reference to parent MapWindow"""
+        self.mapWin = mapWin
+
+    def GetParentMapWindow(self):
+        """!Get reference to parent MapWindow"""
+        return self.mapWin
+
+    def IsDownloading(self):
+        """!Is any layer downloading data from web server e. g. wms"""
+        return self.downloading

+ 18 - 10
gui/wxpython/core/utils.py

@@ -462,32 +462,40 @@ def __ll_parts(value, reverse = False, precision = 3):
         return coef * (float(d) + fm + fs)
         return coef * (float(d) + fm + fs)
     
     
 def GetCmdString(cmd):
 def GetCmdString(cmd):
-    """
-    Get GRASS command as string.
+    """!Get GRASS command as string.
     
     
-    @param cmd GRASS command given as dictionary
+    @param cmd GRASS command given as tuple
     
     
     @return command string
     @return command string
     """
     """
-    scmd = ''
+    return ' '.join(CmdTupleToList(cmd))
+
+def CmdTupleToList(cmd):
+    """!Convert command tuple to list.
+    
+    @param cmd GRASS command given as tuple
+    
+    @return command in list
+    """
+    cmdList = []
     if not cmd:
     if not cmd:
-        return scmd
+        return cmdList
     
     
-    scmd = cmd[0]
+    cmdList.append(cmd[0])
     
     
     if 'flags' in cmd[1]:
     if 'flags' in cmd[1]:
         for flag in cmd[1]['flags']:
         for flag in cmd[1]['flags']:
-            scmd += ' -' + flag
+            cmdList.append('-' + flag)
     for flag in ('verbose', 'quiet', 'overwrite'):
     for flag in ('verbose', 'quiet', 'overwrite'):
         if flag in cmd[1] and cmd[1][flag] is True:
         if flag in cmd[1] and cmd[1][flag] is True:
-            scmd += ' --' + flag
+            cmdList.append('--' + flag)
     
     
     for k, v in cmd[1].iteritems():
     for k, v in cmd[1].iteritems():
         if k in ('flags', 'verbose', 'quiet', 'overwrite'):
         if k in ('flags', 'verbose', 'quiet', 'overwrite'):
             continue
             continue
-        scmd += ' %s=%s' % (k, v)
+        cmdList.append('%s=%s' % (k, v))
             
             
-    return scmd
+    return cmdList
 
 
 def CmdToTuple(cmd):
 def CmdToTuple(cmd):
     """!Convert command list to tuple for gcmd.RunCommand()"""
     """!Convert command list to tuple for gcmd.RunCommand()"""

+ 347 - 0
gui/wxpython/core/ws.py

@@ -0,0 +1,347 @@
+"""!
+@package core.ws
+
+@brief Fetching and preparation of web service data for rendering.
+
+Note: Currently only WMS is implemented.
+
+Classes:
+ - ws::RenderWMSMgr
+ - ws::StdErr
+ - ws::GDALRasterMerger
+
+(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.
+
+@author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
+"""
+import os
+import sys
+
+import wx
+
+from grass.script import core as grass
+
+from core          import utils
+from core.debug    import Debug
+
+from core.gconsole import CmdThread, EVT_CMD_DONE
+from core.gcmd     import GException
+
+try:
+    haveGdal = True
+    from osgeo import gdal
+    from osgeo import gdalconst 
+except ImportError:
+    haveGdal = False
+
+class RenderWMSMgr(wx.EvtHandler):
+    """!Fetch and prepare WMS data for rendering.
+
+    @todo statusbar: updtade by event or call method ???
+    """
+    def __init__(self, parent, mapfile, maskfile):
+        if not haveGdal:
+            sys.stderr.write(_("Unable to load GDAL Python bindings.\n"\
+                               "WMS layers can not be displayed without the bindings.\n"))
+        
+        self.parent = parent
+
+        # thread for d.wms commands
+        self.thread = CmdThread(self)
+        wx.EvtHandler.__init__(self)
+        self.Bind(EVT_CMD_DONE, self.OnDataFetched)
+
+        self.downloading = False
+        self.renderedRegion = None
+        self.updateMap = True
+
+        self.cmdStdErr = StdErr()
+
+        self.mapfile = mapfile
+        self.maskfile = maskfile
+        self.tempMap = grass.tempfile()
+        self.dstSize = {}
+
+    def __del__(self):
+        grass.try_remove(self.tempMap)
+
+    def Render(self, cmd):
+        """!If it is needed, download missing WMS data.
+
+        @todo lmgr deletes mapfile and maskfile when order of layers
+        was changed (drag and drop) - if deleted, fetch data again
+        """
+        if not haveGdal:
+            return
+
+        self.dstSize['cols'] = int(os.environ["GRASS_WIDTH"])
+        self.dstSize['rows'] = int(os.environ["GRASS_HEIGHT"])
+
+        region = self._getRegionDict()
+        self._fitAspect(region, self.dstSize)
+
+        self.updateMap = True
+        fetchData = False
+        zoomChanged = False
+
+        if self.renderedRegion is None:
+            fetchData = True
+        else:
+            for c in ['north', 'south', 'east', 'west']:
+                if self.renderedRegion and \
+                   region[c] != self.renderedRegion[c]:
+                    fetchData = True
+                    break
+
+            for c in ['e-w resol', 'n-s resol']:
+                if self.renderedRegion and \
+                    region[c] != self.renderedRegion[c]:
+                    zoomChanged = True
+                    break
+
+        if fetchData:
+            self.renderedRegion = region
+
+            grass.try_remove(self.mapfile)
+            grass.try_remove(self.tempMap)
+
+            self.currentPid = self.thread.GetId()
+            self.thread.abort()
+            self.downloading = True
+
+            cmdList = utils.CmdTupleToList(cmd)
+
+            if Debug.GetLevel() < 3:
+                cmdList.append('--quiet')
+                
+            tempPngfile = os.environ["GRASS_PNGFILE"]
+            os.environ["GRASS_PNGFILE"] = self.tempMap
+
+            tempRegion = os.environ["GRASS_REGION"]
+            os.environ["GRASS_REGION"] = self._createRegionStr(region)
+
+            self.thread.RunCmd(cmdList, env = os.environ.copy(), stderr = self.cmdStdErr)
+
+            os.environ["GRASS_PNGFILE"] = tempPngfile
+            os.environ["GRASS_REGION"] = tempRegion
+
+    def OnDataFetched(self, event):
+        """!Fetch data
+
+        @todo ?
+        @todo needs refactoring -  self.parent.parent.mapWin.UpdateMap
+        """
+        if event.pid != self.currentPid:
+            return
+        self.downloading = False
+        if not self.updateMap:
+            # TODO
+            self.parent.parent.GetParentMapWindow().frame.GetProgressBar().UpdateProgress(self.parent, self.parent.parent)
+            self.renderedRegion = None
+            return
+
+        self.mapMerger = GDALRasterMerger(targetFile = self.mapfile, region = self.renderedRegion,
+                                          bandsNum = 3, gdalDriver = 'PNM', fillValue = 0)
+        self.mapMerger.AddRasterBands(self.tempMap, {1 : 1, 2 : 2, 3 : 3})
+        del self.mapMerger
+
+        self.maskMerger = GDALRasterMerger(targetFile = self.maskfile, region = self.renderedRegion,
+                                           bandsNum = 1, gdalDriver = 'PNM', fillValue = 0)
+        #{4 : 1} alpha channel (4) to first and only channel (1) in mask
+        self.maskMerger.AddRasterBands(self.tempMap, {4 : 1}) 
+        del self.maskMerger
+
+        self.parent.parent.GetParentMapWindow().UpdateMap(render = True)
+
+    def _getRegionDict(self):
+        """!Parse string from GRASS_REGION env variable into dict.
+        """
+        region = {}
+        parsedRegion = os.environ["GRASS_REGION"].split(';')
+        for r in parsedRegion:
+            r = r.split(':')
+            r[0] = r[0].strip()
+            if len(r) < 2:
+                continue
+            try:
+                if r[0] in ['cols', 'rows']:
+                    region[r[0]] = int(r[1])
+                else:
+                    region[r[0]] = float(r[1])
+            except ValueError:
+                region[r[0]] = r[1]
+
+        return region
+
+    def _createRegionStr(self, region):
+        """!Create string for GRASS_REGION env variable from  dict created by _getRegionDict.
+        """
+        regionStr = ''
+        for k, v in region.iteritems():
+            item = k + ': ' + str(v)
+            if regionStr:
+                regionStr += '; '
+            regionStr += item
+
+        return regionStr
+
+    def IsDownloading(self):
+        """!Is it downloading any data from WMS server? 
+        """
+        return self.downloading
+
+    def _fitAspect(self, region, size):
+        """!Compute region parameters to have direction independent resolution.
+        """
+        if region['n-s resol'] > region['e-w resol']:
+            delta = region['n-s resol'] * size['cols'] / 2
+
+            center = (region['east'] - region['west'])/2
+
+            region['east'] = center + delta + region['west']
+            region['west'] = center - delta + region['west']
+            region['e-w resol'] = region['n-s resol']
+
+        else:
+            delta = region['e-w resol'] * size['rows'] / 2
+
+            center = (region['north'] - region['south'])/2 
+
+            region['north'] = center + delta + region['south']
+            region['south'] = center - delta + region['south']
+            region['n-s resol'] = region['e-w resol']
+
+    def Abort(self):
+        """!Abort process"""
+        self.updateMap = False
+        self.thread.abort(abortall = True)        
+
+class StdErr:
+    """!Redirect error output according to debug mode.
+
+    @todo: replace or move to the other module (gconsole???)
+    """
+    def flush(self):
+        pass
+    
+    def write(self, s):
+        if "GtkPizza" in s:
+            return
+        if Debug.GetLevel() == 0:
+            message = ''
+            for line in s.splitlines():
+                if len(line) == 0:
+                    continue
+                if 'GRASS_INFO_ERROR' in line:
+                    message += line.split(':', 1)[1].strip() + '\n'
+                elif 'ERROR:' in line:
+                    message = line
+                if message:
+                    sys.stderr.write(message)
+                    message = ''
+            sys.stderr.flush()
+
+        elif Debug.GetLevel() >= 1:
+            for line in s.splitlines():
+                if len(line) == 0:
+                    continue
+                Debug.msg(3, line)
+
+class GDALRasterMerger:
+    """!Merge rasters.
+
+        Based on gdal_merge.py utility.
+    """
+    def __init__(self, targetFile, region, bandsNum, gdalDriver, fillValue = None):
+        """!Create raster for merging.
+        """
+        self.gdalDrvType = gdalDriver
+
+        nsRes = (region['south'] - region['north']) / region['rows']
+        ewRes = (region['east'] - region['west']) / region['cols']
+
+        self.tGeotransform = [region['west'], ewRes, 0, region['north'], 0, nsRes]
+
+        self.tUlx, self.tUly, self.tLrx, self.tLry = self._getCorners(self.tGeotransform, region)
+
+        driver = gdal.GetDriverByName(self.gdalDrvType)
+        self.tDataset = driver.Create(targetFile, region['cols'], region['rows'], bandsNum,  gdal.GDT_Byte)
+
+        if fillValue is not None:
+            # fill raster bands with a constant value
+            for iBand in range(1, self.tDataset.RasterCount + 1):
+                self.tDataset.GetRasterBand(iBand).Fill(fillValue)
+
+    def AddRasterBands(self, sourceFile, sTBands):
+        """!Add raster bands from sourceFile into the merging raster.
+        """
+        sDataset = gdal.Open(sourceFile, gdal.GA_ReadOnly) 
+        if sDataset is None:
+            return
+
+        sGeotransform = sDataset.GetGeoTransform()
+
+        sSize = {
+                    'rows' :  sDataset.RasterYSize,
+                    'cols' :  sDataset.RasterXSize
+                 }
+
+        sUlx, sUly, sLrx, sLry = self._getCorners(sGeotransform, sSize)
+
+        # figure out intersection region
+        tIntsctUlx = max(self.tUlx,sUlx)
+        tIntsctLrx = min(self.tLrx,sLrx)
+        if self.tGeotransform[5] < 0:
+            tIntsctUly = min(self.tUly,sUly)
+            tIntsctLry = max(self.tLry,sLry)
+        else:
+            tIntsctUly = max(self.tUly,sUly)
+            tIntsctLry = min(self.tLry,sLry)
+        
+        # do they even intersect?
+        if tIntsctUlx >= tIntsctLrx:
+            return
+        if self.tGeotransform[5] < 0 and tIntsctUly <= tIntsctLry:
+            return
+        if self.tGeotransform[5] > 0 and tIntsctUly >= tIntsctLry:
+            return
+
+
+        # compute target window in pixel coordinates.
+        tXoff = int((tIntsctUlx - self.tGeotransform[0]) / self.tGeotransform[1] + 0.1)
+        tYoff = int((tIntsctUly - self.tGeotransform[3]) / self.tGeotransform[5] + 0.1)
+        tXsize = int((tIntsctLrx - self.tGeotransform[0])/self.tGeotransform[1] + 0.5) - tXoff
+        tYsize = int((tIntsctLry - self.tGeotransform[3])/self.tGeotransform[5] + 0.5) - tYoff
+
+        if tXsize < 1 or tYsize < 1:
+            return
+
+        # Compute source window in pixel coordinates.
+        sXoff = int((tIntsctUlx - sGeotransform[0]) / sGeotransform[1])
+        sYoff = int((tIntsctUly - sGeotransform[3]) / sGeotransform[5])
+        sXsize = int((tIntsctLrx - sGeotransform[0]) / sGeotransform[1] + 0.5) - sXoff
+        sYsize = int((tIntsctLry - sGeotransform[3]) / sGeotransform[5] + 0.5) - sYoff
+
+        if sXsize < 1 or sYsize < 1:
+            return
+
+        for sBandNnum, tBandNum in sTBands.iteritems():
+            bandData = sDataset.GetRasterBand(sBandNnum).ReadRaster(sXoff, sYoff, sXsize,
+                                                                    sYsize, tXsize, tYsize, gdal.GDT_Byte)
+            self.tDataset.GetRasterBand(tBandNum).WriteRaster(tXoff, tYoff, tXsize, tYsize, bandData, 
+                                                              tXsize, tYsize, gdal.GDT_Byte)
+
+    def _getCorners(self, geoTrans, size):
+
+        ulx = geoTrans[0]
+        uly = geoTrans[3]
+        lrx = geoTrans[0] + size['cols'] * geoTrans[1]
+        lry = geoTrans[3] + size['rows'] * geoTrans[5]
+
+        return ulx, uly, lrx, lry
+
+    def __del__(self):
+        self.tDataset = None

+ 1 - 1
gui/wxpython/gcp/mapdisplay.py

@@ -256,7 +256,7 @@ class MapFrame(SingleMapFrame):
         """
         """
         Update progress bar info
         Update progress bar info
         """
         """
-        self.GetProgressBar().SetValue(event.value)
+        self.GetProgressBar().UpdateProgress(event.layer, event.map)
         
         
         event.Skip()
         event.Skip()
         
         

+ 1 - 0
gui/wxpython/gui_core/mapwindow.py

@@ -39,6 +39,7 @@ class MapWindow(object):
     def __init__(self, parent, giface, Map, frame, **kwargs):
     def __init__(self, parent, giface, Map, frame, **kwargs):
         self.parent = parent
         self.parent = parent
         self.Map = Map
         self.Map = Map
+        self.Map.SetParentMapWindow(self)
         self.frame = frame
         self.frame = frame
         self._giface = giface
         self._giface = giface
         
         

+ 4 - 4
gui/wxpython/lmgr/frame.py

@@ -608,7 +608,8 @@ class GMFrame(wx.Frame):
                          'd.rhumbline'    : 'rhumb',
                          'd.rhumbline'    : 'rhumb',
                          'd.labels'       : 'labels',
                          'd.labels'       : 'labels',
                          'd.barscale'     : 'barscale',
                          'd.barscale'     : 'barscale',
-                         'd.redraw'       : 'redraw'}[command[0]]
+                         'd.redraw'       : 'redraw',
+                         'd.wms'          : 'wms'}[command[0]]
         except KeyError:
         except KeyError:
             GMessage(parent = self,
             GMessage(parent = self,
                      message = _("Command '%s' not yet implemented in the WxGUI. "
                      message = _("Command '%s' not yet implemented in the WxGUI. "
@@ -1502,8 +1503,7 @@ class GMFrame(wx.Frame):
         """!Import data from OGC WMS server"""
         """!Import data from OGC WMS server"""
         from ogc_services.wms import WMSDialog
         from ogc_services.wms import WMSDialog
         dlg = WMSDialog(parent = self)
         dlg = WMSDialog(parent = self)
-        dlg.CenterOnScreen()
-        
+        dlg.CenterOnScreen()         
         if dlg.ShowModal() == wx.ID_OK: # -> import layers
         if dlg.ShowModal() == wx.ID_OK: # -> import layers
             layers = dlg.GetLayers()
             layers = dlg.GetLayers()
             
             
@@ -1529,7 +1529,7 @@ class GMFrame(wx.Frame):
                 
                 
                 
                 
         dlg.Destroy()
         dlg.Destroy()
-        
+
     def OnShowAttributeTable(self, event, selection = None):
     def OnShowAttributeTable(self, event, selection = None):
         """!Show attribute table of the given vector map layer
         """!Show attribute table of the given vector map layer
         """
         """

+ 15 - 14
gui/wxpython/lmgr/layertree.py

@@ -91,8 +91,10 @@ LMIcons = {
     'addRast3d'  : MetaIcon(img = 'layer-raster3d-add',
     'addRast3d'  : MetaIcon(img = 'layer-raster3d-add',
                             label = _('Add 3D raster map layer'),
                             label = _('Add 3D raster map layer'),
                             desc  =  _('Note that 3D raster data are rendered only in 3D view mode')),
                             desc  =  _('Note that 3D raster data are rendered only in 3D view mode')),
+    'wsImport'  :  MetaIcon(img = 'layer-wms-add',
+                            label = _('Add WMS layer.')),
     'layerOptions'  : MetaIcon(img = 'options',
     'layerOptions'  : MetaIcon(img = 'options',
-                               label = _('Set options')),
+                               label = _('Set options'))
     }
     }
 
 
 class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
 class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
@@ -224,6 +226,9 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
         
         
         trgif = LMIcons["addCmd"].GetBitmap(bmpsize)
         trgif = LMIcons["addCmd"].GetBitmap(bmpsize)
         self.cmd_icon = il.Add(trgif)
         self.cmd_icon = il.Add(trgif)
+
+        trgif = LMIcons["wsImport"].GetBitmap(bmpsize)
+        self.ws_icon = il.Add(trgif)
         
         
         self.AssignImageList(il)
         self.AssignImageList(il)
 
 
@@ -941,7 +946,10 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
             self.SetItemImage(layer, self.folder, CT.TreeItemIcon_Normal)
             self.SetItemImage(layer, self.folder, CT.TreeItemIcon_Normal)
             self.SetItemImage(layer, self.folder_open, CT.TreeItemIcon_Expanded)
             self.SetItemImage(layer, self.folder_open, CT.TreeItemIcon_Expanded)
             self.SetItemText(layer, grouptext)
             self.SetItemText(layer, grouptext)
-        
+        elif ltype == 'wms':            
+            self.SetItemImage(layer, self.ws_icon)
+            self.SetItemText(layer, '%s %s' % (_('wms'), label))
+    
         self.first = False
         self.first = False
         
         
         if ltype != 'group':
         if ltype != 'group':
@@ -1022,11 +1030,7 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
         else:
         else:
             if ltype == 'group':
             if ltype == 'group':
                 self.OnRenameLayer(None)
                 self.OnRenameLayer(None)
-        
-        # updated progress bar range (mapwindow statusbar)
-        if checked:
-            self.mapdisplay.GetProgressBar().SetRange(len(self.Map.GetListOfLayers(l_active = True)))
-        
+                
         return layer
         return layer
 
 
     def PropertiesDialog(self, layer, show = True):
     def PropertiesDialog(self, layer, show = True):
@@ -1104,7 +1108,10 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
             
             
         elif ltype == 'labels':
         elif ltype == 'labels':
             cmd = ['d.labels']
             cmd = ['d.labels']
-        
+
+        elif ltype == 'wms':
+            cmd = ['d.wms']
+
         if cmd:
         if cmd:
             GUI(parent = self, centreOnParent = False).ParseCommand(cmd,
             GUI(parent = self, centreOnParent = False).ParseCommand(cmd,
                                                                     completed = (self.GetOptData,layer,params))
                                                                     completed = (self.GetOptData,layer,params))
@@ -1156,9 +1163,6 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
         if self.mapdisplay.GetToolbar('vdigit'):
         if self.mapdisplay.GetToolbar('vdigit'):
             self.mapdisplay.toolbars['vdigit'].UpdateListOfLayers (updateTool = True)
             self.mapdisplay.toolbars['vdigit'].UpdateListOfLayers (updateTool = True)
 
 
-        # update progress bar range (mapwindow statusbar)
-        self.mapdisplay.GetProgressBar().SetRange(len(self.Map.GetListOfLayers(l_active = True)))
-
         # here was some dead code related to layer and nviz
         # here was some dead code related to layer and nviz
         # however, in condition was rerender = False
         # however, in condition was rerender = False
         # but rerender is alway True
         # but rerender is alway True
@@ -1206,9 +1210,6 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
                     # ignore when map layer is edited
                     # ignore when map layer is edited
                     self.Map.ChangeLayerActive(mapLayer, checked)
                     self.Map.ChangeLayerActive(mapLayer, checked)
         
         
-        # update progress bar range (mapwindow statusbar)
-        self.mapdisplay.GetProgressBar().SetRange(len(self.Map.GetListOfLayers(l_active = True)))
-        
         # nviz
         # nviz
         if self.lmgr.IsPaneShown('toolbarNviz') and \
         if self.lmgr.IsPaneShown('toolbarNviz') and \
                 self.GetPyData(item) is not None:
                 self.GetPyData(item) is not None:

+ 1 - 1
gui/wxpython/mapdisp/frame.py

@@ -426,7 +426,7 @@ class MapFrame(SingleMapFrame):
     def OnUpdateProgress(self, event):
     def OnUpdateProgress(self, event):
         """!Update progress bar info
         """!Update progress bar info
         """
         """
-        self.GetProgressBar().SetValue(event.value)
+        self.GetProgressBar().UpdateProgress(event.layer, event.map)
         
         
         event.Skip()
         event.Skip()
         
         

+ 3 - 21
gui/wxpython/mapdisp/mapwindow.py

@@ -579,7 +579,6 @@ class BufferedWindow(MapWindow, wx.Window):
         @param renderVector re-render vector map layer enabled for editing (used for digitizer)
         @param renderVector re-render vector map layer enabled for editing (used for digitizer)
         """
         """
         start = time.clock()
         start = time.clock()
-        
         self.resize = False
         self.resize = False
         
         
         # was if self.Map.cmdfile and ...
         # was if self.Map.cmdfile and ...
@@ -587,14 +586,6 @@ class BufferedWindow(MapWindow, wx.Window):
             render = True
             render = True
         
         
         #
         #
-        # initialize process bar (only on 'render')
-        #
-        if render or renderVector:
-            self.frame.GetProgressBar().Show()
-            if self.frame.GetProgressBar().GetRange() > 0:
-                self.frame.GetProgressBar().SetValue(1)
-        
-        #
         # render background image if needed
         # render background image if needed
         #
         #
         # update layer dictionary if there has been a change in layers
         # update layer dictionary if there has been a change in layers
@@ -707,18 +698,6 @@ class BufferedWindow(MapWindow, wx.Window):
             
             
         stop = time.clock()
         stop = time.clock()
         
         
-        #
-        # hide process bar
-        #
-        self.frame.GetProgressBar().Hide()
-
-        #
-        # update statusbar 
-        #
-        ### self.Map.SetRegion()
-        self.frame.StatusbarUpdate()
-        
-        
         Debug.msg (1, "BufferedWindow.UpdateMap(): render=%s, renderVector=%s -> time=%g" % \
         Debug.msg (1, "BufferedWindow.UpdateMap(): render=%s, renderVector=%s -> time=%g" % \
                    (render, renderVector, (stop-start)))
                    (render, renderVector, (stop-start)))
         
         
@@ -779,6 +758,9 @@ class BufferedWindow(MapWindow, wx.Window):
         
         
         self.Draw(self.pdcDec, pdctype = 'clear')
         self.Draw(self.pdcDec, pdctype = 'clear')
         self.Draw(self.pdcTmp, pdctype = 'clear')
         self.Draw(self.pdcTmp, pdctype = 'clear')
+
+        for layer in self.Map.GetListOfLayers(l_active = True):
+            layer.AbortDownload()
         
         
     def DragMap(self, moveto):
     def DragMap(self, moveto):
         """!Drag the entire map image for panning.
         """!Drag the entire map image for panning.

+ 104 - 14
gui/wxpython/mapdisp/statusbar.py

@@ -21,6 +21,8 @@ Classes:
  - statusbar::SbRegionExtent
  - statusbar::SbRegionExtent
  - statusbar::SbCompRegionExtent
  - statusbar::SbCompRegionExtent
  - statusbar::SbProgress
  - statusbar::SbProgress
+ - statusbar::SbRMSError
+ - statusbar::SbGoToGCP
 
 
 (C) 2006-2011 by the GRASS Development Team
 (C) 2006-2011 by the GRASS Development Team
 
 
@@ -83,7 +85,7 @@ class SbManager:
         
         
         self._postInitialized = False
         self._postInitialized = False
         
         
-        self.progressbar = SbProgress(self.mapFrame, self.statusbar)
+        self.progressbar = SbProgress(self.mapFrame, self.statusbar, self)
         
         
         self._hiddenItems = {}
         self._hiddenItems = {}
     
     
@@ -116,7 +118,7 @@ class SbManager:
     def AddStatusbarItem(self, item):
     def AddStatusbarItem(self, item):
         """!Adds item to statusbar
         """!Adds item to statusbar
         
         
-        If item position is 0, item is managed by choice.
+        If item position is 0, item is managed by choice.        
         
         
         @see AddStatusbarItemsByClass
         @see AddStatusbarItemsByClass
         """
         """
@@ -180,7 +182,9 @@ class SbManager:
         
         
         @see Update
         @see Update
         """
         """
-        self.statusbarItems[itemName].Show()
+        if self.statusbarItems[itemName].GetPosition() != 0 or \
+           not self.progressbar.IsShown():
+            self.statusbarItems[itemName].Show()
         
         
     def _postInit(self):
     def _postInit(self):
         """!Post-initialization method
         """!Post-initialization method
@@ -208,16 +212,20 @@ class SbManager:
 
 
         It always updates mask.
         It always updates mask.
         """
         """
+        self.progressbar.Update()
+
         if not self._postInitialized:
         if not self._postInitialized:
             self._postInit()
             self._postInit()
-        
         for item in self.statusbarItems.values():
         for item in self.statusbarItems.values():
             if item.GetPosition() == 0:
             if item.GetPosition() == 0:
-                item.Hide()
+                if not self.progressbar.IsShown():
+                    item.Hide()
             else:
             else:
                 item.Update() # mask, render
                 item.Update() # mask, render
-        
-        if self.choice.GetCount() > 0:
+
+        if self.progressbar.IsShown():
+            pass
+        elif self.choice.GetCount() > 0:
             item = self.choice.GetClientData(self.choice.GetSelection())
             item = self.choice.GetClientData(self.choice.GetSelection())
             item.Update()
             item.Update()
         
         
@@ -233,7 +241,7 @@ class SbManager:
             widgets.append((item.GetPosition(), item.GetWidget()))
             widgets.append((item.GetPosition(), item.GetWidget()))
             
             
         widgets.append((1, self.choice))
         widgets.append((1, self.choice))
-        widgets.append((0, self.progressbar.GetWidget()))
+        widgets.append((1, self.progressbar.GetWidget()))
                 
                 
         for idx, win in widgets:
         for idx, win in widgets:
             if not win:
             if not win:
@@ -242,8 +250,6 @@ class SbManager:
             if idx == 0: # show region / mapscale / process bar
             if idx == 0: # show region / mapscale / process bar
                 # -> size
                 # -> size
                 wWin, hWin = win.GetBestSize()
                 wWin, hWin = win.GetBestSize()
-                if win == self.progressbar.GetWidget():
-                    wWin = rect.width - 6
                 # -> position
                 # -> position
                 # if win == self.statusbarWin['region']:
                 # if win == self.statusbarWin['region']:
                 # x, y = rect.x + rect.width - wWin, rect.y - 1
                 # x, y = rect.x + rect.width - wWin, rect.y - 1
@@ -254,6 +260,8 @@ class SbManager:
             else: # choice || auto-rendering
             else: # choice || auto-rendering
                 x, y = rect.x, rect.y
                 x, y = rect.x, rect.y
                 w, h = rect.width, rect.height + 1
                 w, h = rect.width, rect.height + 1
+                if win == self.progressbar.GetWidget():
+                    wWin = rect.width - 6
                 if idx == 2: # mask
                 if idx == 2: # mask
                     x += 5
                     x += 5
                     y += 4
                     y += 4
@@ -956,19 +964,21 @@ class SbCompRegionExtent(SbRegionExtent):
         return self.mapFrame.GetMap().GetRegion() # computational region
         return self.mapFrame.GetMap().GetRegion() # computational region
         
         
         
         
-class SbProgress(SbItem):
+class SbProgress(SbTextItem):
     """!General progress bar to show progress.
     """!General progress bar to show progress.
     
     
     Underlaying widget is wx.Gauge.
     Underlaying widget is wx.Gauge.
     """
     """
-    def __init__(self, mapframe, statusbar, position = 0):
+    def __init__(self, mapframe, statusbar, sbManager, position = 0):
         SbItem.__init__(self, mapframe, statusbar, position)
         SbItem.__init__(self, mapframe, statusbar, position)
         self.name = 'progress'
         self.name = 'progress'
-
+        self.sbManager = sbManager
         # on-render gauge
         # on-render gauge
         self.widget = wx.Gauge(parent = self.statusbar, id = wx.ID_ANY,
         self.widget = wx.Gauge(parent = self.statusbar, id = wx.ID_ANY,
-                                      range = 0, style = wx.GA_HORIZONTAL)
+                               range = 0, style = wx.GA_HORIZONTAL)
         self.widget.Hide()
         self.widget.Hide()
+
+        self.maps = {}
         
         
     def GetRange(self):
     def GetRange(self):
         """!Returns progress range."""
         """!Returns progress range."""
@@ -978,7 +988,86 @@ class SbProgress(SbItem):
         """!Sets progress range."""
         """!Sets progress range."""
         self.widget.SetRange(range)
         self.widget.SetRange(range)
     
     
+    def IsShown(self):
+        """!Is progress bar shown
+        """
+        return self.widget.IsShown()
+                
+    def UpdateProgress(self, layer, map):
+        """!Update progress"""
+        
+        if map not in self.maps or layer is None:
+            # self.map holds values needed for progress info for every Render instance in mapframe
+            self.maps[map] = {'progresVal' : 0, # current progress value
+                              'downloading' : [], # layers, which are downloading data
+                              'rendered' : [], # already rendered layers
+                              'range' : len(map.GetListOfLayers(l_active = True))}
+        else:
+            if layer not in self.maps[map]['rendered']:
+                self.maps[map]['rendered'].append(layer)
+            if layer.IsDownloading() and \
+                    layer not in self.maps[map]['downloading']:
+                self.maps[map]['downloading'].append(layer)
+            else:
+                self.maps[map]['progresVal'] += 1
+                if layer in self.maps[map]['downloading']:
+                    self.maps[map]['downloading'].remove(layer)
+        
+        self.Update(map)
+        self.sbManager.Update()
+        
+    def Update(self, map = None):      
+        """!Update statusbar"""
+        activeMap = self.mapFrame.GetMap()
+        if map is None:
+                map = activeMap
+        if map not in self.maps:
+            return
+        if map != activeMap:
+            return
+
+        # update progress bar
+        if self.maps[map]['range'] == self.maps[map]['progresVal']:
+            self.widget.Hide()
+            return
+        elif self.maps[map]['range'] > 0:
+            if self.widget.GetRange() != self.maps[map]['range']:
+                self.widget.SetRange(self.maps[map]['range'])
+            self.widget.Show()
+        else:
+            return
+        
+        self.widget.SetValue(self.maps[map]['progresVal'])
+        
+        # update statusbar text
+        st_text = ''
+        first = True
+        for layer in self.maps[map]['downloading']:
+            if first:
+                st_text += _("Downloading data...")
+                first = False
+            else:
+                st_text += ', '
+            st_text += layer.GetName()
+        
+        if  self.maps[map]['range'] != len(self.maps[map]['rendered']):
+            if st_text:
+                st_text = _('Rendering & ') + st_text
+            else:
+                st_text = _('Rendering...')
+
+        self.statusbar.SetStatusText(st_text, self.position)
 
 
+    def GetValue(self):
+        return self.widget.GetValue()
+    
+    def GetWidget(self):
+        """!Returns underlaying winget.
+        
+        @return widget or None if doesn't exist
+        """
+        return self.widget
+    
 class SbGoToGCP(SbItem):
 class SbGoToGCP(SbItem):
     """!SpinCtrl to select GCP to focus on
     """!SpinCtrl to select GCP to focus on
     
     
@@ -1074,3 +1163,4 @@ class SbRMSError(SbTextItem):
                                    { 'forw' : self.mapFrame.GetFwdError(),
                                    { 'forw' : self.mapFrame.GetFwdError(),
                                      'back' : self.mapFrame.GetBkwError() })
                                      'back' : self.mapFrame.GetBkwError() })
         SbTextItem.Show(self)
         SbTextItem.Show(self)
+