utils.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  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 globalvar
  16. grassPath = os.path.join(globalvar.ETCDIR, "python")
  17. sys.path.append(grassPath)
  18. from grass.script import core as grass
  19. import gcmd
  20. try:
  21. import subprocess
  22. except:
  23. compatPath = os.path.join(globalvar.ETCWXDIR, "compat")
  24. sys.path.append(compatPath)
  25. import subprocess
  26. def normalize_whitespace(text):
  27. """!Remove redundant whitespace from a string"""
  28. return string.join( string.split(text), ' ')
  29. def GetTempfile(pref=None):
  30. """
  31. Creates GRASS temporary file using defined prefix.
  32. @todo Fix path on MS Windows/MSYS
  33. @param pref prefer the given path
  34. @return Path to file name (string) or None
  35. """
  36. import gcmd
  37. ret = gcmd.RunCommand('g.tempfile',
  38. read = True,
  39. pid = os.getpid())
  40. tempfile = ret.splitlines()[0].strip()
  41. # FIXME
  42. # ugly hack for MSYS (MS Windows)
  43. if platform.system() == 'Windows':
  44. tempfile = tempfile.replace("/", "\\")
  45. try:
  46. path, file = os.path.split(tempfile)
  47. if pref:
  48. return os.path.join(pref, file)
  49. else:
  50. return tempfile
  51. except:
  52. return None
  53. def GetLayerNameFromCmd(dcmd, fullyQualified=False, param=None,
  54. layerType=None):
  55. """!Get map name from GRASS command
  56. @param dcmd GRASS command (given as list)
  57. @param fullyQualified change map name to be fully qualified
  58. @param force parameter otherwise 'input'/'map'
  59. @param update change map name in command
  60. @param layerType check also layer type ('raster', 'vector', '3d-raster', ...)
  61. @return map name
  62. @return '' if no map name found in command
  63. """
  64. mapname = ''
  65. if len(dcmd) < 1:
  66. return mapname
  67. if 'd.grid' == dcmd[0]:
  68. mapname = 'grid'
  69. elif 'd.geodesic' in dcmd[0]:
  70. mapname = 'geodesic'
  71. elif 'd.rhumbline' in dcmd[0]:
  72. mapname = 'rhumb'
  73. elif 'labels=' in dcmd[0]:
  74. mapname = dcmd[idx].split('=')[1]+' labels'
  75. else:
  76. params = list()
  77. for idx in range(len(dcmd)):
  78. try:
  79. p, v = dcmd[idx].split('=')
  80. except ValueError:
  81. continue
  82. if p == param:
  83. params = [(idx, p, v)]
  84. break
  85. if p in ('map', 'input',
  86. 'red', 'blue', 'green',
  87. 'h_map', 's_map', 'i_map',
  88. 'reliefmap'):
  89. params.append((idx, p, v))
  90. if len(params) < 1:
  91. return mapname
  92. mapname = params[0][2]
  93. mapset = ''
  94. if fullyQualified and '@' not in mapname:
  95. if layerType in ('raster', 'vector', '3d-raster', 'rgb', 'his'):
  96. try:
  97. if layerType in ('raster', 'rgb', 'his'):
  98. findType = 'cell'
  99. else:
  100. findType = layerType
  101. result = grass.find_file(mapname, element=findType)
  102. except AttributeError, e: # not found
  103. return ''
  104. if result:
  105. mapset = result['mapset']
  106. else:
  107. mapset = grass.gisenv()['MAPSET']
  108. else:
  109. mapset = grass.gisenv()['MAPSET']
  110. # update dcmd
  111. for i, p, v in params:
  112. dcmd[i] = p + '=' + v + '@' + mapset
  113. maps = list()
  114. for i, p, v in params:
  115. maps.append(dcmd[i].split('=')[1])
  116. mapname = '\n'.join(maps)
  117. return mapname
  118. def GetValidLayerName(name):
  119. """!Make layer name SQL compliant, based on G_str_to_sql()
  120. @todo: Better use directly GRASS Python SWIG...
  121. """
  122. retName = str(name).strip()
  123. # check if name is fully qualified
  124. if '@' in retName:
  125. retName, mapset = retName.split('@')
  126. else:
  127. mapset = None
  128. for c in retName:
  129. # c = toascii(c)
  130. if not (c >= 'A' and c <= 'Z') and \
  131. not (c >= 'a' and c <= 'z') and \
  132. not (c >= '0' and c <= '9'):
  133. c = '_'
  134. if not (retName[0] >= 'A' and retName[0] <= 'Z') and \
  135. not (retName[0] >= 'a' and retName[0] <= 'z'):
  136. retName = 'x' + retName[1:]
  137. if mapset:
  138. retName = retName + '@' + mapset
  139. return retName
  140. def ListOfCatsToRange(cats):
  141. """!Convert list of category number to range(s)
  142. Used for example for d.vect cats=[range]
  143. @param cats category list
  144. @return category range string
  145. @return '' on error
  146. """
  147. catstr = ''
  148. try:
  149. cats = map(int, cats)
  150. except:
  151. return catstr
  152. i = 0
  153. while i < len(cats):
  154. next = 0
  155. j = i + 1
  156. while j < len(cats):
  157. if cats[i + next] == cats[j] - 1:
  158. next += 1
  159. else:
  160. break
  161. j += 1
  162. if next > 1:
  163. catstr += '%d-%d,' % (cats[i], cats[i + next])
  164. i += next + 1
  165. else:
  166. catstr += '%d,' % (cats[i])
  167. i += 1
  168. return catstr.strip(',')
  169. def ListOfMapsets(all=False):
  170. """!Get list of available/accessible mapsets
  171. @param all if True get list of all mapsets
  172. @return list of mapsets
  173. """
  174. mapsets = []
  175. if all:
  176. ret = gcmd.RunCommand('g.mapsets',
  177. read = True,
  178. flags = 'l',
  179. fs = ';')
  180. if ret:
  181. mapsets = ret.rstrip('\n').split(';')
  182. else:
  183. raise gcmd.CmdError(cmd = 'g.mapsets',
  184. message = _('Unable to get list of available mapsets.'))
  185. else:
  186. ret = gcmd.RunCommand('g.mapsets',
  187. read = True,
  188. flags = 'p',
  189. fs = ';')
  190. if ret:
  191. mapsets = ret.rstrip('\n').split(';')
  192. else:
  193. raise gcmd.CmdError(cmd = 'g.mapsets',
  194. message = _('Unable to get list of accessible mapsets.'))
  195. ListSortLower(mapsets)
  196. return mapsets
  197. def ListSortLower(list):
  198. """!Sort list items (not case-sensitive)"""
  199. list.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
  200. def GetVectorNumberOfLayers(vector):
  201. """!Get list of vector layers"""
  202. layers = list()
  203. ret = gcmd.RunCommand('v.category',
  204. read = True,
  205. input = vector,
  206. option = 'report')
  207. if not ret:
  208. return layers
  209. for line in ret.splitlines():
  210. if not 'Layer' in line:
  211. continue
  212. value = line.split(':')[1].strip()
  213. if '/' in value: # value/name
  214. layers.append(value.split('/')[0])
  215. else:
  216. layers.append(value)
  217. return layers
  218. def Deg2DMS(lon, lat, string = True, hemisphere = True, precision = 3):
  219. """!Convert deg value to dms string
  220. @param lon longitude (x)
  221. @param lat latitude (y)
  222. @param string True to return string otherwise tuple
  223. @param hemisphere print hemisphere
  224. @param precision seconds precision
  225. @return DMS string or tuple of values
  226. @return empty string on error
  227. """
  228. try:
  229. flat = float(lat)
  230. flon = float(lon)
  231. except ValueError:
  232. if string:
  233. return ''
  234. else:
  235. return None
  236. # fix longitude
  237. while flon > 180.0:
  238. flon -= 360.0
  239. while flon < -180.0:
  240. flon += 360.0
  241. # hemisphere
  242. if hemisphere:
  243. if flat < 0.0:
  244. flat = abs(flat)
  245. hlat = 'S'
  246. else:
  247. hlat = 'N'
  248. if flon < 0.0:
  249. hlon = 'W'
  250. flon = abs(flon)
  251. else:
  252. hlon = 'E'
  253. else:
  254. flat = abs(flat)
  255. flon = abs(flon)
  256. hlon = ''
  257. hlat = ''
  258. slat = __ll_parts(flat, precision = precision)
  259. slon = __ll_parts(flon, precision = precision)
  260. if string:
  261. return slon + hlon + '; ' + slat + hlat
  262. return (slon + hlon, slat + hlat)
  263. def DMS2Deg(lon, lat):
  264. """!Convert dms value to deg
  265. @param lon longitude (x)
  266. @param lat latitude (y)
  267. @return tuple of converted values
  268. @return ValueError on error
  269. """
  270. x = __ll_parts(lon, reverse = True)
  271. y = __ll_parts(lat, reverse = True)
  272. return (x, y)
  273. def __ll_parts(value, reverse = False, precision = 3):
  274. """!Converts deg to d:m:s string
  275. @param value value to be converted
  276. @param reverse True to convert from d:m:s to deg
  277. @param precision seconds precision (ignored if reverse is True)
  278. @return converted value (string/float)
  279. @return ValueError on error (reverse == True)
  280. """
  281. if not reverse:
  282. if value == 0.0:
  283. return '%s%.*f' % ('00:00:0', precision, 0.0)
  284. d = int(int(value))
  285. m = int((value - d) * 60)
  286. s = ((value - d) * 60 - m) * 60
  287. if m < 0:
  288. m = '00'
  289. elif m < 10:
  290. m = '0' + str(m)
  291. else:
  292. m = str(m)
  293. if s < 0:
  294. s = '00.0000'
  295. elif s < 10.0:
  296. s = '0%.*f' % (precision, s)
  297. else:
  298. s = '%.*f' % (precision, s)
  299. return str(d) + ':' + m + ':' + s
  300. else: # -> reverse
  301. try:
  302. d, m, s = value.split(':')
  303. hs = s[-1]
  304. s = s[:-1]
  305. except ValueError:
  306. try:
  307. d, m = value.split(':')
  308. hs = m[-1]
  309. m = m[:-1]
  310. s = '0.0'
  311. except ValueError:
  312. try:
  313. d = value
  314. hs = d[-1]
  315. d = d[:-1]
  316. m = '0'
  317. s = '0.0'
  318. except ValueError:
  319. raise ValueError
  320. if hs not in ('N', 'S', 'E', 'W'):
  321. raise ValueError
  322. coef = 1.0
  323. if hs in ('S', 'W'):
  324. coef = -1.0
  325. fm = int(m) / 60.0
  326. fs = float(s) / (60 * 60)
  327. return coef * (float(d) + fm + fs)
  328. def GetCmdString(cmd):
  329. """
  330. Get GRASS command as string.
  331. @param cmd GRASS command given as dictionary
  332. @return command string
  333. """
  334. scmd = ''
  335. if not cmd:
  336. return scmd
  337. scmd = cmd[0]
  338. if cmd[1].has_key('flags'):
  339. for flag in cmd[1]['flags']:
  340. scmd += ' -' + flag
  341. for flag in ('verbose', 'quiet', 'overwrite'):
  342. if cmd[1].has_key(flag) and cmd[1][flag] is True:
  343. scmd += ' --' + flag
  344. for k, v in cmd[1].iteritems():
  345. if k in ('flags', 'verbose', 'quiet', 'overwrite'):
  346. continue
  347. scmd += ' %s=%s' % (k, v)
  348. return scmd
  349. def CmdToTuple(cmd):
  350. """!Convert command list to tuple for gcmd.RunCommand()"""
  351. if len(cmd) < 1:
  352. return None
  353. dcmd = {}
  354. for item in cmd[1:]:
  355. if '=' in item: # params
  356. key, value = item.split('=', 1)
  357. dcmd[str(key)] = str(value)
  358. elif item[:2] == '--': # long flags
  359. flag = item[2:]
  360. if flag in ('verbose', 'quiet', 'overwrite'):
  361. dcmd[str(flag)] = True
  362. else: # -> flags
  363. if not dcmd.has_key('flags'):
  364. dcmd['flags'] = ''
  365. dcmd['flags'] += item.replace('-', '')
  366. return (cmd[0],
  367. dcmd)
  368. def PathJoin(*args):
  369. """!Check path created by os.path.join"""
  370. path = os.path.join(*args)
  371. if platform.system() == 'Windows' and \
  372. '/' in path:
  373. return path[1].upper() + ':\\' + path[3:].replace('/', '\\')
  374. return path
  375. def ReadEpsgCodes(path):
  376. """!Read EPSG code from the file
  377. @param path full path to the file with EPSG codes
  378. @return dictionary of EPSG code
  379. @return string on error
  380. """
  381. epsgCodeDict = dict()
  382. try:
  383. try:
  384. f = open(path, "r")
  385. except IOError:
  386. return _("failed to open '%s'" % path)
  387. i = 0
  388. code = None
  389. for line in f.readlines():
  390. line = line.strip()
  391. if len(line) < 1:
  392. continue
  393. if line[0] == '#':
  394. descr = line[1:].strip()
  395. elif line[0] == '<':
  396. code, params = line.split(" ", 1)
  397. try:
  398. code = int(code.replace('<', '').replace('>', ''))
  399. except ValueError:
  400. return e
  401. if code is not None:
  402. epsgCodeDict[code] = (descr, params)
  403. code = None
  404. i += 1
  405. f.close()
  406. except StandardError, e:
  407. return e
  408. return epsgCodeDict
  409. def ReprojectCoordinates(coord, projOut, projIn = None, flags = ''):
  410. """!Reproject coordinates
  411. @param coord coordinates given as tuple
  412. @param projOut output projection
  413. @param projIn input projection (use location projection settings)
  414. @return reprojected coordinates (returned as tuple)
  415. """
  416. coors = gcmd.RunCommand('m.proj',
  417. flags = flags,
  418. input = '-',
  419. proj_input = projIn,
  420. proj_output = projOut,
  421. fs = ';',
  422. stdin = '%f;%f' % (coord[0], coord[1]),
  423. read = True)
  424. if coors:
  425. coors = coors.split(';')
  426. e = coors[0]
  427. n = coors[1]
  428. try:
  429. proj = projOut.split(' ')[0].split('=')[1]
  430. except IndexError:
  431. proj = ''
  432. if proj in ('ll', 'latlong', 'longlat') and 'd' not in flags:
  433. return (proj, (e, n))
  434. else:
  435. try:
  436. return (proj, (float(e), float(n)))
  437. except ValueError:
  438. return (None, None)
  439. return (None, None)
  440. def GetListOfLocations(dbase):
  441. """!Get list of GRASS locations in given dbase
  442. @param dbase GRASS database path
  443. @return list of locations (sorted)
  444. """
  445. listOfLocations = list()
  446. for location in glob.glob(os.path.join(dbase, "*")):
  447. try:
  448. if os.path.join(location, "PERMANENT") in glob.glob(os.path.join(location, "*")):
  449. listOfLocations.append(os.path.basename(location))
  450. except:
  451. pass
  452. ListSortLower(listOfLocations)
  453. return listOfLocations
  454. def GetListOfMapsets(dbase, location, selectable = False):
  455. """!Get list of mapsets in given GRASS location
  456. @param dbase GRASS database path
  457. @param location GRASS location
  458. @param selectable True to get list of selectable mapsets, otherwise all
  459. @return list of mapsets - sorted (PERMANENT first)
  460. """
  461. listOfMapsets = list()
  462. if selectable:
  463. ret = gcmd.RunCommand('g.mapset',
  464. read = True,
  465. flags = 'l',
  466. location = location,
  467. gisdbase = dbase)
  468. if not ret:
  469. return listOfMapsets
  470. for line in ret.rstrip().splitlines():
  471. listOfMapsets += line.split(' ')
  472. else:
  473. for mapset in glob.glob(os.path.join(dbase, location, "*")):
  474. if os.path.isdir(mapset) and \
  475. os.path.isfile(os.path.join(dbase, location, mapset, "WIND")) and \
  476. os.path.basename(mapset) != 'PERMANENT':
  477. listOfMapsets.append(os.path.basename(mapset))
  478. ListSortLower(listOfMapsets)
  479. listOfMapsets.insert(0, 'PERMANENT')
  480. return listOfMapsets
  481. def GetColorTables():
  482. """!Get list of color tables"""
  483. ret = gcmd.RunCommand('r.colors',
  484. read = True,
  485. flags = 'l')
  486. if not ret:
  487. return list()
  488. return ret.splitlines()