GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
utils.py
Go to the documentation of this file.
1 """!
2 @package utils.py
3 
4 @brief Misc utilities for wxGUI
5 
6 (C) 2007-2009, 2011 by the GRASS Development Team
7 
8 This program is free software under the GNU General Public License
9 (>=v2). Read the file COPYING that comes with GRASS for details.
10 
11 @author Martin Landa <landa.martin gmail.com>
12 @author Jachym Cepicky
13 """
14 
15 import os
16 import sys
17 import platform
18 import string
19 import glob
20 import locale
21 import shlex
22 
23 import globalvar
24 sys.path.append(os.path.join(globalvar.ETCDIR, "python"))
25 
26 from grass.script import core as grass
27 from grass.script import task as gtask
28 
29 import gcmd
30 from debug import Debug
31 
33  """!Remove redundant whitespace from a string"""
34  return string.join(string.split(text), ' ')
35 
36 def split(s):
37  """!Platform spefic shlex.split"""
38  if sys.version_info >= (2, 6):
39  return shlex.split(s, posix = (sys.platform != "win32"))
40  elif sys.platform == "win32":
41  return shlex.split(s.replace('\\', r'\\'))
42  else:
43  return shlex.split(s)
44 
45 def GetTempfile(pref=None):
46  """!Creates GRASS temporary file using defined prefix.
47 
48  @todo Fix path on MS Windows/MSYS
49 
50  @param pref prefer the given path
51 
52  @return Path to file name (string) or None
53  """
54  import gcmd
55 
56  ret = gcmd.RunCommand('g.tempfile',
57  read = True,
58  pid = os.getpid())
59 
60  tempfile = ret.splitlines()[0].strip()
61 
62  # FIXME
63  # ugly hack for MSYS (MS Windows)
64  if platform.system() == 'Windows':
65  tempfile = tempfile.replace("/", "\\")
66  try:
67  path, file = os.path.split(tempfile)
68  if pref:
69  return os.path.join(pref, file)
70  else:
71  return tempfile
72  except:
73  return None
74 
75 def GetLayerNameFromCmd(dcmd, fullyQualified = False, param = None,
76  layerType = None):
77  """!Get map name from GRASS command
78 
79  Parameter dcmd can be modified when first parameter is not
80  defined.
81 
82  @param dcmd GRASS command (given as list)
83  @param fullyQualified change map name to be fully qualified
84  @param param params directory
85  @param layerType check also layer type ('raster', 'vector', '3d-raster', ...)
86 
87  @return tuple (name, found)
88  """
89  mapname = ''
90  found = True
91 
92  if len(dcmd) < 1:
93  return mapname, False
94 
95  if 'd.grid' == dcmd[0]:
96  mapname = 'grid'
97  elif 'd.geodesic' in dcmd[0]:
98  mapname = 'geodesic'
99  elif 'd.rhumbline' in dcmd[0]:
100  mapname = 'rhumb'
101  elif 'labels=' in dcmd[0]:
102  mapname = dcmd[idx].split('=')[1] + ' labels'
103  else:
104  params = list()
105  for idx in range(len(dcmd)):
106  try:
107  p, v = dcmd[idx].split('=', 1)
108  except ValueError:
109  continue
110 
111  if p == param:
112  params = [(idx, p, v)]
113  break
114 
115  if p in ('map', 'input',
116  'red', 'blue', 'green',
117  'h_map', 's_map', 'i_map',
118  'reliefmap', 'labels'):
119  params.append((idx, p, v))
120 
121  if len(params) < 1:
122  if len(dcmd) > 1 and '=' not in dcmd[1]:
123  task = gtask.parse_interface(dcmd[0])
124  p = task.get_options()['params'][0].get('name', '')
125  params.append((1, p, dcmd[1]))
126  else:
127  return mapname, False
128 
129  mapname = params[0][2]
130  mapset = ''
131  if fullyQualified and '@' not in mapname:
132  if layerType in ('raster', 'vector', '3d-raster', 'rgb', 'his'):
133  try:
134  if layerType in ('raster', 'rgb', 'his'):
135  findType = 'cell'
136  else:
137  findType = layerType
138  mapset = grass.find_file(mapname, element = findType)['mapset']
139  except AttributeError, e: # not found
140  return '', False
141  if not mapset:
142  found = False
143  else:
144  mapset = grass.gisenv()['MAPSET']
145 
146  # update dcmd
147  for i, p, v in params:
148  if p:
149  dcmd[i] = p + '=' + v
150  else:
151  dcmd[i] = v
152  if mapset:
153  dcmd[i] += '@' + mapset
154 
155  maps = list()
156  for i, p, v in params:
157  if not p:
158  maps.append(v)
159  else:
160  maps.append(dcmd[i].split('=', 1)[1])
161  mapname = '\n'.join(maps)
162 
163  return mapname, found
164 
166  """!Make layer name SQL compliant, based on G_str_to_sql()
167 
168  @todo: Better use directly GRASS Python SWIG...
169  """
170  retName = str(name).strip()
171 
172  # check if name is fully qualified
173  if '@' in retName:
174  retName, mapset = retName.split('@')
175  else:
176  mapset = None
177 
178  cIdx = 0
179  retNameList = list(retName)
180  for c in retNameList:
181  if not (c >= 'A' and c <= 'Z') and \
182  not (c >= 'a' and c <= 'z') and \
183  not (c >= '0' and c <= '9'):
184  retNameList[cIdx] = '_'
185  cIdx += 1
186  retName = ''.join(retNameList)
187 
188  if not (retName[0] >= 'A' and retName[0] <= 'Z') and \
189  not (retName[0] >= 'a' and retName[0] <= 'z'):
190  retName = 'x' + retName[1:]
191 
192  if mapset:
193  retName = retName + '@' + mapset
194 
195  return retName
196 
198  """!Convert list of category number to range(s)
199 
200  Used for example for d.vect cats=[range]
201 
202  @param cats category list
203 
204  @return category range string
205  @return '' on error
206  """
207 
208  catstr = ''
209 
210  try:
211  cats = map(int, cats)
212  except:
213  return catstr
214 
215  i = 0
216  while i < len(cats):
217  next = 0
218  j = i + 1
219  while j < len(cats):
220  if cats[i + next] == cats[j] - 1:
221  next += 1
222  else:
223  break
224  j += 1
225 
226  if next > 1:
227  catstr += '%d-%d,' % (cats[i], cats[i + next])
228  i += next + 1
229  else:
230  catstr += '%d,' % (cats[i])
231  i += 1
232 
233  return catstr.strip(',')
234 
235 def ListOfMapsets(get = 'ordered'):
236  """!Get list of available/accessible mapsets
237 
238  @param get method ('all', 'accessible', 'ordered')
239 
240  @return list of mapsets
241  @return None on error
242  """
243  mapsets = []
244 
245  if get == 'all' or get == 'ordered':
246  ret = gcmd.RunCommand('g.mapsets',
247  read = True,
248  quiet = True,
249  flags = 'l',
250  fs = ';')
251 
252  if ret:
253  mapsets = ret.replace('\n', '').strip().split(';')
254  ListSortLower(mapsets)
255  else:
256  return None
257 
258  if get == 'accessible' or get == 'ordered':
259  ret = gcmd.RunCommand('g.mapsets',
260  read = True,
261  quiet = True,
262  flags = 'p',
263  fs = ';')
264  if ret:
265  if get == 'accessible':
266  mapsets = ret.replace('\n', '').strip().split(';')
267  else:
268  mapsets_accessible = ret.replace('\n', '').strip().split(';')
269  for mapset in mapsets_accessible:
270  mapsets.remove(mapset)
271  mapsets = mapsets_accessible + mapsets
272  else:
273  return None
274 
275  return mapsets
276 
277 def ListSortLower(list):
278  """!Sort list items (not case-sensitive)"""
279  list.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
280 
281 def GetVectorNumberOfLayers(vector, parent = None):
282  """!Get list of vector layers
283 
284  @param vector name of vector map
285  @param parent parent window (to show dialog) or None
286  """
287  layers = []
288  if not vector:
289  return layers
290 
291  fullname = grass.find_file(name = vector, element = 'vector')['fullname']
292  if not fullname:
293  Debug.msg(5, "utils.GetVectorNumberOfLayers(): vector map '%s' not found" % vector)
294  return layers
295 
296  ret, out, msg = gcmd.RunCommand('v.db.connect',
297  getErrorMsg = True,
298  read = True,
299  flags = 'g',
300  map = fullname,
301  fs = ';')
302  if ret != 0:
303  sys.stderr.write(_("Vector map <%(map)s>: %(msg)s\n") % { 'map' : fullname, 'msg' : msg })
304  return layers
305 
306  Debug.msg(1, "GetVectorNumberOfLayers(): ret %s" % ret)
307 
308  for line in out.splitlines():
309  try:
310  layer = line.split(';')[0]
311  if '/' in layer:
312  layer = layer.split('/')[0]
313  layers.append(layer)
314  except IndexError:
315  pass
316 
317  Debug.msg(3, "utils.GetVectorNumberOfLayers(): vector=%s -> %s" % \
318  (fullname, ','.join(layers)))
319 
320  return layers
321 
322 def Deg2DMS(lon, lat, string = True, hemisphere = True, precision = 3):
323  """!Convert deg value to dms string
324 
325  @param lon longitude (x)
326  @param lat latitude (y)
327  @param string True to return string otherwise tuple
328  @param hemisphere print hemisphere
329  @param precision seconds precision
330 
331  @return DMS string or tuple of values
332  @return empty string on error
333  """
334  try:
335  flat = float(lat)
336  flon = float(lon)
337  except ValueError:
338  if string:
339  return ''
340  else:
341  return None
342 
343  # fix longitude
344  while flon > 180.0:
345  flon -= 360.0
346  while flon < -180.0:
347  flon += 360.0
348 
349  # hemisphere
350  if hemisphere:
351  if flat < 0.0:
352  flat = abs(flat)
353  hlat = 'S'
354  else:
355  hlat = 'N'
356 
357  if flon < 0.0:
358  hlon = 'W'
359  flon = abs(flon)
360  else:
361  hlon = 'E'
362  else:
363  flat = abs(flat)
364  flon = abs(flon)
365  hlon = ''
366  hlat = ''
367 
368  slat = __ll_parts(flat, precision = precision)
369  slon = __ll_parts(flon, precision = precision)
370 
371  if string:
372  return slon + hlon + '; ' + slat + hlat
373 
374  return (slon + hlon, slat + hlat)
375 
376 def DMS2Deg(lon, lat):
377  """!Convert dms value to deg
378 
379  @param lon longitude (x)
380  @param lat latitude (y)
381 
382  @return tuple of converted values
383  @return ValueError on error
384  """
385  x = __ll_parts(lon, reverse = True)
386  y = __ll_parts(lat, reverse = True)
387 
388  return (x, y)
389 
390 def __ll_parts(value, reverse = False, precision = 3):
391  """!Converts deg to d:m:s string
392 
393  @param value value to be converted
394  @param reverse True to convert from d:m:s to deg
395  @param precision seconds precision (ignored if reverse is True)
396 
397  @return converted value (string/float)
398  @return ValueError on error (reverse == True)
399  """
400  if not reverse:
401  if value == 0.0:
402  return '%s%.*f' % ('00:00:0', precision, 0.0)
403 
404  d = int(int(value))
405  m = int((value - d) * 60)
406  s = ((value - d) * 60 - m) * 60
407  if m < 0:
408  m = '00'
409  elif m < 10:
410  m = '0' + str(m)
411  else:
412  m = str(m)
413  if s < 0:
414  s = '00.0000'
415  elif s < 10.0:
416  s = '0%.*f' % (precision, s)
417  else:
418  s = '%.*f' % (precision, s)
419 
420  return str(d) + ':' + m + ':' + s
421  else: # -> reverse
422  try:
423  d, m, s = value.split(':')
424  hs = s[-1]
425  s = s[:-1]
426  except ValueError:
427  try:
428  d, m = value.split(':')
429  hs = m[-1]
430  m = m[:-1]
431  s = '0.0'
432  except ValueError:
433  try:
434  d = value
435  hs = d[-1]
436  d = d[:-1]
437  m = '0'
438  s = '0.0'
439  except ValueError:
440  raise ValueError
441 
442  if hs not in ('N', 'S', 'E', 'W'):
443  raise ValueError
444 
445  coef = 1.0
446  if hs in ('S', 'W'):
447  coef = -1.0
448 
449  fm = int(m) / 60.0
450  fs = float(s) / (60 * 60)
451 
452  return coef * (float(d) + fm + fs)
453 
454 def GetCmdString(cmd):
455  """
456  Get GRASS command as string.
457 
458  @param cmd GRASS command given as dictionary
459 
460  @return command string
461  """
462  scmd = ''
463  if not cmd:
464  return scmd
465 
466  scmd = cmd[0]
467 
468  if 'flags' in cmd[1]:
469  for flag in cmd[1]['flags']:
470  scmd += ' -' + flag
471  for flag in ('verbose', 'quiet', 'overwrite'):
472  if flag in cmd[1] and cmd[1][flag] is True:
473  scmd += ' --' + flag
474 
475  for k, v in cmd[1].iteritems():
476  if k in ('flags', 'verbose', 'quiet', 'overwrite'):
477  continue
478  scmd += ' %s=%s' % (k, v)
479 
480  return scmd
481 
482 def CmdToTuple(cmd):
483  """!Convert command list to tuple for gcmd.RunCommand()"""
484  if len(cmd) < 1:
485  return None
486 
487  dcmd = {}
488  for item in cmd[1:]:
489  if '=' in item: # params
490  key, value = item.split('=', 1)
491  dcmd[str(key)] = str(value)
492  elif item[:2] == '--': # long flags
493  flag = item[2:]
494  if flag in ('verbose', 'quiet', 'overwrite'):
495  dcmd[str(flag)] = True
496  else: # -> flags
497  if 'flags' not in dcmd:
498  dcmd['flags'] = ''
499  dcmd['flags'] += item.replace('-', '')
500 
501  return (cmd[0],
502  dcmd)
503 
504 def PathJoin(*args):
505  """!Check path created by os.path.join"""
506  path = os.path.join(*args)
507  if platform.system() == 'Windows' and \
508  '/' in path:
509  return path[1].upper() + ':\\' + path[3:].replace('/', '\\')
510 
511  return path
512 
513 def ReadEpsgCodes(path):
514  """!Read EPSG code from the file
515 
516  @param path full path to the file with EPSG codes
517 
518  @return dictionary of EPSG code
519  @return string on error
520  """
521  epsgCodeDict = dict()
522  try:
523  try:
524  f = open(path, "r")
525  except IOError:
526  return _("failed to open '%s'" % path)
527 
528  i = 0
529  code = None
530  for line in f.readlines():
531  line = line.strip()
532  if len(line) < 1:
533  continue
534 
535  if line[0] == '#':
536  descr = line[1:].strip()
537  elif line[0] == '<':
538  code, params = line.split(" ", 1)
539  try:
540  code = int(code.replace('<', '').replace('>', ''))
541  except ValueError:
542  return e
543 
544  if code is not None:
545  epsgCodeDict[code] = (descr, params)
546  code = None
547  i += 1
548 
549  f.close()
550  except StandardError, e:
551  return e
552 
553  return epsgCodeDict
554 
555 def ReprojectCoordinates(coord, projOut, projIn = None, flags = ''):
556  """!Reproject coordinates
557 
558  @param coord coordinates given as tuple
559  @param projOut output projection
560  @param projIn input projection (use location projection settings)
561 
562  @return reprojected coordinates (returned as tuple)
563  """
564  if not projIn:
565  projIn = gcmd.RunCommand('g.proj',
566  flags = 'jf',
567  read = True)
568  coors = gcmd.RunCommand('m.proj',
569  flags = flags,
570  proj_in = projIn,
571  proj_out = projOut,
572  stdin = '%f|%f' % (coord[0], coord[1]),
573  read = True)
574  if coors:
575  coors = coors.split('\t')
576  e = coors[0]
577  n = coors[1].split(' ')[0].strip()
578  try:
579  proj = projOut.split(' ')[0].split('=')[1]
580  except IndexError:
581  proj = ''
582  if proj in ('ll', 'latlong', 'longlat') and 'd' not in flags:
583  return (proj, (e, n))
584  else:
585  try:
586  return (proj, (float(e), float(n)))
587  except ValueError:
588  return (None, None)
589 
590  return (None, None)
591 
593  """!Get list of GRASS locations in given dbase
594 
595  @param dbase GRASS database path
596 
597  @return list of locations (sorted)
598  """
599  listOfLocations = list()
600 
601  try:
602  for location in glob.glob(os.path.join(dbase, "*")):
603  try:
604  if os.path.join(location, "PERMANENT") in glob.glob(os.path.join(location, "*")):
605  listOfLocations.append(os.path.basename(location))
606  except:
607  pass
608  except UnicodeEncodeError, e:
609  raise e
610 
611  ListSortLower(listOfLocations)
612 
613  return listOfLocations
614 
615 def GetListOfMapsets(dbase, location, selectable = False):
616  """!Get list of mapsets in given GRASS location
617 
618  @param dbase GRASS database path
619  @param location GRASS location
620  @param selectable True to get list of selectable mapsets, otherwise all
621 
622  @return list of mapsets - sorted (PERMANENT first)
623  """
624  listOfMapsets = list()
625 
626  if selectable:
627  ret = gcmd.RunCommand('g.mapset',
628  read = True,
629  flags = 'l',
630  location = location,
631  gisdbase = dbase)
632 
633  if not ret:
634  return listOfMapsets
635 
636  for line in ret.rstrip().splitlines():
637  listOfMapsets += line.split(' ')
638  else:
639  for mapset in glob.glob(os.path.join(dbase, location, "*")):
640  if os.path.isdir(mapset) and \
641  os.path.isfile(os.path.join(dbase, location, mapset, "WIND")):
642  listOfMapsets.append(os.path.basename(mapset))
643 
644  ListSortLower(listOfMapsets)
645  return listOfMapsets
646 
648  """!Get list of color tables"""
649  ret = gcmd.RunCommand('r.colors',
650  read = True,
651  flags = 'l')
652  if not ret:
653  return list()
654 
655  return ret.splitlines()
656 
657 def DecodeString(string):
658  """!Decode string using system encoding
659 
660  @param string string to be decoded
661 
662  @return decoded string
663  """
664  if not string:
665  return string
666 
667  enc = locale.getdefaultlocale()[1]
668  if enc:
669  Debug.msg(5, "DecodeString(): enc=%s" % enc)
670  return string.decode(enc)
671 
672  return string
673 
674 def EncodeString(string):
675  """!Return encoded string using system locales
676 
677  @param string string to be encoded
678 
679  @return encoded string
680  """
681  if not string:
682  return string
683  enc = locale.getdefaultlocale()[1]
684  if enc:
685  Debug.msg(5, "EncodeString(): enc=%s" % enc)
686  return string.encode(enc)
687 
688  return string
689 
690 def UnicodeString(string):
691  """!Return unicode string
692 
693  @param string string to be converted
694 
695  @return unicode string
696  """
697  if isinstance(string, unicode):
698  return string
699 
700  enc = locale.getdefaultlocale()[1]
701  if enc:
702  return unicode(string, enc)
703 
704  return string
705 
706 def _getGDALFormats():
707  """!Get dictionary of avaialble GDAL drivers"""
708  ret = grass.read_command('r.in.gdal',
709  quiet = True,
710  flags = 'f')
711 
712  return _parseFormats(ret)
713 
714 def _getOGRFormats():
715  """!Get dictionary of avaialble OGR drivers"""
716  ret = grass.read_command('v.in.ogr',
717  quiet = True,
718  flags = 'f')
719 
720  return _parseFormats(ret)
721 
722 def _parseFormats(output):
723  """!Parse r.in.gdal/v.in.ogr -f output"""
724  formats = { 'file' : list(),
725  'database' : list(),
726  'protocol' : list()
727  }
728 
729  if not output:
730  return formats
731 
732  for line in output.splitlines():
733  format = line.strip().rsplit(':', -1)[1].strip()
734  if format in ('Memory', 'Virtual Raster', 'In Memory Raster'):
735  continue
736  if format in ('PostgreSQL', 'SQLite',
737  'ODBC', 'ESRI Personal GeoDatabase',
738  'Rasterlite',
739  'PostGIS WKT Raster driver'):
740  formats['database'].append(format)
741  elif format in ('GeoJSON',
742  'OGC Web Coverage Service',
743  'OGC Web Map Service',
744  'HTTP Fetching Wrapper'):
745  formats['protocol'].append(format)
746  else:
747  formats['file'].append(format)
748 
749  for items in formats.itervalues():
750  items.sort()
751 
752  return formats
753 
754 formats = None
755 
757  """!Get GDAL/OGR formats"""
758  global formats
759  if not formats:
760  formats = {
761  'gdal' : _getGDALFormats(),
762  'ogr' : _getOGRFormats()
763  }
764 
765  return formats
766 
768  """!Get full path to the settings directory
769  """
770  try:
771  verFd = open(os.path.join(globalvar.ETCDIR, "VERSIONNUMBER"))
772  version = int(verFd.readlines()[0].split(' ')[0].split('.')[0])
773  except (IOError, ValueError, TypeError, IndexError), e:
774  sys.exit(_("ERROR: Unable to determine GRASS version. Details: %s") % e)
775 
776  verFd.close()
777 
778  if sys.platform == 'win32':
779  return os.path.join(os.getenv('APPDATA'), '.grass%d' % version)
780 
781  return os.path.join(os.getenv('HOME'), '.grass%d' % version)