GRASS Programmer's Manual  6.5.svn(2012)-r51648
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
render.py
Go to the documentation of this file.
00001 """!
00002 @package core.render
00003 
00004 @brief Rendering map layers and overlays into map composition image.
00005 
00006 Classes:
00007  - render::Layer
00008  - render::MapLayer
00009  - render::Overlay
00010  - render::Map
00011 
00012 (C) 2006-2011 by the GRASS Development Team
00013 
00014 This program is free software under the GNU General Public License
00015 (>=v2). Read the file COPYING that comes with GRASS for details.
00016 
00017 @author Michael Barton
00018 @author Jachym Cepicky
00019 @author Martin Landa <landa.martin gmail.com>
00020 """
00021 
00022 import os
00023 import sys
00024 import glob
00025 import math
00026 import copy
00027 import tempfile
00028 import types
00029 
00030 import wx
00031 from wx.lib.newevent import NewEvent
00032 
00033 from grass.script import core as grass
00034 
00035 from core          import utils
00036 from core.gcmd     import GException, GError, RunCommand
00037 from core.debug    import Debug
00038 from core.settings import UserSettings
00039 
00040 wxUpdateProgressBar, EVT_UPDATE_PRGBAR = NewEvent()
00041 
00042 #
00043 # use g.pnmcomp for creating image composition or
00044 # wxPython functionality
00045 #
00046 USE_GPNMCOMP = True
00047 
00048 class Layer(object):
00049     """!Virtual class which stores information about layers (map layers and
00050     overlays) of the map composition.
00051     
00052     For map layer use MapLayer class.
00053     For overlays use Overlay class.
00054     """
00055     def __init__(self, type, cmd, name = None,
00056                  active = True, hidden = False, opacity = 1.0):
00057         """!
00058         @todo pass cmd as tuple instead of list
00059         
00060         @param type layer type ('raster', 'vector', 'overlay', 'command', etc.)
00061         @param cmd GRASS command to render layer,
00062         given as list, e.g. ['d.rast', 'map=elevation@PERMANENT']
00063         @param name layer name, e.g. 'elevation@PERMANENT' (for layer tree)
00064         @param active layer is active, will be rendered only if True
00065         @param hidden layer is hidden, won't be listed in Layer Manager if True
00066         @param opacity layer opacity <0;1>
00067         """
00068         self.type  = type
00069         self.name  = name
00070         
00071         if self.type == 'command':
00072             self.cmd = list()
00073             for c in cmd:
00074                 self.cmd.append(utils.CmdToTuple(c))
00075         else:
00076             self.cmd = utils.CmdToTuple(cmd)
00077         
00078         self.active  = active
00079         self.hidden  = hidden
00080         self.opacity = opacity
00081         
00082         self.force_render = True
00083         
00084         Debug.msg (3, "Layer.__init__(): type=%s, cmd='%s', name=%s, " \
00085                        "active=%d, opacity=%d, hidden=%d" % \
00086                        (self.type, self.GetCmd(string = True), self.name, self.active,
00087                         self.opacity, self.hidden))
00088         
00089         # generated file for each layer
00090         self.gtemp = tempfile.mkstemp()[1]
00091         self.maskfile = self.gtemp + ".pgm"
00092         if self.type == 'overlay':
00093             self.mapfile  = self.gtemp + ".png"
00094         else:
00095             self.mapfile  = self.gtemp + ".ppm"
00096         
00097     def __del__(self):
00098         Debug.msg (3, "Layer.__del__(): layer=%s, cmd='%s'" %
00099                    (self.name, self.GetCmd(string = True)))
00100 
00101     def Render(self):
00102         """!Render layer to image
00103         
00104         @return rendered image filename
00105         @return None on error
00106         """
00107         if not self.cmd:
00108             return None
00109         
00110         # ignore in 2D
00111         if self.type == '3d-raster':
00112             return None
00113         
00114         Debug.msg (3, "Layer.Render(): type=%s, name=%s" % \
00115                        (self.type, self.name))
00116         
00117         # prepare command for each layer
00118         layertypes = ('raster', 'rgb', 'his', 'shaded', 'rastarrow', 'rastnum',
00119                       'vector','thememap','themechart',
00120                       'grid', 'geodesic', 'rhumb', 'labels',
00121                       'command', 'rastleg',
00122                       'overlay')
00123         
00124         if self.type not in layertypes:
00125             raise GException(_("<%(name)s>: layer type <%(type)s> is not supported") % \
00126                                  {'type' : self.type, 'name' : self.name})
00127         
00128         # start monitor
00129         if UserSettings.Get(group='display', key='driver', subkey='type') == 'cairo':
00130 #            os.environ["GRASS_CAIROFILE"] = self.mapfile
00131 #            if 'cairo' not in gcmd.RunCommand('d.mon',
00132 #                                              flags='p',
00133 #                                              read = True):
00134 #                gcmd.RunCommand('d.mon',
00135 #                                start = 'cairo')
00136             if not self.mapfile:
00137                 self.gtemp = tempfile.mkstemp()[1]
00138                 self.maskfile = self.gtemp + ".pgm"
00139                 if self.type == 'overlay':
00140                     self.mapfile  = self.gtemp + ".png"
00141                 else:
00142                     self.mapfile  = self.gtemp + ".ppm"
00143 
00144             if self.mapfile:
00145                 os.environ["GRASS_CAIROFILE"] = self.mapfile
00146         else:
00147             if not self.mapfile:
00148                 self.gtemp = tempfile.mkstemp()[1]
00149                 self.maskfile = self.gtemp + ".pgm"
00150                 if self.type == 'overlay':
00151                     self.mapfile  = self.gtemp + ".png"
00152                 else:
00153                     self.mapfile  = self.gtemp + ".ppm"
00154 
00155             if self.mapfile:
00156                 os.environ["GRASS_PNGFILE"] = self.mapfile
00157         
00158         # execute command
00159         try:
00160             if self.type == 'command':
00161                 read = False
00162                 for c in self.cmd:
00163                     ret, msg = RunCommand(c[0],
00164                                           getErrorMsg = True,
00165                                           quiet = True,
00166                                           **c[1])
00167                     if ret != 0:
00168                         break
00169                     if not read:
00170                         os.environ["GRASS_PNG_READ"] = "TRUE"
00171                 
00172                 os.environ["GRASS_PNG_READ"] = "FALSE"
00173             else:
00174                 ret, msg = RunCommand(self.cmd[0],
00175                                       getErrorMsg = True,
00176                                       quiet = True,
00177                                       **self.cmd[1])
00178             
00179             if msg:
00180                 sys.stderr.write(_("Command '%s' failed\n") % self.GetCmd(string = True))
00181                 sys.stderr.write(_("Details: %s\n") % msg)
00182             if ret != 0:
00183                 raise GException()
00184         
00185         except GException:
00186             # clean up after problems
00187             try:
00188                 os.remove(self.mapfile)
00189                 os.remove(self.maskfile)
00190                 os.remove(self.gtemp)
00191             except (OSError, TypeError):
00192                 pass
00193             self.mapfile = None
00194             self.maskfile = None
00195         
00196         # stop monitor
00197         if UserSettings.Get(group='display', key='driver', subkey='type') == 'cairo':
00198 #            gcmd.RunCommand('d.mon',
00199 #                            stop = 'cairo')
00200             del os.environ["GRASS_CAIROFILE"]
00201         elif "GRASS_PNGFILE" in os.environ:
00202             del os.environ["GRASS_PNGFILE"]
00203         
00204         self.force_render = False
00205         
00206         return self.mapfile
00207     
00208     def GetCmd(self, string = False):
00209         """!Get GRASS command as list of string.
00210         
00211         @param string get command as string if True otherwise as list
00212         
00213         @return command list/string
00214         """
00215         if string:
00216             if self.type == 'command':
00217                 scmd = []
00218                 for c in self.cmd:
00219                     scmd.append(utils.GetCmdString(c))
00220                 
00221                 return ';'.join(scmd)
00222             else:
00223                 return utils.GetCmdString(self.cmd)
00224         else:
00225             return self.cmd
00226 
00227     def GetType(self):
00228         """!Get map layer type"""
00229         return self.type
00230     
00231     def GetElement(self):
00232         """!Get map element type"""
00233         if self.type == 'raster':
00234             return 'cell'
00235         return self.type
00236     
00237     def GetOpacity(self, float = False):
00238         """
00239         Get layer opacity level
00240         
00241         @param float get opacity level in <0,1> otherwise <0,100>
00242         
00243         @return opacity level
00244         """
00245         if float:
00246             return self.opacity
00247         
00248         return int (self.opacity * 100)
00249 
00250     def GetName(self, fullyQualified = True):
00251         """!Get map layer name
00252 
00253         @param fullyQualified True to return fully qualified name as a
00254         string 'name@mapset' otherwise directory { 'name', 'mapset' }
00255         is returned
00256 
00257         @return string / directory
00258         """
00259         if fullyQualified:
00260             return self.name
00261         else:
00262             if '@' in self.name:
00263                 return { 'name' : self.name.split('@')[0],
00264                          'mapset' : self.name.split('@')[1] }
00265             else:
00266                 return { 'name' : self.name,
00267                          'mapset' : '' }
00268         
00269     def IsActive(self):
00270         """!Check if layer is activated for rendering"""
00271         return self.active
00272     
00273     def SetType(self, type):
00274         """!Set layer type"""
00275         if type not in ('raster', '3d-raster', 'vector',
00276                         'overlay', 'command',
00277                         'shaded', 'rgb', 'his', 'rastarrow', 'rastnum',
00278                         'thememap', 'themechart', 'grid', 'labels',
00279                         'geodesic','rhumb'):
00280             raise GException(_("Unsupported map layer type '%s'") % type)
00281         
00282         self.type = type
00283 
00284     def SetName(self, name):
00285         """!Set layer name"""
00286         self.name = name
00287         
00288     def SetActive(self, enable = True):
00289         """!Active or deactive layer"""
00290         self.active = bool(enable)
00291 
00292     def SetHidden(self, enable = False):
00293         """!Hide or show map layer in Layer Manager"""
00294         self.hidden = bool(enable)
00295 
00296     def SetOpacity(self, value):
00297         """!Set opacity value"""
00298         if value < 0:
00299             value = 0.
00300         elif value > 1:
00301             value = 1.
00302         
00303         self.opacity = float(value)
00304         
00305     def SetCmd(self, cmd):
00306         """!Set new command for layer"""
00307         if self.type == 'command':
00308             self.cmd = []
00309             for c in cmd:
00310                 self.cmd.append(utils.CmdToTuple(c))
00311         else:
00312             self.cmd  = utils.CmdToTuple(cmd)
00313         Debug.msg(3, "Layer.SetCmd(): cmd='%s'" % self.GetCmd(string = True))
00314         
00315         # for re-rendering
00316         self.force_render = True
00317         
00318 class MapLayer(Layer):
00319     def __init__(self, type, cmd, name = None,
00320                  active = True, hidden = False, opacity = 1.0): 
00321         """!Represents map layer in the map canvas
00322         
00323         @param type layer type ('raster', 'vector', 'command', etc.)
00324         @param cmd GRASS command to render layer,
00325         given as list, e.g. ['d.rast', 'map=elevation@PERMANENT']
00326         @param name layer name, e.g. 'elevation@PERMANENT' (for layer tree) or None
00327         @param active layer is active, will be rendered only if True
00328         @param hidden layer is hidden, won't be listed in Layer Manager if True
00329         @param opacity layer opacity <0;1>
00330         """
00331         Layer.__init__(self, type, cmd, name,
00332                        active, hidden, opacity)
00333         
00334     def GetMapset(self):
00335         """!Get mapset of map layer
00336         
00337         @return mapset name
00338         @return '' on error (no name given)
00339         """
00340         if not self.name:
00341             return ''
00342         
00343         try:
00344             return self.name.split('@')[1]
00345         except IndexError:
00346             return self.name
00347         
00348 class Overlay(Layer):
00349     def __init__(self, id, type, cmd,
00350                  active = True, hidden = True, opacity = 1.0):
00351         """!Represents overlay displayed in map canvas
00352         
00353         @param id overlay id (for PseudoDC)
00354         @param type overlay type ('barscale', 'legend', etc.)
00355         @param cmd GRASS command to render overlay,
00356         given as list, e.g. ['d.legend', 'map=elevation@PERMANENT']
00357         @param active layer is active, will be rendered only if True
00358         @param hidden layer is hidden, won't be listed in Layer Manager if True
00359         @param opacity layer opacity <0;1>
00360         """
00361         Layer.__init__(self, 'overlay', cmd, type,
00362                        active, hidden, opacity)
00363         
00364         self.id = id
00365         
00366 class Map(object):
00367     """!Map composition (stack of map layers and overlays)
00368     """
00369     def __init__(self, gisrc = None):
00370         # region/extent settigns
00371         self.wind      = dict() # WIND settings (wind file)
00372         self.region    = dict() # region settings (g.region)
00373         self.width     = 640    # map width
00374         self.height    = 480    # map height
00375         
00376         # list of layers
00377         self.layers    = list()  # stack of available GRASS layer
00378         
00379         self.overlays  = list()  # stack of available overlays
00380         self.ovlookup  = dict()  # lookup dictionary for overlay items and overlays
00381         
00382         # environment settings
00383         # environment variables, like MAPSET, LOCATION_NAME, etc.
00384         self.env   = dict()
00385         # path to external gisrc
00386         self.gisrc = gisrc
00387         
00388         # generated file for g.pnmcomp output for rendering the map
00389         self.mapfile = tempfile.mkstemp(suffix = '.ppm')[1]
00390         
00391         # setting some initial env. variables
00392         self._initGisEnv() # g.gisenv
00393         self.GetWindow()
00394         # GRASS environment variable (for rendering)
00395         os.environ["GRASS_TRANSPARENT"] = "TRUE"
00396         os.environ["GRASS_BACKGROUNDCOLOR"] = "ffffff"
00397         
00398         # projection info
00399         self.projinfo = self._projInfo()
00400         
00401     def _runCommand(self, cmd, **kwargs):
00402         """!Run command in environment defined by self.gisrc if
00403         defined"""
00404         # use external gisrc if defined
00405         gisrc_orig = os.getenv("GISRC")
00406         if self.gisrc:
00407             os.environ["GISRC"] = self.gisrc
00408         
00409         ret = cmd(**kwargs)
00410         
00411         # back to original gisrc
00412         if self.gisrc:
00413             os.environ["GISRC"] = gisrc_orig
00414         
00415         return ret
00416     
00417     def _initGisEnv(self):
00418         """!Stores GRASS variables (g.gisenv) to self.env variable
00419         """
00420         if not os.getenv("GISBASE"):
00421             sys.exit(_("GISBASE not set. You must be in GRASS GIS to run this program."))
00422         
00423         self.env = self._runCommand(grass.gisenv)
00424             
00425     def GetProjInfo(self):
00426         """!Get projection info"""
00427         return self.projinfo
00428     
00429     def _projInfo(self):
00430         """!Return region projection and map units information
00431         """
00432         projinfo = dict()
00433         if not grass.find_program('g.proj', ['--help']):
00434             sys.exit(_("GRASS module '%s' not found. Unable to start map "
00435                        "display window.") % 'g.proj')
00436         
00437         ret = self._runCommand(RunCommand, prog = 'g.proj',
00438                                read = True, flags = 'p')
00439         
00440         if not ret:
00441             return projinfo
00442         
00443         for line in ret.splitlines():
00444             if ':' in line:
00445                 key, val = map(lambda x: x.strip(), line.split(':'))
00446                 if key in ['units']:
00447                     val = val.lower()
00448                 projinfo[key] = val
00449             elif "XY location (unprojected)" in line:
00450                 projinfo['proj'] = 'xy'
00451                 projinfo['units'] = ''
00452                 break
00453         
00454         return projinfo
00455     
00456     def GetWindow(self):
00457         """!Read WIND file and set up self.wind dictionary"""
00458         # FIXME: duplicated region WIND == g.region (at least some values)
00459         filename = os.path.join (self.env['GISDBASE'],
00460                                  self.env['LOCATION_NAME'],
00461                                  self.env['MAPSET'],
00462                                  "WIND")
00463         try:
00464             windfile = open (filename, "r")
00465         except IOError, e:
00466             sys.exit(_("Error: Unable to open '%(file)s'. Reason: %(ret)s. wxGUI exited.\n") % \
00467                          { 'file' : filename, 'ret' : e})
00468         
00469         for line in windfile.readlines():
00470             line = line.strip()
00471             key, value = line.split(":", 1)
00472             self.wind[key.strip()] = value.strip()
00473         
00474         windfile.close()
00475         
00476         return self.wind
00477         
00478     def AdjustRegion(self):
00479         """!Adjusts display resolution to match monitor size in
00480         pixels. Maintains constant display resolution, not related to
00481         computational region. Do NOT use the display resolution to set
00482         computational resolution. Set computational resolution through
00483         g.region.
00484         """
00485         mapwidth    = abs(self.region["e"] - self.region["w"])
00486         mapheight   = abs(self.region['n'] - self.region['s'])
00487         
00488         self.region["nsres"] =  mapheight / self.height
00489         self.region["ewres"] =  mapwidth  / self.width
00490         self.region['rows']  = round(mapheight / self.region["nsres"])
00491         self.region['cols']  = round(mapwidth / self.region["ewres"])
00492         self.region['cells'] = self.region['rows'] * self.region['cols']
00493         
00494         Debug.msg (3, "Map.AdjustRegion(): %s" % self.region)
00495         
00496         return self.region
00497 
00498     def AlignResolution(self):
00499         """!Sets display extents to even multiple of current
00500         resolution defined in WIND file from SW corner. This must be
00501         done manually as using the -a flag can produce incorrect
00502         extents.
00503         """
00504         # new values to use for saving to region file
00505         new = {}
00506         n = s = e = w = 0.0
00507         nwres = ewres = 0.0
00508         
00509         # Get current values for region and display
00510         reg = self.GetRegion()
00511         nsres = reg['nsres']
00512         ewres = reg['ewres']
00513         
00514         n = float(self.region['n'])
00515         s = float(self.region['s'])
00516         e = float(self.region['e'])
00517         w = float(self.region['w'])
00518         
00519         # Calculate rows, columns, and extents
00520         new['rows'] = math.fabs(round((n-s)/nsres))
00521         new['cols'] = math.fabs(round((e-w)/ewres))
00522         
00523         # Calculate new extents
00524         new['s'] = nsres * round(s / nsres)
00525         new['w'] = ewres * round(w / ewres)
00526         new['n'] = new['s'] + (new['rows'] * nsres)
00527         new['e'] = new['w'] + (new['cols'] * ewres)
00528         
00529         return new
00530 
00531     def AlignExtentFromDisplay(self):
00532         """!Align region extent based on display size from center
00533         point"""
00534         # calculate new bounding box based on center of display
00535         if self.region["ewres"] > self.region["nsres"]:
00536             res = self.region["ewres"]
00537         else:
00538             res = self.region["nsres"]
00539         
00540         Debug.msg(3, "Map.AlignExtentFromDisplay(): width=%d, height=%d, res=%f, center=%f,%f" % \
00541                       (self.width, self.height, res, self.region['center_easting'],
00542                        self.region['center_northing']))
00543             
00544         ew = (self.width / 2) * res
00545         ns = (self.height / 2) * res
00546         
00547         self.region['n'] = self.region['center_northing'] + ns
00548         self.region['s'] = self.region['center_northing'] - ns
00549         self.region['e'] = self.region['center_easting'] + ew
00550         self.region['w'] = self.region['center_easting'] - ew
00551         
00552         # LL locations
00553         if self.projinfo['proj'] == 'll':
00554             self.region['n'] = min(self.region['n'], 90.0)
00555             self.region['s'] = max(self.region['s'], -90.0)
00556         
00557     def ChangeMapSize(self, (width, height)):
00558         """!Change size of rendered map.
00559         
00560         @param width,height map size
00561 
00562         @return True on success
00563         @return False on failure
00564         """
00565         try:
00566             self.width  = int(width)
00567             self.height = int(height)
00568             Debug.msg(2, "Map.ChangeMapSize(): width=%d, height=%d" % \
00569                           (self.width, self.height))
00570             return True
00571         except:
00572             self.width  = 640
00573             self.height = 480
00574             return False
00575         
00576     def GetRegion(self, rast = [], zoom = False, vect = [], regionName = None,
00577                   n = None, s = None, e = None, w = None, default = False,
00578                   update = False):
00579         """!Get region settings (g.region -upgc)
00580         
00581         Optionally extent, raster or vector map layer can be given.
00582         
00583         @param rast list of raster maps
00584         @param zoom zoom to raster map (ignore NULLs)
00585         @param vect list of vector maps
00586         @param regionName  named region or None
00587         @param n,s,e,w force extent
00588         @param default force default region settings
00589         @param update if True update current display region settings
00590         
00591         @return region settings as directory, e.g. {
00592         'n':'4928010', 's':'4913700', 'w':'589980',...}
00593         
00594         @see GetCurrentRegion()
00595         """
00596         region = {}
00597         
00598         tmpreg = os.getenv("GRASS_REGION")
00599         if tmpreg:
00600             del os.environ["GRASS_REGION"]
00601         
00602         # use external gisrc if defined
00603         gisrc_orig = os.getenv("GISRC")
00604         if self.gisrc:
00605             os.environ["GISRC"] = self.gisrc
00606         
00607         # do not update & shell style output
00608         cmd = {}
00609         cmd['flags'] = 'ugpc'
00610         
00611         if default:
00612             cmd['flags'] += 'd'
00613         
00614         if regionName:
00615             cmd['region'] = regionName
00616         
00617         if n:
00618             cmd['n'] = n
00619         if s:
00620             cmd['s'] = s
00621         if e:
00622             cmd['e'] = e
00623         if w:
00624             cmd['w'] = w
00625         
00626         if rast:
00627             if zoom:
00628                 cmd['zoom'] = rast[0]
00629             else:
00630                 cmd['rast'] = ','.join(rast)
00631         
00632         if vect:
00633             cmd['vect'] = ','.join(vect)
00634         
00635         ret, reg, msg = RunCommand('g.region',
00636                                    read = True,
00637                                    getErrorMsg = True,
00638                                    **cmd)
00639         
00640         if ret != 0:
00641             if rast:
00642                 message = _("Unable to zoom to raster map <%s>.") % rast[0] + \
00643                     "\n\n" + _("Details:") + " %s" % msg
00644             elif vect:
00645                 message = _("Unable to zoom to vector map <%s>.") % vect[0] + \
00646                     "\n\n" + _("Details:") + " %s" % msg
00647             else:
00648                 message = _("Unable to get current geographic extent. "
00649                             "Force quiting wxGUI. Please manually run g.region to "
00650                             "fix the problem.")
00651             GError(message)
00652             return self.region
00653         
00654         for r in reg.splitlines():
00655             key, val = r.split("=", 1)
00656             try:
00657                 region[key] = float(val)
00658             except ValueError:
00659                 region[key] = val
00660         
00661         # back to original gisrc
00662         if self.gisrc:
00663             os.environ["GISRC"] = gisrc_orig
00664         
00665         # restore region
00666         if tmpreg:
00667             os.environ["GRASS_REGION"] = tmpreg
00668         
00669         Debug.msg (3, "Map.GetRegion(): %s" % region)
00670         
00671         if update:
00672             self.region = region
00673         
00674         return region
00675 
00676     def GetCurrentRegion(self):
00677         """!Get current display region settings
00678         
00679         @see GetRegion()
00680         """
00681         return self.region
00682 
00683     def SetRegion(self, windres = False):
00684         """!Render string for GRASS_REGION env. variable, so that the
00685         images will be rendered from desired zoom level.
00686         
00687         @param windres uses resolution from WIND file rather than
00688         display (for modules that require set resolution like
00689         d.rast.num)
00690 
00691         @return String usable for GRASS_REGION variable or None
00692         """
00693         grass_region = ""
00694         
00695         if windres:
00696             compRegion = self.GetRegion()
00697             region = copy.copy(self.region)
00698             for key in ('nsres', 'ewres', 'cells'):
00699                 region[key] = compRegion[key]
00700         else:
00701             # adjust region settings to match monitor
00702             region = self.AdjustRegion()
00703         
00704         # read values from wind file
00705         try:
00706             for key in self.wind.keys():
00707                 if key == 'north':
00708                     grass_region += "north: %s; " % \
00709                         (region['n'])
00710                     continue
00711                 elif key == "south":
00712                     grass_region += "south: %s; " % \
00713                         (region['s'])
00714                     continue
00715                 elif key == "east":
00716                     grass_region += "east: %s; " % \
00717                         (region['e'])
00718                     continue
00719                 elif key == "west":
00720                     grass_region += "west: %s; " % \
00721                         (region['w'])
00722                     continue
00723                 elif key == "e-w resol":
00724                     grass_region += "e-w resol: %f; " % \
00725                         (region['ewres'])
00726                     continue
00727                 elif key == "n-s resol":
00728                     grass_region += "n-s resol: %f; " % \
00729                         (region['nsres'])
00730                     continue
00731                 elif key == "cols":
00732                     if windres:
00733                         continue
00734                     grass_region += 'cols: %d; ' % \
00735                         region['cols']
00736                     continue
00737                 elif key == "rows":
00738                     if windres:
00739                         continue
00740                     grass_region += 'rows: %d; ' % \
00741                         region['rows']
00742                     continue
00743                 else:
00744                     grass_region += key + ": "  + self.wind[key] + "; "
00745             
00746             Debug.msg (3, "Map.SetRegion(): %s" % grass_region)
00747             
00748             return grass_region
00749         
00750         except:
00751             return None
00752         
00753     def GetListOfLayers(self, l_type = None, l_mapset = None, l_name = None,
00754                         l_active = None, l_hidden = None):
00755         """!Returns list of layers of selected properties or list of
00756         all layers.
00757 
00758         @param l_type layer type, e.g. raster/vector/wms/overlay (value or tuple of values)
00759         @param l_mapset all layers from given mapset (only for maplayers)
00760         @param l_name all layers with given name
00761         @param l_active only layers with 'active' attribute set to True or False
00762         @param l_hidden only layers with 'hidden' attribute set to True or False
00763         
00764         @return list of selected layers
00765         """
00766         selected = []
00767         
00768         if type(l_type) == types.StringType:
00769             one_type = True
00770         else:
00771             one_type = False
00772         
00773         if one_type and l_type == 'overlay':
00774             llist = self.overlays
00775         else:
00776             llist = self.layers
00777         
00778         # ["raster", "vector", "wms", ... ]
00779         for layer in llist:
00780             # specified type only
00781             if l_type != None:
00782                 if one_type and layer.type != l_type:
00783                     continue
00784                 elif not one_type and layer.type not in l_type:
00785                     continue
00786             
00787             # mapset
00788             if (l_mapset != None and l_type != 'overlay') and \
00789                     layer.GetMapset() != l_mapset:
00790                 continue
00791             
00792             # name
00793             if l_name != None and layer.name != l_name:
00794                 continue
00795             
00796             # hidden and active layers
00797             if l_active != None and \
00798                    l_hidden != None:
00799                 if layer.active == l_active and \
00800                        layer.hidden == l_hidden:
00801                     selected.append(layer)
00802             
00803             # active layers
00804             elif l_active != None:
00805                 if layer.active == l_active:
00806                     selected.append(layer)
00807             
00808             # hidden layers
00809             elif l_hidden != None:
00810                 if layer.hidden == l_hidden:
00811                     selected.append(layer)
00812             
00813             # all layers
00814             else:
00815                 selected.append(layer)
00816         
00817         Debug.msg (3, "Map.GetListOfLayers(): numberof=%d" % len(selected))
00818         
00819         return selected
00820 
00821     def _renderLayers(self, force, mapWindow, maps, masks, opacities):
00822         # render map layers
00823         ilayer = 1
00824         for layer in self.layers + self.overlays:
00825             # skip dead or disabled map layers
00826             if layer == None or layer.active == False:
00827                 continue
00828             
00829             # render if there is no mapfile
00830             if force or \
00831                layer.force_render or \
00832                layer.mapfile == None or \
00833                (not os.path.isfile(layer.mapfile) or not os.path.getsize(layer.mapfile)):
00834                 if not layer.Render():
00835                     continue
00836             
00837             if mapWindow:
00838                 # update progress bar
00839                 ### wx.SafeYield(mapWindow)
00840                 event = wxUpdateProgressBar(value = ilayer)
00841                 wx.PostEvent(mapWindow, event)
00842             
00843             # add image to compositing list
00844             if layer.type != "overlay":
00845                 maps.append(layer.mapfile)
00846                 masks.append(layer.maskfile)
00847                 opacities.append(str(layer.opacity))
00848                 
00849             Debug.msg (3, "Map.Render() type=%s, layer=%s " % (layer.type, layer.name))
00850             ilayer += 1
00851         
00852     def Render(self, force = False, mapWindow = None, windres = False):
00853         """!Creates final image composite
00854         
00855         This function can conditionaly use high-level tools, which
00856         should be avaliable in wxPython library
00857         
00858         @param force force rendering
00859         @param reference for MapFrame instance (for progress bar)
00860         @param windres use region resolution (True) otherwise display resolution
00861         
00862         @return name of file with rendered image or None
00863         """
00864         maps = []
00865         masks = []
00866         opacities = []
00867 
00868         wx.BeginBusyCursor()
00869         # use external gisrc if defined
00870         gisrc_orig = os.getenv("GISRC")
00871         if self.gisrc:
00872             os.environ["GISRC"] = self.gisrc
00873         
00874         tmp_region = os.getenv("GRASS_REGION")
00875         os.environ["GRASS_REGION"] = self.SetRegion(windres)
00876         os.environ["GRASS_WIDTH"]  = str(self.width)
00877         os.environ["GRASS_HEIGHT"] = str(self.height)
00878         if UserSettings.Get(group='display', key='driver', subkey='type') == 'cairo':
00879             os.environ["GRASS_AUTO_WRITE"] = "TRUE"
00880             if "GRASS_RENDER_IMMEDIATE" in os.environ:
00881                 del os.environ["GRASS_RENDER_IMMEDIATE"]
00882             os.environ["GRASS_RENDER_IMMEDIATE"] = "TRUE"
00883         else:
00884             os.environ["GRASS_PNG_AUTO_WRITE"] = "TRUE"
00885             os.environ["GRASS_PNG_READ"] = "FALSE"
00886             os.environ["GRASS_COMPRESSION"] = "0"
00887             os.environ["GRASS_TRUECOLOR"] = "TRUE"
00888             os.environ["GRASS_RENDER_IMMEDIATE"] = "TRUE"
00889         
00890         self._renderLayers(force, mapWindow, maps, masks, opacities)
00891         
00892         # ugly hack for MSYS
00893         if sys.platform != 'win32':
00894             mapstr = ",".join(maps)
00895             maskstr = ",".join(masks)
00896             mapoutstr = self.mapfile
00897         else:
00898             mapstr = ""
00899             for item in maps:
00900                 mapstr += item.replace('\\', '/')               
00901             mapstr = mapstr.rstrip(',')
00902             maskstr = ""
00903             for item in masks:
00904                 maskstr += item.replace('\\', '/')
00905             maskstr = maskstr.rstrip(',')
00906             mapoutstr = self.mapfile.replace('\\', '/')
00907         
00908         # compose command
00909         bgcolor = ':'.join(map(str, UserSettings.Get(group = 'display', key = 'bgcolor',
00910                                                      subkey = 'color')))
00911         
00912         # render overlays
00913         if tmp_region:
00914             os.environ["GRASS_REGION"] = tmp_region
00915         else:
00916             del os.environ["GRASS_REGION"]
00917         
00918         if maps:
00919             # run g.pngcomp to get composite image
00920             ret, msg = RunCommand('g.pnmcomp',
00921                                   getErrorMsg = True,
00922                                   input = '%s' % ",".join(maps),
00923                                   mask = '%s' % ",".join(masks),
00924                                   opacity = '%s' % ",".join(opacities),
00925                                   background = bgcolor,
00926                                   width = self.width,
00927                                   height = self.height,
00928                                   output = self.mapfile)
00929             
00930             if ret != 0:
00931                 print >> sys.stderr, _("ERROR: Rendering failed. Details: %s") % msg
00932                 wx.EndBusyCursor()
00933                 return None
00934             
00935             Debug.msg (3, "Map.Render() force=%s file=%s" % (force, self.mapfile))
00936         
00937         # back to original gisrc
00938         if self.gisrc:
00939             os.environ["GISRC"] = gisrc_orig
00940         
00941         wx.EndBusyCursor()
00942         if not maps:
00943             return None
00944         
00945         return self.mapfile
00946 
00947     def AddLayer(self, type, command, name = None,
00948                  l_active = True, l_hidden = False, l_opacity = 1.0, l_render = False,
00949                  pos = -1):
00950         """!Adds generic map layer to list of layers
00951         
00952         @param type layer type ('raster', 'vector', etc.)
00953         @param command  GRASS command given as list
00954         @param name layer name
00955         @param l_active layer render only if True
00956         @param l_hidden layer not displayed in layer tree if True
00957         @param l_opacity opacity level range from 0(transparent) - 1(not transparent)
00958         @param l_render render an image if True
00959         @param pos position in layer list (-1 for append)
00960         
00961         @return new layer on success
00962         @return None on failure
00963         """
00964         wx.BeginBusyCursor()
00965         # l_opacity must be <0;1>
00966         if l_opacity < 0: l_opacity = 0
00967         elif l_opacity > 1: l_opacity = 1
00968         layer = MapLayer(type = type, name = name, cmd = command,
00969                          active = l_active, hidden = l_hidden, opacity = l_opacity)
00970         
00971         # add maplayer to the list of layers
00972         if pos > -1:
00973             self.layers.insert(pos, layer)
00974         else:
00975             self.layers.append(layer)
00976         
00977         Debug.msg (3, "Map.AddLayer(): layer=%s" % layer.name)
00978         if l_render:
00979             if not layer.Render():
00980                 raise GException(_("Unable to render map layer <%s>.") % name)
00981         
00982         wx.EndBusyCursor()
00983 
00984         return layer
00985 
00986     def DeleteLayer(self, layer, overlay = False):
00987         """!Removes layer from list of layers
00988         
00989         @param layer layer instance in layer tree
00990         @param overlay delete overlay (use self.DeleteOverlay() instead)
00991 
00992         @return removed layer on success or None
00993         """
00994         Debug.msg (3, "Map.DeleteLayer(): name=%s" % layer.name)
00995         
00996         if overlay:
00997             list = self.overlays
00998         else:
00999             list = self.layers
01000         
01001         if layer in list:
01002             if layer.mapfile:
01003                 base = os.path.split(layer.mapfile)[0]
01004                 mapfile = os.path.split(layer.mapfile)[1]
01005                 tempbase = mapfile.split('.')[0]
01006                 if base == '' or tempbase == '':
01007                     return None
01008                 basefile = os.path.join(base, tempbase) + r'.*'
01009                 for f in glob.glob(basefile):
01010                     os.remove(f)
01011             list.remove(layer)
01012             
01013             return layer
01014         
01015         return None
01016 
01017     def ReorderLayers(self, layerList):
01018         """!Reorder list to match layer tree
01019         
01020         @param layerList list of layers
01021         """
01022         self.layers = layerList
01023         
01024         layerNameList = ""
01025         for layer in self.layers:
01026             if layer.name:
01027                 layerNameList += layer.name + ','
01028         Debug.msg (4, "Map.ReoderLayers(): layers=%s" % \
01029                    (layerNameList))
01030         
01031     def ChangeLayer(self, layer, render = False, **kargs):
01032         """!Change map layer properties
01033 
01034         @param layer map layer instance
01035         @param type layer type ('raster', 'vector', etc.)
01036         @param command  GRASS command given as list
01037         @param name layer name
01038         @param active layer render only if True
01039         @param hidden layer not displayed in layer tree if True
01040         @param opacity opacity level range from 0(transparent) - 1(not transparent)
01041         @param render render an image if True
01042         """
01043         Debug.msg (3, "Map.ChangeLayer(): layer=%s" % layer.name)
01044         
01045         if 'type' in kargs:
01046             layer.SetType(kargs['type']) # check type
01047         
01048         if 'command' in kargs:
01049             layer.SetCmd(kargs['command'])
01050         
01051         if 'name' in kargs:
01052             layer.SetName(kargs['name'])
01053         
01054         if 'active' in kargs:
01055             layer.SetActive(kargs['active'])
01056         
01057         if 'hidden' in kargs:
01058             layer.SetHidden(kargs['hidden'])
01059         
01060         if 'opacity' in kargs:
01061             layer.SetOpacity(kargs['opacity'])
01062         
01063         if render and not layer.Render():
01064             raise GException(_("Unable to render map layer <%s>.") % 
01065                              name)
01066         
01067         return layer
01068 
01069     def ChangeOpacity(self, layer, l_opacity):
01070         """!Changes opacity value of map layer
01071 
01072         @param layer layer instance in layer tree
01073         @param l_opacity opacity level <0;1>
01074         """
01075         # l_opacity must be <0;1>
01076         if l_opacity < 0: l_opacity = 0
01077         elif l_opacity > 1: l_opacity = 1
01078         
01079         layer.opacity = l_opacity
01080         Debug.msg (3, "Map.ChangeOpacity(): layer=%s, opacity=%f" % \
01081                    (layer.name, layer.opacity))
01082 
01083     def ChangeLayerActive(self, layer, active):
01084         """!Enable or disable map layer
01085         
01086         @param layer layer instance in layer tree
01087         @param active to be rendered (True)
01088         """
01089         layer.active = active
01090         
01091         Debug.msg (3, "Map.ChangeLayerActive(): name='%s' -> active=%d" % \
01092                    (layer.name, layer.active))
01093 
01094     def ChangeLayerName (self, layer, name):
01095         """!Change name of the layer
01096         
01097         @param layer layer instance in layer tree
01098         @param name  layer name to set up
01099         """
01100         Debug.msg (3, "Map.ChangeLayerName(): from=%s to=%s" % \
01101                    (layer.name, name))
01102         layer.name =  name
01103 
01104     def RemoveLayer(self, name = None, id = None):
01105         """!Removes layer from layer list
01106         
01107         Layer is defined by name@mapset or id.
01108         
01109         @param name layer name (must be unique)
01110         @param id layer index in layer list
01111 
01112         @return removed layer on success
01113         @return None on failure
01114         """
01115         # delete by name
01116         if name:
01117             retlayer = None
01118             for layer in self.layers:
01119                 if layer.name == name:
01120                     retlayer = layer
01121                     os.remove(layer.mapfile)
01122                     os.remove(layer.maskfile)
01123                     self.layers.remove(layer)
01124                     return layer
01125         # del by id
01126         elif id != None:
01127             return self.layers.pop(id)
01128         
01129         return None
01130 
01131     def GetLayerIndex(self, layer, overlay = False):
01132         """!Get index of layer in layer list.
01133         
01134         @param layer layer instace in layer tree
01135         @param overlay use list of overlays instead
01136         
01137         @return layer index
01138         @return -1 if layer not found
01139         """
01140         if overlay:
01141             list = self.overlay
01142         else:
01143             list = self.layers
01144             
01145         if layer in list:
01146             return list.index(layer)
01147         
01148         return -1
01149 
01150     def AddOverlay(self, id, type, command,
01151                    l_active = True, l_hidden = True, l_opacity = 1.0, l_render = False):
01152         """!Adds overlay (grid, barscale, legend, etc.) to list of
01153         overlays
01154         
01155         @param id overlay id (PseudoDC)
01156         @param type overlay type (barscale, legend)
01157         @param command GRASS command to render overlay
01158         @param l_active overlay activated (True) or disabled (False)
01159         @param l_hidden overlay is not shown in layer tree (if True)
01160         @param l_render render an image (if True)
01161         
01162         @return new layer on success
01163         @retutn None on failure
01164         """
01165         Debug.msg (2, "Map.AddOverlay(): cmd=%s, render=%d" % (command, l_render))
01166         overlay = Overlay(id = id, type = type, cmd = command,
01167                           active = l_active, hidden = l_hidden, opacity = l_opacity)
01168         
01169         # add maplayer to the list of layers
01170         self.overlays.append(overlay)
01171         
01172         if l_render and command != '' and not overlay.Render():
01173             raise GException(_("Unable to render overlay <%s>.") % 
01174                              name)
01175         
01176         return self.overlays[-1]
01177 
01178     def ChangeOverlay(self, id, render = False, **kargs):
01179         """!Change overlay properities
01180         
01181         Add new overlay if overlay with 'id' doesn't exist.
01182         
01183         @param id overlay id (PseudoDC)
01184         @param type overlay type (barscale, legend)
01185         @param command GRASS command to render overlay
01186         @param l_active overlay activated (True) or disabled (False)
01187         @param l_hidden overlay is not shown in layer tree (if True)
01188         @param l_render render an image (if True)
01189         
01190         @return new layer on success
01191         """
01192         overlay = self.GetOverlay(id, list = False)
01193         if  overlay is None:
01194             overlay = Overlay(id, type = None, cmd = None)
01195         
01196         if 'type' in kargs:
01197             overlay.SetName(kargs['type']) # type -> overlay
01198         
01199         if 'command' in kargs:
01200             overlay.SetCmd(kargs['command'])
01201         
01202         if 'active' in kargs:
01203             overlay.SetActive(kargs['active'])
01204         
01205         if 'hidden' in kargs:
01206             overlay.SetHidden(kargs['hidden'])
01207         
01208         if 'opacity' in kargs:
01209             overlay.SetOpacity(kargs['opacity'])
01210         
01211         if render and overlay.GetCmd() != [] and not overlay.Render():
01212             raise GException(_("Unable to render overlay <%s>.") % 
01213                              name)
01214         
01215         return overlay
01216 
01217     def GetOverlay(self, id, list = False):
01218         """!Return overlay(s) with 'id'
01219         
01220         @param id overlay id
01221         @param list return list of overlays of True
01222         otherwise suppose 'id' to be unique
01223         
01224         @return list of overlays (list=True)
01225         @return overlay (list=False)
01226         @retur None (list=False) if no overlay or more overlays found
01227         """
01228         ovl = []
01229         for overlay in self.overlays:
01230             if overlay.id == id:
01231                 ovl.append(overlay)
01232                 
01233         if not list:
01234             if len(ovl) != 1:
01235                 return None
01236             else:
01237                 return ovl[0]
01238         
01239         return ovl
01240 
01241     def DeleteOverlay(self, overlay):
01242         """!Delete overlay
01243         
01244         @param overlay overlay layer
01245         
01246         @return removed overlay on success or None
01247         """
01248         return self.DeleteLayer(overlay, overlay = True)
01249 
01250     def Clean(self):
01251         """!Clean layer stack - go trough all layers and remove them
01252         from layer list.
01253 
01254         Removes also l_mapfile and l_maskfile
01255         
01256         @return False on failure
01257         @return True on success
01258         """
01259         try:
01260             dir = os.path.dirname(self.mapfile)
01261             base = os.path.basename(self.mapfile).split('.')[0]
01262             removepath = os.path.join(dir,base)+r'*'
01263             for f in glob.glob(removepath):
01264                 os.remove(f)
01265             for layer in self.layers:
01266                 if layer.mapfile:
01267                     dir = os.path.dirname(layer.mapfile)
01268                     base = os.path.basename(layer.mapfile).split('.')[0]
01269                     removepath = os.path.join(dir,base)+r'*'
01270                     for f in glob.glob(removepath):
01271                         os.remove(f)
01272                 self.layers.remove(layer)
01273             
01274             for overlay in self.overlays:
01275                 if overlay.mapfile:
01276                     dir = os.path.dirname(overlay.mapfile)
01277                     base = os.path.basename(overlay.mapfile).split('.')[0]
01278                     removepath = os.path.join(dir,base)+r'*'
01279                     for f in glob.glob(removepath):
01280                         os.remove(f)
01281                 self.overlays.remove(overlay)
01282         except:
01283             return False
01284         
01285         return True
01286     
01287     def ReverseListOfLayers(self):
01288         """!Reverse list of layers"""
01289         return self.layers.reverse()
01290 
01291     def RenderOverlays(self, force):
01292         """!Render overlays only (for nviz)"""
01293         for layer in self.overlays:
01294             if force or layer.force_render:
01295                 layer.Render()
01296                 
01297 if __name__ == "__main__":
01298     import gettext
01299     gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
01300     
01301     Map = Map()
01302     Map.GetRegion(update = True)
01303     
01304     Map.AddLayer(type = "raster",
01305                  name = "elevation",
01306                  command = ["d.rast", "map=elevation@PERMANENT"],
01307                  l_opacity = .7)
01308     
01309     Map.AddLayer(type = "vector",
01310                  name = "roadsmajor",
01311                  command = ["d.vect", "map=roadsmajor@PERMANENT", "color=red", "width=3", "type=line"])
01312     
01313     image = Map.Render(force = True)
01314     
01315     if image:
01316         grass.call(["display", image])