utils.py 17 KB

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