GRASS Programmer's Manual  6.5.svn(2014)-r66266
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
psmap/utils.py
Go to the documentation of this file.
1 """!
2 @package psmap.utils
3 
4 @brief utilities for wxpsmap (classes, functions)
5 
6 Classes:
7  - utils::Rect2D
8  - utils::Rect2DPP
9  - utils::Rect2DPS
10  - utils::UnitConversion
11 
12 (C) 2012 by Anna Kratochvilova, and the GRASS Development Team
13 This program is free software under the GNU General Public License
14 (>=v2). Read the file COPYING that comes with GRASS for details.
15 
16 @author Anna Kratochvilova <kratochanna gmail.com>
17 """
18 import os
19 import wx
20 import string
21 from math import ceil, floor, sin, cos, pi
22 
23 try:
24  from PIL import Image as PILImage
25  havePILImage = True
26 except ImportError:
27  havePILImage = False
28 
29 import grass.script as grass
30 from core.gcmd import RunCommand
31 
32 class Rect2D(wx.Rect2D):
33  """!Class representing rectangle with floating point values.
34 
35  Overrides wx.Rect2D to unify Rect access methods, which are
36  different (e.g. wx.Rect.GetTopLeft() x wx.Rect2D.GetLeftTop()).
37  More methods can be added depending on needs.
38  """
39  def __init__(self, x = 0, y = 0, width = 0, height = 0):
40  wx.Rect2D.__init__(self, x = x, y = y, w = width, h = height)
41 
42  def GetX(self):
43  return self.x
44 
45  def GetY(self):
46  return self.y
47 
48  def GetWidth(self):
49  return self.width
50 
51  def SetWidth(self, width):
52  self.width = width
53 
54  def GetHeight(self):
55  return self.height
56 
57  def SetHeight(self, height):
58  self.height = height
59 
61  """!Rectangle specified by 2 points (with floating point values).
62 
63  @see Rect2D, Rect2DPS
64  """
65  def __init__(self, topLeft = wx.Point2D(), bottomRight = wx.Point2D()):
66  Rect2D.__init__(self, x = 0, y = 0, width = 0, height = 0)
67 
68  x1, y1 = topLeft[0], topLeft[1]
69  x2, y2 = bottomRight[0], bottomRight[1]
70 
71  self.SetLeft(min(x1, x2))
72  self.SetTop(min(y1, y2))
73  self.SetRight(max(x1, x2))
74  self.SetBottom(max(y1, y2))
75 
77  """!Rectangle specified by point and size (with floating point values).
78 
79  @see Rect2D, Rect2DPP
80  """
81  def __init__(self, pos = wx.Point2D(), size = (0, 0)):
82  Rect2D.__init__(self, x = pos[0], y = pos[1], width = size[0], height = size[1])
83 
85  """! Class for converting units"""
86  def __init__(self, parent = None):
87  self.parent = parent
88  if self.parent:
89  ppi = wx.ClientDC(self.parent).GetPPI()
90  else:
91  ppi = (72, 72)
92  self._unitsPage = { 'inch' : {'val': 1.0, 'tr' : _("inch")},
93  'point' : {'val': 72.0, 'tr' : _("point")},
94  'centimeter' : {'val': 2.54, 'tr' : _("centimeter")},
95  'millimeter' : {'val': 25.4, 'tr' : _("millimeter")}}
96  self._unitsMap = { 'meters' : {'val': 0.0254, 'tr' : _("meters")},
97  'kilometers' : {'val': 2.54e-5, 'tr' : _("kilometers")},
98  'feet' : {'val': 1./12, 'tr' : _("feet")},
99  'miles' : {'val': 1./63360, 'tr' : _("miles")},
100  'nautical miles': {'val': 1/72913.386, 'tr' : _("nautical miles")}}
101 
102  self._units = { 'pixel' : {'val': ppi[0], 'tr' : _("pixel")},
103  'meter' : {'val': 0.0254, 'tr' : _("meter")},
104  'nautmiles' : {'val': 1/72913.386, 'tr' :_("nautical miles")},
105  'degrees' : {'val': 0.0254 , 'tr' : _("degree")} #like 1 meter, incorrect
106  }
107  self._units.update(self._unitsPage)
108  self._units.update(self._unitsMap)
109 
110  def getPageUnitsNames(self):
111  return sorted(self._unitsPage[unit]['tr'] for unit in self._unitsPage.keys())
112 
113  def getMapUnitsNames(self):
114  return sorted(self._unitsMap[unit]['tr'] for unit in self._unitsMap.keys())
115 
116  def getAllUnits(self):
117  return sorted(self._units.keys())
118 
119  def findUnit(self, name):
120  """!Returns unit by its tr. string"""
121  for unit in self._units.keys():
122  if self._units[unit]['tr'] == name:
123  return unit
124  return None
125 
126  def findName(self, unit):
127  """!Returns tr. string of a unit"""
128  try:
129  return self._units[unit]['tr']
130  except KeyError:
131  return None
132 
133  def convert(self, value, fromUnit = None, toUnit = None):
134  return float(value)/self._units[fromUnit]['val']*self._units[toUnit]['val']
135 
136 def convertRGB(rgb):
137  """!Converts wx.Colour(r,g,b,a) to string 'r:g:b' or named color,
138  or named color/r:g:b string to wx.Colour, depending on input"""
139  # transform a wx.Colour tuple into an r:g:b string
140  if type(rgb) == wx.Colour:
141  for name, color in grass.named_colors.items():
142  if rgb.Red() == int(color[0] * 255) and\
143  rgb.Green() == int(color[1] * 255) and\
144  rgb.Blue() == int(color[2] * 255):
145  return name
146  return str(rgb.Red()) + ':' + str(rgb.Green()) + ':' + str(rgb.Blue())
147  # transform a GRASS named color or an r:g:b string into a wx.Colour tuple
148  else:
149  color = (grass.parse_color(rgb)[0]*255,
150  grass.parse_color(rgb)[1]*255,
151  grass.parse_color(rgb)[2]*255)
152  color = wx.Colour(*color)
153  if color.IsOk():
154  return color
155  else:
156  return None
157 
158 
159 def PaperMapCoordinates(mapInstr, x, y, paperToMap = True):
160  """!Converts paper (inch) coordinates <-> map coordinates.
161 
162  @param mapInstr map frame instruction
163  @param x,y paper coords in inches or mapcoords in map units
164  @param paperToMap specify conversion direction
165  """
166  region = grass.region()
167  mapWidthPaper = mapInstr['rect'].GetWidth()
168  mapHeightPaper = mapInstr['rect'].GetHeight()
169  mapWidthEN = region['e'] - region['w']
170  mapHeightEN = region['n'] - region['s']
171 
172  if paperToMap:
173  diffX = x - mapInstr['rect'].GetX()
174  diffY = y - mapInstr['rect'].GetY()
175  diffEW = diffX * mapWidthEN / mapWidthPaper
176  diffNS = diffY * mapHeightEN / mapHeightPaper
177  e = region['w'] + diffEW
178  n = region['n'] - diffNS
179 
180  if projInfo()['proj'] == 'll':
181  return e, n
182  else:
183  return int(e), int(n)
184 
185  else:
186  diffEW = x - region['w']
187  diffNS = region['n'] - y
188  diffX = mapWidthPaper * diffEW / mapWidthEN
189  diffY = mapHeightPaper * diffNS / mapHeightEN
190  xPaper = mapInstr['rect'].GetX() + diffX
191  yPaper = mapInstr['rect'].GetY() + diffY
192 
193  return xPaper, yPaper
194 
195 
196 def AutoAdjust(self, scaleType, rect, map = None, mapType = None, region = None):
197  """!Computes map scale, center and map frame rectangle to fit region (scale is not fixed)"""
198  currRegionDict = {}
199  if scaleType == 0 and map:# automatic, region from raster or vector
200  res = ''
201  if mapType == 'raster':
202  try:
203  res = grass.read_command("g.region", flags = 'gu', rast = map)
204  except grass.ScriptError:
205  pass
206  elif mapType == 'vector':
207  res = grass.read_command("g.region", flags = 'gu', vect = map)
208  currRegionDict = grass.parse_key_val(res, val_type = float)
209  elif scaleType == 1 and region: # saved region
210  res = grass.read_command("g.region", flags = 'gu', region = region)
211  currRegionDict = grass.parse_key_val(res, val_type = float)
212  elif scaleType == 2: # current region
213  env = grass.gisenv()
214  windFilePath = os.path.join(env['GISDBASE'], env['LOCATION_NAME'], env['MAPSET'], 'WIND')
215  try:
216  windFile = open(windFilePath, 'r').read()
217  except IOError:
218  currRegionDict = grass.region()
219  regionDict = grass.parse_key_val(windFile, sep = ':', val_type = float)
220  region = grass.read_command("g.region", flags = 'gu', n = regionDict['north'], s = regionDict['south'],
221  e = regionDict['east'], w = regionDict['west'])
222  currRegionDict = grass.parse_key_val(region, val_type = float)
223 
224  else:
225  return None, None, None
226 
227  if not currRegionDict:
228  return None, None, None
229  rX = rect.x
230  rY = rect.y
231  rW = rect.width
232  rH = rect.height
233  if not hasattr(self, 'unitConv'):
234  self.unitConv = UnitConversion(self)
235  toM = 1
236  if projInfo()['proj'] != 'xy':
237  toM = float(projInfo()['meters'])
238 
239  mW = self.unitConv.convert(value = (currRegionDict['e'] - currRegionDict['w']) * toM, fromUnit = 'meter', toUnit = 'inch')
240  mH = self.unitConv.convert(value = (currRegionDict['n'] - currRegionDict['s']) * toM, fromUnit = 'meter', toUnit = 'inch')
241  scale = min(rW/mW, rH/mH)
242 
243  if rW/rH > mW/mH:
244  x = rX - (rH*(mW/mH) - rW)/2
245  y = rY
246  rWNew = rH*(mW/mH)
247  rHNew = rH
248  else:
249  x = rX
250  y = rY - (rW*(mH/mW) - rH)/2
251  rHNew = rW*(mH/mW)
252  rWNew = rW
253 
254  # center
255  cE = (currRegionDict['w'] + currRegionDict['e'])/2
256  cN = (currRegionDict['n'] + currRegionDict['s'])/2
257  return scale, (cE, cN), Rect2D(x, y, rWNew, rHNew) #inch
258 
259 def SetResolution(dpi, width, height):
260  """!If resolution is too high, lower it
261 
262  @param dpi max DPI
263  @param width map frame width
264  @param height map frame height
265  """
266  region = grass.region()
267  if region['cols'] > width * dpi or region['rows'] > height * dpi:
268  rows = height * dpi
269  cols = width * dpi
270  RunCommand('g.region', rows = rows, cols = cols)
271 
272 def ComputeSetRegion(self, mapDict):
273  """!Computes and sets region from current scale, map center coordinates and map rectangle"""
274 
275  if mapDict['scaleType'] == 3: # fixed scale
276  scale = mapDict['scale']
277 
278  if not hasattr(self, 'unitConv'):
279  self.unitConv = UnitConversion(self)
280 
281  fromM = 1
282  if projInfo()['proj'] != 'xy':
283  fromM = float(projInfo()['meters'])
284  rectHalfInch = (mapDict['rect'].width/2, mapDict['rect'].height/2)
285  rectHalfMeter = (self.unitConv.convert(value = rectHalfInch[0], fromUnit = 'inch', toUnit = 'meter')/ fromM /scale,
286  self.unitConv.convert(value = rectHalfInch[1], fromUnit = 'inch', toUnit = 'meter')/ fromM /scale)
287 
288  centerE = mapDict['center'][0]
289  centerN = mapDict['center'][1]
290 
291  raster = self.instruction.FindInstructionByType('raster')
292  if raster:
293  rasterId = raster.id
294  else:
295  rasterId = None
296 
297  if rasterId:
298  RunCommand('g.region', n = ceil(centerN + rectHalfMeter[1]),
299  s = floor(centerN - rectHalfMeter[1]),
300  e = ceil(centerE + rectHalfMeter[0]),
301  w = floor(centerE - rectHalfMeter[0]),
302  rast = self.instruction[rasterId]['raster'])
303  else:
304  RunCommand('g.region', n = ceil(centerN + rectHalfMeter[1]),
305  s = floor(centerN - rectHalfMeter[1]),
306  e = ceil(centerE + rectHalfMeter[0]),
307  w = floor(centerE - rectHalfMeter[0]))
308 
309 def projInfo():
310  """!Return region projection and map units information,
311  taken from render.py"""
312 
313  projinfo = dict()
314 
315  ret = RunCommand('g.proj', read = True, flags = 'p')
316 
317  if not ret:
318  return projinfo
319 
320  for line in ret.splitlines():
321  if ':' in line:
322  key, val = line.split(':')
323  projinfo[key.strip()] = val.strip()
324  elif "XY location (unprojected)" in line:
325  projinfo['proj'] = 'xy'
326  projinfo['units'] = ''
327  break
328 
329  return projinfo
330 
331 def GetMapBounds(filename, portrait = True):
332  """!Run ps.map -b to get information about map bounding box
333 
334  @param filename psmap input file
335  @param portrait page orientation"""
336  orient = ''
337  if not portrait:
338  orient = 'r'
339  try:
340  bb = map(float, grass.read_command('ps.map',
341  flags = 'b' + orient,
342  quiet = True,
343  input = filename).strip().split('=')[1].split(','))
344  except (grass.ScriptError, IndexError):
345  GError(message = _("Unable to run `ps.map -b`"))
346  return None
347  return Rect2D(bb[0], bb[3], bb[2] - bb[0], bb[1] - bb[3])
348 
349 def getRasterType(map):
350  """!Returns type of raster map (CELL, FCELL, DCELL)"""
351  if map is None:
352  map = ''
353  file = grass.find_file(name = map, element = 'cell')
354  if file['file']:
355  rasterType = grass.raster_info(map)['datatype']
356  return rasterType
357  else:
358  return None
359 
360 def PilImageToWxImage(pilImage, copyAlpha = True):
361  """!Convert PIL image to wx.Image
362 
363  Based on http://wiki.wxpython.org/WorkingWithImages
364  """
365  hasAlpha = pilImage.mode[-1] == 'A'
366  if copyAlpha and hasAlpha : # Make sure there is an alpha layer copy.
367  wxImage = wx.EmptyImage( *pilImage.size )
368  pilImageCopyRGBA = pilImage.copy()
369  pilImageCopyRGB = pilImageCopyRGBA.convert('RGB') # RGBA --> RGB
370  pilImageRgbData = pilImageCopyRGB.tostring()
371  wxImage.SetData(pilImageRgbData)
372  wxImage.SetAlphaData(pilImageCopyRGBA.tostring()[3::4]) # Create layer and insert alpha values.
373 
374  else : # The resulting image will not have alpha.
375  wxImage = wx.EmptyImage(*pilImage.size)
376  pilImageCopy = pilImage.copy()
377  pilImageCopyRGB = pilImageCopy.convert('RGB') # Discard any alpha from the PIL image.
378  pilImageRgbData = pilImageCopyRGB.tostring()
379  wxImage.SetData(pilImageRgbData)
380 
381  return wxImage
382 
383 def BBoxAfterRotation(w, h, angle):
384  """!Compute bounding box or rotated rectangle
385 
386  @param w rectangle width
387  @param h rectangle height
388  @param angle angle (0, 360) in degrees
389  """
390  angleRad = angle / 180. * pi
391  ct = cos(angleRad)
392  st = sin(angleRad)
393 
394  hct = h * ct
395  wct = w * ct
396  hst = h * st
397  wst = w * st
398  y = x = 0
399 
400  if 0 < angle <= 90:
401  y_min = y
402  y_max = y + hct + wst
403  x_min = x - hst
404  x_max = x + wct
405  elif 90 < angle <= 180:
406  y_min = y + hct
407  y_max = y + wst
408  x_min = x - hst + wct
409  x_max = x
410  elif 180 < angle <= 270:
411  y_min = y + wst + hct
412  y_max = y
413  x_min = x + wct
414  x_max = x - hst
415  elif 270 < angle <= 360:
416  y_min = y + wst
417  y_max = y + hct
418  x_min = x
419  x_max = x + wct - hst
420 
421  width = int(ceil(abs(x_max) + abs(x_min)))
422  height = int(ceil(abs(y_max) + abs(y_min)))
423  return width, height
424 
425 # hack for Windows, loading EPS works only on Unix
426 # these functions are taken from EpsImagePlugin.py
428  # Load EPS via Ghostscript
429  if not self.tile:
430  return
431  self.im = GhostscriptForWindows(self.tile, self.size, self.fp)
432  self.mode = self.im.mode
433  self.size = self.im.size
434  self.tile = []
435 
436 def GhostscriptForWindows(tile, size, fp):
437  """Render an image using Ghostscript (Windows only)"""
438  # Unpack decoder tile
439  decoder, tile, offset, data = tile[0]
440  length, bbox = data
441 
442  import tempfile, os
443 
444  file = tempfile.mkstemp()[1]
445 
446  # Build ghostscript command - for Windows
447  command = ["gswin32c",
448  "-q", # quite mode
449  "-g%dx%d" % size, # set output geometry (pixels)
450  "-dNOPAUSE -dSAFER", # don't pause between pages, safe mode
451  "-sDEVICE=ppmraw", # ppm driver
452  "-sOutputFile=%s" % file # output file
453  ]
454 
455  command = string.join(command)
456 
457  # push data through ghostscript
458  try:
459  gs = os.popen(command, "w")
460  # adjust for image origin
461  if bbox[0] != 0 or bbox[1] != 0:
462  gs.write("%d %d translate\n" % (-bbox[0], -bbox[1]))
463  fp.seek(offset)
464  while length > 0:
465  s = fp.read(8192)
466  if not s:
467  break
468  length = length - len(s)
469  gs.write(s)
470  status = gs.close()
471  if status:
472  raise IOError("gs failed (status %d)" % status)
473  im = PILImage.core.open_ppm(file)
474 
475  finally:
476  try: os.unlink(file)
477  except: pass
478 
479  return im
def convertRGB
Converts wx.Colour(r,g,b,a) to string &#39;r:g:b&#39; or named color, or named color/r:g:b string to wx...
Definition: psmap/utils.py:136
def loadPSForWindows
Definition: psmap/utils.py:427
def projInfo
Return region projection and map units information, taken from render.py.
Definition: psmap/utils.py:309
wxGUI command interface
def getRasterType
Returns type of raster map (CELL, FCELL, DCELL)
Definition: psmap/utils.py:349
def PaperMapCoordinates
Converts paper (inch) coordinates &lt;-&gt; map coordinates.
Definition: psmap/utils.py:159
#define min(x, y)
Definition: draw2.c:68
Class representing rectangle with floating point values.
Definition: psmap/utils.py:32
def findName
Returns tr.
Definition: psmap/utils.py:126
def SetResolution
If resolution is too high, lower it.
Definition: psmap/utils.py:259
#define max(x, y)
Definition: draw2.c:69
def AutoAdjust
Computes map scale, center and map frame rectangle to fit region (scale is not fixed) ...
Definition: psmap/utils.py:196
Rectangle specified by point and size (with floating point values).
Definition: psmap/utils.py:76
def split
Platform spefic shlex.split.
Definition: core/utils.py:37
def ComputeSetRegion
Computes and sets region from current scale, map center coordinates and map rectangle.
Definition: psmap/utils.py:272
def BBoxAfterRotation
Compute bounding box or rotated rectangle.
Definition: psmap/utils.py:383
Rectangle specified by 2 points (with floating point values).
Definition: psmap/utils.py:60
Class for converting units.
Definition: psmap/utils.py:84
def PilImageToWxImage
Convert PIL image to wx.Image.
Definition: psmap/utils.py:360
def GhostscriptForWindows
Definition: psmap/utils.py:436
def GetMapBounds
Run ps.map -b to get information about map bounding box.
Definition: psmap/utils.py:331
def findUnit
Returns unit by its tr.
Definition: psmap/utils.py:119
def RunCommand
Run GRASS command.
Definition: gcmd.py:625