~ubuntu-branches/ubuntu/wily/grass/wily

« back to all changes in this revision

Viewing changes to gui/wxpython/core/utils.py

Tags: 7.0.0~rc1+ds1-1~exp1
* New upstream release candidate.
* Repack upstream tarball, remove precompiled Python objects.
* Add upstream metadata.
* Update gbp.conf and Vcs-Git URL to use the experimental branch.
* Update watch file for GRASS 7.0.
* Drop build dependencies for Tcl/Tk, add build dependencies:
  python-numpy, libnetcdf-dev, netcdf-bin, libblas-dev, liblapack-dev
* Update Vcs-Browser URL to use cgit instead of gitweb.
* Update paths to use grass70.
* Add configure options: --with-netcdf, --with-blas, --with-lapack,
  remove --with-tcltk-includes.
* Update patches for GRASS 7.
* Update copyright file, changes:
  - Update copyright years
  - Group files by license
  - Remove unused license sections
* Add patches for various typos.
* Fix desktop file with patch instead of d/rules.
* Use minimal dh rules.
* Bump Standards-Version to 3.9.6, no changes.
* Use dpkg-maintscript-helper to replace directories with symlinks.
  (closes: #776349)
* Update my email to use @debian.org address.

Show diffs side-by-side

added added

removed removed

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