2
Core functions to be used in Python scripts.
8
from grass.script import core as grass
11
(C) 2008-2014 by the GRASS Development Team
12
This program is free software under the GNU General Public
13
License (>=v2). Read the file COPYING that comes with GRASS
16
.. sectionauthor:: Glynn Clements
17
.. sectionauthor:: Martin Landa <landa.martin gmail.com>
18
.. sectionauthor:: Michael Barton <michael.barton asu.edu>
29
import types as python_types
31
from utils import KeyValue, parse_key_val, basename, encode
32
from grass.exceptions import ScriptError, CalledModuleError
36
gettext.install('grasslibs', os.path.join(os.getenv("GISBASE"), 'locale'))
38
# subprocess wrapper that uses shell on Windows
41
class Popen(subprocess.Popen):
42
_builtin_exts = set(['.com', '.exe', '.bat', '.cmd'])
45
def _escape_for_shell(arg):
46
# TODO: what are cmd.exe's parsing rules?
49
def __init__(self, args, **kwargs):
50
if (sys.platform == 'win32'
51
and isinstance(args, list)
52
and not kwargs.get('shell', False)
53
and kwargs.get('executable') is None):
54
cmd = shutil_which(args[0])
57
args = [cmd] + args[1:]
58
name, ext = os.path.splitext(cmd)
59
if ext.lower() not in self._builtin_exts:
60
kwargs['shell'] = True
61
args = [self._escape_for_shell(arg) for arg in args]
62
subprocess.Popen.__init__(self, args, **kwargs)
64
PIPE = subprocess.PIPE
65
STDOUT = subprocess.STDOUT
68
raise_on_error = False # raise exception instead of calling fatal()
71
def call(*args, **kwargs):
72
return Popen(*args, **kwargs).wait()
74
# GRASS-oriented interface to subprocess module
76
_popen_args = ["bufsize", "executable", "stdin", "stdout", "stderr",
77
"preexec_fn", "close_fds", "cwd", "env",
78
"universal_newlines", "startupinfo", "creationflags"]
82
if isinstance(val, types.StringType) or \
83
isinstance(val, types.UnicodeType):
85
if isinstance(val, types.ListType):
86
return ",".join(map(_make_val, val))
87
if isinstance(val, types.TupleType):
88
return _make_val(list(val))
93
"""Create list of available GRASS commands to use when parsing
94
string from the command line
96
:return: list of commands (set) and directory of scripts (collected
97
by extension - MS Windows only)
99
>>> cmds = list(get_commands()[0])
102
['d.barscale', 'd.colorlist', 'd.colortable', 'd.correlate', 'd.erase']
105
gisbase = os.environ['GISBASE']
107
scripts = {'.py': list()} if sys.platform == 'win32' else {}
109
def scan(gisbase, directory):
110
dir_path = os.path.join(gisbase, directory)
111
if os.path.exists(dir_path):
112
for fname in os.listdir(os.path.join(gisbase, directory)):
114
name, ext = os.path.splitext(fname)
115
if ext != '.manifest':
117
if ext in scripts.keys():
118
scripts[ext].append(name)
122
for directory in ('bin', 'scripts'):
123
scan(gisbase, directory)
126
gui_path = os.path.join(gisbase, 'etc', 'gui', 'scripts')
127
if os.path.exists(gui_path):
128
os.environ["PATH"] = os.getenv("PATH") + os.pathsep + gui_path
129
cmd = cmd + os.listdir(gui_path)
131
return set(cmd), scripts
134
# replacement for which function from shutil (not available in all versions)
135
# from http://hg.python.org/cpython/file/6860263c05b3/Lib/shutil.py#l1068
136
# added because of Python scripts running Python scripts on MS Windows
137
# see also ticket #2008 which is unrelated but same function was proposed
138
def shutil_which(cmd, mode=os.F_OK | os.X_OK, path=None):
139
"""Given a command, mode, and a PATH string, return the path which
140
conforms to the given mode on the PATH, or None if there is no such
143
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
144
of os.environ.get("PATH"), or can be overridden with a custom search
147
:param cmd: the command
152
# Check that a given file can be accessed with the correct mode.
153
# Additionally check that `file` is not a directory, as on Windows
154
# directories pass the os.access check.
155
def _access_check(fn, mode):
156
return (os.path.exists(fn) and os.access(fn, mode)
157
and not os.path.isdir(fn))
159
# If we're given a path with a directory part, look it up directly rather
160
# than referring to PATH directories. This includes checking relative to the
161
# current directory, e.g. ./script
162
if os.path.dirname(cmd):
163
if _access_check(cmd, mode):
168
path = os.environ.get("PATH", os.defpath)
171
path = path.split(os.pathsep)
173
if sys.platform == "win32":
174
# The current directory takes precedence on Windows.
175
if not os.curdir in path:
176
path.insert(0, os.curdir)
178
# PATHEXT is necessary to check on Windows.
179
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
180
map(lambda x: x.lower(), pathext) # force lowercase
181
if '.py' not in pathext: # we assume that PATHEXT contains always '.py'
182
pathext.insert(0, '.py')
183
# See if the given file matches any of the expected path extensions.
184
# This will allow us to short circuit when given "python.exe".
185
# If it does match, only test that one, otherwise we have to try
187
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
190
files = [cmd + ext for ext in pathext]
192
# On other platforms you don't have things like PATHEXT to tell you
193
# what file suffixes are executable, so just pass on cmd as-is.
198
normdir = os.path.normcase(dir)
199
if not normdir in seen:
201
for thefile in files:
202
name = os.path.join(dir, thefile)
203
if _access_check(name, mode):
208
# Added because of scripts calling scripts on MS Windows.
209
# Module name (here cmd) differs from the file name (does not have extension).
210
# Additionally, we don't run scripts using system executable mechanism,
211
# so we need the full path name.
212
# However, scripts are on the PATH and '.PY' in in PATHEXT, so we can use
213
# shutil.which to get the full file path. Addons are on PATH too.
214
# An alternative to which function call would be to check the script path and
215
# addons path. This is proposed improvement for the future.
216
# Another alternative is to check some global list of scripts but this list
217
# needs to be created first. The question is what is less expensive.
218
# Note that getting the full path is only part of the solution,
219
# the other part is to use the right Python as an executable and pass the full
220
# script path as a parameter.
221
# Nevertheless, it is unclear on which places which extensions are added.
222
# This function also could skip the check for platform but depends
223
# how will be used, this is most general but not most effective.
224
def get_real_command(cmd):
225
"""Returns the real file commad for a module (cmd)
227
For Python scripts on MS Windows it returns full path to the script
228
and adds a '.py' extension.
229
For other cases it just returns a module (name).
230
So, you can just use this function for all without further check.
232
>>> get_real_command('g.region')
235
:param cmd: the command
237
if sys.platform == 'win32':
238
# we in fact expect pure module name (without extension)
239
# so, lets remove extension
240
if os.path.splitext(cmd)[1] == '.py':
242
full_path = shutil_which(cmd + '.py')
249
def make_command(prog, flags="", overwrite=False, quiet=False, verbose=False,
250
errors=None, **options):
251
"""Return a list of strings suitable for use as the args parameter to
252
Popen() or call(). Example:
255
>>> make_command("g.message", flags = 'w', message = 'this is a warning')
256
['g.message', '-w', 'message=this is a warning']
259
:param str prog: GRASS module
260
:param str flags: flags to be used (given as a string)
261
:param bool overwrite: True to enable overwriting the output (<tt>--o</tt>)
262
:param bool quiet: True to run quietly (<tt>--q</tt>)
263
:param bool verbose: True to run verbosely (<tt>--v</tt>)
264
:param options: module's parameters
266
:return: list of arguments
277
raise ScriptError("'-' is not a valid flag")
278
args.append("-%s" % flags)
279
for opt, val in options.iteritems():
280
if opt in _popen_args:
283
if opt.startswith('_'):
285
warning(_("To run the module <%s> add underscore at the end"
286
" of the option <%s> to avoid conflict with Python"
287
" keywords. Underscore at the beginning is"
288
" depreciated in GRASS GIS 7.0 and will be removed"
289
" in version 7.1.") % (prog, opt))
290
elif opt.endswith('_'):
292
args.append("%s=%s" % (opt, _make_val(val)))
296
def handle_errors(returncode, result, args, kwargs):
299
handler = kwargs.get('errors', 'raise')
300
if handler.lower() == 'ignore':
302
elif handler.lower() == 'status':
304
elif handler.lower() == 'exit':
307
# TODO: construction of the whole command is far from perfect
308
args = make_command(*args, **kwargs)
309
raise CalledModuleError(module=None, code=repr(args),
310
returncode=returncode)
312
def start_command(prog, flags="", overwrite=False, quiet=False,
313
verbose=False, **kwargs):
314
"""Returns a Popen object with the command created by make_command.
315
Accepts any of the arguments which Popen() accepts apart from "args"
318
>>> p = start_command("g.gisenv", stdout=subprocess.PIPE)
319
>>> print p # doctest: +ELLIPSIS
320
<...Popen object at 0x...>
321
>>> print p.communicate()[0] # doctest: +SKIP
322
GISDBASE='/opt/grass-data';
323
LOCATION_NAME='spearfish60';
325
GRASS_DB_ENCODING='ascii';
329
If the module parameter is the same as Python keyword, add
330
underscore at the end of the parameter. For example, use
331
``lambda_=1.6`` instead of ``lambda=1.6``.
333
:param str prog: GRASS module
334
:param str flags: flags to be used (given as a string)
335
:param bool overwrite: True to enable overwriting the output (<tt>--o</tt>)
336
:param bool quiet: True to run quietly (<tt>--q</tt>)
337
:param bool verbose: True to run verbosely (<tt>--v</tt>)
338
:param kwargs: module's parameters
340
:return: Popen object
344
for opt, val in kwargs.iteritems():
345
if opt in _popen_args:
348
if isinstance(val, unicode):
352
args = make_command(prog, flags, overwrite, quiet, verbose, **options)
354
if debug_level() > 0:
355
sys.stderr.write("D1/%d: %s.start_command(): %s\n" % (debug_level(),
360
return Popen(args, **popts)
363
def run_command(*args, **kwargs):
364
"""Passes all arguments to start_command(), then waits for the process to
365
complete, returning its exit code. Similar to subprocess.check_call(), but
366
with the make_command() interface.
368
:param list args: list of unnamed arguments (see start_command() for details)
369
:param list kwargs: list of named arguments (see start_command() for details)
371
:return: exit code (0 for success)
373
ps = start_command(*args, **kwargs)
374
returncode = ps.wait()
375
return handle_errors(returncode, returncode, args, kwargs)
378
def pipe_command(*args, **kwargs):
379
"""Passes all arguments to start_command(), but also adds
380
"stdout = PIPE". Returns the Popen object.
382
>>> p = pipe_command("g.gisenv")
383
>>> print p # doctest: +ELLIPSIS
384
<....Popen object at 0x...>
385
>>> print p.communicate()[0] # doctest: +SKIP
386
GISDBASE='/opt/grass-data';
387
LOCATION_NAME='spearfish60';
389
GRASS_DB_ENCODING='ascii';
393
:param list args: list of unnamed arguments (see start_command() for details)
394
:param list kwargs: list of named arguments (see start_command() for details)
396
:return: Popen object
398
kwargs['stdout'] = PIPE
399
return start_command(*args, **kwargs)
402
def feed_command(*args, **kwargs):
403
"""Passes all arguments to start_command(), but also adds
404
"stdin = PIPE". Returns the Popen object.
406
:param list args: list of unnamed arguments (see start_command() for details)
407
:param list kwargs: list of named arguments (see start_command() for details)
409
:return: Popen object
411
kwargs['stdin'] = PIPE
412
return start_command(*args, **kwargs)
415
def read_command(*args, **kwargs):
416
"""Passes all arguments to pipe_command, then waits for the process to
417
complete, returning its stdout (i.e. similar to shell `backticks`).
419
:param list args: list of unnamed arguments (see start_command() for details)
420
:param list kwargs: list of named arguments (see start_command() for details)
424
process = pipe_command(*args, **kwargs)
425
stdout, unused = process.communicate()
426
returncode = process.poll()
427
return handle_errors(returncode, stdout, args, kwargs)
430
def parse_command(*args, **kwargs):
431
"""Passes all arguments to read_command, then parses the output
434
Parsing function can be optionally given by <em>parse</em> parameter
435
including its arguments, e.g.
439
parse_command(..., parse = (grass.parse_key_val, { 'sep' : ':' }))
441
or you can simply define <em>delimiter</em>
445
parse_command(..., delimiter = ':')
447
:param args: list of unnamed arguments (see start_command() for details)
448
:param kwargs: list of named arguments (see start_command() for details)
450
:return: parsed module output
454
if 'parse' in kwargs:
455
if type(kwargs['parse']) is types.TupleType:
456
parse = kwargs['parse'][0]
457
parse_args = kwargs['parse'][1]
460
if 'delimiter' in kwargs:
461
parse_args = {'sep': kwargs['delimiter']}
462
del kwargs['delimiter']
465
parse = parse_key_val # use default fn
467
res = read_command(*args, **kwargs)
469
return parse(res, **parse_args)
472
def write_command(*args, **kwargs):
473
"""Passes all arguments to feed_command, with the string specified
474
by the 'stdin' argument fed to the process' stdin.
476
:param list args: list of unnamed arguments (see start_command() for details)
477
:param list kwargs: list of named arguments (see start_command() for details)
481
stdin = kwargs['stdin']
482
process = feed_command(*args, **kwargs)
483
process.communicate(stdin)
484
returncode = process.poll()
485
return handle_errors(returncode, returncode, args, kwargs)
488
def exec_command(prog, flags="", overwrite=False, quiet=False, verbose=False,
490
"""Interface to os.execvpe(), but with the make_command() interface.
492
:param str prog: GRASS module
493
:param str flags: flags to be used (given as a string)
494
:param bool overwrite: True to enable overwriting the output (<tt>--o</tt>)
495
:param bool quiet: True to run quietly (<tt>--q</tt>)
496
:param bool verbose: True to run verbosely (<tt>--v</tt>)
497
:param env: directory with environmental variables
498
:param list kwargs: module's parameters
501
args = make_command(prog, flags, overwrite, quiet, verbose, **kwargs)
505
os.execvpe(prog, args, env)
507
# interface to g.message
510
def message(msg, flag=None):
511
"""Display a message using `g.message`
513
:param str msg: message to be displayed
514
:param str flag: flags (given as string)
516
run_command("g.message", flags=flag, message=msg, errors='ignore')
519
def debug(msg, debug=1):
520
"""Display a debugging message using `g.message -d`
522
:param str msg: debugging message to be displayed
523
:param str debug: debug level (0-5)
525
if debug_level() >= debug:
526
if sys.platform == "win32":
527
msg = msg.replace('&', '^&')
529
run_command("g.message", flags='d', message=msg, debug=debug)
532
"""Display a verbose message using `g.message -v`
534
:param str msg: verbose message to be displayed
536
message(msg, flag='v')
540
"""Display an informational message using `g.message -i`
542
:param str msg: informational message to be displayed
544
message(msg, flag='i')
547
def percent(i, n, s):
548
"""Display a progress info message using `g.message -p`
552
message(_("Percent complete..."))
558
:param int i: current item
559
:param int n: total number of items
560
:param int s: increment size
562
message("%d %d %d" % (i, n, s), flag='p')
566
"""Display a warning message using `g.message -w`
568
:param str msg: warning message to be displayed
570
message(msg, flag='w')
574
"""Display an error message using `g.message -e`
576
This function does not end the execution of the program.
577
The right action after the error is up to the caller.
578
For error handling using the standard mechanism use :func:`fatal`.
580
:param str msg: error message to be displayed
582
message(msg, flag='e')
586
"""Display an error message using `g.message -e`, then abort or raise
588
Raises exception when module global raise_on_error is 'True', abort
589
(calls exit) otherwise.
590
Use func:`set_raise_on_error` to set the behavior.
592
:param str msg: error message to be displayed
594
global raise_on_error
596
raise ScriptError(msg)
602
def set_raise_on_error(raise_exp=True):
603
"""Define behaviour on fatal error (fatal() called)
605
:param bool raise_exp: True to raise ScriptError instead of calling
606
sys.exit(1) in fatal()
608
:return: current status
610
global raise_on_error
611
tmp_raise = raise_on_error
612
raise_on_error = raise_exp
616
def get_raise_on_error():
617
"""Return True if a ScriptError exception is raised instead of calling
618
sys.exit(1) in case a fatal error was invoked with fatal()
620
global raise_on_error
621
return raise_on_error
623
# interface to g.parser
626
def _parse_opts(lines):
633
[var, val] = line.split('=', 1)
635
raise SyntaxError("invalid output from g.parser: %s" % line)
637
if var.startswith('flag_'):
638
flags[var[5:]] = bool(int(val))
639
elif var.startswith('opt_'):
640
options[var[4:]] = val
641
elif var in ['GRASS_OVERWRITE', 'GRASS_VERBOSE']:
642
os.environ[var] = val
644
raise SyntaxError("invalid output from g.parser: %s" % line)
646
return (options, flags)
650
"""Interface to g.parser, intended to be run from the top-level, e.g.:
654
if __name__ == "__main__":
655
options, flags = grass.parser()
658
Thereafter, the global variables "options" and "flags" will be
659
dictionaries containing option/flag values, keyed by lower-case
660
option/flag names. The values in "options" are strings, those in
661
"flags" are Python booleans.
663
if not os.getenv("GISBASE"):
664
print >> sys.stderr, "You must be in GRASS GIS to run this program."
667
cmdline = [basename(sys.argv[0])]
668
cmdline += ['"' + arg + '"' for arg in sys.argv[1:]]
669
os.environ['CMDLINE'] = ' '.join(cmdline)
673
if not os.path.isabs(name):
674
if os.sep in name or (os.altsep and os.altsep in name):
675
argv[0] = os.path.abspath(name)
677
argv[0] = os.path.join(sys.path[0], name)
679
prog = "g.parser.exe" if sys.platform == "win32" else "g.parser"
680
p = subprocess.Popen([prog, '-n'] + argv, stdout=subprocess.PIPE)
681
s = p.communicate()[0]
682
lines = s.split('\0')
684
if not lines or lines[0] != "@ARGS_PARSED@":
686
sys.exit(p.returncode)
688
return _parse_opts(lines[1:])
690
# interface to g.tempfile
693
def tempfile(create=True):
694
"""Returns the name of a temporary file, created with g.tempfile.
696
:param bool create: True to create a file
698
:return: path to a tmp file
704
return read_command("g.tempfile", flags=flags, pid=os.getpid()).strip()
708
"""Returns the name of a temporary dir, created with g.tempfile."""
709
tmp = tempfile(create=False)
715
def _compare_projection(dic):
716
"""Check if projection has some possibility of duplicate names like
717
Universal Transverse Mercator and Universe Transverse Mercator and
720
:param dic: The dictionary containing information about projection
722
:return: The dictionary with the new values if needed
725
# the lookup variable is a list of list, each list contains all the
726
# possible name for a projection system
727
lookup = [['Universal Transverse Mercator', 'Universe Transverse Mercator']]
729
for n in range(len(dic['name'])):
730
if dic['name'][n] in lo:
731
dic['name'][n] = lo[0]
735
def _compare_units(dic):
736
"""Check if units has some possibility of duplicate names like
737
meter and metre and unify them
739
:param dic: The dictionary containing information about units
741
:return: The dictionary with the new values if needed
744
# the lookup variable is a list of list, each list contains all the
745
# possible name for a units
746
lookup = [['meter', 'metre'], ['meters', 'metres'], ['kilometer',
747
'kilometre'], ['kilometers', 'kilometres']]
749
for n in range(len(dic['unit'])):
750
if dic['unit'][n].lower() in l:
751
dic['unit'][n] = l[0]
752
for n in range(len(dic['units'])):
753
if dic['units'][n].lower() in l:
754
dic['units'][n] = l[0]
758
def _text_to_key_value_dict(filename, sep=":", val_sep=",", checkproj=False,
760
"""Convert a key-value text file, where entries are separated by newlines
761
and the key and value are separated by `sep', into a key-value dictionary
762
and discover/use the correct data types (float, int or string) for values.
764
:param str filename: The name or name and path of the text file to convert
765
:param str sep: The character that separates the keys and values, default
767
:param str val_sep: The character that separates the values of a single
769
:param bool checkproj: True if it has to check some information about
771
:param bool checkproj: True if it has to check some information about units
773
:return: The dictionary
775
A text file with this content:
783
Will be represented as this dictionary:
787
{'a': ['Hello'], 'c': [1, 2, 3, 4, 5], 'b': [1.0], 'd': ['hello', 8, 0.1]}
790
text = open(filename, "r").readlines()
794
if line.find(sep) >= 0:
795
key, value = line.split(sep)
797
value = value.strip()
799
# Jump over empty values
801
values = value.split(val_sep)
808
# Convert values into correct types
809
# We first try integer then float
811
value_converted = int(value)
816
value_converted = float(value)
820
if not_int and not_float:
821
value_converted = value.strip()
823
value_list.append(value_converted)
825
kvdict[key] = value_list
827
kvdict = _compare_projection(kvdict)
829
kvdict = _compare_units(kvdict)
833
def compare_key_value_text_files(filename_a, filename_b, sep=":",
834
val_sep=",", precision=0.000001,
835
proj=False, units=False):
836
"""Compare two key-value text files
838
This method will print a warning in case keys that are present in the first
839
file are not present in the second one.
840
The comparison method tries to convert the values into their native format
841
(float, int or string) to allow correct comparison.
843
An example key-value text file may have this content:
852
:param str filename_a: name of the first key-value text file
853
:param str filenmae_b: name of the second key-value text file
854
:param str sep: character that separates the keys and values, default is ":"
855
:param str val_sep: character that separates the values of a single key, default is ","
856
:param double precision: precision with which the floating point values are compared
857
:param bool proj: True if it has to check some information about projection system
858
:param bool units: True if it has to check some information about units
860
:return: True if full or almost identical, False if different
862
dict_a = _text_to_key_value_dict(filename_a, sep, checkproj=proj,
864
dict_b = _text_to_key_value_dict(filename_b, sep, checkproj=proj,
867
if sorted(dict_a.keys()) != sorted(dict_b.keys()):
870
# We compare matching keys
871
for key in dict_a.keys():
872
# Floating point values must be handled separately
873
if isinstance(dict_a[key], float) and isinstance(dict_b[key], float):
874
if abs(dict_a[key] - dict_b[key]) > precision:
876
elif isinstance(dict_a[key], float) or isinstance(dict_b[key], float):
877
warning(_("Mixing value types. Will try to compare after "
878
"integer conversion"))
879
return int(dict_a[key]) == int(dict_b[key])
880
elif key == "+towgs84":
881
# We compare the sum of the entries
882
if abs(sum(dict_a[key]) - sum(dict_b[key])) > precision:
885
if dict_a[key] != dict_b[key]:
889
# interface to g.gisenv
893
"""Returns the output from running g.gisenv (with no arguments), as a
897
>>> print env['GISDBASE'] # doctest: +SKIP
900
:return: list of GRASS variables
902
s = read_command("g.gisenv", flags='n')
903
return parse_key_val(s)
905
# interface to g.region
908
def locn_is_latlong():
909
"""Tests if location is lat/long. Value is obtained
910
by checking the "g.region -pu" projection code.
912
:return: True for a lat/long region, False otherwise
914
s = read_command("g.region", flags='pu')
915
kv = parse_key_val(s, ':')
916
if kv['projection'].split(' ')[0] == '3':
922
def region(region3d=False, complete=False):
923
"""Returns the output from running "g.region -gu", as a
926
:param bool region3d: True to get 3D region
927
:param bool complete:
929
>>> curent_region = region()
930
>>> # obtain n, s, e and w values
931
>>> [curent_region[key] for key in "nsew"] # doctest: +ELLIPSIS
933
>>> # obtain ns and ew resulutions
934
>>> (curent_region['nsres'], curent_region['ewres']) # doctest: +ELLIPSIS
937
:return: dictionary of region values
945
s = read_command("g.region", flags=flgs)
946
reg = parse_key_val(s, val_type=float)
947
for k in ['rows', 'cols', 'cells',
948
'rows3', 'cols3', 'cells3', 'depths']:
956
def region_env(region3d=False, **kwargs):
957
"""Returns region settings as a string which can used as
958
GRASS_REGION environmental variable.
960
If no 'kwargs' are given then the current region is used. Note
961
that this function doesn't modify the current region!
963
See also use_temp_region() for alternative method how to define
964
temporary region used for raster-based computation.
966
:param bool region3d: True to get 3D region
967
:param kwargs: g.region's parameters like 'raster', 'vector' or 'region'
971
os.environ['GRASS_REGION'] = grass.region_env(region='detail')
972
grass.mapcalc('map=1', overwrite=True)
973
os.environ.pop('GRASS_REGION')
975
:return: string with region values
976
:return: empty string on error
978
# read proj/zone from WIND file
980
windfile = os.path.join(env['GISDBASE'], env['LOCATION_NAME'],
981
env['MAPSET'], "WIND")
982
fd = open(windfile, "r")
984
for line in fd.readlines():
985
key, value = map(lambda x: x.strip(), line.split(":", 1))
986
if kwargs and key not in ('proj', 'zone'):
988
if not kwargs and not region3d and \
989
key in ('top', 'bottom', 'cols3', 'rows3',
990
'depths', 'e-w resol3', 'n-s resol3', 't-b resol'):
993
grass_region += '%s: %s;' % (key, value)
995
if not kwargs: # return current region
998
# read other values from `g.region -gu`
1003
s = read_command('g.region', flags=flgs, **kwargs)
1006
reg = parse_key_val(s)
1008
kwdata = [('north', 'n'),
1014
('e-w resol', 'ewres'),
1015
('n-s resol', 'nsres')]
1017
kwdata += [('top', 't'),
1021
('depths', 'depths'),
1022
('e-w resol3', 'ewres3'),
1023
('n-s resol3', 'nsres3'),
1024
('t-b resol', 'tbres')]
1026
for wkey, rkey in kwdata:
1027
grass_region += '%s: %s;' % (wkey, reg[rkey])
1032
def use_temp_region():
1033
"""Copies the current region to a temporary region with "g.region save=",
1034
then sets WIND_OVERRIDE to refer to that region. Installs an atexit
1035
handler to delete the temporary region upon termination.
1037
name = "tmp.%s.%d" % (os.path.basename(sys.argv[0]), os.getpid())
1038
run_command("g.region", save=name, overwrite=True)
1039
os.environ['WIND_OVERRIDE'] = name
1040
atexit.register(del_temp_region)
1043
def del_temp_region():
1044
"""Unsets WIND_OVERRIDE and removes any region named by it."""
1046
name = os.environ.pop('WIND_OVERRIDE')
1047
run_command("g.remove", flags='f', quiet=True, type='region', name=name)
1051
# interface to g.findfile
1054
def find_file(name, element='cell', mapset=None):
1055
"""Returns the output from running g.findfile as a
1056
dictionary. Example:
1058
>>> result = find_file('elevation', element='cell')
1059
>>> print result['fullname']
1061
>>> print result['file'] # doctest: +ELLIPSIS
1062
/.../PERMANENT/cell/elevation
1065
:param str name: file name
1066
:param str element: element type (default 'cell')
1067
:param str mapset: mapset name (default all mapsets in search path)
1069
:return: parsed output of g.findfile
1071
if element == 'raster' or element == 'rast':
1072
verbose(_('Element type should be "cell" and not "%s"') % element)
1074
# g.findfile returns non-zero when file was not found
1075
# se we ignore return code and just focus on stdout
1076
process = start_command('g.findfile', flags='n',
1077
element=element, file=name, mapset=mapset,
1079
stdout = process.communicate()[0]
1080
return parse_key_val(stdout)
1082
# interface to g.list
1085
def list_strings(type, pattern=None, mapset=None, exclude=None, flag=''):
1086
"""List of elements as strings.
1088
Returns the output from running g.list, as a list of qualified
1091
:param str type: element type (raster, vector, raster_3d, region, ...)
1092
:param str pattern: pattern string
1093
:param str mapset: mapset name (if not given use search path)
1094
:param str exclude: pattern string to exclude maps from the research
1095
:param str flag: pattern type: 'r' (basic regexp), 'e' (extended regexp),
1096
or '' (glob pattern)
1098
:return: list of elements
1101
verbose(_('Element type should be "raster" and not "%s"') % type)
1104
for line in read_command("g.list",
1110
mapset=mapset).splitlines():
1111
result.append(line.strip())
1116
def list_pairs(type, pattern=None, mapset=None, exclude=None, flag=''):
1117
"""List of elements as pairs
1119
Returns the output from running g.list, as a list of
1120
(name, mapset) pairs
1122
:param str type: element type (raster, vector, raster_3d, region, ...)
1123
:param str pattern: pattern string
1124
:param str mapset: mapset name (if not given use search path)
1125
:param str exclude: pattern string to exclude maps from the research
1126
:param str flag: pattern type: 'r' (basic regexp), 'e' (extended regexp),
1127
or '' (glob pattern)
1129
:return: list of elements
1131
return [tuple(map.split('@', 1)) for map in list_strings(type, pattern,
1136
def list_grouped(type, pattern=None, check_search_path=True, exclude=None,
1138
"""List of elements grouped by mapsets.
1140
Returns the output from running g.list, as a dictionary where the
1141
keys are mapset names and the values are lists of maps in that
1144
>>> list_grouped('vect', pattern='*roads*')['PERMANENT']
1145
['railroads', 'roadsmajor']
1147
:param str type: element type (raster, vector, raster_3d, region, ...) or list of elements
1148
:param str pattern: pattern string
1149
:param str check_search_path: True to add mapsets for the search path
1150
with no found elements
1151
:param str exclude: pattern string to exclude maps from the research
1152
:param str flag: pattern type: 'r' (basic regexp), 'e' (extended regexp),
1153
or '' (glob pattern)
1155
:return: directory of mapsets/elements
1157
if isinstance(type, python_types.StringTypes) or len(type) == 1:
1164
for i in range(len(types)):
1165
if types[i] == 'cell':
1166
verbose(_('Element type should be "raster" and not "%s"') % types[i])
1169
if check_search_path:
1170
for mapset in mapsets(search_path=True):
1177
for line in read_command("g.list", quiet=True, flags="m" + flag,
1178
type=types, pattern=pattern, exclude=exclude).splitlines():
1180
name, mapset = line.split('@')
1182
warning(_("Invalid element '%s'") % line)
1186
type_, name = name.split('/')
1187
if mapset in result:
1188
if type_ in result[mapset]:
1189
result[mapset][type_].append(name)
1191
result[mapset][type_] = [name, ]
1193
result[mapset] = {type_: [name, ]}
1195
if mapset in result:
1196
result[mapset].append(name)
1198
result[mapset] = [name, ]
1205
"white": (1.00, 1.00, 1.00),
1206
"black": (0.00, 0.00, 0.00),
1207
"red": (1.00, 0.00, 0.00),
1208
"green": (0.00, 1.00, 0.00),
1209
"blue": (0.00, 0.00, 1.00),
1210
"yellow": (1.00, 1.00, 0.00),
1211
"magenta": (1.00, 0.00, 1.00),
1212
"cyan": (0.00, 1.00, 1.00),
1213
"aqua": (0.00, 0.75, 0.75),
1214
"grey": (0.75, 0.75, 0.75),
1215
"gray": (0.75, 0.75, 0.75),
1216
"orange": (1.00, 0.50, 0.00),
1217
"brown": (0.75, 0.50, 0.25),
1218
"purple": (0.50, 0.00, 1.00),
1219
"violet": (0.50, 0.00, 1.00),
1220
"indigo": (0.00, 0.50, 1.00)}
1223
def parse_color(val, dflt=None):
1224
"""Parses the string "val" as a GRASS colour, which can be either one of
1225
the named colours or an R:G:B tuple e.g. 255:255:255. Returns an
1226
(r,g,b) triple whose components are floating point values between 0
1229
>>> parse_color("red")
1231
>>> parse_color("255:0:0")
1234
:param val: color value
1235
:param dflt: default color value
1239
if val in named_colors:
1240
return named_colors[val]
1242
vals = val.split(':')
1244
return tuple(float(v) / 255 for v in vals)
1248
# check GRASS_OVERWRITE
1252
"""Return True if existing files may be overwritten"""
1253
owstr = 'GRASS_OVERWRITE'
1254
return owstr in os.environ and os.environ[owstr] != '0'
1256
# check GRASS_VERBOSE
1260
"""Return the verbosity level selected by GRASS_VERBOSE"""
1261
vbstr = os.getenv('GRASS_VERBOSE')
1267
## various utilities, not specific to GRASS
1269
def find_program(pgm, *args):
1270
"""Attempt to run a program, with optional arguments.
1272
You must call the program in a way that will return a successful
1273
exit code. For GRASS modules this means you need to pass it some
1274
valid CLI option, like "--help". For other programs a common
1275
valid do-little option is usually "--version".
1279
>>> find_program('r.sun', '--help')
1281
>>> find_program('ls', '--version')
1284
:param str pgm: program name
1285
:param args: list of arguments
1287
:return: False if the attempt failed due to a missing executable
1288
or non-zero return code
1289
:return: True otherwise
1291
nuldev = file(os.devnull, 'w+')
1293
# TODO: the doc or impl is not correct, any return code is accepted
1294
call([pgm] + list(args), stdin = nuldev, stdout = nuldev, stderr = nuldev)
1302
# interface to g.mapsets
1305
def mapsets(search_path=False):
1306
"""List available mapsets
1308
:param bool search_path: True to list mapsets only in search path
1310
:return: list of mapsets
1316
mapsets = read_command('g.mapsets',
1321
fatal(_("Unable to list mapsets"))
1323
return mapsets.splitlines()
1325
# interface to `g.proj -c`
1328
def create_location(dbase, location, epsg=None, proj4=None, filename=None,
1329
wkt=None, datum=None, datum_trans=None, desc=None,
1331
"""Create new location
1333
Raise ScriptError on error.
1335
:param str dbase: path to GRASS database
1336
:param str location: location name to create
1337
:param epsg: if given create new location based on EPSG code
1338
:param proj4: if given create new location based on Proj4 definition
1339
:param str filename: if given create new location based on georeferenced file
1340
:param str wkt: if given create new location based on WKT definition
1342
:param datum: GRASS format datum code
1343
:param datum_trans: datum transformation parameters (used for epsg and proj4)
1344
:param desc: description of the location (creates MYNAME file)
1345
:param bool overwrite: True to overwrite location if exists(WARNING:
1346
ALL DATA from existing location ARE DELETED!)
1349
if epsg or proj4 or filename or wkt:
1350
# FIXME: changing GISDBASE mid-session is not background-job safe
1351
gisdbase = gisenv()['GISDBASE']
1352
run_command('g.gisenv', set='GISDBASE=%s' % dbase)
1353
# create dbase if not exists
1354
if not os.path.exists(dbase):
1357
# check if location already exists
1358
if os.path.exists(os.path.join(dbase, location)):
1360
warning(_("Location <%s> already exists. Operation canceled.") % location)
1363
warning(_("Location <%s> already exists and will be overwritten") % location)
1364
shutil.rmtree(os.path.join(dbase, location))
1368
kwargs['datum'] = datum
1370
kwargs['datum_trans'] = datum_trans
1373
ps = pipe_command('g.proj', quiet=True, flags='t', epsg=epsg,
1374
location=location, stderr=PIPE, **kwargs)
1376
ps = pipe_command('g.proj', quiet=True, flags='t', proj4=proj4,
1377
location=location, stderr=PIPE, **kwargs)
1379
ps = pipe_command('g.proj', quiet=True, georef=filename,
1380
location=location, stderr=PIPE)
1382
ps = pipe_command('g.proj', quiet=True, wkt=wkt, location=location,
1385
_create_location_xy(dbase, location)
1387
if epsg or proj4 or filename or wkt:
1388
error = ps.communicate()[1]
1389
run_command('g.gisenv', set='GISDBASE=%s' % gisdbase)
1391
if ps.returncode != 0 and error:
1392
raise ScriptError(repr(error))
1395
fd = codecs.open(os.path.join(dbase, location, 'PERMANENT', 'MYNAME'),
1396
encoding='utf-8', mode='w')
1398
fd.write(desc + os.linesep)
1400
fd.write(os.linesep)
1402
except OSError as e:
1403
raise ScriptError(repr(e))
1406
def _create_location_xy(database, location):
1407
"""Create unprojected location
1409
Raise ScriptError on error.
1411
:param database: GRASS database where to create new location
1412
:param location: location name
1414
cur_dir = os.getcwd()
1418
os.mkdir(os.path.join(location, 'PERMANENT'))
1420
# create DEFAULT_WIND and WIND files
1421
regioninfo = ['proj: 0',
1440
defwind = open(os.path.join(location,
1441
"PERMANENT", "DEFAULT_WIND"), 'w')
1442
for param in regioninfo:
1443
defwind.write(param + '%s' % os.linesep)
1446
shutil.copy(os.path.join(location, "PERMANENT", "DEFAULT_WIND"),
1447
os.path.join(location, "PERMANENT", "WIND"))
1450
except OSError as e:
1451
raise ScriptError(repr(e))
1453
# interface to g.version
1457
"""Get GRASS version as dictionary
1463
{'proj4': '4.8.0', 'geos': '3.3.5', 'libgis_revision': '52468',
1464
'libgis_date': '2012-07-27 22:53:30 +0200 (Fri, 27 Jul 2012)',
1465
'version': '7.0.svn', 'date': '2012', 'gdal': '2.0dev',
1466
'revision': '53670'}
1469
data = parse_command('g.version', flags='rge', errors='ignore')
1470
for k, v in data.iteritems():
1471
data[k.strip()] = v.replace('"', '').strip()
1481
if _debug_level is not None:
1484
if find_program('g.gisenv', '--help'):
1485
_debug_level = int(gisenv().get('DEBUG', 0))
1490
"""Checks if the string contains only allowed characters.
1492
This is the Python implementation of G_legal_filename() function.
1496
It is not clear when to use this function.
1498
if not s or s[0] == '.':
1499
warning(_("Illegal filename <%s>. Cannot be 'NULL' or start with " \
1505
if c in '/"\'@,=*~' or c <= ' ' or c >= '\177']
1507
illegal = ''.join(sorted(set(illegal)))
1508
warning(_("Illegal filename <%(s)s>. <%(il)s> not allowed.\n") % {
1509
's': s, 'il': illegal})
1515
if __name__ == '__main__':