GRASS Programmer's Manual
6.4.2(2012)
|
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