utils.py 20 KB

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