utils.py 21 KB

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