utils.py 34 KB

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