utils.py 17 KB

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