utils.py 20 KB

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