utils.py 20 KB

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