utils.py 20 KB

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