GRASS Programmer's Manual  6.4.2(2012)
utils.py
Go to the documentation of this file.
00001 """!
00002 @package utils.py
00003 
00004 @brief Misc utilities for wxGUI
00005 
00006 (C) 2007-2009, 2011 by the GRASS Development Team
00007 
00008 This program is free software under the GNU General Public License
00009 (>=v2). Read the file COPYING that comes with GRASS for details.
00010 
00011 @author Martin Landa <landa.martin gmail.com>
00012 @author Jachym Cepicky
00013 """
00014 
00015 import os
00016 import sys
00017 import platform
00018 import string
00019 import glob
00020 import locale
00021 import shlex
00022 
00023 import globalvar
00024 sys.path.append(os.path.join(globalvar.ETCDIR, "python"))
00025 
00026 from grass.script import core as grass
00027 from grass.script import task as gtask
00028 
00029 import gcmd
00030 from debug import Debug
00031 
00032 def normalize_whitespace(text):
00033     """!Remove redundant whitespace from a string"""
00034     return string.join(string.split(text), ' ')
00035 
00036 def split(s):
00037     """!Platform spefic shlex.split"""
00038     if sys.version_info >= (2, 6):
00039         return shlex.split(s, posix = (sys.platform != "win32"))
00040     elif sys.platform == "win32":
00041         return shlex.split(s.replace('\\', r'\\'))
00042     else:
00043         return shlex.split(s)
00044 
00045 def GetTempfile(pref=None):
00046     """!Creates GRASS temporary file using defined prefix.
00047 
00048     @todo Fix path on MS Windows/MSYS
00049 
00050     @param pref prefer the given path
00051 
00052     @return Path to file name (string) or None
00053     """
00054     import gcmd
00055 
00056     ret = gcmd.RunCommand('g.tempfile',
00057                           read = True,
00058                           pid = os.getpid())
00059 
00060     tempfile = ret.splitlines()[0].strip()
00061 
00062     # FIXME
00063     # ugly hack for MSYS (MS Windows)
00064     if platform.system() == 'Windows':
00065         tempfile = tempfile.replace("/", "\\")
00066     try:
00067         path, file = os.path.split(tempfile)
00068         if pref:
00069             return os.path.join(pref, file)
00070         else:
00071             return tempfile
00072     except:
00073         return None
00074 
00075 def GetLayerNameFromCmd(dcmd, fullyQualified = False, param = None,
00076                         layerType = None):
00077     """!Get map name from GRASS command
00078     
00079     Parameter dcmd can be modified when first parameter is not
00080     defined.
00081     
00082     @param dcmd GRASS command (given as list)
00083     @param fullyQualified change map name to be fully qualified
00084     @param param params directory
00085     @param layerType check also layer type ('raster', 'vector', '3d-raster', ...)
00086     
00087     @return tuple (name, found)
00088     """
00089     mapname = ''
00090     found   = True
00091     
00092     if len(dcmd) < 1:
00093         return mapname, False
00094     
00095     if 'd.grid' == dcmd[0]:
00096         mapname = 'grid'
00097     elif 'd.geodesic' in dcmd[0]:
00098         mapname = 'geodesic'
00099     elif 'd.rhumbline' in dcmd[0]:
00100         mapname = 'rhumb'
00101     elif 'labels=' in dcmd[0]:
00102         mapname = dcmd[idx].split('=')[1] + ' labels'
00103     else:
00104         params = list()
00105         for idx in range(len(dcmd)):
00106             try:
00107                 p, v = dcmd[idx].split('=', 1)
00108             except ValueError:
00109                 continue
00110             
00111             if p == param:
00112                 params = [(idx, p, v)]
00113                 break
00114             
00115             if p in ('map', 'input',
00116                      'red', 'blue', 'green',
00117                      'h_map', 's_map', 'i_map',
00118                      'reliefmap', 'labels'):
00119                 params.append((idx, p, v))
00120         
00121         if len(params) < 1:
00122             if len(dcmd) > 1 and '=' not in dcmd[1]:
00123                 task = gtask.parse_interface(dcmd[0])
00124                 p = task.get_options()['params'][0].get('name', '')
00125                 params.append((1, p, dcmd[1]))
00126             else:
00127                 return mapname, False
00128         
00129         mapname = params[0][2]
00130         mapset = ''
00131         if fullyQualified and '@' not in mapname:
00132             if layerType in ('raster', 'vector', '3d-raster', 'rgb', 'his'):
00133                 try:
00134                     if layerType in ('raster', 'rgb', 'his'):
00135                         findType = 'cell'
00136                     else:
00137                         findType = layerType
00138                     mapset = grass.find_file(mapname, element = findType)['mapset']
00139                 except AttributeError, e: # not found
00140                     return '', False
00141                 if not mapset:
00142                     found = False
00143             else:
00144                 mapset = grass.gisenv()['MAPSET']
00145             
00146             # update dcmd
00147             for i, p, v in params:
00148                 if p:
00149                     dcmd[i] = p + '=' + v
00150                 else:
00151                     dcmd[i] = v
00152                 if mapset:
00153                     dcmd[i] += '@' + mapset
00154         
00155         maps = list()
00156         for i, p, v in params:
00157             if not p:
00158                 maps.append(v)
00159             else:
00160                 maps.append(dcmd[i].split('=', 1)[1])
00161         mapname = '\n'.join(maps)
00162     
00163     return mapname, found
00164 
00165 def GetValidLayerName(name):
00166     """!Make layer name SQL compliant, based on G_str_to_sql()
00167     
00168     @todo: Better use directly GRASS Python SWIG...
00169     """
00170     retName = str(name).strip()
00171     
00172     # check if name is fully qualified
00173     if '@' in retName:
00174         retName, mapset = retName.split('@')
00175     else:
00176         mapset = None
00177     
00178     cIdx = 0
00179     retNameList = list(retName)
00180     for c in retNameList:
00181         if not (c >= 'A' and c <= 'Z') and \
00182                not (c >= 'a' and c <= 'z') and \
00183                not (c >= '0' and c <= '9'):
00184             retNameList[cIdx] = '_'
00185         cIdx += 1
00186     retName = ''.join(retNameList)
00187     
00188     if not (retName[0] >= 'A' and retName[0] <= 'Z') and \
00189            not (retName[0] >= 'a' and retName[0] <= 'z'):
00190         retName = 'x' + retName[1:]
00191 
00192     if mapset:
00193         retName = retName + '@' + mapset
00194         
00195     return retName
00196 
00197 def ListOfCatsToRange(cats):
00198     """!Convert list of category number to range(s)
00199 
00200     Used for example for d.vect cats=[range]
00201 
00202     @param cats category list
00203 
00204     @return category range string
00205     @return '' on error
00206     """
00207 
00208     catstr = ''
00209 
00210     try:
00211         cats = map(int, cats)
00212     except:
00213         return catstr
00214 
00215     i = 0
00216     while i < len(cats):
00217         next = 0
00218         j = i + 1
00219         while j < len(cats):
00220             if cats[i + next] == cats[j] - 1:
00221                 next += 1
00222             else:
00223                 break
00224             j += 1
00225 
00226         if next > 1:
00227             catstr += '%d-%d,' % (cats[i], cats[i + next])
00228             i += next + 1
00229         else:
00230             catstr += '%d,' % (cats[i])
00231             i += 1
00232         
00233     return catstr.strip(',')
00234 
00235 def ListOfMapsets(get = 'ordered'):
00236     """!Get list of available/accessible mapsets
00237 
00238     @param get method ('all', 'accessible', 'ordered')
00239     
00240     @return list of mapsets
00241     @return None on error
00242     """
00243     mapsets = []
00244     
00245     if get == 'all' or get == 'ordered':
00246         ret = gcmd.RunCommand('g.mapsets',
00247                               read = True,
00248                               quiet = True,
00249                               flags = 'l',
00250                               fs = ';')
00251         
00252         if ret:
00253             mapsets = ret.replace('\n', '').strip().split(';')
00254             ListSortLower(mapsets)
00255         else:
00256             return None
00257         
00258     if get == 'accessible' or get == 'ordered':
00259         ret = gcmd.RunCommand('g.mapsets',
00260                               read = True,
00261                               quiet = True,
00262                               flags = 'p',
00263                               fs = ';')
00264         if ret:
00265             if get == 'accessible':
00266                 mapsets = ret.replace('\n', '').strip().split(';')
00267             else:
00268                 mapsets_accessible = ret.replace('\n', '').strip().split(';')
00269                 for mapset in mapsets_accessible:
00270                     mapsets.remove(mapset)
00271                 mapsets = mapsets_accessible + mapsets
00272         else:
00273             return None
00274     
00275     return mapsets
00276 
00277 def ListSortLower(list):
00278     """!Sort list items (not case-sensitive)"""
00279     list.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
00280 
00281 def GetVectorNumberOfLayers(vector, parent = None):
00282     """!Get list of vector layers
00283 
00284     @param vector name of vector map
00285     @param parent parent window (to show dialog) or None
00286     """
00287     layers = []
00288     if not vector:
00289         return layers
00290     
00291     fullname = grass.find_file(name = vector, element = 'vector')['fullname']
00292     if not fullname:
00293         Debug.msg(5, "utils.GetVectorNumberOfLayers(): vector map '%s' not found" % vector)
00294         return layers
00295     
00296     ret, out, msg = gcmd.RunCommand('v.db.connect',
00297                                     getErrorMsg = True,
00298                                     read = True,
00299                                     flags = 'g',
00300                                     map = fullname,
00301                                     fs = ';')
00302     if ret != 0:
00303         sys.stderr.write(_("Vector map <%(map)s>: %(msg)s\n") % { 'map' : fullname, 'msg' : msg })
00304         return layers
00305     
00306     Debug.msg(1, "GetVectorNumberOfLayers(): ret %s" % ret)
00307     
00308     for line in out.splitlines():
00309         try:
00310             layer = line.split(';')[0]
00311             if '/' in layer:
00312                 layer = layer.split('/')[0]
00313             layers.append(layer)
00314         except IndexError:
00315             pass
00316     
00317     Debug.msg(3, "utils.GetVectorNumberOfLayers(): vector=%s -> %s" % \
00318                   (fullname, ','.join(layers)))
00319     
00320     return layers
00321 
00322 def Deg2DMS(lon, lat, string = True, hemisphere = True, precision = 3):
00323     """!Convert deg value to dms string
00324 
00325     @param lon longitude (x)
00326     @param lat latitude (y)
00327     @param string True to return string otherwise tuple
00328     @param hemisphere print hemisphere
00329     @param precision seconds precision
00330     
00331     @return DMS string or tuple of values
00332     @return empty string on error
00333     """
00334     try:
00335         flat = float(lat)
00336         flon = float(lon)
00337     except ValueError:
00338         if string:
00339             return ''
00340         else:
00341             return None
00342 
00343     # fix longitude
00344     while flon > 180.0:
00345         flon -= 360.0
00346     while flon < -180.0:
00347         flon += 360.0
00348 
00349     # hemisphere
00350     if hemisphere:
00351         if flat < 0.0:
00352             flat = abs(flat)
00353             hlat = 'S'
00354         else:
00355             hlat = 'N'
00356 
00357         if flon < 0.0:
00358             hlon = 'W'
00359             flon = abs(flon)
00360         else:
00361             hlon = 'E'
00362     else:
00363         flat = abs(flat)
00364         flon = abs(flon)
00365         hlon = ''
00366         hlat = ''
00367     
00368     slat = __ll_parts(flat, precision = precision)
00369     slon = __ll_parts(flon, precision = precision)
00370 
00371     if string:
00372         return slon + hlon + '; ' + slat + hlat
00373     
00374     return (slon + hlon, slat + hlat)
00375 
00376 def DMS2Deg(lon, lat):
00377     """!Convert dms value to deg
00378 
00379     @param lon longitude (x)
00380     @param lat latitude (y)
00381     
00382     @return tuple of converted values
00383     @return ValueError on error
00384     """
00385     x = __ll_parts(lon, reverse = True)
00386     y = __ll_parts(lat, reverse = True)
00387     
00388     return (x, y)
00389 
00390 def __ll_parts(value, reverse = False, precision = 3):
00391     """!Converts deg to d:m:s string
00392 
00393     @param value value to be converted
00394     @param reverse True to convert from d:m:s to deg
00395     @param precision seconds precision (ignored if reverse is True)
00396     
00397     @return converted value (string/float)
00398     @return ValueError on error (reverse == True)
00399     """
00400     if not reverse:
00401         if value == 0.0:
00402             return '%s%.*f' % ('00:00:0', precision, 0.0)
00403     
00404         d = int(int(value))
00405         m = int((value - d) * 60)
00406         s = ((value - d) * 60 - m) * 60
00407         if m < 0:
00408             m = '00'
00409         elif m < 10:
00410             m = '0' + str(m)
00411         else:
00412             m = str(m)
00413         if s < 0:
00414             s = '00.0000'
00415         elif s < 10.0:
00416             s = '0%.*f' % (precision, s)
00417         else:
00418             s = '%.*f' % (precision, s)
00419         
00420         return str(d) + ':' + m + ':' + s
00421     else: # -> reverse
00422         try:
00423             d, m, s = value.split(':')
00424             hs = s[-1]
00425             s = s[:-1]
00426         except ValueError:
00427             try:
00428                 d, m = value.split(':')
00429                 hs = m[-1]
00430                 m = m[:-1]
00431                 s = '0.0'
00432             except ValueError:
00433                 try:
00434                     d = value
00435                     hs = d[-1]
00436                     d = d[:-1]
00437                     m = '0'
00438                     s = '0.0'
00439                 except ValueError:
00440                     raise ValueError
00441         
00442         if hs not in ('N', 'S', 'E', 'W'):
00443             raise ValueError
00444         
00445         coef = 1.0
00446         if hs in ('S', 'W'):
00447             coef = -1.0
00448         
00449         fm = int(m) / 60.0
00450         fs = float(s) / (60 * 60)
00451         
00452         return coef * (float(d) + fm + fs)
00453     
00454 def GetCmdString(cmd):
00455     """
00456     Get GRASS command as string.
00457     
00458     @param cmd GRASS command given as dictionary
00459     
00460     @return command string
00461     """
00462     scmd = ''
00463     if not cmd:
00464         return scmd
00465     
00466     scmd = cmd[0]
00467     
00468     if 'flags' in cmd[1]:
00469         for flag in cmd[1]['flags']:
00470             scmd += ' -' + flag
00471     for flag in ('verbose', 'quiet', 'overwrite'):
00472         if flag in cmd[1] and cmd[1][flag] is True:
00473             scmd += ' --' + flag
00474     
00475     for k, v in cmd[1].iteritems():
00476         if k in ('flags', 'verbose', 'quiet', 'overwrite'):
00477             continue
00478         scmd += ' %s=%s' % (k, v)
00479             
00480     return scmd
00481 
00482 def CmdToTuple(cmd):
00483     """!Convert command list to tuple for gcmd.RunCommand()"""
00484     if len(cmd) < 1:
00485         return None
00486         
00487     dcmd = {}
00488     for item in cmd[1:]:
00489         if '=' in item: # params
00490             key, value = item.split('=', 1)
00491             dcmd[str(key)] = str(value)
00492         elif item[:2] == '--': # long flags
00493             flag = item[2:]
00494             if flag in ('verbose', 'quiet', 'overwrite'):
00495                 dcmd[str(flag)] = True
00496         else: # -> flags
00497             if 'flags' not in dcmd:
00498                 dcmd['flags'] = ''
00499             dcmd['flags'] += item.replace('-', '')
00500                 
00501     return (cmd[0],
00502             dcmd)
00503 
00504 def PathJoin(*args):
00505     """!Check path created by os.path.join"""
00506     path = os.path.join(*args)
00507     if platform.system() == 'Windows' and \
00508             '/' in path:
00509         return path[1].upper() + ':\\' + path[3:].replace('/', '\\')
00510     
00511     return path
00512 
00513 def ReadEpsgCodes(path):
00514     """!Read EPSG code from the file
00515 
00516     @param path full path to the file with EPSG codes
00517 
00518     @return dictionary of EPSG code
00519     @return string on error
00520     """
00521     epsgCodeDict = dict()
00522     try:
00523         try:
00524             f = open(path, "r")
00525         except IOError:
00526             return _("failed to open '%s'" % path)
00527         
00528         i = 0
00529         code = None
00530         for line in f.readlines():
00531             line = line.strip()
00532             if len(line) < 1:
00533                 continue
00534                 
00535             if line[0] == '#':
00536                 descr = line[1:].strip()
00537             elif line[0] == '<':
00538                 code, params = line.split(" ", 1)
00539                 try:
00540                     code = int(code.replace('<', '').replace('>', ''))
00541                 except ValueError:
00542                     return e
00543             
00544             if code is not None:
00545                 epsgCodeDict[code] = (descr, params)
00546                 code = None
00547             i += 1
00548         
00549         f.close()
00550     except StandardError, e:
00551         return e
00552     
00553     return epsgCodeDict
00554 
00555 def ReprojectCoordinates(coord, projOut, projIn = None, flags = ''):
00556     """!Reproject coordinates
00557 
00558     @param coord coordinates given as tuple
00559     @param projOut output projection
00560     @param projIn input projection (use location projection settings)
00561 
00562     @return reprojected coordinates (returned as tuple)
00563     """
00564     if not projIn:
00565         projIn = gcmd.RunCommand('g.proj',
00566                                  flags = 'jf',
00567                                  read = True)
00568     coors = gcmd.RunCommand('m.proj',
00569                             flags = flags,
00570                             proj_in = projIn,
00571                             proj_out = projOut,
00572                             stdin = '%f|%f' % (coord[0], coord[1]),
00573                             read = True)
00574     if coors:
00575         coors = coors.split('\t')
00576         e = coors[0]
00577         n = coors[1].split(' ')[0].strip()
00578         try:
00579             proj = projOut.split(' ')[0].split('=')[1]
00580         except IndexError:
00581             proj = ''
00582         if proj in ('ll', 'latlong', 'longlat') and 'd' not in flags:
00583             return (proj, (e, n))
00584         else:
00585             try:
00586                 return (proj, (float(e), float(n)))
00587             except ValueError:
00588                 return (None, None)
00589     
00590     return (None, None)
00591 
00592 def GetListOfLocations(dbase):
00593     """!Get list of GRASS locations in given dbase
00594 
00595     @param dbase GRASS database path
00596 
00597     @return list of locations (sorted)
00598     """
00599     listOfLocations = list()
00600 
00601     try:
00602         for location in glob.glob(os.path.join(dbase, "*")):
00603             try:
00604                 if os.path.join(location, "PERMANENT") in glob.glob(os.path.join(location, "*")):
00605                     listOfLocations.append(os.path.basename(location))
00606             except:
00607                 pass
00608     except UnicodeEncodeError, e:
00609         raise e
00610     
00611     ListSortLower(listOfLocations)
00612     
00613     return listOfLocations
00614 
00615 def GetListOfMapsets(dbase, location, selectable = False):
00616     """!Get list of mapsets in given GRASS location
00617 
00618     @param dbase      GRASS database path
00619     @param location   GRASS location
00620     @param selectable True to get list of selectable mapsets, otherwise all
00621 
00622     @return list of mapsets - sorted (PERMANENT first)
00623     """
00624     listOfMapsets = list()
00625     
00626     if selectable:
00627         ret = gcmd.RunCommand('g.mapset',
00628                               read = True,
00629                               flags = 'l',
00630                               location = location,
00631                               gisdbase = dbase)
00632         
00633         if not ret:
00634             return listOfMapsets
00635         
00636         for line in ret.rstrip().splitlines():
00637             listOfMapsets += line.split(' ')
00638     else:
00639         for mapset in glob.glob(os.path.join(dbase, location, "*")):
00640             if os.path.isdir(mapset) and \
00641                     os.path.isfile(os.path.join(dbase, location, mapset, "WIND")):
00642                 listOfMapsets.append(os.path.basename(mapset))
00643     
00644     ListSortLower(listOfMapsets)
00645     return listOfMapsets
00646 
00647 def GetColorTables():
00648     """!Get list of color tables"""
00649     ret = gcmd.RunCommand('r.colors',
00650                           read = True,
00651                           flags = 'l')
00652     if not ret:
00653         return list()
00654     
00655     return ret.splitlines()
00656 
00657 def DecodeString(string):
00658     """!Decode string using system encoding
00659     
00660     @param string string to be decoded
00661     
00662     @return decoded string
00663     """
00664     if not string:
00665         return string
00666     
00667     enc = locale.getdefaultlocale()[1]
00668     if enc:
00669         Debug.msg(5, "DecodeString(): enc=%s" % enc)
00670         return string.decode(enc)
00671     
00672     return string
00673 
00674 def EncodeString(string):
00675     """!Return encoded string using system locales
00676     
00677     @param string string to be encoded
00678     
00679     @return encoded string
00680     """
00681     if not string:
00682         return string
00683     enc = locale.getdefaultlocale()[1]
00684     if enc:
00685         Debug.msg(5, "EncodeString(): enc=%s" % enc)
00686         return string.encode(enc)
00687     
00688     return string
00689 
00690 def UnicodeString(string):
00691     """!Return unicode string
00692     
00693     @param string string to be converted
00694     
00695     @return unicode string
00696     """
00697     if isinstance(string, unicode):
00698         return string
00699     
00700     enc = locale.getdefaultlocale()[1]
00701     if enc:
00702         return unicode(string, enc)
00703     
00704     return string
00705 
00706 def _getGDALFormats():
00707     """!Get dictionary of avaialble GDAL drivers"""
00708     ret = grass.read_command('r.in.gdal',
00709                              quiet = True,
00710                              flags = 'f')
00711     
00712     return _parseFormats(ret)
00713 
00714 def _getOGRFormats():
00715     """!Get dictionary of avaialble OGR drivers"""
00716     ret = grass.read_command('v.in.ogr',
00717                              quiet = True,
00718                              flags = 'f')
00719     
00720     return _parseFormats(ret)
00721 
00722 def _parseFormats(output):
00723     """!Parse r.in.gdal/v.in.ogr -f output"""
00724     formats = { 'file'     : list(),
00725                 'database' : list(),
00726                 'protocol' : list()
00727                 }
00728     
00729     if not output:
00730         return formats
00731     
00732     for line in output.splitlines():
00733         format = line.strip().rsplit(':', -1)[1].strip()
00734         if format in ('Memory', 'Virtual Raster', 'In Memory Raster'):
00735             continue
00736         if format in ('PostgreSQL', 'SQLite',
00737                       'ODBC', 'ESRI Personal GeoDatabase',
00738                       'Rasterlite',
00739                       'PostGIS WKT Raster driver'):
00740             formats['database'].append(format)
00741         elif format in ('GeoJSON',
00742                         'OGC Web Coverage Service',
00743                         'OGC Web Map Service',
00744                         'HTTP Fetching Wrapper'):
00745             formats['protocol'].append(format)
00746         else:
00747             formats['file'].append(format)
00748     
00749     for items in formats.itervalues():
00750         items.sort()
00751     
00752     return formats
00753 
00754 formats = None
00755 
00756 def GetFormats():
00757     """!Get GDAL/OGR formats"""
00758     global formats
00759     if not formats:
00760         formats = {
00761             'gdal' : _getGDALFormats(),
00762             'ogr' : _getOGRFormats()
00763             }
00764     
00765     return formats
00766 
00767 def GetSettingsPath():
00768     """!Get full path to the settings directory
00769     """
00770     try:
00771         verFd = open(os.path.join(globalvar.ETCDIR, "VERSIONNUMBER"))
00772         version = int(verFd.readlines()[0].split(' ')[0].split('.')[0])
00773     except (IOError, ValueError, TypeError, IndexError), e:
00774         sys.exit(_("ERROR: Unable to determine GRASS version. Details: %s") % e)
00775     
00776     verFd.close()
00777     
00778     if sys.platform == 'win32':
00779         return os.path.join(os.getenv('APPDATA'), '.grass%d' % version)
00780     
00781     return os.path.join(os.getenv('HOME'), '.grass%d' % version)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines