utils.py 21 KB

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