utils.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. """!
  2. @package core.utils
  3. @brief Misc utilities for wxGUI
  4. (C) 2007-2009, 2011 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. from core.globalvar import ETCDIR
  18. sys.path.append(os.path.join(ETCDIR, "python"))
  19. from grass.script import core as grass
  20. from grass.script import task as gtask
  21. from core.gcmd import RunCommand
  22. from core.debug import Debug
  23. # from core.settings import UserSettings
  24. def normalize_whitespace(text):
  25. """!Remove redundant whitespace from a string"""
  26. return string.join(string.split(text), ' ')
  27. def split(s):
  28. """!Platform spefic shlex.split"""
  29. if sys.version_info >= (2, 6):
  30. return shlex.split(s, posix = (sys.platform != "win32"))
  31. elif sys.platform == "win32":
  32. return shlex.split(s.replace('\\', r'\\'))
  33. else:
  34. return shlex.split(s)
  35. def GetTempfile(pref=None):
  36. """!Creates GRASS temporary file using defined prefix.
  37. @todo Fix path on MS Windows/MSYS
  38. @param pref prefer the given path
  39. @return Path to file name (string) or None
  40. """
  41. ret = RunCommand('g.tempfile',
  42. read = True,
  43. pid = os.getpid())
  44. tempfile = ret.splitlines()[0].strip()
  45. # FIXME
  46. # ugly hack for MSYS (MS Windows)
  47. if platform.system() == 'Windows':
  48. tempfile = tempfile.replace("/", "\\")
  49. try:
  50. path, file = os.path.split(tempfile)
  51. if pref:
  52. return os.path.join(pref, file)
  53. else:
  54. return tempfile
  55. except:
  56. return None
  57. def GetLayerNameFromCmd(dcmd, fullyQualified = False, param = None,
  58. layerType = None):
  59. """!Get map name from GRASS command
  60. Parameter dcmd can be modified when first parameter is not
  61. defined.
  62. @param dcmd GRASS command (given as list)
  63. @param fullyQualified change map name to be fully qualified
  64. @param param params directory
  65. @param layerType check also layer type ('raster', 'vector', '3d-raster', ...)
  66. @return tuple (name, found)
  67. """
  68. mapname = ''
  69. found = True
  70. if len(dcmd) < 1:
  71. return mapname, False
  72. if 'd.grid' == dcmd[0]:
  73. mapname = 'grid'
  74. elif 'd.geodesic' in dcmd[0]:
  75. mapname = 'geodesic'
  76. elif 'd.rhumbline' in dcmd[0]:
  77. mapname = 'rhumb'
  78. else:
  79. params = list()
  80. for idx in range(len(dcmd)):
  81. try:
  82. p, v = dcmd[idx].split('=', 1)
  83. except ValueError:
  84. continue
  85. if p == param:
  86. params = [(idx, p, v)]
  87. break
  88. if p in ('map', 'input', 'layer',
  89. 'red', 'blue', 'green',
  90. 'h_map', 's_map', 'i_map',
  91. 'reliefmap', 'labels'):
  92. params.append((idx, p, v))
  93. if len(params) < 1:
  94. if len(dcmd) > 1 and '=' not in dcmd[1]:
  95. task = gtask.parse_interface(dcmd[0])
  96. p = task.get_options()['params'][0].get('name', '')
  97. params.append((1, p, dcmd[1]))
  98. else:
  99. return mapname, False
  100. mapname = params[0][2]
  101. mapset = ''
  102. if fullyQualified and '@' not in mapname:
  103. if layerType in ('raster', 'vector', '3d-raster', 'rgb', 'his'):
  104. try:
  105. if layerType in ('raster', 'rgb', 'his'):
  106. findType = 'cell'
  107. else:
  108. findType = layerType
  109. mapset = grass.find_file(mapname, element = findType)['mapset']
  110. except AttributeError, e: # not found
  111. return '', False
  112. if not mapset:
  113. found = False
  114. else:
  115. mapset = grass.gisenv()['MAPSET']
  116. # update dcmd
  117. for i, p, v in params:
  118. if p == 'layer':
  119. continue
  120. dcmd[i] = p + '=' + v
  121. if mapset:
  122. dcmd[i] += '@' + mapset
  123. maps = list()
  124. ogr = False
  125. for i, p, v in params:
  126. if v.lower().rfind('@ogr') > -1:
  127. ogr = True
  128. if p == 'layer' and not ogr:
  129. continue
  130. maps.append(dcmd[i].split('=', 1)[1])
  131. mapname = '\n'.join(maps)
  132. return mapname, found
  133. def GetValidLayerName(name):
  134. """!Make layer name SQL compliant, based on G_str_to_sql()
  135. @todo: Better use directly GRASS Python SWIG...
  136. """
  137. retName = str(name).strip()
  138. # check if name is fully qualified
  139. if '@' in retName:
  140. retName, mapset = retName.split('@')
  141. else:
  142. mapset = None
  143. cIdx = 0
  144. retNameList = list(retName)
  145. for c in retNameList:
  146. if not (c >= 'A' and c <= 'Z') and \
  147. not (c >= 'a' and c <= 'z') and \
  148. not (c >= '0' and c <= '9'):
  149. retNameList[cIdx] = '_'
  150. cIdx += 1
  151. retName = ''.join(retNameList)
  152. if not (retName[0] >= 'A' and retName[0] <= 'Z') and \
  153. not (retName[0] >= 'a' and retName[0] <= 'z'):
  154. retName = 'x' + retName[1:]
  155. if mapset:
  156. retName = retName + '@' + mapset
  157. return retName
  158. def ListOfCatsToRange(cats):
  159. """!Convert list of category number to range(s)
  160. Used for example for d.vect cats=[range]
  161. @param cats category list
  162. @return category range string
  163. @return '' on error
  164. """
  165. catstr = ''
  166. try:
  167. cats = map(int, cats)
  168. except:
  169. return catstr
  170. i = 0
  171. while i < len(cats):
  172. next = 0
  173. j = i + 1
  174. while j < len(cats):
  175. if cats[i + next] == cats[j] - 1:
  176. next += 1
  177. else:
  178. break
  179. j += 1
  180. if next > 1:
  181. catstr += '%d-%d,' % (cats[i], cats[i + next])
  182. i += next + 1
  183. else:
  184. catstr += '%d,' % (cats[i])
  185. i += 1
  186. return catstr.strip(',')
  187. def ListOfMapsets(get = 'ordered'):
  188. """!Get list of available/accessible mapsets
  189. @param get method ('all', 'accessible', 'ordered')
  190. @return list of mapsets
  191. @return None on error
  192. """
  193. mapsets = []
  194. if get == 'all' or get == 'ordered':
  195. ret = RunCommand('g.mapsets',
  196. read = True,
  197. quiet = True,
  198. flags = 'l',
  199. fs = 'newline')
  200. if ret:
  201. mapsets = ret.splitlines()
  202. ListSortLower(mapsets)
  203. else:
  204. return None
  205. if get == 'accessible' or get == 'ordered':
  206. ret = RunCommand('g.mapsets',
  207. read = True,
  208. quiet = True,
  209. flags = 'p',
  210. fs = 'newline')
  211. if ret:
  212. if get == 'accessible':
  213. mapsets = ret.splitlines()
  214. else:
  215. mapsets_accessible = ret.splitlines()
  216. for mapset in mapsets_accessible:
  217. mapsets.remove(mapset)
  218. mapsets = mapsets_accessible + mapsets
  219. else:
  220. return None
  221. return mapsets
  222. def ListSortLower(list):
  223. """!Sort list items (not case-sensitive)"""
  224. list.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
  225. def GetVectorNumberOfLayers(parent, vector):
  226. """!Get list of vector layers"""
  227. layers = list()
  228. if not vector:
  229. return layers
  230. fullname = grass.find_file(name = vector, element = 'vector')['fullname']
  231. if not fullname:
  232. Debug.msg(5, "utils.GetVectorNumberOfLayers(): vector map '%s' not found" % vector)
  233. return layers
  234. ret, out, msg = RunCommand('v.db.connect',
  235. getErrorMsg = True,
  236. read = True,
  237. flags = 'g',
  238. map = fullname,
  239. fs = ';')
  240. if ret != 0:
  241. sys.stderr.write(_("Vector map <%(map)s>: %(msg)s\n") % { 'map' : fullname, 'msg' : msg })
  242. return layers
  243. else:
  244. Debug.msg(1, "GetVectorNumberOfLayers(): ret %s" % ret)
  245. for line in ret.splitlines():
  246. try:
  247. layer = line.split(';')[0]
  248. if '/' in layer:
  249. layer = layer.split('/')[0]
  250. layers.append(layer)
  251. except IndexError:
  252. pass
  253. Debug.msg(3, "utils.GetVectorNumberOfLayers(): vector=%s -> %s" % \
  254. (fullname, ','.join(layers)))
  255. return layers
  256. def Deg2DMS(lon, lat, string = True, hemisphere = True, precision = 3):
  257. """!Convert deg value to dms string
  258. @param lon longitude (x)
  259. @param lat latitude (y)
  260. @param string True to return string otherwise tuple
  261. @param hemisphere print hemisphere
  262. @param precision seconds precision
  263. @return DMS string or tuple of values
  264. @return empty string on error
  265. """
  266. try:
  267. flat = float(lat)
  268. flon = float(lon)
  269. except ValueError:
  270. if string:
  271. return ''
  272. else:
  273. return None
  274. # fix longitude
  275. while flon > 180.0:
  276. flon -= 360.0
  277. while flon < -180.0:
  278. flon += 360.0
  279. # hemisphere
  280. if hemisphere:
  281. if flat < 0.0:
  282. flat = abs(flat)
  283. hlat = 'S'
  284. else:
  285. hlat = 'N'
  286. if flon < 0.0:
  287. hlon = 'W'
  288. flon = abs(flon)
  289. else:
  290. hlon = 'E'
  291. else:
  292. flat = abs(flat)
  293. flon = abs(flon)
  294. hlon = ''
  295. hlat = ''
  296. slat = __ll_parts(flat, precision = precision)
  297. slon = __ll_parts(flon, precision = precision)
  298. if string:
  299. return slon + hlon + '; ' + slat + hlat
  300. return (slon + hlon, slat + hlat)
  301. def DMS2Deg(lon, lat):
  302. """!Convert dms value to deg
  303. @param lon longitude (x)
  304. @param lat latitude (y)
  305. @return tuple of converted values
  306. @return ValueError on error
  307. """
  308. x = __ll_parts(lon, reverse = True)
  309. y = __ll_parts(lat, reverse = True)
  310. return (x, y)
  311. def __ll_parts(value, reverse = False, precision = 3):
  312. """!Converts deg to d:m:s string
  313. @param value value to be converted
  314. @param reverse True to convert from d:m:s to deg
  315. @param precision seconds precision (ignored if reverse is True)
  316. @return converted value (string/float)
  317. @return ValueError on error (reverse == True)
  318. """
  319. if not reverse:
  320. if value == 0.0:
  321. return '%s%.*f' % ('00:00:0', precision, 0.0)
  322. d = int(int(value))
  323. m = int((value - d) * 60)
  324. s = ((value - d) * 60 - m) * 60
  325. if m < 0:
  326. m = '00'
  327. elif m < 10:
  328. m = '0' + str(m)
  329. else:
  330. m = str(m)
  331. if s < 0:
  332. s = '00.0000'
  333. elif s < 10.0:
  334. s = '0%.*f' % (precision, s)
  335. else:
  336. s = '%.*f' % (precision, s)
  337. return str(d) + ':' + m + ':' + s
  338. else: # -> reverse
  339. try:
  340. d, m, s = value.split(':')
  341. hs = s[-1]
  342. s = s[:-1]
  343. except ValueError:
  344. try:
  345. d, m = value.split(':')
  346. hs = m[-1]
  347. m = m[:-1]
  348. s = '0.0'
  349. except ValueError:
  350. try:
  351. d = value
  352. hs = d[-1]
  353. d = d[:-1]
  354. m = '0'
  355. s = '0.0'
  356. except ValueError:
  357. raise ValueError
  358. if hs not in ('N', 'S', 'E', 'W'):
  359. raise ValueError
  360. coef = 1.0
  361. if hs in ('S', 'W'):
  362. coef = -1.0
  363. fm = int(m) / 60.0
  364. fs = float(s) / (60 * 60)
  365. return coef * (float(d) + fm + fs)
  366. def GetCmdString(cmd):
  367. """
  368. Get GRASS command as string.
  369. @param cmd GRASS command given as dictionary
  370. @return command string
  371. """
  372. scmd = ''
  373. if not cmd:
  374. return scmd
  375. scmd = cmd[0]
  376. if 'flags' in cmd[1]:
  377. for flag in cmd[1]['flags']:
  378. scmd += ' -' + flag
  379. for flag in ('verbose', 'quiet', 'overwrite'):
  380. if flag in cmd[1] and cmd[1][flag] is True:
  381. scmd += ' --' + flag
  382. for k, v in cmd[1].iteritems():
  383. if k in ('flags', 'verbose', 'quiet', 'overwrite'):
  384. continue
  385. scmd += ' %s=%s' % (k, v)
  386. return scmd
  387. def CmdToTuple(cmd):
  388. """!Convert command list to tuple for gcmd.RunCommand()"""
  389. if len(cmd) < 1:
  390. return None
  391. dcmd = {}
  392. for item in cmd[1:]:
  393. if '=' in item: # params
  394. key, value = item.split('=', 1)
  395. dcmd[str(key)] = str(value).replace('"', '')
  396. elif item[:2] == '--': # long flags
  397. flag = item[2:]
  398. if flag in ('verbose', 'quiet', 'overwrite'):
  399. dcmd[str(flag)] = True
  400. else: # -> flags
  401. if 'flags' not in dcmd:
  402. dcmd['flags'] = ''
  403. dcmd['flags'] += item.replace('-', '')
  404. return (cmd[0],
  405. dcmd)
  406. def PathJoin(*args):
  407. """!Check path created by os.path.join"""
  408. path = os.path.join(*args)
  409. if platform.system() == 'Windows' and \
  410. '/' in path:
  411. return path[1].upper() + ':\\' + path[3:].replace('/', '\\')
  412. return path
  413. def ReadEpsgCodes(path):
  414. """!Read EPSG code from the file
  415. @param path full path to the file with EPSG codes
  416. @return dictionary of EPSG code
  417. @return string on error
  418. """
  419. epsgCodeDict = dict()
  420. try:
  421. try:
  422. f = open(path, "r")
  423. except IOError:
  424. return _("failed to open '%s'" % path)
  425. i = 0
  426. code = None
  427. for line in f.readlines():
  428. line = line.strip()
  429. if len(line) < 1:
  430. continue
  431. if line[0] == '#':
  432. descr = line[1:].strip()
  433. elif line[0] == '<':
  434. code, params = line.split(" ", 1)
  435. try:
  436. code = int(code.replace('<', '').replace('>', ''))
  437. except ValueError:
  438. return e
  439. if code is not None:
  440. epsgCodeDict[code] = (descr, params)
  441. code = None
  442. i += 1
  443. f.close()
  444. except StandardError, e:
  445. return e
  446. return epsgCodeDict
  447. def ReprojectCoordinates(coord, projOut, projIn = None, flags = ''):
  448. """!Reproject coordinates
  449. @param coord coordinates given as tuple
  450. @param projOut output projection
  451. @param projIn input projection (use location projection settings)
  452. @return reprojected coordinates (returned as tuple)
  453. """
  454. coors = RunCommand('m.proj',
  455. flags = flags,
  456. input = '-',
  457. proj_input = projIn,
  458. proj_output = projOut,
  459. fs = ';',
  460. stdin = '%f;%f' % (coord[0], coord[1]),
  461. read = True)
  462. if coors:
  463. coors = coors.split(';')
  464. e = coors[0]
  465. n = coors[1]
  466. try:
  467. proj = projOut.split(' ')[0].split('=')[1]
  468. except IndexError:
  469. proj = ''
  470. if proj in ('ll', 'latlong', 'longlat') and 'd' not in flags:
  471. return (proj, (e, n))
  472. else:
  473. try:
  474. return (proj, (float(e), float(n)))
  475. except ValueError:
  476. return (None, None)
  477. return (None, None)
  478. def GetListOfLocations(dbase):
  479. """!Get list of GRASS locations in given dbase
  480. @param dbase GRASS database path
  481. @return list of locations (sorted)
  482. """
  483. listOfLocations = list()
  484. try:
  485. for location in glob.glob(os.path.join(dbase, "*")):
  486. try:
  487. if os.path.join(location, "PERMANENT") in glob.glob(os.path.join(location, "*")):
  488. listOfLocations.append(os.path.basename(location))
  489. except:
  490. pass
  491. except UnicodeEncodeError, e:
  492. raise e
  493. ListSortLower(listOfLocations)
  494. return listOfLocations
  495. def GetListOfMapsets(dbase, location, selectable = False):
  496. """!Get list of mapsets in given GRASS location
  497. @param dbase GRASS database path
  498. @param location GRASS location
  499. @param selectable True to get list of selectable mapsets, otherwise all
  500. @return list of mapsets - sorted (PERMANENT first)
  501. """
  502. listOfMapsets = list()
  503. if selectable:
  504. ret = RunCommand('g.mapset',
  505. read = True,
  506. flags = 'l',
  507. location = location,
  508. gisdbase = dbase)
  509. if not ret:
  510. return listOfMapsets
  511. for line in ret.rstrip().splitlines():
  512. listOfMapsets += line.split(' ')
  513. else:
  514. for mapset in glob.glob(os.path.join(dbase, location, "*")):
  515. if os.path.isdir(mapset) and \
  516. os.path.isfile(os.path.join(dbase, location, mapset, "WIND")):
  517. listOfMapsets.append(os.path.basename(mapset))
  518. ListSortLower(listOfMapsets)
  519. return listOfMapsets
  520. def GetColorTables():
  521. """!Get list of color tables"""
  522. ret = RunCommand('r.colors',
  523. read = True,
  524. flags = 'l')
  525. if not ret:
  526. return list()
  527. return ret.splitlines()
  528. def _getGDALFormats():
  529. """!Get dictionary of avaialble GDAL drivers"""
  530. ret = grass.read_command('r.in.gdal',
  531. quiet = True,
  532. flags = 'f')
  533. return _parseFormats(ret), _parseFormats(ret, writableOnly = True)
  534. def _getOGRFormats():
  535. """!Get dictionary of avaialble OGR drivers"""
  536. ret = grass.read_command('v.in.ogr',
  537. quiet = True,
  538. flags = 'f')
  539. return _parseFormats(ret), _parseFormats(ret, writableOnly = True)
  540. def _parseFormats(output, writableOnly = False):
  541. """!Parse r.in.gdal/v.in.ogr -f output"""
  542. formats = { 'file' : list(),
  543. 'database' : list(),
  544. 'protocol' : list()
  545. }
  546. if not output:
  547. return formats
  548. patt = None
  549. if writableOnly:
  550. patt = re.compile('\(rw\+?\)$', re.IGNORECASE)
  551. for line in output.splitlines():
  552. key, name = map(lambda x: x.strip(), line.strip().rsplit(':', -1))
  553. if writableOnly and not patt.search(key):
  554. continue
  555. if name in ('Memory', 'Virtual Raster', 'In Memory Raster'):
  556. continue
  557. if name in ('PostgreSQL', 'SQLite',
  558. 'ODBC', 'ESRI Personal GeoDatabase',
  559. 'Rasterlite',
  560. 'PostGIS WKT Raster driver',
  561. 'CouchDB'):
  562. formats['database'].append(name)
  563. elif name in ('GeoJSON',
  564. 'OGC Web Coverage Service',
  565. 'OGC Web Map Service',
  566. 'WFS',
  567. 'GeoRSS',
  568. 'HTTP Fetching Wrapper'):
  569. formats['protocol'].append(name)
  570. else:
  571. formats['file'].append(name)
  572. for items in formats.itervalues():
  573. items.sort()
  574. return formats
  575. formats = None
  576. def GetFormats(writableOnly = False):
  577. """!Get GDAL/OGR formats"""
  578. global formats
  579. if not formats:
  580. gdalAll, gdalWritable = _getGDALFormats()
  581. ogrAll, ogrWritable = _getOGRFormats()
  582. formats = {
  583. 'all' : {
  584. 'gdal' : gdalAll,
  585. 'ogr' : ogrAll,
  586. },
  587. 'writable' : {
  588. 'gdal' : gdalWritable,
  589. 'ogr' : ogrWritable,
  590. },
  591. }
  592. if writableOnly:
  593. return formats['writable']
  594. return formats['all']
  595. def GetSettingsPath():
  596. """!Get full path to the settings directory
  597. """
  598. try:
  599. verFd = open(os.path.join(ETCDIR, "VERSIONNUMBER"))
  600. version = int(verFd.readlines()[0].split(' ')[0].split('.')[0])
  601. except (IOError, ValueError, TypeError, IndexError), e:
  602. sys.exit(_("ERROR: Unable to determine GRASS version. Details: %s") % e)
  603. verFd.close()
  604. # keep location of settings files rc and wx in sync with lib/init/grass.py
  605. if sys.platform == 'win32':
  606. return os.path.join(os.getenv('APPDATA'), 'grass%d' % version)
  607. return os.path.join(os.getenv('HOME'), '.grass%d' % version)
  608. # From lib/gis/col_str.c, except purple which is mentioned
  609. # there but not given RGB values
  610. str2rgb = {'aqua': (100, 128, 255),
  611. 'black': (0, 0, 0),
  612. 'blue': (0, 0, 255),
  613. 'brown': (180, 77, 25),
  614. 'cyan': (0, 255, 255),
  615. 'gray': (128, 128, 128),
  616. 'green': (0, 255, 0),
  617. 'grey': (128, 128, 128),
  618. 'indigo': (0, 128, 255),
  619. 'magenta': (255, 0, 255),
  620. 'orange': (255, 128, 0),
  621. 'purple': (128, 0, 128),
  622. 'red': (255, 0, 0),
  623. 'violet': (128, 0, 255),
  624. 'white': (255, 255, 255),
  625. 'yellow': (255, 255, 0)}
  626. rgb2str = {}
  627. for (s,r) in str2rgb.items():
  628. rgb2str[ r ] = s
  629. def color_resolve(color):
  630. if len(color) > 0 and color[0] in "0123456789":
  631. rgb = tuple(map(int, color.split(':')))
  632. label = color
  633. else:
  634. # Convert color names to RGB
  635. try:
  636. rgb = str2rgb[color]
  637. label = color
  638. except KeyError:
  639. rgb = (200, 200, 200)
  640. label = _('Select Color')
  641. return (rgb, label)