utils.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  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('=')
  81. except ValueError:
  82. continue
  83. if p == param:
  84. params = [(idx, p, v)]
  85. break
  86. if p in ('map', 'input',
  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. dcmd[i] = p + '=' + v + '@' + mapset
  114. maps = list()
  115. for i, p, v in params:
  116. maps.append(dcmd[i].split('=')[1])
  117. mapname = '\n'.join(maps)
  118. return mapname
  119. def GetValidLayerName(name):
  120. """!Make layer name SQL compliant, based on G_str_to_sql()
  121. @todo: Better use directly GRASS Python SWIG...
  122. """
  123. retName = str(name).strip()
  124. # check if name is fully qualified
  125. if '@' in retName:
  126. retName, mapset = retName.split('@')
  127. else:
  128. mapset = None
  129. for c in retName:
  130. # c = toascii(c)
  131. if not (c >= 'A' and c <= 'Z') and \
  132. not (c >= 'a' and c <= 'z') and \
  133. not (c >= '0' and c <= '9'):
  134. c = '_'
  135. if not (retName[0] >= 'A' and retName[0] <= 'Z') and \
  136. not (retName[0] >= 'a' and retName[0] <= 'z'):
  137. retName = 'x' + retName[1:]
  138. if mapset:
  139. retName = retName + '@' + mapset
  140. return retName
  141. def ListOfCatsToRange(cats):
  142. """!Convert list of category number to range(s)
  143. Used for example for d.vect cats=[range]
  144. @param cats category list
  145. @return category range string
  146. @return '' on error
  147. """
  148. catstr = ''
  149. try:
  150. cats = map(int, cats)
  151. except:
  152. return catstr
  153. i = 0
  154. while i < len(cats):
  155. next = 0
  156. j = i + 1
  157. while j < len(cats):
  158. if cats[i + next] == cats[j] - 1:
  159. next += 1
  160. else:
  161. break
  162. j += 1
  163. if next > 1:
  164. catstr += '%d-%d,' % (cats[i], cats[i + next])
  165. i += next + 1
  166. else:
  167. catstr += '%d,' % (cats[i])
  168. i += 1
  169. return catstr.strip(',')
  170. def ListOfMapsets(all=False):
  171. """!Get list of available/accessible mapsets
  172. @param all if True get list of all mapsets
  173. @return list of mapsets
  174. """
  175. mapsets = []
  176. if all:
  177. ret = gcmd.RunCommand('g.mapsets',
  178. read = True,
  179. flags = 'l',
  180. fs = ';')
  181. if ret:
  182. mapsets = ret.rstrip('\n').split(';')
  183. else:
  184. raise gcmd.CmdError(cmd = 'g.mapsets',
  185. message = _('Unable to get list of available mapsets.'))
  186. else:
  187. ret = gcmd.RunCommand('g.mapsets',
  188. read = True,
  189. flags = 'p',
  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 accessible mapsets.'))
  196. ListSortLower(mapsets)
  197. return mapsets
  198. def ListSortLower(list):
  199. """!Sort list items (not case-sensitive)"""
  200. list.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
  201. def GetVectorNumberOfLayers(vector):
  202. """!Get list of vector layers"""
  203. layers = list()
  204. ret = gcmd.RunCommand('v.category',
  205. read = True,
  206. input = vector,
  207. option = 'report')
  208. if not ret:
  209. return layers
  210. for line in ret.splitlines():
  211. if not 'Layer' in line:
  212. continue
  213. value = line.split(':')[1].strip()
  214. if '/' in value: # value/name
  215. layers.append(value.split('/')[0])
  216. else:
  217. layers.append(value)
  218. return layers
  219. def Deg2DMS(lon, lat, string = True, hemisphere = True, precision = 3):
  220. """!Convert deg value to dms string
  221. @param lon longitude (x)
  222. @param lat latitude (y)
  223. @param string True to return string otherwise tuple
  224. @param hemisphere print hemisphere
  225. @param precision seconds precision
  226. @return DMS string or tuple of values
  227. @return empty string on error
  228. """
  229. try:
  230. flat = float(lat)
  231. flon = float(lon)
  232. except ValueError:
  233. if string:
  234. return ''
  235. else:
  236. return None
  237. # fix longitude
  238. while flon > 180.0:
  239. flon -= 360.0
  240. while flon < -180.0:
  241. flon += 360.0
  242. # hemisphere
  243. if hemisphere:
  244. if flat < 0.0:
  245. flat = abs(flat)
  246. hlat = 'S'
  247. else:
  248. hlat = 'N'
  249. if flon < 0.0:
  250. hlon = 'W'
  251. flon = abs(flon)
  252. else:
  253. hlon = 'E'
  254. else:
  255. flat = abs(flat)
  256. flon = abs(flon)
  257. hlon = ''
  258. hlat = ''
  259. slat = __ll_parts(flat, precision = precision)
  260. slon = __ll_parts(flon, precision = precision)
  261. if string:
  262. return slon + hlon + '; ' + slat + hlat
  263. return (slon + hlon, slat + hlat)
  264. def DMS2Deg(lon, lat):
  265. """!Convert dms value to deg
  266. @param lon longitude (x)
  267. @param lat latitude (y)
  268. @return tuple of converted values
  269. @return ValueError on error
  270. """
  271. x = __ll_parts(lon, reverse = True)
  272. y = __ll_parts(lat, reverse = True)
  273. return (x, y)
  274. def __ll_parts(value, reverse = False, precision = 3):
  275. """!Converts deg to d:m:s string
  276. @param value value to be converted
  277. @param reverse True to convert from d:m:s to deg
  278. @param precision seconds precision (ignored if reverse is True)
  279. @return converted value (string/float)
  280. @return ValueError on error (reverse == True)
  281. """
  282. if not reverse:
  283. if value == 0.0:
  284. return '%s%.*f' % ('00:00:0', precision, 0.0)
  285. d = int(int(value))
  286. m = int((value - d) * 60)
  287. s = ((value - d) * 60 - m) * 60
  288. if m < 0:
  289. m = '00'
  290. elif m < 10:
  291. m = '0' + str(m)
  292. else:
  293. m = str(m)
  294. if s < 0:
  295. s = '00.0000'
  296. elif s < 10.0:
  297. s = '0%.*f' % (precision, s)
  298. else:
  299. s = '%.*f' % (precision, s)
  300. return str(d) + ':' + m + ':' + s
  301. else: # -> reverse
  302. try:
  303. d, m, s = value.split(':')
  304. hs = s[-1]
  305. s = s[:-1]
  306. except ValueError:
  307. try:
  308. d, m = value.split(':')
  309. hs = m[-1]
  310. m = m[:-1]
  311. s = '0.0'
  312. except ValueError:
  313. try:
  314. d = value
  315. hs = d[-1]
  316. d = d[:-1]
  317. m = '0'
  318. s = '0.0'
  319. except ValueError:
  320. raise ValueError
  321. if hs not in ('N', 'S', 'E', 'W'):
  322. raise ValueError
  323. coef = 1.0
  324. if hs in ('S', 'W'):
  325. coef = -1.0
  326. fm = int(m) / 60.0
  327. fs = float(s) / (60 * 60)
  328. return coef * (float(d) + fm + fs)
  329. def GetCmdString(cmd):
  330. """
  331. Get GRASS command as string.
  332. @param cmd GRASS command given as dictionary
  333. @return command string
  334. """
  335. scmd = ''
  336. if not cmd:
  337. return scmd
  338. scmd = cmd[0]
  339. if cmd[1].has_key('flags'):
  340. for flag in cmd[1]['flags']:
  341. scmd += ' -' + flag
  342. for flag in ('verbose', 'quiet', 'overwrite'):
  343. if cmd[1].has_key(flag) and cmd[1][flag] is True:
  344. scmd += ' --' + flag
  345. for k, v in cmd[1].iteritems():
  346. if k in ('flags', 'verbose', 'quiet', 'overwrite'):
  347. continue
  348. scmd += ' %s=%s' % (k, v)
  349. return scmd
  350. def CmdToTuple(cmd):
  351. """!Convert command list to tuple for gcmd.RunCommand()"""
  352. if len(cmd) < 1:
  353. return None
  354. dcmd = {}
  355. for item in cmd[1:]:
  356. if '=' in item: # params
  357. key, value = item.split('=', 1)
  358. dcmd[str(key)] = str(value)
  359. elif item[:2] == '--': # long flags
  360. flag = item[2:]
  361. if flag in ('verbose', 'quiet', 'overwrite'):
  362. dcmd[str(flag)] = True
  363. else: # -> flags
  364. if not dcmd.has_key('flags'):
  365. dcmd['flags'] = ''
  366. dcmd['flags'] += item.replace('-', '')
  367. return (cmd[0],
  368. dcmd)
  369. def PathJoin(*args):
  370. """!Check path created by os.path.join"""
  371. path = os.path.join(*args)
  372. if platform.system() == 'Windows' and \
  373. '/' in path:
  374. return path[1].upper() + ':\\' + path[3:].replace('/', '\\')
  375. return path
  376. def ReadEpsgCodes(path):
  377. """!Read EPSG code from the file
  378. @param path full path to the file with EPSG codes
  379. @return dictionary of EPSG code
  380. @return string on error
  381. """
  382. epsgCodeDict = dict()
  383. try:
  384. try:
  385. f = open(path, "r")
  386. except IOError:
  387. return _("failed to open '%s'" % path)
  388. i = 0
  389. code = None
  390. for line in f.readlines():
  391. line = line.strip()
  392. if len(line) < 1:
  393. continue
  394. if line[0] == '#':
  395. descr = line[1:].strip()
  396. elif line[0] == '<':
  397. code, params = line.split(" ", 1)
  398. try:
  399. code = int(code.replace('<', '').replace('>', ''))
  400. except ValueError:
  401. return e
  402. if code is not None:
  403. epsgCodeDict[code] = (descr, params)
  404. code = None
  405. i += 1
  406. f.close()
  407. except StandardError, e:
  408. return e
  409. return epsgCodeDict
  410. def ReprojectCoordinates(coord, projOut, projIn = None, flags = ''):
  411. """!Reproject coordinates
  412. @param coord coordinates given as tuple
  413. @param projOut output projection
  414. @param projIn input projection (use location projection settings)
  415. @return reprojected coordinates (returned as tuple)
  416. """
  417. coors = gcmd.RunCommand('m.proj',
  418. flags = flags,
  419. input = '-',
  420. proj_input = projIn,
  421. proj_output = projOut,
  422. fs = ';',
  423. stdin = '%f;%f' % (coord[0], coord[1]),
  424. read = True)
  425. if coors:
  426. coors = coors.split(';')
  427. e = coors[0]
  428. n = coors[1]
  429. try:
  430. proj = projOut.split(' ')[0].split('=')[1]
  431. except IndexError:
  432. proj = ''
  433. if proj in ('ll', 'latlong', 'longlat') and 'd' not in flags:
  434. return (proj, (e, n))
  435. else:
  436. try:
  437. return (proj, (float(e), float(n)))
  438. except ValueError:
  439. return (None, None)
  440. return (None, None)
  441. def GetListOfLocations(dbase):
  442. """!Get list of GRASS locations in given dbase
  443. @param dbase GRASS database path
  444. @return list of locations (sorted)
  445. """
  446. listOfLocations = list()
  447. try:
  448. for location in glob.glob(os.path.join(dbase, "*")):
  449. try:
  450. if os.path.join(location, "PERMANENT") in glob.glob(os.path.join(location, "*")):
  451. listOfLocations.append(os.path.basename(location))
  452. except:
  453. pass
  454. except UnicodeEncodeError, e:
  455. raise e
  456. ListSortLower(listOfLocations)
  457. return listOfLocations
  458. def GetListOfMapsets(dbase, location, selectable = False):
  459. """!Get list of mapsets in given GRASS location
  460. @param dbase GRASS database path
  461. @param location GRASS location
  462. @param selectable True to get list of selectable mapsets, otherwise all
  463. @return list of mapsets - sorted (PERMANENT first)
  464. """
  465. listOfMapsets = list()
  466. if selectable:
  467. ret = gcmd.RunCommand('g.mapset',
  468. read = True,
  469. flags = 'l',
  470. location = location,
  471. gisdbase = dbase)
  472. if not ret:
  473. return listOfMapsets
  474. for line in ret.rstrip().splitlines():
  475. listOfMapsets += line.split(' ')
  476. else:
  477. for mapset in glob.glob(os.path.join(dbase, location, "*")):
  478. if os.path.isdir(mapset) and \
  479. os.path.isfile(os.path.join(dbase, location, mapset, "WIND")) and \
  480. os.path.basename(mapset) != 'PERMANENT':
  481. listOfMapsets.append(EncodeString(os.path.basename(mapset)))
  482. ListSortLower(listOfMapsets)
  483. listOfMapsets.insert(0, 'PERMANENT')
  484. return listOfMapsets
  485. def GetColorTables():
  486. """!Get list of color tables"""
  487. ret = gcmd.RunCommand('r.colors',
  488. read = True,
  489. flags = 'l')
  490. if not ret:
  491. return list()
  492. return ret.splitlines()
  493. def EncodeString(string):
  494. """!Return encoded string
  495. @param string string to be encoded
  496. @return encoded string
  497. """
  498. enc = locale.getdefaultlocale()[1]
  499. if enc:
  500. return string.encode(enc)
  501. return string
  502. def UnicodeString(string):
  503. """!Return unicode string
  504. @param string string to be converted
  505. @return unicode string
  506. """
  507. if isinstance(string, unicode):
  508. return string
  509. enc = locale.getdefaultlocale()[1]
  510. if enc:
  511. return unicode(string, enc)
  512. return string