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