source: grass/trunk/gui/wxpython/core/utils.py

Last change on this file was 74357, checked in by mmetz, 5 years ago

wxGUI: convert EPSG code to int, fixes #3808

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id
  • Property svn:mime-type set to text/x-python
File size: 33.0 KB
Line 
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().rsplit(':', -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 fn = getattr(
1049 pilImageCopyRGB,
1050 "tobytes",
1051 getattr(
1052 pilImageCopyRGB,
1053 "tostring"))
1054 pilImageRgbData = fn()
1055 wxImage.SetData(pilImageRgbData)
1056 fn = getattr(
1057 pilImageCopyRGBA,
1058 "tobytes",
1059 getattr(
1060 pilImageCopyRGBA,
1061 "tostring"))
1062 # Create layer and insert alpha values.
1063 if wxPythonPhoenix:
1064 wxImage.SetAlpha(fn()[3::4])
1065 else:
1066 wxImage.SetAlphaData(fn()[3::4])
1067
1068 else: # The resulting image will not have alpha.
1069 wxImage = EmptyImage(*pilImage.size)
1070 pilImageCopy = pilImage.copy()
1071 # Discard any alpha from the PIL image.
1072 pilImageCopyRGB = pilImageCopy.convert('RGB')
1073 fn = getattr(
1074 pilImageCopyRGB,
1075 "tobytes",
1076 getattr(
1077 pilImageCopyRGB,
1078 "tostring"))
1079 pilImageRgbData = fn()
1080 wxImage.SetData(pilImageRgbData)
1081
1082 return wxImage
1083
1084
1085def autoCropImageFromFile(filename):
1086 """Loads image from file and crops it automatically.
1087
1088 If PIL is not installed, it does not crop it.
1089
1090 :param filename: path to file
1091 :return: wx.Image instance
1092 """
1093 try:
1094 from PIL import Image
1095 pilImage = Image.open(filename)
1096 imageBox = pilImage.getbbox()
1097 cropped = pilImage.crop(imageBox)
1098 return PilImageToWxImage(cropped, copyAlpha=True)
1099 except ImportError:
1100 import wx
1101 return wx.Image(filename)
1102
1103
1104def isInRegion(regionA, regionB):
1105 """Tests if 'regionA' is inside of 'regionB'.
1106
1107 For example, region A is a display region and region B is some reference
1108 region, e.g., a computational region.
1109
1110 >>> displayRegion = {'n': 223900, 's': 217190, 'w': 630780, 'e': 640690}
1111 >>> compRegion = {'n': 228500, 's': 215000, 'w': 630000, 'e': 645000}
1112 >>> isInRegion(displayRegion, compRegion)
1113 True
1114 >>> displayRegion = {'n':226020, 's': 212610, 'w': 626510, 'e': 646330}
1115 >>> isInRegion(displayRegion, compRegion)
1116 False
1117
1118 :param regionA: input region A as dictionary
1119 :param regionB: input region B as dictionary
1120
1121 :return: True if region A is inside of region B
1122 :return: False othewise
1123 """
1124 if regionA['s'] >= regionB['s'] and \
1125 regionA['n'] <= regionB['n'] and \
1126 regionA['w'] >= regionB['w'] and \
1127 regionA['e'] <= regionB['e']:
1128 return True
1129
1130 return False
1131
1132
1133def do_doctest_gettext_workaround():
1134 """Setups environment for doing a doctest with gettext usage.
1135
1136 When using gettext with dynamically defined underscore function
1137 (`_("For translation")`), doctest does not work properly. One option is to
1138 use `import as` instead of dynamically defined underscore function but this
1139 would require change all modules which are used by tested module. This
1140 should be considered for the future. The second option is to define dummy
1141 underscore function and one other function which creates the right
1142 environment to satisfy all. This is done by this function.
1143 """
1144 def new_displayhook(string):
1145 """A replacement for default `sys.displayhook`"""
1146 if string is not None:
1147 sys.stdout.write("%r\n" % (string,))
1148
1149 def new_translator(string):
1150 """A fake gettext underscore function."""
1151 return string
1152
1153 sys.displayhook = new_displayhook
1154
1155 import __builtin__
1156 __builtin__._ = new_translator
1157
1158
1159def doc_test():
1160 """Tests the module using doctest
1161
1162 :return: a number of failed tests
1163 """
1164 import doctest
1165 do_doctest_gettext_workaround()
1166 return doctest.testmod().failed
1167
1168
1169def registerPid(pid):
1170 """Register process id as GUI_PID GRASS variable
1171
1172 :param: pid process id
1173 """
1174 env = grass.gisenv()
1175 guiPid = []
1176 if 'GUI_PID' in env:
1177 guiPid = env['GUI_PID'].split(',')
1178 guiPid.append(str(pid))
1179 grass.run_command('g.gisenv', set='GUI_PID={0}'.format(','.join(guiPid)))
1180
1181
1182def unregisterPid(pid):
1183 """Unregister process id from GUI_PID GRASS variable
1184
1185 :param: pid process id
1186 """
1187 env = grass.gisenv()
1188 if 'GUI_PID' not in env:
1189 return
1190
1191 guiPid = env['GUI_PID'].split(',')
1192 pid = str(os.getpid())
1193 if pid in guiPid:
1194 guiPid.remove(pid)
1195 grass.run_command(
1196 'g.gisenv',
1197 set='GUI_PID={0}'.format(
1198 ','.join(guiPid)))
1199
1200if __name__ == '__main__':
1201 sys.exit(doc_test())
Note: See TracBrowser for help on using the repository browser.