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