1""" 2@package core.utils 3 4@brief Misc utilities for wxGUI 5 6(C) 2007-2015 by the GRASS Development Team 7 8This 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 15import os 16import sys 17import platform 18import string 19import glob 20import shlex 21import re 22import inspect 23import six 24 25from grass.script import core as grass 26from grass.script import task as gtask 27from grass.exceptions import OpenError 28 29from core.gcmd import RunCommand 30from core.debug import Debug 31from core.globalvar import ETCDIR, wxPythonPhoenix 32 33def cmp(a, b): 34 """cmp function""" 35 return ((a > b) - (a < b)) 36 37 38def normalize_whitespace(text): 39 """Remove redundant whitespace from a string""" 40 return (' ').join(text.split()) 41 42 43def split(s): 44 """Platform spefic shlex.split""" 45 try: 46 if sys.platform == "win32": 47 return shlex.split(s.replace('\\', r'\\')) 48 else: 49 return shlex.split(s) 50 except ValueError as e: 51 sys.stderr.write(_("Syntax error: %s") % e) 52 53 return [] 54 55 56def GetTempfile(pref=None): 57 """Creates GRASS temporary file using defined prefix. 58 59 .. todo:: 60 Fix path on MS Windows/MSYS 61 62 :param pref: prefer the given path 63 64 :return: Path to file name (string) or None 65 """ 66 ret = RunCommand('g.tempfile', 67 read=True, 68 pid=os.getpid()) 69 70 tempfile = ret.splitlines()[0].strip() 71 72 # FIXME 73 # ugly hack for MSYS (MS Windows) 74 if platform.system() == 'Windows': 75 tempfile = tempfile.replace("/", "\\") 76 try: 77 path, file = os.path.split(tempfile) 78 if pref: 79 return os.path.join(pref, file) 80 else: 81 return tempfile 82 except: 83 return None 84 85 86def GetLayerNameFromCmd(dcmd, fullyQualified=False, param=None, 87 layerType=None): 88 """Get map name from GRASS command 89 90 Parameter dcmd can be modified when first parameter is not 91 defined. 92 93 :param dcmd: GRASS command (given as list) 94 :param fullyQualified: change map name to be fully qualified 95 :param param: params directory 96 :param str layerType: check also layer type ('raster', 'vector', 97 'raster_3d', ...) 98 99 :return: tuple (name, found) 100 """ 101 mapname = '' 102 found = True 103 104 if len(dcmd) < 1: 105 return mapname, False 106 107 if 'd.grid' == dcmd[0]: 108 mapname = 'grid' 109 elif 'd.geodesic' in dcmd[0]: 110 mapname = 'geodesic' 111 elif 'd.rhumbline' in dcmd[0]: 112 mapname = 'rhumb' 113 elif 'd.graph' in dcmd[0]: 114 mapname = 'graph' 115 else: 116 params = list() 117 for idx in range(len(dcmd)): 118 try: 119 p, v = dcmd[idx].split('=', 1) 120 except ValueError: 121 continue 122 123 if p == param: 124 params = [(idx, p, v)] 125 break 126 127 # this does not use types, just some (incomplete subset of?) names 128 if p in ('map', 'input', 'layer', 129 'red', 'blue', 'green', 130 'hue', 'saturation', 'intensity', 131 'shade', 'labels'): 132 params.append((idx, p, v)) 133 134 if len(params) < 1: 135 if len(dcmd) > 1: 136 i = 1 137 while i < len(dcmd): 138 if '=' not in dcmd[i] and not dcmd[i].startswith('-'): 139 task = gtask.parse_interface(dcmd[0]) 140 # this expects the first parameter to be the right one 141 p = task.get_options()['params'][0].get('name', '') 142 params.append((i, p, dcmd[i])) 143 break 144 i += 1 145 else: 146 return mapname, False 147 148 if len(params) < 1: 149 return mapname, False 150 151 # need to add mapset for all maps 152 mapsets = {} 153 for i, p, v in params: 154 if p == 'layer': 155 continue 156 mapname = v 157 mapset = '' 158 if fullyQualified and '@' not in mapname: 159 if layerType in ('raster', 'vector', 160 'raster_3d', 'rgb', 'his'): 161 try: 162 if layerType in ('raster', 'rgb', 'his'): 163 findType = 'cell' 164 elif layerType == 'raster_3d': 165 findType = 'grid3' 166 else: 167 findType = layerType 168 mapset = grass.find_file( 169 mapname, element=findType)['mapset'] 170 except AttributeError: # not found 171 return '', False 172 if not mapset: 173 found = False 174 else: 175 mapset = '' # grass.gisenv()['MAPSET'] 176 mapsets[i] = mapset 177 178 # update dcmd 179 for i, p, v in params: 180 if p == 'layer': 181 continue 182 dcmd[i] = p + '=' + v 183 if i in mapsets and mapsets[i]: 184 dcmd[i] += '@' + mapsets[i] 185 186 maps = list() 187 ogr = False 188 for i, p, v in params: 189 if v.lower().rfind('@ogr') > -1: 190 ogr = True 191 if p == 'layer' and not ogr: 192 continue 193 maps.append(dcmd[i].split('=', 1)[1]) 194 195 mapname = '\n'.join(maps) 196 197 return mapname, found 198 199 200def GetValidLayerName(name): 201 """Make layer name SQL compliant, based on G_str_to_sql() 202 203 .. todo:: 204 Better use directly Ctypes to reuse venerable libgis C fns... 205 """ 206 retName = name.strip() 207 208 # check if name is fully qualified 209 if '@' in retName: 210 retName, mapset = retName.split('@') 211 else: 212 mapset = None 213 214 cIdx = 0 215 retNameList = list(retName) 216 for c in retNameList: 217 if not (c >= 'A' and c <= 'Z') and \ 218 not (c >= 'a' and c <= 'z') and \ 219 not (c >= '0' and c <= '9'): 220 retNameList[cIdx] = '_' 221 cIdx += 1 222 retName = ''.join(retNameList) 223 224 if not (retName[0] >= 'A' and retName[0] <= 'Z') and \ 225 not (retName[0] >= 'a' and retName[0] <= 'z'): 226 retName = 'x' + retName[1:] 227 228 if mapset: 229 retName = retName + '@' + mapset 230 231 return retName 232 233 234def ListOfCatsToRange(cats): 235 """Convert list of category number to range(s) 236 237 Used for example for d.vect cats=[range] 238 239 :param cats: category list 240 241 :return: category range string 242 :return: '' on error 243 """ 244 245 catstr = '' 246 247 try: 248 cats = list(map(int, cats)) 249 except: 250 return catstr 251 252 i = 0 253 while i < len(cats): 254 next = 0 255 j = i + 1 256 while j < len(cats): 257 if cats[i + next] == cats[j] - 1: 258 next += 1 259 else: 260 break 261 j += 1 262 263 if next > 1: 264 catstr += '%d-%d,' % (cats[i], cats[i + next]) 265 i += next + 1 266 else: 267 catstr += '%d,' % (cats[i]) 268 i += 1 269 270 return catstr.strip(',') 271 272 273def ListOfMapsets(get='ordered'): 274 """Get list of available/accessible mapsets 275 276 :param str get: method ('all', 'accessible', 'ordered') 277 278 :return: list of mapsets 279 :return: None on error 280 """ 281 mapsets = [] 282 283 if get == 'all' or get == 'ordered': 284 ret = RunCommand('g.mapsets', 285 read=True, 286 quiet=True, 287 flags='l', 288 sep='newline') 289 290 if ret: 291 mapsets = ret.splitlines() 292 ListSortLower(mapsets) 293 else: 294 return None 295 296 if get == 'accessible' or get == 'ordered': 297 ret = RunCommand('g.mapsets', 298 read=True, 299 quiet=True, 300 flags='p', 301 sep='newline') 302 if ret: 303 if get == 'accessible': 304 mapsets = ret.splitlines() 305 else: 306 mapsets_accessible = ret.splitlines() 307 for mapset in mapsets_accessible: 308 mapsets.remove(mapset) 309 mapsets = mapsets_accessible + mapsets 310 else: 311 return None 312 313 return mapsets 314 315 316def ListSortLower(list): 317 """Sort list items (not case-sensitive)""" 318 list.sort(key=lambda x: x.lower()) 319 320 321def GetVectorNumberOfLayers(vector): 322 """Get list of all vector layers""" 323 layers = list() 324 if not vector: 325 return layers 326 327 fullname = grass.find_file(name=vector, element='vector')['fullname'] 328 if not fullname: 329 Debug.msg( 330 5, 331 "utils.GetVectorNumberOfLayers(): vector map '%s' not found" % 332 vector) 333 return layers 334 335 ret, out, msg = RunCommand('v.category', 336 getErrorMsg=True, 337 read=True, 338 input=fullname, 339 option='layers') 340 if ret != 0: 341 sys.stderr.write( 342 _("Vector map <%(map)s>: %(msg)s\n") % 343 {'map': fullname, 'msg': msg}) 344 return layers 345 else: 346 Debug.msg(1, "GetVectorNumberOfLayers(): ret %s" % ret) 347 348 for layer in out.splitlines(): 349 layers.append(layer) 350 351 Debug.msg(3, "utils.GetVectorNumberOfLayers(): vector=%s -> %s" % 352 (fullname, ','.join(layers))) 353 354 return layers 355 356 357def Deg2DMS(lon, lat, string=True, hemisphere=True, precision=3): 358 """Convert deg value to dms string 359 360 :param lon: longitude (x) 361 :param lat: latitude (y) 362 :param string: True to return string otherwise tuple 363 :param hemisphere: print hemisphere 364 :param precision: seconds precision 365 366 :return: DMS string or tuple of values 367 :return: empty string on error 368 """ 369 try: 370 flat = float(lat) 371 flon = float(lon) 372 except ValueError: 373 if string: 374 return '' 375 else: 376 return None 377 378 # fix longitude 379 while flon > 180.0: 380 flon -= 360.0 381 while flon < -180.0: 382 flon += 360.0 383 384 # hemisphere 385 if hemisphere: 386 if flat < 0.0: 387 flat = abs(flat) 388 hlat = 'S' 389 else: 390 hlat = 'N' 391 392 if flon < 0.0: 393 hlon = 'W' 394 flon = abs(flon) 395 else: 396 hlon = 'E' 397 else: 398 flat = abs(flat) 399 flon = abs(flon) 400 hlon = '' 401 hlat = '' 402 403 slat = __ll_parts(flat, precision=precision) 404 slon = __ll_parts(flon, precision=precision) 405 406 if string: 407 return slon + hlon + '; ' + slat + hlat 408 409 return (slon + hlon, slat + hlat) 410 411 412def DMS2Deg(lon, lat): 413 """Convert dms value to deg 414 415 :param lon: longitude (x) 416 :param lat: latitude (y) 417 418 :return: tuple of converted values 419 :return: ValueError on error 420 """ 421 x = __ll_parts(lon, reverse=True) 422 y = __ll_parts(lat, reverse=True) 423 424 return (x, y) 425 426 427def __ll_parts(value, reverse=False, precision=3): 428 """Converts deg to d:m:s string 429 430 :param value: value to be converted 431 :param reverse: True to convert from d:m:s to deg 432 :param precision: seconds precision (ignored if reverse is True) 433 434 :return: converted value (string/float) 435 :return: ValueError on error (reverse == True) 436 """ 437 if not reverse: 438 if value == 0.0: 439 return '%s%.*f' % ('00:00:0', precision, 0.0) 440 441 d = int(int(value)) 442 m = int((value - d) * 60) 443 s = ((value - d) * 60 - m) * 60 444 if m < 0: 445 m = '00' 446 elif m < 10: 447 m = '0' + str(m) 448 else: 449 m = str(m) 450 if s < 0: 451 s = '00.0000' 452 elif s < 10.0: 453 s = '0%.*f' % (precision, s) 454 else: 455 s = '%.*f' % (precision, s) 456 457 return str(d) + ':' + m + ':' + s 458 else: # -> reverse 459 try: 460 d, m, s = value.split(':') 461 hs = s[-1] 462 s = s[:-1] 463 except ValueError: 464 try: 465 d, m = value.split(':') 466 hs = m[-1] 467 m = m[:-1] 468 s = '0.0' 469 except ValueError: 470 try: 471 d = value 472 hs = d[-1] 473 d = d[:-1] 474 m = '0' 475 s = '0.0' 476 except ValueError: 477 raise ValueError 478 479 if hs not in ('N', 'S', 'E', 'W'): 480 raise ValueError 481 482 coef = 1.0 483 if hs in ('S', 'W'): 484 coef = -1.0 485 486 fm = int(m) / 60.0 487 fs = float(s) / (60 * 60) 488 489 return coef * (float(d) + fm + fs) 490 491 492def GetCmdString(cmd): 493 """Get GRASS command as string. 494 495 :param cmd: GRASS command given as tuple 496 497 :return: command string 498 """ 499 return ' '.join(gtask.cmdtuple_to_list(cmd)) 500 501 502def PathJoin(*args): 503 """Check path created by os.path.join""" 504 path = os.path.join(*args) 505 if platform.system() == 'Windows' and \ 506 '/' in path: 507 return path[1].upper() + ':\\' + path[3:].replace('/', '\\') 508 509 return path 510 511 512def ReadEpsgCodes(): 513 """Read EPSG codes with g.proj 514 515 :return: dictionary of EPSG code 516 """ 517 epsgCodeDict = dict() 518 519 ret = RunCommand('g.proj', 520 read=True, 521 list_codes="EPSG") 522 523 for line in ret.splitlines(): 524 code, descr, params = line.split("|") 525 epsgCodeDict[int(code)] = (descr, params) 526 527 return epsgCodeDict 528 529 530def ReprojectCoordinates(coord, projOut, projIn=None, flags=''): 531 """Reproject coordinates 532 533 :param coord: coordinates given as tuple 534 :param projOut: output projection 535 :param projIn: input projection (use location projection settings) 536 537 :return: reprojected coordinates (returned as tuple) 538 """ 539 coors = RunCommand('m.proj', 540 flags=flags, 541 input='-', 542 proj_in=projIn, 543 proj_out=projOut, 544 sep=';', 545 stdin='%f;%f' % (coord[0], coord[1]), 546 read=True) 547 if coors: 548 coors = coors.split(';') 549 e = coors[0] 550 n = coors[1] 551 try: 552 proj = projOut.split(' ')[0].split('=')[1] 553 except IndexError: 554 proj = '' 555 if proj in ('ll', 'latlong', 'longlat') and 'd' not in flags: 556 return (proj, (e, n)) 557 else: 558 try: 559 return (proj, (float(e), float(n))) 560 except ValueError: 561 return (None, None) 562 563 return (None, None) 564 565 566def GetListOfLocations(dbase): 567 """Get list of GRASS locations in given dbase 568 569 :param dbase: GRASS database path 570 571 :return: list of locations (sorted) 572 """ 573 listOfLocations = list() 574 575 try: 576 for location in glob.glob(os.path.join(dbase, "*")): 577 try: 578 if os.path.join( 579 location, "PERMANENT") in glob.glob( 580 os.path.join(location, "*")): 581 listOfLocations.append(os.path.basename(location)) 582 except: 583 pass 584 except (UnicodeEncodeError, UnicodeDecodeError) as e: 585 raise e 586 587 ListSortLower(listOfLocations) 588 589 return listOfLocations 590 591 592def GetListOfMapsets(dbase, location, selectable=False): 593 """Get list of mapsets in given GRASS location 594 595 :param dbase: GRASS database path 596 :param location: GRASS location 597 :param selectable: True to get list of selectable mapsets, otherwise all 598 599 :return: list of mapsets - sorted (PERMANENT first) 600 """ 601 listOfMapsets = list() 602 603 if selectable: 604 ret = RunCommand('g.mapset', 605 read=True, 606 flags='l', 607 location=location, 608 dbase=dbase) 609 610 if not ret: 611 return listOfMapsets 612 613 for line in ret.rstrip().splitlines(): 614 listOfMapsets += line.split(' ') 615 else: 616 for mapset in glob.glob(os.path.join(dbase, location, "*")): 617 if os.path.isdir(mapset) and os.path.isfile( 618 os.path.join(dbase, location, mapset, "WIND")): 619 listOfMapsets.append(os.path.basename(mapset)) 620 621 ListSortLower(listOfMapsets) 622 return listOfMapsets 623 624 625def GetColorTables(): 626 """Get list of color tables""" 627 ret = RunCommand('r.colors', 628 read=True, 629 flags='l') 630 if not ret: 631 return list() 632 633 return ret.splitlines() 634 635 636def _getGDALFormats(): 637 """Get dictionary of avaialble GDAL drivers""" 638 try: 639 ret = grass.read_command('r.in.gdal', 640 quiet=True, 641 flags='f') 642 except: 643 ret = None 644 645 return _parseFormats(ret), _parseFormats(ret, writableOnly=True) 646 647 648def _getOGRFormats(): 649 """Get dictionary of avaialble OGR drivers""" 650 try: 651 ret = grass.read_command('v.in.ogr', 652 quiet=True, 653 flags='f') 654 except: 655 ret = None 656 657 return _parseFormats(ret), _parseFormats(ret, writableOnly=True) 658 659 660def _parseFormats(output, writableOnly=False): 661 """Parse r.in.gdal/v.in.ogr -f output""" 662 formats = {'file': list(), 663 'database': list(), 664 'protocol': list() 665 } 666 667 if not output: 668 return formats 669 670 patt = None 671 if writableOnly: 672 patt = re.compile('\(rw\+?\)$', re.IGNORECASE) 673 674 for line in output.splitlines(): 675 key, name = map(lambda x: x.strip(), line.strip().split(':', 1)) 676 677 if writableOnly and not patt.search(key): 678 continue 679 680 if name in ('Memory', 'Virtual Raster', 'In Memory Raster'): 681 continue 682 if name in ('PostgreSQL', 'SQLite', 683 'ODBC', 'ESRI Personal GeoDatabase', 684 'Rasterlite', 685 'PostGIS WKT Raster driver', 686 'PostGIS Raster driver', 687 'CouchDB', 688 'MSSQLSpatial', 689 'FileGDB'): 690 formats['database'].append(name) 691 elif name in ('GeoJSON', 692 'OGC Web Coverage Service', 693 'OGC Web Map Service', 694 'WFS', 695 'GeoRSS', 696 'HTTP Fetching Wrapper'): 697 formats['protocol'].append(name) 698 else: 699 formats['file'].append(name) 700 701 for items in six.itervalues(formats): 702 items.sort() 703 704 return formats 705 706formats = None 707 708 709def GetFormats(writableOnly=False): 710 """Get GDAL/OGR formats""" 711 global formats 712 if not formats: 713 gdalAll, gdalWritable = _getGDALFormats() 714 ogrAll, ogrWritable = _getOGRFormats() 715 formats = { 716 'all': { 717 'gdal': gdalAll, 718 'ogr': ogrAll, 719 }, 720 'writable': { 721 'gdal': gdalWritable, 722 'ogr': ogrWritable, 723 }, 724 } 725 726 if writableOnly: 727 return formats['writable'] 728 729 return formats['all'] 730 731 732rasterFormatExtension = { 733 'GeoTIFF': 'tif', 734 'Erdas Imagine Images (.img)': 'img', 735 'Ground-based SAR Applications Testbed File Format (.gff)': 'gff', 736 'Arc/Info Binary Grid': 'adf', 737 'Portable Network Graphics': 'png', 738 'JPEG JFIF': 'jpg', 739 'Japanese DEM (.mem)': 'mem', 740 'Graphics Interchange Format (.gif)': 'gif', 741 'X11 PixMap Format': 'xpm', 742 'MS Windows Device Independent Bitmap': 'bmp', 743 'SPOT DIMAP': 'dim', 744 'RadarSat 2 XML Product': 'xml', 745 'EarthWatch .TIL': 'til', 746 'ERMapper .ers Labelled': 'ers', 747 'ERMapper Compressed Wavelets': 'ecw', 748 'GRIdded Binary (.grb)': 'grb', 749 'EUMETSAT Archive native (.nat)': 'nat', 750 'Idrisi Raster A.1': 'rst', 751 'Golden Software ASCII Grid (.grd)': 'grd', 752 'Golden Software Binary Grid (.grd)': 'grd', 753 'Golden Software 7 Binary Grid (.grd)': 'grd', 754 'R Object Data Store': 'r', 755 'USGS DOQ (Old Style)': 'doq', 756 'USGS DOQ (New Style)': 'doq', 757 'ENVI .hdr Labelled': 'hdr', 758 'ESRI .hdr Labelled': 'hdr', 759 'Generic Binary (.hdr Labelled)': 'hdr', 760 'PCI .aux Labelled': 'aux', 761 'EOSAT FAST Format': 'fst', 762 'VTP .bt (Binary Terrain) 1.3 Format': 'bt', 763 'FARSITE v.4 Landscape File (.lcp)': 'lcp', 764 'Swedish Grid RIK (.rik)': 'rik', 765 'USGS Optional ASCII DEM (and CDED)': 'dem', 766 'Northwood Numeric Grid Format .grd/.tab': '', 767 'Northwood Classified Grid Format .grc/.tab': '', 768 'ARC Digitized Raster Graphics': 'arc', 769 'Magellan topo (.blx)': 'blx', 770 'SAGA GIS Binary Grid (.sdat)': 'sdat', 771 'GeoPackage (.gpkg)': 'gpkg' 772} 773 774 775vectorFormatExtension = { 776 'ESRI Shapefile': 'shp', 777 'GeoPackage': 'gpkg', 778 'UK .NTF': 'ntf', 779 'SDTS': 'ddf', 780 'DGN': 'dgn', 781 'VRT': 'vrt', 782 'REC': 'rec', 783 'BNA': 'bna', 784 'CSV': 'csv', 785 'GML': 'gml', 786 'GPX': 'gpx', 787 'KML': 'kml', 788 'GMT': 'gmt', 789 'PGeo': 'mdb', 790 'XPlane': 'dat', 791 'AVCBin': 'adf', 792 'AVCE00': 'e00', 793 'DXF': 'dxf', 794 'Geoconcept': 'gxt', 795 'GeoRSS': 'xml', 796 'GPSTrackMaker': 'gtm', 797 'VFK': 'vfk', 798 'SVG': 'svg' 799} 800 801 802def GetSettingsPath(): 803 """Get full path to the settings directory 804 """ 805 try: 806 verFd = open(os.path.join(ETCDIR, "VERSIONNUMBER")) 807 version = int(verFd.readlines()[0].split(' ')[0].split('.')[0]) 808 except (IOError, ValueError, TypeError, IndexError) as e: 809 sys.exit( 810 _("ERROR: Unable to determine GRASS version. Details: %s") % 811 e) 812 813 verFd.close() 814 815 # keep location of settings files rc and wx in sync with lib/init/grass.py 816 if sys.platform == 'win32': 817 return os.path.join(os.getenv('APPDATA'), 'GRASS%d' % version) 818 819 return os.path.join(os.getenv('HOME'), '.grass%d' % version) 820 821 822def StoreEnvVariable(key, value=None, envFile=None): 823 """Store environmental variable 824 825 If value is not given (is None) then environmental variable is 826 unset. 827 828 :param key: env key 829 :param value: env value 830 :param envFile: path to the environmental file (None for default location) 831 """ 832 windows = sys.platform == 'win32' 833 if not envFile: 834 gVersion = grass.version()['version'].split('.', 1)[0] 835 if not windows: 836 envFile = os.path.join( 837 os.getenv('HOME'), '.grass%s' % 838 gVersion, 'bashrc') 839 else: 840 envFile = os.path.join( 841 os.getenv('APPDATA'), 'GRASS%s' % 842 gVersion, 'env.bat') 843 844 # read env file 845 environ = dict() 846 lineSkipped = list() 847 if os.path.exists(envFile): 848 try: 849 fd = open(envFile) 850 except IOError as e: 851 sys.stderr.write(_("Unable to open file '%s'\n") % envFile) 852 return 853 for line in fd.readlines(): 854 line = line.rstrip(os.linesep) 855 try: 856 k, v = map( 857 lambda x: x.strip(), line.split( 858 ' ', 1)[1].split( 859 '=', 1)) 860 except Exception as e: 861 sys.stderr.write(_("%s: line skipped - unable to parse '%s'\n" 862 "Reason: %s\n") % (envFile, line, e)) 863 lineSkipped.append(line) 864 continue 865 if k in environ: 866 sys.stderr.write(_("Duplicated key: %s\n") % k) 867 environ[k] = v 868 869 fd.close() 870 871 # update environmental variables 872 if value is None: 873 if key in environ: 874 del environ[key] 875 else: 876 environ[key] = value 877 878 # write update env file 879 try: 880 fd = open(envFile, 'w') 881 except IOError as e: 882 sys.stderr.write(_("Unable to create file '%s'\n") % envFile) 883 return 884 if windows: 885 expCmd = 'set' 886 else: 887 expCmd = 'export' 888 889 for key, value in six.iteritems(environ): 890 fd.write('%s %s=%s\n' % (expCmd, key, value)) 891 892 # write also skipped lines 893 for line in lineSkipped: 894 fd.write(line + os.linesep) 895 896 fd.close() 897 898 899def SetAddOnPath(addonPath=None, key='PATH'): 900 """Set default AddOn path 901 902 :param addonPath: path to addons (None for default) 903 :param key: env key - 'PATH' or 'BASE' 904 """ 905 gVersion = grass.version()['version'].split('.', 1)[0] 906 # update env file 907 if not addonPath: 908 if sys.platform != 'win32': 909 addonPath = os.path.join(os.path.join(os.getenv('HOME'), 910 '.grass%s' % gVersion, 911 'addons')) 912 else: 913 addonPath = os.path.join(os.path.join(os.getenv('APPDATA'), 914 'GRASS%s' % gVersion, 915 'addons')) 916 917 StoreEnvVariable(key='GRASS_ADDON_' + key, value=addonPath) 918 os.environ['GRASS_ADDON_' + key] = addonPath 919 920 # update path 921 if addonPath not in os.environ['PATH']: 922 os.environ['PATH'] = addonPath + os.pathsep + os.environ['PATH'] 923 924 925# predefined colors and their names 926# must be in sync with lib/gis/color_str.c 927str2rgb = {'aqua': (100, 128, 255), 928 'black': (0, 0, 0), 929 'blue': (0, 0, 255), 930 'brown': (180, 77, 25), 931 'cyan': (0, 255, 255), 932 'gray': (128, 128, 128), 933 'grey': (128, 128, 128), 934 'green': (0, 255, 0), 935 'indigo': (0, 128, 255), 936 'magenta': (255, 0, 255), 937 'orange': (255, 128, 0), 938 'red': (255, 0, 0), 939 'violet': (128, 0, 255), 940 'purple': (128, 0, 255), 941 'white': (255, 255, 255), 942 'yellow': (255, 255, 0)} 943rgb2str = {} 944for (s, r) in str2rgb.items(): 945 rgb2str[r] = s 946# ensure that gray value has 'gray' string and not 'grey' 947rgb2str[str2rgb['gray']] = 'gray' 948# purple is defined as nickname for violet in lib/gis 949# (although Wikipedia says that purple is (128, 0, 128)) 950# we will prefer the defined color, not nickname 951rgb2str[str2rgb['violet']] = 'violet' 952 953 954def color_resolve(color): 955 if len(color) > 0 and color[0] in "0123456789": 956 rgb = tuple(map(int, color.split(':'))) 957 label = color 958 else: 959 # Convert color names to RGB 960 try: 961 rgb = str2rgb[color] 962 label = color 963 except KeyError: 964 rgb = (200, 200, 200) 965 label = _('Select Color') 966 return (rgb, label) 967 968command2ltype = {'d.rast': 'raster', 969 'd.rast3d': 'raster_3d', 970 'd.rgb': 'rgb', 971 'd.his': 'his', 972 'd.shade': 'shaded', 973 'd.legend': 'rastleg', 974 'd.rast.arrow': 'rastarrow', 975 'd.rast.num': 'rastnum', 976 'd.rast.leg': 'maplegend', 977 'd.vect': 'vector', 978 'd.vect.thematic': 'thememap', 979 'd.vect.chart': 'themechart', 980 'd.grid': 'grid', 981 'd.geodesic': 'geodesic', 982 'd.rhumbline': 'rhumb', 983 'd.labels': 'labels', 984 'd.barscale': 'barscale', 985 'd.redraw': 'redraw', 986 'd.wms': 'wms', 987 'd.histogram': 'histogram', 988 'd.colortable': 'colortable', 989 'd.graph': 'graph', 990 'd.out.file': 'export', 991 'd.to.rast': 'torast', 992 'd.text': 'text', 993 'd.northarrow': 'northarrow', 994 'd.polar': 'polar', 995 'd.legend.vect': 'vectleg' 996 } 997ltype2command = {} 998for (cmd, ltype) in command2ltype.items(): 999 ltype2command[ltype] = cmd 1000 1001 1002def GetGEventAttribsForHandler(method, event): 1003 """Get attributes from event, which can be used by handler method. 1004 1005 Be aware of event class attributes. 1006 1007 :param method: handler method (including self arg) 1008 :param event: event 1009 1010 :return: (valid kwargs for method, 1011 list of method's args without default value 1012 which were not found among event attributes) 1013 """ 1014 args_spec = inspect.getargspec(method) 1015 1016 args = args_spec[0] 1017 1018 defaults = [] 1019 if args_spec[3]: 1020 defaults = args_spec[3] 1021 1022 # number of arguments without def value 1023 req_args = len(args) - 1 - len(defaults) 1024 1025 kwargs = {} 1026 missing_args = [] 1027 1028 for i, a in enumerate(args): 1029 if hasattr(event, a): 1030 kwargs[a] = getattr(event, a) 1031 elif i < req_args: 1032 missing_args.append(a) 1033 1034 return kwargs, missing_args 1035 1036 1037def PilImageToWxImage(pilImage, copyAlpha=True): 1038 """Convert PIL image to wx.Image 1039 1040 Based on http://wiki.wxpython.org/WorkingWithImages 1041 """ 1042 from gui_core.wrap import EmptyImage 1043 hasAlpha = pilImage.mode[-1] == 'A' 1044 if copyAlpha and hasAlpha: # Make sure there is an alpha layer copy. 1045 wxImage = EmptyImage(*pilImage.size) 1046 pilImageCopyRGBA = pilImage.copy() 1047 pilImageCopyRGB = pilImageCopyRGBA.convert('RGB') # RGBA --> RGB 1048 wxImage.SetData(pilImageCopyRGB.tobytes()) 1049 # Create layer and insert alpha values. 1050 if wxPythonPhoenix: 1051 wxImage.SetAlpha(pilImageCopyRGBA.tobytes()[3::4]) 1052 else: 1053 wxImage.SetAlphaData(pilImageCopyRGBA.tobytes()[3::4]) 1054 1055 else: # The resulting image will not have alpha. 1056 wxImage = EmptyImage(*pilImage.size) 1057 pilImageCopy = pilImage.copy() 1058 # Discard any alpha from the PIL image. 1059 pilImageCopyRGB = pilImageCopy.convert('RGB') 1060 wxImage.SetData(pilImageCopyRGB.tobytes()) 1061 1062 return wxImage 1063 1064 1065def autoCropImageFromFile(filename): 1066 """Loads image from file and crops it automatically. 1067 1068 If PIL is not installed, it does not crop it. 1069 1070 :param filename: path to file 1071 :return: wx.Image instance 1072 """ 1073 try: 1074 from PIL import Image 1075 pilImage = Image.open(filename) 1076 imageBox = pilImage.getbbox() 1077 cropped = pilImage.crop(imageBox) 1078 return PilImageToWxImage(cropped, copyAlpha=True) 1079 except ImportError: 1080 import wx 1081 return wx.Image(filename) 1082 1083 1084def isInRegion(regionA, regionB): 1085 """Tests if 'regionA' is inside of 'regionB'. 1086 1087 For example, region A is a display region and region B is some reference 1088 region, e.g., a computational region. 1089 1090 >>> displayRegion = {'n': 223900, 's': 217190, 'w': 630780, 'e': 640690} 1091 >>> compRegion = {'n': 228500, 's': 215000, 'w': 630000, 'e': 645000} 1092 >>> isInRegion(displayRegion, compRegion) 1093 True 1094 >>> displayRegion = {'n':226020, 's': 212610, 'w': 626510, 'e': 646330} 1095 >>> isInRegion(displayRegion, compRegion) 1096 False 1097 1098 :param regionA: input region A as dictionary 1099 :param regionB: input region B as dictionary 1100 1101 :return: True if region A is inside of region B 1102 :return: False othewise 1103 """ 1104 if regionA['s'] >= regionB['s'] and \ 1105 regionA['n'] <= regionB['n'] and \ 1106 regionA['w'] >= regionB['w'] and \ 1107 regionA['e'] <= regionB['e']: 1108 return True 1109 1110 return False 1111 1112 1113def do_doctest_gettext_workaround(): 1114 """Setups environment for doing a doctest with gettext usage. 1115 1116 When using gettext with dynamically defined underscore function 1117 (`_("For translation")`), doctest does not work properly. One option is to 1118 use `import as` instead of dynamically defined underscore function but this 1119 would require change all modules which are used by tested module. This 1120 should be considered for the future. The second option is to define dummy 1121 underscore function and one other function which creates the right 1122 environment to satisfy all. This is done by this function. 1123 """ 1124 def new_displayhook(string): 1125 """A replacement for default `sys.displayhook`""" 1126 if string is not None: 1127 sys.stdout.write("%r\n" % (string,)) 1128 1129 def new_translator(string): 1130 """A fake gettext underscore function.""" 1131 return string 1132 1133 sys.displayhook = new_displayhook 1134 1135 import __builtin__ 1136 __builtin__._ = new_translator 1137 1138 1139def doc_test(): 1140 """Tests the module using doctest 1141 1142 :return: a number of failed tests 1143 """ 1144 import doctest 1145 do_doctest_gettext_workaround() 1146 return doctest.testmod().failed 1147 1148 1149def registerPid(pid): 1150 """Register process id as GUI_PID GRASS variable 1151 1152 :param: pid process id 1153 """ 1154 env = grass.gisenv() 1155 guiPid = [] 1156 if 'GUI_PID' in env: 1157 guiPid = env['GUI_PID'].split(',') 1158 guiPid.append(str(pid)) 1159 grass.run_command('g.gisenv', set='GUI_PID={0}'.format(','.join(guiPid))) 1160 1161 1162def unregisterPid(pid): 1163 """Unregister process id from GUI_PID GRASS variable 1164 1165 :param: pid process id 1166 """ 1167 env = grass.gisenv() 1168 if 'GUI_PID' not in env: 1169 return 1170 1171 guiPid = env['GUI_PID'].split(',') 1172 pid = str(os.getpid()) 1173 if pid in guiPid: 1174 guiPid.remove(pid) 1175 grass.run_command( 1176 'g.gisenv', 1177 set='GUI_PID={0}'.format( 1178 ','.join(guiPid))) 1179 1180if __name__ == '__main__': 1181 sys.exit(doc_test()) 1182