core.py 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505
  1. """!@package grass.script.core
  2. @brief GRASS Python scripting module (core functions)
  3. Core functions to be used in Python scripts.
  4. Usage:
  5. @code
  6. from grass.script import core as grass
  7. grass.parser()
  8. ...
  9. @endcode
  10. (C) 2008-2011 by the GRASS Development Team
  11. This program is free software under the GNU General Public
  12. License (>=v2). Read the file COPYING that comes with GRASS
  13. for details.
  14. @author Glynn Clements
  15. @author Martin Landa <landa.martin gmail.com>
  16. @author Michael Barton <michael.barton asu.edu>
  17. """
  18. import os
  19. import sys
  20. import types
  21. import re
  22. import atexit
  23. import subprocess
  24. import shutil
  25. import locale
  26. import codecs
  27. # i18N
  28. import gettext
  29. gettext.install('grasslibs', os.path.join(os.getenv("GISBASE"), 'locale'))
  30. # subprocess wrapper that uses shell on Windows
  31. class Popen(subprocess.Popen):
  32. def __init__(self, args, bufsize=0, executable=None,
  33. stdin=None, stdout=None, stderr=None,
  34. preexec_fn=None, close_fds=False, shell=None,
  35. cwd=None, env=None, universal_newlines=False,
  36. startupinfo=None, creationflags=0):
  37. if shell == None:
  38. shell = (sys.platform == "win32")
  39. subprocess.Popen.__init__(self, args, bufsize, executable,
  40. stdin, stdout, stderr,
  41. preexec_fn, close_fds, shell,
  42. cwd, env, universal_newlines,
  43. startupinfo, creationflags)
  44. PIPE = subprocess.PIPE
  45. STDOUT = subprocess.STDOUT
  46. class ScriptError(Exception):
  47. def __init__(self, msg):
  48. self.value = msg
  49. def __str__(self):
  50. return self.value
  51. raise_on_error = False # raise exception instead of calling fatal()
  52. def call(*args, **kwargs):
  53. return Popen(*args, **kwargs).wait()
  54. # GRASS-oriented interface to subprocess module
  55. _popen_args = ["bufsize", "executable", "stdin", "stdout", "stderr",
  56. "preexec_fn", "close_fds", "cwd", "env",
  57. "universal_newlines", "startupinfo", "creationflags"]
  58. def decode(string):
  59. enc = locale.getdefaultlocale()[1]
  60. if enc:
  61. return string.decode(enc)
  62. return string
  63. def _make_val(val):
  64. if isinstance(val, types.StringType) or \
  65. isinstance(val, types.UnicodeType):
  66. return val
  67. if isinstance(val, types.ListType):
  68. return ",".join(map(_make_val, val))
  69. if isinstance(val, types.TupleType):
  70. return _make_val(list(val))
  71. return str(val)
  72. def get_commands():
  73. """!Create list of available GRASS commands to use when parsing
  74. string from the command line
  75. @return list of commands (set) and directory of scripts (collected
  76. by extension - MS Windows only)
  77. @code
  78. >>> cmds = list(get_commands()[0])
  79. >>> cmds.sort()
  80. >>> cmds[:5]
  81. ['d.barscale', 'd.colorlist', 'd.colortable', 'd.correlate', 'd.erase']
  82. @endcode
  83. """
  84. gisbase = os.environ['GISBASE']
  85. cmd = list()
  86. scripts = {'.py': list()} if sys.platform == 'win32' else {}
  87. def scan(gisbase, directory):
  88. dir_path = os.path.join(gisbase, directory)
  89. if os.path.exists(dir_path):
  90. for fname in os.listdir(os.path.join(gisbase, directory)):
  91. if scripts: # win32
  92. name, ext = os.path.splitext(fname)
  93. if ext != '.manifest':
  94. cmd.append(name)
  95. if ext in scripts.keys():
  96. scripts[ext].append(name)
  97. else:
  98. cmd.append(fname)
  99. for directory in ('bin', 'scripts'):
  100. scan(gisbase, directory)
  101. # scan gui/scripts/
  102. gui_path = os.path.join(gisbase, 'etc', 'gui', 'scripts')
  103. if os.path.exists(gui_path):
  104. os.environ["PATH"] = os.getenv("PATH") + os.pathsep + gui_path
  105. cmd = cmd + os.listdir(gui_path)
  106. return set(cmd), scripts
  107. def make_command(prog, flags="", overwrite=False, quiet=False, verbose=False,
  108. **options):
  109. """!Return a list of strings suitable for use as the args parameter to
  110. Popen() or call(). Example:
  111. @code
  112. >>> make_command("g.message", flags = 'w', message = 'this is a warning')
  113. ['g.message', '-w', 'message=this is a warning']
  114. @endcode
  115. @param prog GRASS module
  116. @param flags flags to be used (given as a string)
  117. @param overwrite True to enable overwriting the output (<tt>--o</tt>)
  118. @param quiet True to run quietly (<tt>--q</tt>)
  119. @param verbose True to run verbosely (<tt>--v</tt>)
  120. @param options module's parameters
  121. @return list of arguments
  122. """
  123. args = [prog]
  124. if overwrite:
  125. args.append("--o")
  126. if quiet:
  127. args.append("--q")
  128. if verbose:
  129. args.append("--v")
  130. if flags:
  131. if '-' in flags:
  132. raise ScriptError("'-' is not a valid flag")
  133. args.append("-%s" % flags)
  134. for opt, val in options.iteritems():
  135. if val != None:
  136. if opt[0] == '_':
  137. opt = opt[1:]
  138. args.append("%s=%s" % (opt, _make_val(val)))
  139. return args
  140. def start_command(prog, flags="", overwrite=False, quiet=False,
  141. verbose=False, **kwargs):
  142. """!Returns a Popen object with the command created by make_command.
  143. Accepts any of the arguments which Popen() accepts apart from "args"
  144. and "shell".
  145. @code
  146. >>> p = start_command("g.gisenv", stdout=subprocess.PIPE)
  147. >>> print p # doctest: +ELLIPSIS
  148. <...Popen object at 0x...>
  149. >>> print p.communicate()[0] # doctest: +SKIP
  150. GISDBASE='/opt/grass-data';
  151. LOCATION_NAME='spearfish60';
  152. MAPSET='glynn';
  153. GRASS_DB_ENCODING='ascii';
  154. GUI='text';
  155. MONITOR='x0';
  156. @endcode
  157. @param prog GRASS module
  158. @param flags flags to be used (given as a string)
  159. @param overwrite True to enable overwriting the output (<tt>--o</tt>)
  160. @param quiet True to run quietly (<tt>--q</tt>)
  161. @param verbose True to run verbosely (<tt>--v</tt>)
  162. @param kwargs module's parameters
  163. @return Popen object
  164. """
  165. options = {}
  166. popts = {}
  167. for opt, val in kwargs.iteritems():
  168. if opt in _popen_args:
  169. popts[opt] = val
  170. else:
  171. options[opt] = val
  172. args = make_command(prog, flags, overwrite, quiet, verbose, **options)
  173. if debug_level() > 0:
  174. sys.stderr.write("D1/%d: %s.start_command(): %s\n" % (debug_level(),
  175. __name__,
  176. ' '.join(args)))
  177. sys.stderr.flush()
  178. return Popen(args, **popts)
  179. def run_command(*args, **kwargs):
  180. """!Passes all arguments to start_command(), then waits for the process to
  181. complete, returning its exit code. Similar to subprocess.call(), but
  182. with the make_command() interface.
  183. @param args list of unnamed arguments (see start_command() for details)
  184. @param kwargs list of named arguments (see start_command() for details)
  185. @return exit code (0 for success)
  186. """
  187. ps = start_command(*args, **kwargs)
  188. return ps.wait()
  189. def pipe_command(*args, **kwargs):
  190. """!Passes all arguments to start_command(), but also adds
  191. "stdout = PIPE". Returns the Popen object.
  192. @code
  193. >>> p = pipe_command("g.gisenv")
  194. >>> print p # doctest: +ELLIPSIS
  195. <....Popen object at 0x...>
  196. >>> print p.communicate()[0] # doctest: +SKIP
  197. GISDBASE='/opt/grass-data';
  198. LOCATION_NAME='spearfish60';
  199. MAPSET='glynn';
  200. GRASS_DB_ENCODING='ascii';
  201. GUI='text';
  202. MONITOR='x0';
  203. @endcode
  204. @param args list of unnamed arguments (see start_command() for details)
  205. @param kwargs list of named arguments (see start_command() for details)
  206. @return Popen object
  207. """
  208. kwargs['stdout'] = PIPE
  209. return start_command(*args, **kwargs)
  210. def feed_command(*args, **kwargs):
  211. """!Passes all arguments to start_command(), but also adds
  212. "stdin = PIPE". Returns the Popen object.
  213. @param args list of unnamed arguments (see start_command() for details)
  214. @param kwargs list of named arguments (see start_command() for details)
  215. @return Popen object
  216. """
  217. kwargs['stdin'] = PIPE
  218. return start_command(*args, **kwargs)
  219. def read_command(*args, **kwargs):
  220. """!Passes all arguments to pipe_command, then waits for the process to
  221. complete, returning its stdout (i.e. similar to shell `backticks`).
  222. @param args list of unnamed arguments (see start_command() for details)
  223. @param kwargs list of named arguments (see start_command() for details)
  224. @return stdout
  225. """
  226. ps = pipe_command(*args, **kwargs)
  227. return ps.communicate()[0]
  228. def parse_command(*args, **kwargs):
  229. """!Passes all arguments to read_command, then parses the output
  230. by parse_key_val().
  231. Parsing function can be optionally given by <em>parse</em> parameter
  232. including its arguments, e.g.
  233. @code
  234. parse_command(..., parse = (grass.parse_key_val, { 'sep' : ':' }))
  235. @endcode
  236. or you can simply define <em>delimiter</em>
  237. @code
  238. parse_command(..., delimiter = ':')
  239. @endcode
  240. @param args list of unnamed arguments (see start_command() for details)
  241. @param kwargs list of named arguments (see start_command() for details)
  242. @return parsed module output
  243. """
  244. parse = None
  245. parse_args = {}
  246. if 'parse' in kwargs:
  247. if type(kwargs['parse']) is types.TupleType:
  248. parse = kwargs['parse'][0]
  249. parse_args = kwargs['parse'][1]
  250. del kwargs['parse']
  251. if 'delimiter' in kwargs:
  252. parse_args = {'sep': kwargs['delimiter']}
  253. del kwargs['delimiter']
  254. if not parse:
  255. parse = parse_key_val # use default fn
  256. res = read_command(*args, **kwargs)
  257. return parse(res, **parse_args)
  258. def write_command(*args, **kwargs):
  259. """!Passes all arguments to feed_command, with the string specified
  260. by the 'stdin' argument fed to the process' stdin.
  261. @param args list of unnamed arguments (see start_command() for details)
  262. @param kwargs list of named arguments (see start_command() for details)
  263. @return return code
  264. """
  265. stdin = kwargs['stdin']
  266. p = feed_command(*args, **kwargs)
  267. p.stdin.write(stdin)
  268. p.stdin.close()
  269. return p.wait()
  270. def exec_command(prog, flags="", overwrite=False, quiet=False, verbose=False,
  271. env=None, **kwargs):
  272. """!Interface to os.execvpe(), but with the make_command() interface.
  273. @param prog GRASS module
  274. @param flags flags to be used (given as a string)
  275. @param overwrite True to enable overwriting the output (<tt>--o</tt>)
  276. @param quiet True to run quietly (<tt>--q</tt>)
  277. @param verbose True to run verbosely (<tt>--v</tt>)
  278. @param env directory with environmental variables
  279. @param kwargs module's parameters
  280. """
  281. args = make_command(prog, flags, overwrite, quiet, verbose, **kwargs)
  282. if env == None:
  283. env = os.environ
  284. os.execvpe(prog, args, env)
  285. # interface to g.message
  286. def message(msg, flag=None):
  287. """!Display a message using `g.message`
  288. @param msg message to be displayed
  289. @param flag flags (given as string)
  290. """
  291. run_command("g.message", flags=flag, message=msg)
  292. def debug(msg, debug=1):
  293. """!Display a debugging message using `g.message -d`
  294. @param msg debugging message to be displayed
  295. @param debug debug level (0-5)
  296. """
  297. run_command("g.message", flags='d', message=msg, debug=debug)
  298. def verbose(msg):
  299. """!Display a verbose message using `g.message -v`
  300. @param msg verbose message to be displayed
  301. """
  302. message(msg, flag='v')
  303. def info(msg):
  304. """!Display an informational message using `g.message -i`
  305. @param msg informational message to be displayed
  306. """
  307. message(msg, flag='i')
  308. def percent(i, n, s):
  309. """!Display a progress info message using `g.message -p`
  310. @code
  311. message(_("Percent complete..."))
  312. n = 100
  313. for i in range(n):
  314. percent(i, n, 1)
  315. percent(1, 1, 1)
  316. @endcode
  317. @param i current item
  318. @param n total number of items
  319. @param s increment size
  320. """
  321. message("%d %d %d" % (i, n, s), flag='p')
  322. def warning(msg):
  323. """!Display a warning message using `g.message -w`
  324. @param msg warning message to be displayed
  325. """
  326. message(msg, flag='w')
  327. def error(msg):
  328. """!Display an error message using `g.message -e`
  329. @param msg error message to be displayed
  330. """
  331. message(msg, flag='e')
  332. def fatal(msg):
  333. """!Display an error message using `g.message -e`, then abort
  334. Raise exception when raise_on_error is 'True'.
  335. @param msg error message to be displayed
  336. """
  337. global raise_on_error
  338. if raise_on_error:
  339. raise ScriptError(msg)
  340. error(msg)
  341. sys.exit(1)
  342. def set_raise_on_error(raise_exp=True):
  343. """!Define behaviour on fatal error (fatal() called)
  344. @param raise_exp True to raise ScriptError instead of calling
  345. sys.exit(1) in fatal()
  346. @return current status
  347. """
  348. global raise_on_error
  349. tmp_raise = raise_on_error
  350. raise_on_error = raise_exp
  351. return tmp_raise
  352. # interface to g.parser
  353. def _parse_opts(lines):
  354. options = {}
  355. flags = {}
  356. for line in lines:
  357. line = line.rstrip('\r\n')
  358. if not line:
  359. break
  360. try:
  361. [var, val] = line.split('=', 1)
  362. except:
  363. raise SyntaxError("invalid output from g.parser: %s" % line)
  364. if var.startswith('flag_'):
  365. flags[var[5:]] = bool(int(val))
  366. elif var.startswith('opt_'):
  367. options[var[4:]] = val
  368. elif var in ['GRASS_OVERWRITE', 'GRASS_VERBOSE']:
  369. os.environ[var] = val
  370. else:
  371. raise SyntaxError("invalid output from g.parser: %s" % line)
  372. return (options, flags)
  373. def parser():
  374. """!Interface to g.parser, intended to be run from the top-level, e.g.:
  375. @code
  376. if __name__ == "__main__":
  377. options, flags = grass.parser()
  378. main()
  379. @endcode
  380. Thereafter, the global variables "options" and "flags" will be
  381. dictionaries containing option/flag values, keyed by lower-case
  382. option/flag names. The values in "options" are strings, those in
  383. "flags" are Python booleans.
  384. """
  385. if not os.getenv("GISBASE"):
  386. print >> sys.stderr, "You must be in GRASS GIS to run this program."
  387. sys.exit(1)
  388. cmdline = [basename(sys.argv[0])]
  389. cmdline += ['"' + arg + '"' for arg in sys.argv[1:]]
  390. os.environ['CMDLINE'] = ' '.join(cmdline)
  391. argv = sys.argv[:]
  392. name = argv[0]
  393. if not os.path.isabs(name):
  394. if os.sep in name or (os.altsep and os.altsep in name):
  395. argv[0] = os.path.abspath(name)
  396. else:
  397. argv[0] = os.path.join(sys.path[0], name)
  398. p = Popen(['g.parser', '-s'] + argv, stdout=PIPE)
  399. s = p.communicate()[0]
  400. lines = s.splitlines()
  401. if not lines or lines[0].rstrip('\r\n') != "@ARGS_PARSED@":
  402. sys.stdout.write(s)
  403. sys.exit(p.returncode)
  404. return _parse_opts(lines[1:])
  405. # interface to g.tempfile
  406. def tempfile(create=True):
  407. """!Returns the name of a temporary file, created with
  408. g.tempfile.
  409. @param create True to create a file
  410. @return path to a tmp file
  411. """
  412. flags = ''
  413. if not create:
  414. flags += 'd'
  415. return read_command("g.tempfile", flags=flags, pid=os.getpid()).strip()
  416. def tempdir():
  417. """!Returns the name of a temporary dir, created with g.tempfile."""
  418. tmp = tempfile(create=False)
  419. os.mkdir(tmp)
  420. return tmp
  421. class KeyValue(dict):
  422. """A general-purpose key-value store.
  423. KeyValue is a subclass of dict, but also allows entries to be read and
  424. written using attribute syntax. Example:
  425. \code
  426. >>> reg = KeyValue()
  427. >>> reg['north'] = 489
  428. >>> reg.north
  429. 489
  430. >>> reg.south = 205
  431. >>> reg['south']
  432. 205
  433. \endcode
  434. """
  435. def __getattr__(self, key):
  436. return self[key]
  437. def __setattr__(self, key, value):
  438. self[key] = value
  439. # key-value parsers
  440. def parse_key_val(s, sep='=', dflt=None, val_type=None, vsep=None):
  441. """!Parse a string into a dictionary, where entries are separated
  442. by newlines and the key and value are separated by `sep' (default: `=')
  443. @param s string to be parsed
  444. @param sep key/value separator
  445. @param dflt default value to be used
  446. @param val_type value type (None for no cast)
  447. @param vsep vertical separator (default os.linesep)
  448. @return parsed input (dictionary of keys/values)
  449. """
  450. result = KeyValue()
  451. if not s:
  452. return result
  453. if vsep:
  454. lines = s.split(vsep)
  455. try:
  456. lines.remove('\n')
  457. except ValueError:
  458. pass
  459. else:
  460. lines = s.splitlines()
  461. for line in lines:
  462. kv = line.split(sep, 1)
  463. k = kv[0].strip()
  464. if len(kv) > 1:
  465. v = kv[1].strip()
  466. else:
  467. v = dflt
  468. if val_type:
  469. result[k] = val_type(v)
  470. else:
  471. result[k] = v
  472. return result
  473. def _text_to_key_value_dict(filename, sep=":", val_sep=","):
  474. """
  475. !Convert a key-value text file, where entries are separated
  476. by newlines and the key and value are separated by `sep',
  477. into a key-value dictionary and discover/use the correct
  478. data types (float, int or string) for values.
  479. @param filename The name or name and path of the text file to convert
  480. @param sep The character that separates the keys and values, default is ":"
  481. @param val_sep The character that separates the values of a single key, default is ","
  482. @return The dictionary
  483. A text file with this content:
  484. \code
  485. a: Hello
  486. b: 1.0
  487. c: 1,2,3,4,5
  488. d : hello,8,0.1
  489. \endcode
  490. Will be represented as this dictionary:
  491. \code
  492. {'a': ['Hello'], 'c': [1, 2, 3, 4, 5], 'b': [1.0], 'd': ['hello', 8, 0.1]}
  493. \endcode
  494. """
  495. text = open(filename, "r").readlines()
  496. kvdict = KeyValue()
  497. for line in text:
  498. if line.find(sep) >= 0:
  499. key, value = line.split(sep)
  500. key = key.strip()
  501. value = value.strip()
  502. else:
  503. # Jump over empty values
  504. continue
  505. values = value.split(val_sep)
  506. value_list = []
  507. for value in values:
  508. not_float = False
  509. not_int = False
  510. # Convert values into correct types
  511. # We first try integer then float
  512. try:
  513. value_converted = int(value)
  514. except:
  515. not_int = True
  516. if not_int:
  517. try:
  518. value_converted = float(value)
  519. except:
  520. not_float = True
  521. if not_int and not_float:
  522. value_converted = value.strip()
  523. value_list.append(value_converted)
  524. kvdict[key] = value_list
  525. return kvdict
  526. def compare_key_value_text_files(filename_a, filename_b, sep=":",
  527. val_sep=",", precision=0.000001):
  528. """
  529. !Compare two key-value text two files
  530. This method will print a warning in case keys that are present in the first
  531. file are not present in the second one.
  532. The comparison method tries to convert the values into there native format
  533. (float, int or string) to allow correct comparison.
  534. An example key-value text file may have this content:
  535. \code
  536. a: Hello
  537. b: 1.0
  538. c: 1,2,3,4,5
  539. d : hello,8,0.1
  540. \endcode
  541. @param filename_a name of the first key-value text file
  542. @param filenmae_b name of the second key-value text file
  543. @param sep character that separates the keys and values, default is ":"
  544. @param val_sep character that separates the values of a single key, default is ","
  545. @param precision precision with which the floating point values are compares
  546. @return True if full or almost identical, False if different
  547. """
  548. dict_a = _text_to_key_value_dict(filename_a, sep)
  549. dict_b = _text_to_key_value_dict(filename_b, sep)
  550. missing_keys = 0
  551. # We compare matching keys
  552. for key in dict_a.keys():
  553. if key in dict_b:
  554. # Floating point values must be handled separately
  555. if isinstance(dict_a[key], float) and isinstance(dict_b[key], float):
  556. if abs(dict_a[key] - dict_b[key]) > precision:
  557. return False
  558. elif isinstance(dict_a[key], float) or isinstance(dict_b[key], float):
  559. warning(_("Mixing value types. Will try to compare after "
  560. "integer conversion"))
  561. return int(dict_a[key]) == int(dict_b[key])
  562. elif key == "+towgs84":
  563. # We compare the sum of the entries
  564. if abs(sum(dict_a[key]) - sum(dict_b[key])) > precision:
  565. return False
  566. else:
  567. if dict_a[key] != dict_b[key]:
  568. return False
  569. else:
  570. missing_keys += 1
  571. if missing_keys == len(dict_a):
  572. return False
  573. if missing_keys > 0:
  574. warning(_("Several keys (%(miss)i out of %(a)i) are missing "
  575. "in the target file") % {'miss': missing_keys,
  576. 'a': len(dict_a)})
  577. return True
  578. # interface to g.gisenv
  579. def gisenv():
  580. """!Returns the output from running g.gisenv (with no arguments), as a
  581. dictionary. Example:
  582. @code
  583. >>> env = gisenv()
  584. >>> print env['GISDBASE'] # doctest: +SKIP
  585. /opt/grass-data
  586. @endcode
  587. @return list of GRASS variables
  588. """
  589. s = read_command("g.gisenv", flags='n')
  590. return parse_key_val(s)
  591. # interface to g.region
  592. def locn_is_latlong():
  593. """!Tests if location is lat/long. Value is obtained
  594. by checking the "g.region -p" projection code.
  595. @return True for a lat/long region, False otherwise
  596. """
  597. s = read_command("g.region", flags='p')
  598. kv = parse_key_val(s, ':')
  599. if kv['projection'].split(' ')[1] == '3':
  600. return True
  601. else:
  602. return False
  603. def region(region3d=False, complete=False):
  604. """!Returns the output from running "g.region -g", as a
  605. dictionary. Example:
  606. @param region3d True to get 3D region
  607. @code
  608. >>> curent_region = region()
  609. >>> # obtain n, s, e and w values
  610. >>> [curent_region[key] for key in "nsew"] # doctest: +ELLIPSIS
  611. [..., ..., ..., ...]
  612. >>> # obtain ns and ew resulutions
  613. >>> (curent_region['nsres'], curent_region['ewres']) # doctest: +ELLIPSIS
  614. (..., ...)
  615. @endcode
  616. @return dictionary of region values
  617. """
  618. flgs = 'g'
  619. if region3d:
  620. flgs += '3'
  621. if complete:
  622. flgs += 'cep'
  623. s = read_command("g.region", flags=flgs)
  624. reg = parse_key_val(s, val_type=float)
  625. for k in ['rows', 'cols', 'cells',
  626. 'rows3', 'cols3', 'cells3', 'depths']:
  627. if k not in reg:
  628. continue
  629. reg[k] = int(reg[k])
  630. return reg
  631. def region_env(region3d=False, **kwargs):
  632. """!Returns region settings as a string which can used as
  633. GRASS_REGION environmental variable.
  634. If no 'kwargs' are given then the current region is used. Note
  635. that this function doesn't modify the current region!
  636. See also use_temp_region() for alternative method how to define
  637. temporary region used for raster-based computation.
  638. \param region3d True to get 3D region
  639. \param kwargs g.region's parameters like 'rast', 'vect' or 'region'
  640. \code
  641. os.environ['GRASS_REGION'] = grass.region_env(region='detail')
  642. grass.mapcalc('map=1', overwrite=True)
  643. os.environ.pop('GRASS_REGION')
  644. \endcode
  645. @return string with region values
  646. @return empty string on error
  647. """
  648. # read proj/zone from WIND file
  649. env = gisenv()
  650. windfile = os.path.join(env['GISDBASE'], env['LOCATION_NAME'],
  651. env['MAPSET'], "WIND")
  652. fd = open(windfile, "r")
  653. grass_region = ''
  654. for line in fd.readlines():
  655. key, value = map(lambda x: x.strip(), line.split(":", 1))
  656. if kwargs and key not in ('proj', 'zone'):
  657. continue
  658. if not kwargs and not region3d and \
  659. key in ('top', 'bottom', 'cols3', 'rows3',
  660. 'depths', 'e-w resol3', 'n-s resol3', 't-b resol'):
  661. continue
  662. grass_region += '%s: %s;' % (key, value)
  663. if not kwargs: # return current region
  664. return grass_region
  665. # read other values from `g.region -g`
  666. flgs = 'ug'
  667. if region3d:
  668. flgs += '3'
  669. s = read_command('g.region', flags=flgs, **kwargs)
  670. if not s:
  671. return ''
  672. reg = parse_key_val(s)
  673. kwdata = [('north', 'n'),
  674. ('south', 's'),
  675. ('east', 'e'),
  676. ('west', 'w'),
  677. ('cols', 'cols'),
  678. ('rows', 'rows'),
  679. ('e-w resol', 'ewres'),
  680. ('n-s resol', 'nsres')]
  681. if region3d:
  682. kwdata += [('top', 't'),
  683. ('bottom', 'b'),
  684. ('cols3', 'cols3'),
  685. ('rows3', 'rows3'),
  686. ('depths', 'depths'),
  687. ('e-w resol3', 'ewres3'),
  688. ('n-s resol3', 'nsres3'),
  689. ('t-b resol', 'tbres')]
  690. for wkey, rkey in kwdata:
  691. grass_region += '%s: %s;' % (wkey, reg[rkey])
  692. return grass_region
  693. def use_temp_region():
  694. """!Copies the current region to a temporary region with "g.region save=",
  695. then sets WIND_OVERRIDE to refer to that region. Installs an atexit
  696. handler to delete the temporary region upon termination.
  697. """
  698. name = "tmp.%s.%d" % (os.path.basename(sys.argv[0]), os.getpid())
  699. run_command("g.region", save=name, overwrite=True)
  700. os.environ['WIND_OVERRIDE'] = name
  701. atexit.register(del_temp_region)
  702. def del_temp_region():
  703. """!Unsets WIND_OVERRIDE and removes any region named by it."""
  704. try:
  705. name = os.environ.pop('WIND_OVERRIDE')
  706. run_command("g.remove", quiet=True, region=name)
  707. except:
  708. pass
  709. # interface to g.findfile
  710. def find_file(name, element='cell', mapset=None):
  711. """!Returns the output from running g.findfile as a
  712. dictionary. Example:
  713. @code
  714. >>> result = find_file('elevation', element='cell')
  715. >>> print result['fullname']
  716. elevation@PERMANENT
  717. >>> print result['file'] # doctest: +ELLIPSIS
  718. /.../PERMANENT/cell/elevation
  719. @endcode
  720. @param name file name
  721. @param element element type (default 'cell')
  722. @param mapset mapset name (default all mapsets in search path)
  723. @return parsed output of g.findfile
  724. """
  725. if element == 'raster' or element == 'rast':
  726. verbose(_('Element type should be "cell" and not "%s"') % element)
  727. element = 'cell'
  728. s = read_command("g.findfile", flags='n', element=element, file=name,
  729. mapset=mapset)
  730. return parse_key_val(s)
  731. # interface to g.list
  732. def list_grouped(type, check_search_path=True):
  733. """!List elements grouped by mapsets.
  734. Returns the output from running g.list, as a dictionary where the
  735. keys are mapset names and the values are lists of maps in that
  736. mapset. Example:
  737. @code
  738. >>> list_grouped('rast')['PERMANENT'] # doctest: +ELLIPSIS
  739. [..., 'lakes', ..., 'slope', ...
  740. @endcode
  741. @param type element type (rast, vect, rast3d, region, ...)
  742. @param check_search_path True to add mapsets for the search path with no
  743. found elements
  744. @return directory of mapsets/elements
  745. """
  746. if type == 'raster' or type == 'cell':
  747. verbose(_('Element type should be "rast" and not "%s"') % type)
  748. type = 'rast'
  749. dashes_re = re.compile("^----+$")
  750. mapset_re = re.compile("<(.*)>")
  751. result = {}
  752. if check_search_path:
  753. for mapset in mapsets(search_path=True):
  754. result[mapset] = []
  755. mapset = None
  756. for line in read_command("g.list", type=type).splitlines():
  757. if line == "":
  758. continue
  759. if dashes_re.match(line):
  760. continue
  761. m = mapset_re.search(line)
  762. if m:
  763. mapset = m.group(1)
  764. if mapset not in result.keys():
  765. result[mapset] = []
  766. continue
  767. if mapset:
  768. result[mapset].extend(line.split())
  769. return result
  770. def _concat(xs):
  771. result = []
  772. for x in xs:
  773. result.extend(x)
  774. return result
  775. def list_pairs(type):
  776. """!List of elements as tuples.
  777. Returns the output from running g.list, as a list of (map, mapset)
  778. pairs. Example:
  779. @code
  780. >>> list_pairs('rast') # doctest: +ELLIPSIS
  781. [..., ('lakes', 'PERMANENT'), ..., ('slope', 'PERMANENT'), ...
  782. @endcode
  783. @param type element type (rast, vect, rast3d, region, ...)
  784. @return list of tuples (map, mapset)
  785. """
  786. return _concat([[(map, mapset) for map in maps]
  787. for mapset, maps in list_grouped(type).iteritems()])
  788. def list_strings(type):
  789. """!List of elements as strings.
  790. Returns the output from running g.list, as a list of qualified
  791. names. Example:
  792. @code
  793. >>> list_strings('rast') # doctest: +ELLIPSIS
  794. [..., 'lakes@PERMANENT', ..., 'slope@PERMANENT', ...
  795. @endcode
  796. @param type element type
  797. @return list of strings ('map@@mapset')
  798. """
  799. return ["%s@%s" % pair for pair in list_pairs(type)]
  800. # interface to g.mlist
  801. def mlist_strings(type, pattern=None, mapset=None, flag=''):
  802. """!List of elements as strings.
  803. Returns the output from running g.mlist, as a list of qualified
  804. names.
  805. @param type element type (rast, vect, rast3d, region, ...)
  806. @param pattern pattern string
  807. @param mapset mapset name (if not given use search path)
  808. @param flag pattern type: 'r' (basic regexp), 'e' (extended regexp), or ''
  809. (glob pattern)
  810. @return list of elements
  811. """
  812. if type == 'raster' or type == 'cell':
  813. verbose(_('Element type should be "rast" and not "%s"') % type)
  814. type = 'rast'
  815. result = list()
  816. for line in read_command("g.mlist",
  817. quiet=True,
  818. flags='m' + flag,
  819. type=type,
  820. pattern=pattern,
  821. mapset=mapset).splitlines():
  822. result.append(line.strip())
  823. return result
  824. def mlist_pairs(type, pattern=None, mapset=None, flag=''):
  825. """!List of elements as pairs
  826. Returns the output from running g.mlist, as a list of
  827. (name, mapset) pairs
  828. @param type element type (rast, vect, rast3d, region, ...)
  829. @param pattern pattern string
  830. @param mapset mapset name (if not given use search path)
  831. @param flag pattern type: 'r' (basic regexp), 'e' (extended regexp), or ''
  832. (glob pattern)
  833. @return list of elements
  834. """
  835. return [tuple(map.split('@', 1)) for map in mlist_strings(type, pattern,
  836. mapset, flag)]
  837. def mlist_grouped(type, pattern=None, check_search_path=True, flag=''):
  838. """!List of elements grouped by mapsets.
  839. Returns the output from running g.mlist, as a dictionary where the
  840. keys are mapset names and the values are lists of maps in that
  841. mapset. Example:
  842. @code
  843. >>> mlist_grouped('vect', pattern='*roads*')['PERMANENT']
  844. ['railroads', 'roadsmajor']
  845. @endcode
  846. @param type element type (rast, vect, rast3d, region, ...)
  847. @param pattern pattern string
  848. @param check_search_path True to add mapsets for the search path with no
  849. found elements
  850. @param flag pattern type: 'r' (basic regexp), 'e' (extended regexp), or ''
  851. (glob pattern)
  852. @return directory of mapsets/elements
  853. """
  854. if type == 'raster' or type == 'cell':
  855. verbose(_('Element type should be "rast" and not "%s"') % type)
  856. type = 'rast'
  857. result = {}
  858. if check_search_path:
  859. for mapset in mapsets(search_path=True):
  860. result[mapset] = []
  861. mapset = None
  862. for line in read_command("g.mlist", quiet=True, flags="m" + flag,
  863. type=type, pattern=pattern).splitlines():
  864. try:
  865. name, mapset = line.split('@')
  866. except ValueError:
  867. warning(_("Invalid element '%s'") % line)
  868. continue
  869. if mapset in result:
  870. result[mapset].append(name)
  871. else:
  872. result[mapset] = [name, ]
  873. return result
  874. # color parsing
  875. named_colors = {
  876. "white": (1.00, 1.00, 1.00),
  877. "black": (0.00, 0.00, 0.00),
  878. "red": (1.00, 0.00, 0.00),
  879. "green": (0.00, 1.00, 0.00),
  880. "blue": (0.00, 0.00, 1.00),
  881. "yellow": (1.00, 1.00, 0.00),
  882. "magenta": (1.00, 0.00, 1.00),
  883. "cyan": (0.00, 1.00, 1.00),
  884. "aqua": (0.00, 0.75, 0.75),
  885. "grey": (0.75, 0.75, 0.75),
  886. "gray": (0.75, 0.75, 0.75),
  887. "orange": (1.00, 0.50, 0.00),
  888. "brown": (0.75, 0.50, 0.25),
  889. "purple": (0.50, 0.00, 1.00),
  890. "violet": (0.50, 0.00, 1.00),
  891. "indigo": (0.00, 0.50, 1.00)}
  892. def parse_color(val, dflt=None):
  893. """!Parses the string "val" as a GRASS colour, which can be either one of
  894. the named colours or an R:G:B tuple e.g. 255:255:255. Returns an
  895. (r,g,b) triple whose components are floating point values between 0
  896. and 1. Example:
  897. @code
  898. >>> parse_color("red")
  899. (1.0, 0.0, 0.0)
  900. >>> parse_color("255:0:0")
  901. (1.0, 0.0, 0.0)
  902. @endcode
  903. @param val color value
  904. @param dflt default color value
  905. @return tuple RGB
  906. """
  907. if val in named_colors:
  908. return named_colors[val]
  909. vals = val.split(':')
  910. if len(vals) == 3:
  911. return tuple(float(v) / 255 for v in vals)
  912. return dflt
  913. # check GRASS_OVERWRITE
  914. def overwrite():
  915. """!Return True if existing files may be overwritten"""
  916. owstr = 'GRASS_OVERWRITE'
  917. return owstr in os.environ and os.environ[owstr] != '0'
  918. # check GRASS_VERBOSE
  919. def verbosity():
  920. """!Return the verbosity level selected by GRASS_VERBOSE"""
  921. vbstr = os.getenv('GRASS_VERBOSE')
  922. if vbstr:
  923. return int(vbstr)
  924. else:
  925. return 2
  926. ## various utilities, not specific to GRASS
  927. # basename inc. extension stripping
  928. def basename(path, ext=None):
  929. """!Remove leading directory components and an optional extension
  930. from the specified path
  931. @param path path
  932. @param ext extension
  933. """
  934. name = os.path.basename(path)
  935. if not ext:
  936. return name
  937. fs = name.rsplit('.', 1)
  938. if len(fs) > 1 and fs[1].lower() == ext:
  939. name = fs[0]
  940. return name
  941. def find_program(pgm, *args):
  942. """!Attempt to run a program, with optional arguments.
  943. You must call the program in a way that will return a successful
  944. exit code. For GRASS modules this means you need to pass it some
  945. valid CLI option, like "--help". For other programs a common
  946. valid do-little option is "--version".
  947. Example:
  948. @code
  949. >>> grass.find_program('r.sun', 'help')
  950. True
  951. >>> grass.find_program('gdalwarp', '--version')
  952. True
  953. @endcode
  954. @param pgm program name
  955. @param args list of arguments
  956. @return False if the attempt failed due to a missing executable
  957. or non-zero return code
  958. @return True otherwise
  959. """
  960. nuldev = file(os.devnull, 'w+')
  961. try:
  962. call([pgm] + list(args), stdin = nuldev, stdout = nuldev, stderr = nuldev)
  963. found = True
  964. except:
  965. found = False
  966. nuldev.close()
  967. return found
  968. # try to remove a file, without complaints
  969. def try_remove(path):
  970. """!Attempt to remove a file; no exception is generated if the
  971. attempt fails.
  972. @param path path to file to remove
  973. """
  974. try:
  975. os.remove(path)
  976. except:
  977. pass
  978. # try to remove a directory, without complaints
  979. def try_rmdir(path):
  980. """!Attempt to remove a directory; no exception is generated if the
  981. attempt fails.
  982. @param path path to directory to remove
  983. """
  984. try:
  985. os.rmdir(path)
  986. except:
  987. shutil.rmtree(path, ignore_errors=True)
  988. def float_or_dms(s):
  989. """!Convert DMS to float.
  990. @param s DMS value
  991. @return float value
  992. """
  993. return sum(float(x) / 60 ** n for (n, x) in enumerate(s.split(':')))
  994. # interface to g.mapsets
  995. def mapsets(search_path=False):
  996. """!List available mapsets
  997. @param search_path True to list mapsets only in search path
  998. @return list of mapsets
  999. """
  1000. if search_path:
  1001. flags = 'p'
  1002. else:
  1003. flags = 'l'
  1004. mapsets = read_command('g.mapsets',
  1005. flags=flags,
  1006. sep='newline',
  1007. quiet=True)
  1008. if not mapsets:
  1009. fatal(_("Unable to list mapsets"))
  1010. return mapsets.splitlines()
  1011. # interface to `g.proj -c`
  1012. def create_location(dbase, location, epsg=None, proj4=None, filename=None,
  1013. wkt=None, datum=None, datum_trans=None, desc=None):
  1014. """!Create new location
  1015. Raise ScriptError on error.
  1016. @param dbase path to GRASS database
  1017. @param location location name to create
  1018. @param epsg if given create new location based on EPSG code
  1019. @param proj4 if given create new location based on Proj4 definition
  1020. @param filename if given create new location based on georeferenced file
  1021. @param wkt if given create new location based on WKT definition (path to PRJ file)
  1022. @param datum GRASS format datum code
  1023. @param datum_trans datum transformation parameters (used for epsg and proj4)
  1024. @param desc description of the location (creates MYNAME file)
  1025. """
  1026. gisdbase = None
  1027. if epsg or proj4 or filename or wkt:
  1028. # FIXME: changing GISDBASE mid-session is not background-job safe
  1029. gisdbase = gisenv()['GISDBASE']
  1030. run_command('g.gisenv', set='GISDBASE=%s' % dbase)
  1031. if not os.path.exists(dbase):
  1032. os.mkdir(dbase)
  1033. kwargs = dict()
  1034. if datum:
  1035. kwargs['datum'] = datum
  1036. if datum_trans:
  1037. kwargs['datum_trans'] = datum_trans
  1038. if epsg:
  1039. ps = pipe_command('g.proj', quiet=True, flags='t', epsg=epsg,
  1040. location=location, stderr=PIPE, **kwargs)
  1041. elif proj4:
  1042. ps = pipe_command('g.proj', quiet=True, flags='t', proj4=proj4,
  1043. location=location, stderr=PIPE, **kwargs)
  1044. elif filename:
  1045. ps = pipe_command('g.proj', quiet=True, georef=filename,
  1046. location=location, stderr=PIPE)
  1047. elif wkt:
  1048. ps = pipe_command('g.proj', quiet=True, wkt=wkt, location=location,
  1049. stderr=PIPE)
  1050. else:
  1051. _create_location_xy(dbase, location)
  1052. if epsg or proj4 or filename or wkt:
  1053. error = ps.communicate()[1]
  1054. run_command('g.gisenv', set='GISDBASE=%s' % gisdbase)
  1055. if ps.returncode != 0 and error:
  1056. raise ScriptError(repr(error))
  1057. try:
  1058. fd = codecs.open(os.path.join(dbase, location, 'PERMANENT', 'MYNAME'),
  1059. encoding='utf-8', mode='w')
  1060. if desc:
  1061. fd.write(desc + os.linesep)
  1062. else:
  1063. fd.write(os.linesep)
  1064. fd.close()
  1065. except OSError, e:
  1066. raise ScriptError(repr(e))
  1067. def _create_location_xy(database, location):
  1068. """!Create unprojected location
  1069. Raise ScriptError on error.
  1070. @param database GRASS database where to create new location
  1071. @param location location name
  1072. """
  1073. cur_dir = os.getcwd()
  1074. try:
  1075. os.chdir(database)
  1076. os.mkdir(location)
  1077. os.mkdir(os.path.join(location, 'PERMANENT'))
  1078. # create DEFAULT_WIND and WIND files
  1079. regioninfo = ['proj: 0',
  1080. 'zone: 0',
  1081. 'north: 1',
  1082. 'south: 0',
  1083. 'east: 1',
  1084. 'west: 0',
  1085. 'cols: 1',
  1086. 'rows: 1',
  1087. 'e-w resol: 1',
  1088. 'n-s resol: 1',
  1089. 'top: 1',
  1090. 'bottom: 0',
  1091. 'cols3: 1',
  1092. 'rows3: 1',
  1093. 'depths: 1',
  1094. 'e-w resol3: 1',
  1095. 'n-s resol3: 1',
  1096. 't-b resol: 1']
  1097. defwind = open(os.path.join(location,
  1098. "PERMANENT", "DEFAULT_WIND"), 'w')
  1099. for param in regioninfo:
  1100. defwind.write(param + '%s' % os.linesep)
  1101. defwind.close()
  1102. shutil.copy(os.path.join(location, "PERMANENT", "DEFAULT_WIND"),
  1103. os.path.join(location, "PERMANENT", "WIND"))
  1104. os.chdir(cur_dir)
  1105. except OSError, e:
  1106. raise ScriptError(repr(e))
  1107. # interface to g.version
  1108. def version():
  1109. """!Get GRASS version as dictionary
  1110. @code
  1111. print version()
  1112. {'proj4': '4.8.0', 'geos': '3.3.5', 'libgis_revision': '52468',
  1113. 'libgis_date': '2012-07-27 22:53:30 +0200 (Fri, 27 Jul 2012)',
  1114. 'version': '7.0.svn', 'date': '2012', 'gdal': '2.0dev',
  1115. 'revision': '53670'}
  1116. @endcode
  1117. """
  1118. data = parse_command('g.version', flags='rge')
  1119. for k, v in data.iteritems():
  1120. data[k.strip()] = v.replace('"', '').strip()
  1121. return data
  1122. # get debug_level
  1123. _debug_level = None
  1124. def debug_level():
  1125. global _debug_level
  1126. if _debug_level is not None:
  1127. return _debug_level
  1128. _debug_level = 0
  1129. if find_program('g.gisenv', '--help'):
  1130. _debug_level = int(gisenv().get('DEBUG', 0))
  1131. def legal_name(s):
  1132. """!Checks if the string contains only allowed characters.
  1133. This is the Python implementation of G_legal_filename() function.
  1134. @note It is not clear when to use this function.
  1135. """
  1136. if not s or s[0] == '.':
  1137. warning(_("Illegal filename <%s>. Cannot be 'NULL' or start with " \
  1138. "'.'.") % s)
  1139. return False
  1140. illegal = [c
  1141. for c in s
  1142. if c in '/"\'@,=*~' or c <= ' ' or c >= '\177']
  1143. if illegal:
  1144. illegal = ''.join(sorted(set(illegal)))
  1145. warning(_("Illegal filename <%(s)s>. <%(il)s> not allowed.\n") % {
  1146. 's': s, 'il': illegal})
  1147. return False
  1148. return True
  1149. if __name__ == '__main__':
  1150. import doctest
  1151. doctest.testmod()