GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
render.py
Go to the documentation of this file.
1 """!
2 @package render
3 
4 Rendering map layers and overlays into map composition image.
5 
6 Classes:
7  - Layer
8  - MapLayer
9  - Overlay
10  - Map
11 
12 (C) 2006-2011 by the GRASS Development Team
13 This program is free software under the GNU General Public
14 License (>=v2). Read the file COPYING that comes with GRASS
15 for details.
16 
17 @author Michael Barton
18 @author Jachym Cepicky
19 @author Martin Landa <landa.martin gmail.com>
20 """
21 
22 import os
23 import sys
24 import glob
25 import math
26 import copy
27 
28 try:
29  import subprocess
30 except:
31  compatPath = os.path.join(globalvar.ETCWXDIR, "compat")
32  sys.path.append(compatPath)
33  import subprocess
34 import tempfile
35 import types
36 
37 import wx
38 from wx.lib.newevent import NewEvent
39 
40 from grass.script import core as grass
41 
42 import globalvar
43 import utils
44 import gcmd
45 from debug import Debug as Debug
46 from preferences import globalSettings as UserSettings
47 
48 wxUpdateProgressBar, EVT_UPDATE_PRGBAR = NewEvent()
49 
50 #
51 # use g.pnmcomp for creating image composition or
52 # wxPython functionality
53 #
54 USE_GPNMCOMP = True
55 
56 class Layer(object):
57  """!Virtual class which stores information about layers (map layers and
58  overlays) of the map composition.
59 
60  For map layer use MapLayer class.
61  For overlays use Overlay class.
62  """
63  def __init__(self, type, cmd, name = None,
64  active = True, hidden = False, opacity = 1.0):
65  """!
66  @todo pass cmd as tuple instead of list
67 
68  @param type layer type ('raster', 'vector', 'overlay', 'command', etc.)
69  @param cmd GRASS command to render layer,
70  given as list, e.g. ['d.rast', 'map=elevation@PERMANENT']
71  @param name layer name, e.g. 'elevation@PERMANENT' (for layer tree)
72  @param active layer is active, will be rendered only if True
73  @param hidden layer is hidden, won't be listed in Layer Manager if True
74  @param opacity layer opacity <0;1>
75  """
76  self.type = type
77  self.name = name
78 
79  if self.type == 'command':
80  self.cmd = list()
81  for c in cmd:
82  self.cmd.append(utils.CmdToTuple(c))
83  else:
84  self.cmd = utils.CmdToTuple(cmd)
85 
86  self.active = active
87  self.hidden = hidden
88  self.opacity = opacity
89 
90  self.force_render = True
91 
92  Debug.msg (3, "Layer.__init__(): type=%s, cmd='%s', name=%s, " \
93  "active=%d, opacity=%d, hidden=%d" % \
94  (self.type, self.GetCmd(string = True), self.name, self.active,
95  self.opacity, self.hidden))
96 
97  # generated file for each layer
98  self.gtemp = tempfile.mkstemp()[1]
99  self.maskfile = self.gtemp + ".pgm"
100  if self.type == 'overlay':
101  self.mapfile = self.gtemp + ".png"
102  else:
103  self.mapfile = self.gtemp + ".ppm"
104 
105  def __del__(self):
106  Debug.msg (3, "Layer.__del__(): layer=%s, cmd='%s'" %
107  (self.name, self.GetCmd(string = True)))
108 
109  def Render(self):
110  """!Render layer to image
111 
112  @return rendered image filename
113  @return None on error
114  """
115  if not self.cmd:
116  return None
117 
118  # ignore in 2D
119  if self.type == '3d-raster':
120  return None
121 
122  Debug.msg (3, "Layer.Render(): type=%s, name=%s" % \
123  (self.type, self.name))
124 
125  # prepare command for each layer
126  layertypes = ('raster', 'rgb', 'his', 'shaded', 'rastarrow', 'rastnum',
127  'vector','thememap','themechart',
128  'grid', 'geodesic', 'rhumb', 'labels',
129  'command', 'rastleg',
130  'overlay')
131 
132  if self.type not in layertypes:
133  raise gcmd.GException(_("<%(name)s>: layer type <%(type)s> is not supported") % \
134  {'type' : self.type, 'name' : self.name})
135 
136  # start monitor
137  if UserSettings.Get(group='display', key='driver', subkey='type') == 'cairo':
138 # os.environ["GRASS_CAIROFILE"] = self.mapfile
139 # if 'cairo' not in gcmd.RunCommand('d.mon',
140 # flags='p',
141 # read = True):
142 # gcmd.RunCommand('d.mon',
143 # start = 'cairo')
144  if not self.mapfile:
145  self.gtemp = tempfile.mkstemp()[1]
146  self.maskfile = self.gtemp + ".pgm"
147  if self.type == 'overlay':
148  self.mapfile = self.gtemp + ".png"
149  else:
150  self.mapfile = self.gtemp + ".ppm"
151 
152  if self.mapfile:
153  os.environ["GRASS_CAIROFILE"] = self.mapfile
154  else:
155  if not self.mapfile:
156  self.gtemp = tempfile.mkstemp()[1]
157  self.maskfile = self.gtemp + ".pgm"
158  if self.type == 'overlay':
159  self.mapfile = self.gtemp + ".png"
160  else:
161  self.mapfile = self.gtemp + ".ppm"
162 
163  if self.mapfile:
164  os.environ["GRASS_PNGFILE"] = self.mapfile
165 
166  # execute command
167  try:
168  if self.type == 'command':
169  read = False
170  for c in self.cmd:
171  ret, msg = gcmd.RunCommand(c[0],
172  getErrorMsg = True,
173  quiet = True,
174  **c[1])
175  if ret != 0:
176  break
177  if not read:
178  os.environ["GRASS_PNG_READ"] = "TRUE"
179 
180  os.environ["GRASS_PNG_READ"] = "FALSE"
181  else:
182  ret, msg = gcmd.RunCommand(self.cmd[0],
183  getErrorMsg = True,
184  quiet = True,
185  **self.cmd[1])
186 
187  if ret != 0:
188  raise gcmd.GException(value = _("'%(cmd)s' failed. Details: %(det)s") % \
189  { 'cmd' : self.cmd[0], 'det' : msg })
190 
191  except gcmd.GException, e:
192  print >> sys.stderr, e.value
193  # clean up after problems
194  try:
195  os.remove(self.mapfile)
196  os.remove(self.maskfile)
197  os.remove(self.gtemp)
198  except (OSError, TypeError):
199  pass
200  self.mapfile = None
201  self.maskfile = None
202 
203  # stop monitor
204  if UserSettings.Get(group='display', key='driver', subkey='type') == 'cairo':
205 # gcmd.RunCommand('d.mon',
206 # stop = 'cairo')
207  del os.environ["GRASS_CAIROFILE"]
208  elif "GRASS_PNGFILE" in os.environ:
209  del os.environ["GRASS_PNGFILE"]
210 
211  self.force_render = False
212 
213  return self.mapfile
214 
215  def GetCmd(self, string = False):
216  """!Get GRASS command as list of string.
217 
218  @param string get command as string if True otherwise as list
219 
220  @return command list/string
221  """
222  if string:
223  if self.type == 'command':
224  scmd = []
225  for c in self.cmd:
226  scmd.append(utils.GetCmdString(c))
227 
228  return ';'.join(scmd)
229  else:
230  return utils.GetCmdString(self.cmd)
231  else:
232  return self.cmd
233 
234  def GetType(self):
235  """!Get map layer type"""
236  return self.type
237 
238  def GetElement(self):
239  """!Get map element type"""
240  if self.type == 'raster':
241  return 'cell'
242  return self.type
243 
244  def GetOpacity(self, float = False):
245  """
246  Get layer opacity level
247 
248  @param float get opacity level in <0,1> otherwise <0,100>
249 
250  @return opacity level
251  """
252  if float:
253  return self.opacity
254 
255  return int (self.opacity * 100)
256 
257  def GetName(self, fullyQualified = True):
258  """!Get map layer name
259 
260  @param fullyQualified True to return fully qualified name as a
261  string 'name@mapset' otherwise directory { 'name', 'mapset' }
262  is returned
263 
264  @return string / directory
265  """
266  if fullyQualified:
267  return self.name
268  else:
269  if '@' in self.name:
270  return { 'name' : self.name.split('@')[0],
271  'mapset' : self.name.split('@')[1] }
272  else:
273  return { 'name' : self.name,
274  'mapset' : '' }
275 
276  def IsActive(self):
277  """!Check if layer is activated for rendering"""
278  return self.active
279 
280  def SetType(self, type):
281  """!Set layer type"""
282  if type not in ('raster', '3d-raster', 'vector',
283  'overlay', 'command',
284  'shaded', 'rgb', 'his', 'rastarrow', 'rastnum',
285  'thememap', 'themechart', 'grid', 'labels',
286  'geodesic','rhumb'):
287  raise gcmd.GException(_("Unsupported map layer type '%s'") % type)
288 
289  self.type = type
290 
291  def SetName(self, name):
292  """!Set layer name"""
293  self.name = name
294 
295  def SetActive(self, enable = True):
296  """!Active or deactive layer"""
297  self.active = bool(enable)
298 
299  def SetHidden(self, enable = False):
300  """!Hide or show map layer in Layer Manager"""
301  self.hidden = bool(enable)
302 
303  def SetOpacity(self, value):
304  """!Set opacity value"""
305  if value < 0:
306  value = 0.
307  elif value > 1:
308  value = 1.
309 
310  self.opacity = float(value)
311 
312  def SetCmd(self, cmd):
313  """!Set new command for layer"""
314  if self.type == 'command':
315  self.cmd = []
316  for c in cmd:
317  self.cmd.append(utils.CmdToTuple(c))
318  else:
319  self.cmd = utils.CmdToTuple(cmd)
320  Debug.msg(3, "Layer.SetCmd(): cmd='%s'" % self.GetCmd(string = True))
321 
322  # for re-rendering
323  self.force_render = True
324 
326  def __init__(self, type, cmd, name = None,
327  active = True, hidden = False, opacity = 1.0):
328  """!Represents map layer in the map canvas
329 
330  @param type layer type ('raster', 'vector', 'command', etc.)
331  @param cmd GRASS command to render layer,
332  given as list, e.g. ['d.rast', 'map=elevation@PERMANENT']
333  @param name layer name, e.g. 'elevation@PERMANENT' (for layer tree) or None
334  @param active layer is active, will be rendered only if True
335  @param hidden layer is hidden, won't be listed in Layer Manager if True
336  @param opacity layer opacity <0;1>
337  """
338  Layer.__init__(self, type, cmd, name,
339  active, hidden, opacity)
340 
341  def GetMapset(self):
342  """!Get mapset of map layer
343 
344  @return mapset name
345  @return '' on error (no name given)
346  """
347  if not self.name:
348  return ''
349 
350  try:
351  return self.name.split('@')[1]
352  except IndexError:
353  return self.name
354 
355 class Overlay(Layer):
356  def __init__(self, id, type, cmd,
357  active = True, hidden = True, opacity = 1.0):
358  """!Represents overlay displayed in map canvas
359 
360  @param id overlay id (for PseudoDC)
361  @param type overlay type ('barscale', 'legend', etc.)
362  @param cmd GRASS command to render overlay,
363  given as list, e.g. ['d.legend', 'map=elevation@PERMANENT']
364  @param active layer is active, will be rendered only if True
365  @param hidden layer is hidden, won't be listed in Layer Manager if True
366  @param opacity layer opacity <0;1>
367  """
368  Layer.__init__(self, 'overlay', cmd, type,
369  active, hidden, opacity)
370 
371  self.id = id
372 
373 class Map(object):
374  """!Map composition (stack of map layers and overlays)
375  """
376  def __init__(self, gisrc = None):
377  # region/extent settigns
378  self.wind = dict() # WIND settings (wind file)
379  self.region = dict() # region settings (g.region)
380  self.width = 640 # map width
381  self.height = 480 # map height
382 
383  # list of layers
384  self.layers = list() # stack of available GRASS layer
385 
386  self.overlays = list() # stack of available overlays
387  self.ovlookup = dict() # lookup dictionary for overlay items and overlays
388 
389  # environment settings
390  # environment variables, like MAPSET, LOCATION_NAME, etc.
391  self.env = dict()
392  # path to external gisrc
393  self.gisrc = gisrc
394 
395  # generated file for g.pnmcomp output for rendering the map
396  self.mapfile = tempfile.mkstemp(suffix = '.ppm')[1]
397 
398  # setting some initial env. variables
399  self._initGisEnv() # g.gisenv
400  self.GetWindow()
401  # GRASS environment variable (for rendering)
402  os.environ["GRASS_TRANSPARENT"] = "TRUE"
403  os.environ["GRASS_BACKGROUNDCOLOR"] = "ffffff"
404 
405  # projection info
406  self.projinfo = self._projInfo()
407 
408  def _runCommand(self, cmd, **kwargs):
409  """!Run command in environment defined by self.gisrc if
410  defined"""
411  # use external gisrc if defined
412  gisrc_orig = os.getenv("GISRC")
413  if self.gisrc:
414  os.environ["GISRC"] = self.gisrc
415 
416  ret = cmd(**kwargs)
417 
418  # back to original gisrc
419  if self.gisrc:
420  os.environ["GISRC"] = gisrc_orig
421 
422  return ret
423 
424  def _initGisEnv(self):
425  """!Stores GRASS variables (g.gisenv) to self.env variable
426  """
427  if not os.getenv("GISBASE"):
428  sys.exit(_("GISBASE not set. You must be in GRASS GIS to run this program."))
429 
430  self.env = self._runCommand(grass.gisenv)
431 
432  def GetProjInfo(self):
433  """!Get projection info"""
434  return self.projinfo
435 
436  def _projInfo(self):
437  """!Return region projection and map units information
438  """
439  projinfo = dict()
440  if not grass.find_program('g.proj', ['--help']):
441  sys.exit(_("GRASS module '%s' not found. Unable to start map "
442  "display window.") % 'g.proj')
443 
444  ret = self._runCommand(gcmd.RunCommand, prog = 'g.proj',
445  read = True, flags = 'p')
446 
447  if not ret:
448  return projinfo
449 
450  for line in ret.splitlines():
451  if ':' in line:
452  key, val = map(lambda x: x.strip(), line.split(':'))
453  if key in ['units']:
454  val = val.lower()
455  projinfo[key] = val
456  elif "XY location (unprojected)" in line:
457  projinfo['proj'] = 'xy'
458  projinfo['units'] = ''
459  break
460 
461  return projinfo
462 
463  def GetWindow(self):
464  """!Read WIND file and set up self.wind dictionary"""
465  # FIXME: duplicated region WIND == g.region (at least some values)
466  filename = os.path.join (self.env['GISDBASE'],
467  self.env['LOCATION_NAME'],
468  self.env['MAPSET'],
469  "WIND")
470  try:
471  windfile = open (filename, "r")
472  except IOError, e:
473  sys.exit(_("Error: Unable to open '%(file)s'. Reason: %(ret)s. wxGUI exited.\n") % \
474  { 'file' : filename, 'ret' : e})
475 
476  for line in windfile.readlines():
477  line = line.strip()
478  key, value = line.split(":", 1)
479  self.wind[key.strip()] = value.strip()
480 
481  windfile.close()
482 
483  return self.wind
484 
485  def AdjustRegion(self):
486  """!Adjusts display resolution to match monitor size in
487  pixels. Maintains constant display resolution, not related to
488  computational region. Do NOT use the display resolution to set
489  computational resolution. Set computational resolution through
490  g.region.
491  """
492  mapwidth = abs(self.region["e"] - self.region["w"])
493  mapheight = abs(self.region['n'] - self.region['s'])
494 
495  self.region["nsres"] = mapheight / self.height
496  self.region["ewres"] = mapwidth / self.width
497  self.region['rows'] = round(mapheight / self.region["nsres"])
498  self.region['cols'] = round(mapwidth / self.region["ewres"])
499  self.region['cells'] = self.region['rows'] * self.region['cols']
500 
501  Debug.msg (3, "Map.AdjustRegion(): %s" % self.region)
502 
503  return self.region
504 
505  def AlignResolution(self):
506  """!Sets display extents to even multiple of current
507  resolution defined in WIND file from SW corner. This must be
508  done manually as using the -a flag can produce incorrect
509  extents.
510  """
511  # new values to use for saving to region file
512  new = {}
513  n = s = e = w = 0.0
514  nwres = ewres = 0.0
515 
516  # Get current values for region and display
517  nsres = self.GetRegion()['nsres']
518  ewres = self.GetRegion()['ewres']
519 
520  n = float(self.region['n'])
521  s = float(self.region['s'])
522  e = float(self.region['e'])
523  w = float(self.region['w'])
524 
525  # Calculate rows, columns, and extents
526  new['rows'] = math.fabs(round((n-s)/nsres))
527  new['cols'] = math.fabs(round((e-w)/ewres))
528 
529  # Calculate new extents
530  new['s'] = nsres * round(s/nsres)
531  new['w'] = ewres * round(w/ewres)
532  new['n'] = new['s'] + (new['rows'] * nsres)
533  new['e'] = new['w'] + (new['cols'] * ewres)
534 
535  return new
536 
538  """!Align region extent based on display size from center point"""
539  # calculate new bounding box based on center of display
540  if self.region["ewres"] > self.region["nsres"]:
541  res = self.region["ewres"]
542  else:
543  res = self.region["nsres"]
544 
545  Debug.msg(3, "Map.AlignExtentFromDisplay(): width=%d, height=%d, res=%f, center=%f,%f" % \
546  (self.width, self.height, res, self.region['center_easting'],
547  self.region['center_northing']))
548 
549  ew = (self.width / 2) * res
550  ns = (self.height / 2) * res
551 
552  self.region['n'] = self.region['center_northing'] + ns
553  self.region['s'] = self.region['center_northing'] - ns
554  self.region['e'] = self.region['center_easting'] + ew
555  self.region['w'] = self.region['center_easting'] - ew
556 
557  # LL locations
558  if self.projinfo['proj'] == 'll':
559  if self.region['n'] > 90.0:
560  self.region['n'] = 90.0
561  if self.region['s'] < -90.0:
562  self.region['s'] = -90.0
563 
564  def ChangeMapSize(self, (width, height)):
565  """!Change size of rendered map.
566 
567  @param width,height map size
568 
569  @return True on success
570  @return False on failure
571  """
572  try:
573  self.width = int(width)
574  self.height = int(height)
575  Debug.msg(2, "Map.ChangeMapSize(): width=%d, height=%d" % \
576  (self.width, self.height))
577  return True
578  except:
579  self.width = 640
580  self.height = 480
581  return False
582 
583  def GetRegion(self, rast = [], zoom = False, vect = [], regionName = None,
584  n = None, s = None, e = None, w = None, default = False,
585  update = False):
586  """!Get region settings (g.region -upgc)
587 
588  Optionally extent, raster or vector map layer can be given.
589 
590  @param rast list of raster maps
591  @param zoom zoom to raster map (ignore NULLs)
592  @param vect list of vector maps
593  @param regionName named region or None
594  @param n,s,e,w force extent
595  @param default force default region settings
596  @param update if True update current display region settings
597 
598  @return region settings as directory, e.g. {
599  'n':'4928010', 's':'4913700', 'w':'589980',...}
600  """
601  region = {}
602 
603  tmpreg = os.getenv("GRASS_REGION")
604  if tmpreg:
605  del os.environ["GRASS_REGION"]
606 
607  # use external gisrc if defined
608  gisrc_orig = os.getenv("GISRC")
609  if self.gisrc:
610  os.environ["GISRC"] = self.gisrc
611 
612  # do not update & shell style output
613  cmd = {}
614  cmd['flags'] = 'ugpc'
615 
616  if default:
617  cmd['flags'] += 'd'
618 
619  if regionName:
620  cmd['region'] = regionName
621 
622  if n:
623  cmd['n'] = n
624  if s:
625  cmd['s'] = s
626  if e:
627  cmd['e'] = e
628  if w:
629  cmd['w'] = w
630 
631  if rast:
632  if zoom:
633  cmd['zoom'] = rast[0]
634  else:
635  cmd['rast'] = ','.join(rast)
636 
637  if vect:
638  cmd['vect'] = ','.join(vect)
639 
640  ret, reg, msg = gcmd.RunCommand('g.region',
641  read = True,
642  getErrorMsg = True,
643  **cmd)
644 
645  if ret != 0:
646  if rast:
647  message = _("Unable to zoom to raster map <%s>.") % rast[0] + \
648  "\n\n" + _("Details:") + " %s" % msg
649  elif vect:
650  message = _("Unable to zoom to vector map <%s>.") % vect[0] + \
651  "\n\n" + _("Details:") + " %s" % msg
652  else:
653  message = _("Unable to get current geographic extent. "
654  "Force quiting wxGUI. Please manually run g.region to "
655  "fix the problem.")
656  gcmd.GError(message)
657  return self.region
658 
659  for r in reg.splitlines():
660  key, val = r.split("=", 1)
661  try:
662  region[key] = float(val)
663  except ValueError:
664  region[key] = val
665 
666  # back to original gisrc
667  if self.gisrc:
668  os.environ["GISRC"] = gisrc_orig
669 
670  # restore region
671  if tmpreg:
672  os.environ["GRASS_REGION"] = tmpreg
673 
674  Debug.msg (3, "Map.GetRegion(): %s" % region)
675 
676  if update:
677  self.region = region
678 
679  return region
680 
681  def GetCurrentRegion(self):
682  """!Get current display region settings"""
683  return self.region
684 
685  def SetRegion(self, windres = False):
686  """!Render string for GRASS_REGION env. variable, so that the
687  images will be rendered from desired zoom level.
688 
689  @param windres uses resolution from WIND file rather than
690  display (for modules that require set resolution like
691  d.rast.num)
692 
693  @return String usable for GRASS_REGION variable or None
694  """
695  grass_region = ""
696 
697  if windres:
698  compRegion = self.GetRegion()
699  region = copy.copy(self.region)
700  for key in ('nsres', 'ewres',
701  'rows', 'cols', 'cells'):
702  region[key] = compRegion[key]
703  else:
704  # adjust region settings to match monitor
705  region = self.AdjustRegion()
706 
707  # read values from wind file
708  try:
709  for key in self.wind.keys():
710  if key == 'north':
711  grass_region += "north: %s; " % \
712  (region['n'])
713  continue
714  elif key == "south":
715  grass_region += "south: %s; " % \
716  (region['s'])
717  continue
718  elif key == "east":
719  grass_region += "east: %s; " % \
720  (region['e'])
721  continue
722  elif key == "west":
723  grass_region += "west: %s; " % \
724  (region['w'])
725  continue
726  elif key == "e-w resol":
727  grass_region += "e-w resol: %f; " % \
728  (region['ewres'])
729  continue
730  elif key == "n-s resol":
731  grass_region += "n-s resol: %f; " % \
732  (region['nsres'])
733  continue
734  elif key == "cols":
735  grass_region += 'cols: %d; ' % \
736  region['cols']
737  continue
738  elif key == "rows":
739  grass_region += 'rows: %d; ' % \
740  region['rows']
741  continue
742  else:
743  grass_region += key + ": " + self.wind[key] + "; "
744 
745  Debug.msg (3, "Map.SetRegion(): %s" % grass_region)
746 
747  return grass_region
748 
749  except:
750  return None
751 
752  def GetListOfLayers(self, l_type = None, l_mapset = None, l_name = None,
753  l_active = None, l_hidden = None):
754  """!Returns list of layers of selected properties or list of
755  all layers.
756 
757  @param l_type layer type, e.g. raster/vector/wms/overlay (value or tuple of values)
758  @param l_mapset all layers from given mapset (only for maplayers)
759  @param l_name all layers with given name
760  @param l_active only layers with 'active' attribute set to True or False
761  @param l_hidden only layers with 'hidden' attribute set to True or False
762 
763  @return list of selected layers
764  """
765  selected = []
766 
767  if type(l_type) == types.StringType:
768  one_type = True
769  else:
770  one_type = False
771 
772  if one_type and l_type == 'overlay':
773  llist = self.overlays
774  else:
775  llist = self.layers
776 
777  # ["raster", "vector", "wms", ... ]
778  for layer in llist:
779  # specified type only
780  if l_type != None:
781  if one_type and layer.type != l_type:
782  continue
783  elif not one_type and layer.type not in l_type:
784  continue
785 
786  # mapset
787  if (l_mapset != None and l_type != 'overlay') and \
788  layer.GetMapset() != l_mapset:
789  continue
790 
791  # name
792  if l_name != None and layer.name != l_name:
793  continue
794 
795  # hidden and active layers
796  if l_active != None and \
797  l_hidden != None:
798  if layer.active == l_active and \
799  layer.hidden == l_hidden:
800  selected.append(layer)
801 
802  # active layers
803  elif l_active != None:
804  if layer.active == l_active:
805  selected.append(layer)
806 
807  # hidden layers
808  elif l_hidden != None:
809  if layer.hidden == l_hidden:
810  selected.append(layer)
811 
812  # all layers
813  else:
814  selected.append(layer)
815 
816  Debug.msg (3, "Map.GetListOfLayers(): numberof=%d" % len(selected))
817 
818  return selected
819 
820  def _renderLayers(self, force, mapWindow, maps, masks, opacities):
821  # render map layers
822  ilayer = 1
823  for layer in self.layers + self.overlays:
824  # skip dead or disabled map layers
825  if layer == None or layer.active == False:
826  continue
827 
828  # render if there is no mapfile
829  if force or \
830  layer.force_render or \
831  layer.mapfile == None or \
832  (not os.path.isfile(layer.mapfile) or not os.path.getsize(layer.mapfile)):
833  if not layer.Render():
834  continue
835 
836  if mapWindow:
837  # update progress bar
838  ### wx.SafeYield(mapWindow)
839  event = wxUpdateProgressBar(value = ilayer)
840  wx.PostEvent(mapWindow, event)
841 
842  # add image to compositing list
843  if layer.type != "overlay":
844  maps.append(layer.mapfile)
845  masks.append(layer.maskfile)
846  opacities.append(str(layer.opacity))
847 
848  Debug.msg (3, "Map.Render() type=%s, layer=%s " % (layer.type, layer.name))
849  ilayer += 1
850 
851  def Render(self, force = False, mapWindow = None, windres = False):
852  """!Creates final image composite
853 
854  This function can conditionaly use high-level tools, which
855  should be avaliable in wxPython library
856 
857  @param force force rendering
858  @param reference for MapFrame instance (for progress bar)
859  @param windres use region resolution (True) otherwise display resolution
860 
861  @return name of file with rendered image or None
862  """
863  maps = []
864  masks = []
865  opacities = []
866 
867  wx.BeginBusyCursor()
868  # use external gisrc if defined
869  gisrc_orig = os.getenv("GISRC")
870  if self.gisrc:
871  os.environ["GISRC"] = self.gisrc
872 
873  tmp_region = os.getenv("GRASS_REGION")
874  os.environ["GRASS_REGION"] = self.SetRegion(windres)
875  os.environ["GRASS_WIDTH"] = str(self.width)
876  os.environ["GRASS_HEIGHT"] = str(self.height)
877  if UserSettings.Get(group='display', key='driver', subkey='type') == 'cairo':
878  os.environ["GRASS_AUTO_WRITE"] = "TRUE"
879  if "GRASS_RENDER_IMMEDIATE" in os.environ:
880  del os.environ["GRASS_RENDER_IMMEDIATE"]
881  os.environ["GRASS_RENDER_IMMEDIATE"] = "TRUE"
882  else:
883  os.environ["GRASS_PNG_AUTO_WRITE"] = "TRUE"
884  os.environ["GRASS_PNG_READ"] = "FALSE"
885  os.environ["GRASS_COMPRESSION"] = "0"
886  os.environ["GRASS_TRUECOLOR"] = "TRUE"
887  os.environ["GRASS_RENDER_IMMEDIATE"] = "TRUE"
888 
889  self._renderLayers(force, mapWindow, maps, masks, opacities)
890 
891  # ugly hack for MSYS
892  if not subprocess.mswindows:
893  mapstr = ",".join(maps)
894  maskstr = ",".join(masks)
895  mapoutstr = self.mapfile
896  else:
897  mapstr = ""
898  for item in maps:
899  mapstr += item.replace('\\', '/')
900  mapstr = mapstr.rstrip(',')
901  maskstr = ""
902  for item in masks:
903  maskstr += item.replace('\\', '/')
904  maskstr = maskstr.rstrip(',')
905  mapoutstr = self.mapfile.replace('\\', '/')
906 
907  # compose command
908  bgcolor = ':'.join(map(str, UserSettings.Get(group = 'display', key = 'bgcolor',
909  subkey = 'color')))
910 
911  complist = ["g.pnmcomp",
912  "in=%s" % ",".join(maps),
913  "mask=%s" % ",".join(masks),
914  "opacity=%s" % ",".join(opacities),
915  "background=%s" % bgcolor,
916  "width=%s" % str(self.width),
917  "height=%s" % str(self.height),
918  "output=%s" % self.mapfile]
919 
920  # render overlays
921  if tmp_region:
922  os.environ["GRASS_REGION"] = tmp_region
923  else:
924  del os.environ["GRASS_REGION"]
925 
926  if maps:
927  # run g.pngcomp to get composite image
928  ret = gcmd.RunCommand('g.pnmcomp',
929  input = '%s' % ",".join(maps),
930  mask = '%s' % ",".join(masks),
931  opacity = '%s' % ",".join(opacities),
932  background = bgcolor,
933  width = self.width,
934  height = self.height,
935  output = self.mapfile)
936 
937  if ret != 0:
938  print >> sys.stderr, _("ERROR: Rendering failed")
939  wx.EndBusyCursor()
940  return None
941 
942  Debug.msg (3, "Map.Render() force=%s file=%s" % (force, self.mapfile))
943 
944  # back to original gisrc
945  if self.gisrc:
946  os.environ["GISRC"] = gisrc_orig
947 
948  wx.EndBusyCursor()
949  if not maps:
950  return None
951 
952  return self.mapfile
953 
954  def AddLayer(self, type, command, name = None,
955  l_active = True, l_hidden = False, l_opacity = 1.0, l_render = False,
956  pos = -1):
957  """!Adds generic map layer to list of layers
958 
959  @param type layer type ('raster', 'vector', etc.)
960  @param command GRASS command given as list
961  @param name layer name
962  @param l_active layer render only if True
963  @param l_hidden layer not displayed in layer tree if True
964  @param l_opacity opacity level range from 0(transparent) - 1(not transparent)
965  @param l_render render an image if True
966  @param pos position in layer list (-1 for append)
967 
968  @return new layer on success
969  @return None on failure
970  """
971  wx.BeginBusyCursor()
972  # l_opacity must be <0;1>
973  if l_opacity < 0: l_opacity = 0
974  elif l_opacity > 1: l_opacity = 1
975  layer = MapLayer(type = type, name = name, cmd = command,
976  active = l_active, hidden = l_hidden, opacity = l_opacity)
977 
978  # add maplayer to the list of layers
979  if pos > -1:
980  self.layers.insert(pos, layer)
981  else:
982  self.layers.append(layer)
983 
984  Debug.msg (3, "Map.AddLayer(): layer=%s" % layer.name)
985  if l_render:
986  if not layer.Render():
987  raise gcmd.GException(_("Unable to render map layer <%s>.") % name)
988 
989  wx.EndBusyCursor()
990 
991  return layer
992 
993  def DeleteLayer(self, layer, overlay = False):
994  """!Removes layer from list of layers
995 
996  @param layer layer instance in layer tree
997  @param overlay delete overlay (use self.DeleteOverlay() instead)
998 
999  @return removed layer on success or None
1000  """
1001  Debug.msg (3, "Map.DeleteLayer(): name=%s" % layer.name)
1002 
1003  if overlay:
1004  list = self.overlays
1005  else:
1006  list = self.layers
1007 
1008  if layer in list:
1009  if layer.mapfile:
1010  base = os.path.split(layer.mapfile)[0]
1011  mapfile = os.path.split(layer.mapfile)[1]
1012  tempbase = mapfile.split('.')[0]
1013  if base == '' or tempbase == '':
1014  return None
1015  basefile = os.path.join(base, tempbase) + r'.*'
1016  for f in glob.glob(basefile):
1017  os.remove(f)
1018  list.remove(layer)
1019 
1020  return layer
1021 
1022  return None
1023 
1024  def ReorderLayers(self, layerList):
1025  """!Reorder list to match layer tree
1026 
1027  @param layerList list of layers
1028  """
1029  self.layers = layerList
1030 
1031  layerNameList = ""
1032  for layer in self.layers:
1033  if layer.name:
1034  layerNameList += layer.name + ','
1035  Debug.msg (4, "Map.ReoderLayers(): layers=%s" % \
1036  (layerNameList))
1037 
1038  def ChangeLayer(self, layer, render = False, **kargs):
1039  """!Change map layer properties
1040 
1041  @param layer map layer instance
1042  @param type layer type ('raster', 'vector', etc.)
1043  @param command GRASS command given as list
1044  @param name layer name
1045  @param active layer render only if True
1046  @param hidden layer not displayed in layer tree if True
1047  @param opacity opacity level range from 0(transparent) - 1(not transparent)
1048  @param render render an image if True
1049  """
1050  Debug.msg (3, "Map.ChangeLayer(): layer=%s" % layer.name)
1051 
1052  if 'type' in kargs:
1053  layer.SetType(kargs['type']) # check type
1054 
1055  if 'command' in kargs:
1056  layer.SetCmd(kargs['command'])
1057 
1058  if 'name' in kargs:
1059  layer.SetName(kargs['name'])
1060 
1061  if 'active' in kargs:
1062  layer.SetActive(kargs['active'])
1063 
1064  if 'hidden' in kargs:
1065  layer.SetHidden(kargs['hidden'])
1066 
1067  if 'opacity' in kargs:
1068  layer.SetOpacity(kargs['opacity'])
1069 
1070  if render and not layer.Render():
1071  raise gcmd.GException(_("Unable to render map layer <%s>.") %
1072  name)
1073 
1074  return layer
1075 
1076  def ChangeOpacity(self, layer, l_opacity):
1077  """!Changes opacity value of map layer
1078 
1079  @param layer layer instance in layer tree
1080  @param l_opacity opacity level <0;1>
1081  """
1082  # l_opacity must be <0;1>
1083  if l_opacity < 0: l_opacity = 0
1084  elif l_opacity > 1: l_opacity = 1
1085 
1086  layer.opacity = l_opacity
1087  Debug.msg (3, "Map.ChangeOpacity(): layer=%s, opacity=%f" % \
1088  (layer.name, layer.opacity))
1089 
1090  def ChangeLayerActive(self, layer, active):
1091  """!Enable or disable map layer
1092 
1093  @param layer layer instance in layer tree
1094  @param active to be rendered (True)
1095  """
1096  layer.active = active
1097 
1098  Debug.msg (3, "Map.ChangeLayerActive(): name='%s' -> active=%d" % \
1099  (layer.name, layer.active))
1100 
1101  def ChangeLayerName (self, layer, name):
1102  """!Change name of the layer
1103 
1104  @param layer layer instance in layer tree
1105  @param name layer name to set up
1106  """
1107  Debug.msg (3, "Map.ChangeLayerName(): from=%s to=%s" % \
1108  (layer.name, name))
1109  layer.name = name
1110 
1111  def RemoveLayer(self, name = None, id = None):
1112  """!Removes layer from layer list
1113 
1114  Layer is defined by name@mapset or id.
1115 
1116  @param name layer name (must be unique)
1117  @param id layer index in layer list
1118 
1119  @return removed layer on success
1120  @return None on failure
1121  """
1122  # delete by name
1123  if name:
1124  retlayer = None
1125  for layer in self.layers:
1126  if layer.name == name:
1127  retlayer = layer
1128  os.remove(layer.mapfile)
1129  os.remove(layer.maskfile)
1130  self.layers.remove(layer)
1131  return layer
1132  # del by id
1133  elif id != None:
1134  return self.layers.pop(id)
1135 
1136  return None
1137 
1138  def GetLayerIndex(self, layer, overlay = False):
1139  """!Get index of layer in layer list.
1140 
1141  @param layer layer instace in layer tree
1142  @param overlay use list of overlays instead
1143 
1144  @return layer index
1145  @return -1 if layer not found
1146  """
1147  if overlay:
1148  list = self.overlay
1149  else:
1150  list = self.layers
1151 
1152  if layer in list:
1153  return list.index(layer)
1154 
1155  return -1
1156 
1157  def AddOverlay(self, id, type, command,
1158  l_active = True, l_hidden = True, l_opacity = 1.0, l_render = False):
1159  """!Adds overlay (grid, barscale, legend, etc.) to list of
1160  overlays
1161 
1162  @param id overlay id (PseudoDC)
1163  @param type overlay type (barscale, legend)
1164  @param command GRASS command to render overlay
1165  @param l_active overlay activated (True) or disabled (False)
1166  @param l_hidden overlay is not shown in layer tree (if True)
1167  @param l_render render an image (if True)
1168 
1169  @return new layer on success
1170  @retutn None on failure
1171  """
1172  Debug.msg (2, "Map.AddOverlay(): cmd=%s, render=%d" % (command, l_render))
1173  overlay = Overlay(id = id, type = type, cmd = command,
1174  active = l_active, hidden = l_hidden, opacity = l_opacity)
1175 
1176  # add maplayer to the list of layers
1177  self.overlays.append(overlay)
1178 
1179  if l_render and command != '' and not overlay.Render():
1180  raise gcmd.GException(_("Unable to render overlay <%s>.") %
1181  name)
1182 
1183  return self.overlays[-1]
1184 
1185  def ChangeOverlay(self, id, render = False, **kargs):
1186  """!Change overlay properities
1187 
1188  Add new overlay if overlay with 'id' doesn't exist.
1189 
1190  @param id overlay id (PseudoDC)
1191  @param type overlay type (barscale, legend)
1192  @param command GRASS command to render overlay
1193  @param l_active overlay activated (True) or disabled (False)
1194  @param l_hidden overlay is not shown in layer tree (if True)
1195  @param l_render render an image (if True)
1196 
1197  @return new layer on success
1198  """
1199  overlay = self.GetOverlay(id, list = False)
1200  if overlay is None:
1201  overlay = Overlay(id, type = None, cmd = None)
1202 
1203  if 'type' in kargs:
1204  overlay.SetName(kargs['type']) # type -> overlay
1205 
1206  if 'command' in kargs:
1207  overlay.SetCmd(kargs['command'])
1208 
1209  if 'active' in kargs:
1210  overlay.SetActive(kargs['active'])
1211 
1212  if 'hidden' in kargs:
1213  overlay.SetHidden(kargs['hidden'])
1214 
1215  if 'opacity' in kargs:
1216  overlay.SetOpacity(kargs['opacity'])
1217 
1218  if render and command != [] and not overlay.Render():
1219  raise gcmd.GException(_("Unable to render overlay <%s>.") %
1220  name)
1221 
1222  return overlay
1223 
1224  def GetOverlay(self, id, list = False):
1225  """!Return overlay(s) with 'id'
1226 
1227  @param id overlay id
1228  @param list return list of overlays of True
1229  otherwise suppose 'id' to be unique
1230 
1231  @return list of overlays (list=True)
1232  @return overlay (list=False)
1233  @retur None (list=False) if no overlay or more overlays found
1234  """
1235  ovl = []
1236  for overlay in self.overlays:
1237  if overlay.id == id:
1238  ovl.append(overlay)
1239 
1240  if not list:
1241  if len(ovl) != 1:
1242  return None
1243  else:
1244  return ovl[0]
1245 
1246  return ovl
1247 
1248  def DeleteOverlay(self, overlay):
1249  """!Delete overlay
1250 
1251  @param id overlay id
1252 
1253  @return removed overlay on success or None
1254  """
1255  return self.DeleteLayer(overlay, overlay = True)
1256 
1257  def Clean(self):
1258  """!Clean layer stack - go trough all layers and remove them
1259  from layer list.
1260 
1261  Removes also l_mapfile and l_maskfile
1262 
1263  @return False on failure
1264  @return True on success
1265  """
1266  try:
1267  dir = os.path.dirname(self.mapfile)
1268  base = os.path.basename(self.mapfile).split('.')[0]
1269  removepath = os.path.join(dir,base)+r'*'
1270  for f in glob.glob(removepath):
1271  os.remove(f)
1272  for layer in self.layers:
1273  if layer.mapfile:
1274  dir = os.path.dirname(layer.mapfile)
1275  base = os.path.basename(layer.mapfile).split('.')[0]
1276  removepath = os.path.join(dir,base)+r'*'
1277  for f in glob.glob(removepath):
1278  os.remove(f)
1279  self.layers.remove(layer)
1280 
1281  for overlay in self.overlays:
1282  if overlay.mapfile:
1283  dir = os.path.dirname(overlay.mapfile)
1284  base = os.path.basename(overlay.mapfile).split('.')[0]
1285  removepath = os.path.join(dir,base)+r'*'
1286  for f in glob.glob(removepath):
1287  os.remove(f)
1288  self.overlays.remove(overlay)
1289  except:
1290  return False
1291 
1292  return True
1293 
1295  """!Reverse list of layers"""
1296  return self.layers.reverse()
1297 
1298 if __name__ == "__main__":
1299  """!Test of Display class.
1300  Usage: display=Render()
1301  """
1302  import gettext
1303  gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
1304 
1305  print "Initializing..."
1306  grass.run_command("g.region", flags = "d")
1307 
1308  map = Map()
1309  map.width = 640
1310  map.height = 480
1311 
1312  map.AddLayer(type = "raster",
1313  name = "elevation.dem",
1314  command = ["d.rast", "elevation.dem@PERMANENT", "catlist=1000-1500", "-i"],
1315  l_opacity = .7)
1316 
1317  map.AddLayer(type = "vector",
1318  name = "streams",
1319  command = ["d.vect", "streams@PERMANENT", "color=red", "width=3", "type=line"])
1320 
1321  image = map.Render(force = True)
1322 
1323  if image:
1324  os.system("display %s" % image)
1325