Grass7Utils.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. """
  2. ***************************************************************************
  3. GrassUtils.py
  4. ---------------------
  5. Date : February 2015
  6. Copyright : (C) 2014-2015 by Victor Olaya
  7. Email : volayaf at gmail dot com
  8. ***************************************************************************
  9. * *
  10. * This program is free software; you can redistribute it and/or modify *
  11. * it under the terms of the GNU General Public License as published by *
  12. * the Free Software Foundation; either version 2 of the License, or *
  13. * (at your option) any later version. *
  14. * *
  15. ***************************************************************************
  16. """
  17. __author__ = 'Victor Olaya'
  18. __date__ = 'February 2015'
  19. __copyright__ = '(C) 2014-2015, Victor Olaya'
  20. import stat
  21. import shutil
  22. import subprocess
  23. import os
  24. import sys
  25. from pathlib import Path
  26. from qgis.core import (Qgis,
  27. QgsApplication,
  28. QgsProcessingUtils,
  29. QgsMessageLog,
  30. QgsCoordinateReferenceSystem,
  31. QgsProcessingContext)
  32. from qgis.PyQt.QtCore import QCoreApplication
  33. from processing.core.ProcessingConfig import ProcessingConfig
  34. from processing.tools.system import userFolder, isWindows, isMac, mkdir
  35. from processing.algs.gdal.GdalUtils import GdalUtils
  36. class Grass7Utils:
  37. GRASS_REGION_XMIN = 'GRASS7_REGION_XMIN'
  38. GRASS_REGION_YMIN = 'GRASS7_REGION_YMIN'
  39. GRASS_REGION_XMAX = 'GRASS7_REGION_XMAX'
  40. GRASS_REGION_YMAX = 'GRASS7_REGION_YMAX'
  41. GRASS_REGION_CELLSIZE = 'GRASS7_REGION_CELLSIZE'
  42. GRASS_LOG_COMMANDS = 'GRASS7_LOG_COMMANDS'
  43. GRASS_LOG_CONSOLE = 'GRASS7_LOG_CONSOLE'
  44. GRASS_HELP_URL = 'GRASS_HELP_URL'
  45. GRASS_USE_REXTERNAL = 'GRASS_USE_REXTERNAL'
  46. GRASS_USE_VEXTERNAL = 'GRASS_USE_VEXTERNAL'
  47. # TODO Review all default options formats
  48. GRASS_RASTER_FORMATS_CREATEOPTS = {
  49. 'GTiff': 'TFW=YES,COMPRESS=LZW',
  50. 'PNG': 'ZLEVEL=9',
  51. 'WEBP': 'QUALITY=85'
  52. }
  53. sessionRunning = False
  54. sessionLayers = {}
  55. projectionSet = False
  56. isGrassInstalled = False
  57. version = None
  58. path = None
  59. command = None
  60. @staticmethod
  61. def grassBatchJobFilename():
  62. """
  63. The Batch file is executed by GRASS binary.
  64. On GNU/Linux and MacOSX it will be executed by a shell.
  65. On MS-Windows, it will be executed by cmd.exe.
  66. """
  67. gisdbase = Grass7Utils.grassDataFolder()
  68. if isWindows():
  69. batchFile = os.path.join(gisdbase, 'grass_batch_job.cmd')
  70. else:
  71. batchFile = os.path.join(gisdbase, 'grass_batch_job.sh')
  72. return batchFile
  73. @staticmethod
  74. def exportCrsWktToFile(crs, context: QgsProcessingContext):
  75. """
  76. Exports a crs as a WKT definition to a text file, and returns the path
  77. to this file
  78. """
  79. wkt = crs.toWkt(QgsCoordinateReferenceSystem.WKT_PREFERRED)
  80. wkt_file = QgsProcessingUtils.generateTempFilename('crs.prj', context)
  81. with open(wkt_file, 'w', encoding='utf-8') as f:
  82. f.write(wkt)
  83. return wkt_file
  84. @staticmethod
  85. def installedVersion(run=False):
  86. """
  87. Returns the installed version of GRASS by
  88. launching the GRASS command with -v parameter.
  89. """
  90. if Grass7Utils.isGrassInstalled and not run:
  91. return Grass7Utils.version
  92. if Grass7Utils.grassBin() is None:
  93. return None
  94. # Launch GRASS command with -v parameter
  95. # For MS-Windows, hide the console
  96. if isWindows():
  97. si = subprocess.STARTUPINFO()
  98. si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
  99. si.wShowWindow = subprocess.SW_HIDE
  100. with subprocess.Popen(
  101. [Grass7Utils.command, '-v'],
  102. shell=False,
  103. stdout=subprocess.PIPE,
  104. stdin=subprocess.DEVNULL,
  105. stderr=subprocess.STDOUT,
  106. universal_newlines=True,
  107. startupinfo=si if isWindows() else None
  108. ) as proc:
  109. try:
  110. lines = proc.stdout.readlines()
  111. for line in lines:
  112. if "GRASS GIS " in line:
  113. line = line.split(" ")[-1].strip()
  114. if line.startswith("7.") or line.startswith("8."):
  115. Grass7Utils.version = line
  116. return Grass7Utils.version
  117. except Exception:
  118. pass
  119. return None
  120. @staticmethod
  121. def grassBin():
  122. """
  123. Find GRASS binary path on the operating system.
  124. Sets global variable Grass7Utils.command
  125. """
  126. def searchFolder(folder):
  127. """
  128. Inline function to search for grass binaries into a folder
  129. with os.walk
  130. """
  131. if os.path.exists(folder):
  132. for root, dirs, files in os.walk(folder):
  133. for cmd in cmdList:
  134. if cmd in files:
  135. return os.path.join(root, cmd)
  136. return None
  137. if Grass7Utils.command:
  138. return Grass7Utils.command
  139. path = Grass7Utils.grassPath()
  140. command = None
  141. vn = os.path.join(path, "etc", "VERSIONNUMBER")
  142. if os.path.isfile(vn):
  143. with open(vn) as f:
  144. major, minor, patch = f.readlines()[0].split(' ')[0].split('.')
  145. if patch != 'svn':
  146. patch = ''
  147. cmdList = [
  148. "grass{}{}{}".format(major, minor, patch),
  149. "grass",
  150. "grass{}{}{}.{}".format(major, minor, patch, "bat" if isWindows() else "sh"),
  151. "grass.{}".format("bat" if isWindows() else "sh")
  152. ]
  153. else:
  154. cmdList = ["grass80", "grass78", "grass76", "grass74", "grass72", "grass70", "grass"]
  155. cmdList.extend(["{}.{}".format(b, "bat" if isWindows() else "sh") for b in cmdList])
  156. # For MS-Windows there is a difference between GRASS Path and GRASS binary
  157. if isWindows():
  158. # If nothing found, use OSGEO4W or QgsPrefix:
  159. if "OSGEO4W_ROOT" in os.environ:
  160. testFolder = str(os.environ['OSGEO4W_ROOT'])
  161. else:
  162. testFolder = str(QgsApplication.prefixPath())
  163. testFolder = os.path.join(testFolder, 'bin')
  164. command = searchFolder(testFolder)
  165. elif isMac():
  166. # Search in grassPath
  167. command = searchFolder(path)
  168. # If everything has failed, use shutil (but not for Windows as it'd include .)
  169. if not command and not isWindows():
  170. for cmd in cmdList:
  171. testBin = shutil.which(cmd)
  172. if testBin:
  173. command = os.path.abspath(testBin)
  174. break
  175. if command:
  176. Grass7Utils.command = command
  177. if path == '':
  178. Grass7Utils.path = os.path.dirname(command)
  179. return command
  180. @staticmethod
  181. def grassPath():
  182. """
  183. Find GRASS path on the operating system.
  184. Sets global variable Grass7Utils.path
  185. """
  186. if Grass7Utils.path is not None:
  187. return Grass7Utils.path
  188. if not isWindows() and not isMac():
  189. return ''
  190. folder = None
  191. # Under MS-Windows, we use GISBASE or QGIS Path for folder
  192. if isWindows():
  193. if "GISBASE" in os.environ:
  194. folder = os.environ["GISBASE"]
  195. else:
  196. testfolder = os.path.join(os.path.dirname(QgsApplication.prefixPath()), 'grass')
  197. if os.path.isdir(testfolder):
  198. grassfolders = sorted([f for f in os.listdir(testfolder) if f.startswith("grass-7.") and os.path.isdir(os.path.join(testfolder, f))], reverse=True, key=lambda x: [int(v) for v in x[len("grass-"):].split('.') if v != 'svn'])
  199. if grassfolders:
  200. folder = os.path.join(testfolder, grassfolders[0])
  201. elif isMac():
  202. # For MacOSX, first check environment
  203. if "GISBASE" in os.environ:
  204. folder = os.environ["GISBASE"]
  205. else:
  206. # Find grass folder if it exists inside QGIS bundle
  207. for version in ['', '8', '7', '80', '78', '76', '74', '72', '71', '70']:
  208. testfolder = os.path.join(str(QgsApplication.prefixPath()),
  209. 'grass{}'.format(version))
  210. if os.path.isdir(testfolder):
  211. folder = testfolder
  212. break
  213. # If nothing found, try standalone GRASS installation
  214. if folder is None:
  215. for version in ['8', '6', '4', '2', '1', '0']:
  216. testfolder = '/Applications/GRASS-7.{}.app/Contents/MacOS'.format(version)
  217. if os.path.isdir(testfolder):
  218. folder = testfolder
  219. break
  220. if folder is not None:
  221. Grass7Utils.path = folder
  222. return folder or ''
  223. @staticmethod
  224. def userDescriptionFolder():
  225. """
  226. Creates and returns a directory for users to create additional algorithm descriptions.
  227. Or modified versions of stock algorithm descriptions shipped with QGIS.
  228. """
  229. folder = Path(userFolder(), 'grassaddons', 'description')
  230. folder.mkdir(parents=True, exist_ok=True)
  231. return folder
  232. @staticmethod
  233. def grassDescriptionFolders():
  234. """
  235. Returns the directories to search for algorithm descriptions.
  236. Note that the provider will load from these in sequence, so we put the userDescriptionFolder first
  237. to allow users to create modified versions of stock algorithms shipped with QGIS.
  238. """
  239. return [Grass7Utils.userDescriptionFolder(), Path(__file__).parent.joinpath('description')]
  240. @staticmethod
  241. def getWindowsCodePage():
  242. """
  243. Determines MS-Windows CMD.exe shell codepage.
  244. Used into GRASS exec script under MS-Windows.
  245. """
  246. from ctypes import cdll
  247. return str(cdll.kernel32.GetACP())
  248. @staticmethod
  249. def createGrassBatchJobFileFromGrassCommands(commands):
  250. with open(Grass7Utils.grassBatchJobFilename(), 'w') as fout:
  251. if not isWindows():
  252. fout.write('#!/bin/sh\n')
  253. else:
  254. fout.write('chcp {}>NUL\n'.format(Grass7Utils.getWindowsCodePage()))
  255. for command in commands:
  256. Grass7Utils.writeCommand(fout, command)
  257. fout.write('exit')
  258. @staticmethod
  259. def grassMapsetFolder():
  260. """
  261. Creates and returns the GRASS temporary DB LOCATION directory.
  262. """
  263. folder = os.path.join(Grass7Utils.grassDataFolder(), 'temp_location')
  264. mkdir(folder)
  265. return folder
  266. @staticmethod
  267. def grassDataFolder():
  268. """
  269. Creates and returns the GRASS temporary DB directory.
  270. """
  271. tempfolder = os.path.normpath(
  272. os.path.join(QgsProcessingUtils.tempFolder(), 'grassdata'))
  273. mkdir(tempfolder)
  274. return tempfolder
  275. @staticmethod
  276. def createTempMapset():
  277. """
  278. Creates a temporary location and mapset(s) for GRASS data
  279. processing. A minimal set of folders and files is created in the
  280. system's default temporary directory. The settings files are
  281. written with sane defaults, so GRASS can do its work. The mapset
  282. projection will be set later, based on the projection of the first
  283. input image or vector
  284. """
  285. folder = Grass7Utils.grassMapsetFolder()
  286. mkdir(os.path.join(folder, 'PERMANENT'))
  287. mkdir(os.path.join(folder, 'PERMANENT', '.tmp'))
  288. Grass7Utils.writeGrassWindow(os.path.join(folder, 'PERMANENT', 'DEFAULT_WIND'))
  289. with open(os.path.join(folder, 'PERMANENT', 'MYNAME'), 'w') as outfile:
  290. outfile.write(
  291. 'QGIS GRASS GIS 7 interface: temporary data processing location.\n')
  292. Grass7Utils.writeGrassWindow(os.path.join(folder, 'PERMANENT', 'WIND'))
  293. mkdir(os.path.join(folder, 'PERMANENT', 'sqlite'))
  294. with open(os.path.join(folder, 'PERMANENT', 'VAR'), 'w') as outfile:
  295. outfile.write('DB_DRIVER: sqlite\n')
  296. outfile.write('DB_DATABASE: $GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db\n')
  297. @staticmethod
  298. def writeGrassWindow(filename):
  299. """
  300. Creates the GRASS Window file
  301. """
  302. with open(filename, 'w') as out:
  303. out.write('proj: 0\n')
  304. out.write('zone: 0\n')
  305. out.write('north: 1\n')
  306. out.write('south: 0\n')
  307. out.write('east: 1\n')
  308. out.write('west: 0\n')
  309. out.write('cols: 1\n')
  310. out.write('rows: 1\n')
  311. out.write('e-w resol: 1\n')
  312. out.write('n-s resol: 1\n')
  313. out.write('top: 1\n')
  314. out.write('bottom: 0\n')
  315. out.write('cols3: 1\n')
  316. out.write('rows3: 1\n')
  317. out.write('depths: 1\n')
  318. out.write('e-w resol3: 1\n')
  319. out.write('n-s resol3: 1\n')
  320. out.write('t-b resol: 1\n')
  321. @staticmethod
  322. def prepareGrassExecution(commands):
  323. """
  324. Prepare GRASS batch job in a script and
  325. returns it as a command ready for subprocess.
  326. """
  327. if Grass7Utils.command is None:
  328. Grass7Utils.grassBin()
  329. env = os.environ.copy()
  330. env['GRASS_MESSAGE_FORMAT'] = 'plain'
  331. if 'GISBASE' in env:
  332. del env['GISBASE']
  333. Grass7Utils.createGrassBatchJobFileFromGrassCommands(commands)
  334. os.chmod(Grass7Utils.grassBatchJobFilename(), stat.S_IEXEC | stat.S_IREAD | stat.S_IWRITE)
  335. command = [Grass7Utils.command,
  336. os.path.join(Grass7Utils.grassMapsetFolder(), 'PERMANENT'),
  337. '--exec', Grass7Utils.grassBatchJobFilename()]
  338. return command, env
  339. @staticmethod
  340. def executeGrass(commands, feedback, outputCommands=None):
  341. loglines = [Grass7Utils.tr('GRASS GIS 7 execution console output')]
  342. grassOutDone = False
  343. command, grassenv = Grass7Utils.prepareGrassExecution(commands)
  344. # QgsMessageLog.logMessage('exec: {}'.format(command), 'DEBUG', Qgis.Info)
  345. # For MS-Windows, we need to hide the console window.
  346. kw = {}
  347. if isWindows():
  348. si = subprocess.STARTUPINFO()
  349. si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
  350. si.wShowWindow = subprocess.SW_HIDE
  351. kw['startupinfo'] = si
  352. if sys.version_info >= (3, 6):
  353. kw['encoding'] = "cp{}".format(Grass7Utils.getWindowsCodePage())
  354. def readline_with_recover(stdout):
  355. """A method wrapping stdout.readline() with try-except recovering.
  356. detailed in https://github.com/qgis/QGIS/pull/49226
  357. Args:
  358. stdout: io.TextIOWrapper - proc.stdout
  359. Returns:
  360. str: read line or replaced text when recovered
  361. """
  362. try:
  363. return stdout.readline()
  364. except UnicodeDecodeError:
  365. return '' # replaced-text
  366. with subprocess.Popen(
  367. command,
  368. shell=False,
  369. stdout=subprocess.PIPE,
  370. stdin=subprocess.DEVNULL,
  371. stderr=subprocess.STDOUT,
  372. universal_newlines=True,
  373. env=grassenv,
  374. **kw
  375. ) as proc:
  376. for line in iter(lambda: readline_with_recover(proc.stdout), ''):
  377. if 'GRASS_INFO_PERCENT' in line:
  378. try:
  379. feedback.setProgress(int(line[len('GRASS_INFO_PERCENT') + 2:]))
  380. except Exception:
  381. pass
  382. else:
  383. if 'r.out' in line or 'v.out' in line:
  384. grassOutDone = True
  385. loglines.append(line)
  386. if any([l in line for l in ['WARNING', 'ERROR']]):
  387. feedback.reportError(line.strip())
  388. elif 'Segmentation fault' in line:
  389. feedback.reportError(line.strip())
  390. feedback.reportError('\n' + Grass7Utils.tr('GRASS command crashed :( Try a different set of input parameters and consult the GRASS algorithm manual for more information.') + '\n')
  391. if ProcessingConfig.getSetting(Grass7Utils.GRASS_USE_REXTERNAL):
  392. feedback.reportError(Grass7Utils.tr(
  393. 'Suggest disabling the experimental "use r.external" option from the Processing GRASS Provider options.') + '\n')
  394. if ProcessingConfig.getSetting(Grass7Utils.GRASS_USE_VEXTERNAL):
  395. feedback.reportError(Grass7Utils.tr(
  396. 'Suggest disabling the experimental "use v.external" option from the Processing GRASS Provider options.') + '\n')
  397. elif line.strip():
  398. feedback.pushConsoleInfo(line.strip())
  399. # Some GRASS scripts, like r.mapcalculator or r.fillnulls, call
  400. # other GRASS scripts during execution. This may override any
  401. # commands that are still to be executed by the subprocess, which
  402. # are usually the output ones. If that is the case runs the output
  403. # commands again.
  404. if not grassOutDone and outputCommands:
  405. command, grassenv = Grass7Utils.prepareGrassExecution(outputCommands)
  406. # For MS-Windows, we need to hide the console window.
  407. kw = {}
  408. if isWindows():
  409. si = subprocess.STARTUPINFO()
  410. si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
  411. si.wShowWindow = subprocess.SW_HIDE
  412. kw['startupinfo'] = si
  413. if sys.version_info >= (3, 6):
  414. kw['encoding'] = "cp{}".format(Grass7Utils.getWindowsCodePage())
  415. with subprocess.Popen(
  416. command,
  417. shell=False,
  418. stdout=subprocess.PIPE,
  419. stdin=subprocess.DEVNULL,
  420. stderr=subprocess.STDOUT,
  421. universal_newlines=True,
  422. env=grassenv,
  423. **kw
  424. ) as proc:
  425. for line in iter(lambda: readline_with_recover(proc.stdout), ''):
  426. if 'GRASS_INFO_PERCENT' in line:
  427. try:
  428. feedback.setProgress(int(
  429. line[len('GRASS_INFO_PERCENT') + 2:]))
  430. except Exception:
  431. pass
  432. if any(l in line for l in ['WARNING', 'ERROR']):
  433. loglines.append(line.strip())
  434. feedback.reportError(line.strip())
  435. elif line.strip():
  436. loglines.append(line.strip())
  437. feedback.pushConsoleInfo(line.strip())
  438. if ProcessingConfig.getSetting(Grass7Utils.GRASS_LOG_CONSOLE):
  439. QgsMessageLog.logMessage('\n'.join(loglines), 'Processing', Qgis.Info)
  440. # GRASS session is used to hold the layers already exported or
  441. # produced in GRASS between multiple calls to GRASS algorithms.
  442. # This way they don't have to be loaded multiple times and
  443. # following algorithms can use the results of the previous ones.
  444. # Starting a session just involves creating the temp mapset
  445. # structure
  446. @staticmethod
  447. def startGrassSession():
  448. if not Grass7Utils.sessionRunning:
  449. Grass7Utils.createTempMapset()
  450. Grass7Utils.sessionRunning = True
  451. # End session by removing the temporary GRASS mapset and all
  452. # the layers.
  453. @staticmethod
  454. def endGrassSession():
  455. # shutil.rmtree(Grass7Utils.grassMapsetFolder(), True)
  456. Grass7Utils.sessionRunning = False
  457. Grass7Utils.sessionLayers = {}
  458. Grass7Utils.projectionSet = False
  459. @staticmethod
  460. def getSessionLayers():
  461. return Grass7Utils.sessionLayers
  462. @staticmethod
  463. def addSessionLayers(exportedLayers):
  464. Grass7Utils.sessionLayers = dict(
  465. list(Grass7Utils.sessionLayers.items()) +
  466. list(exportedLayers.items()))
  467. @staticmethod
  468. def checkGrassIsInstalled(ignorePreviousState=False):
  469. if not ignorePreviousState:
  470. if Grass7Utils.isGrassInstalled:
  471. return
  472. # We check the version of Grass7
  473. if Grass7Utils.installedVersion() is not None:
  474. # For Ms-Windows, we check GRASS binaries
  475. if isWindows():
  476. cmdpath = os.path.join(Grass7Utils.path, 'bin', 'r.out.gdal.exe')
  477. if not os.path.exists(cmdpath):
  478. return Grass7Utils.tr(
  479. 'The GRASS GIS folder "{}" does not contain a valid set '
  480. 'of GRASS modules.\nPlease, check that GRASS is correctly '
  481. 'installed and available on your system.'.format(os.path.join(Grass7Utils.path, 'bin')))
  482. Grass7Utils.isGrassInstalled = True
  483. return
  484. # Return error messages
  485. else:
  486. # MS-Windows or MacOSX
  487. if isWindows() or isMac():
  488. if Grass7Utils.path is None:
  489. return Grass7Utils.tr(
  490. 'Could not locate GRASS GIS folder. Please make '
  491. 'sure that GRASS GIS is correctly installed before '
  492. 'running GRASS algorithms.')
  493. if Grass7Utils.command is None:
  494. return Grass7Utils.tr(
  495. 'GRASS GIS 7 binary {} can\'t be found on this system from a shell. '
  496. 'Please install it or configure your PATH {} environment variable.'.format(
  497. '(grass.bat)' if isWindows() else '(grass.sh)',
  498. 'or OSGEO4W_ROOT' if isWindows() else ''))
  499. # GNU/Linux
  500. else:
  501. return Grass7Utils.tr(
  502. 'GRASS 7 can\'t be found on this system from a shell. '
  503. 'Please install it or configure your PATH environment variable.')
  504. @staticmethod
  505. def tr(string, context=''):
  506. if context == '':
  507. context = 'Grass7Utils'
  508. return QCoreApplication.translate(context, string)
  509. @staticmethod
  510. def writeCommand(output, command):
  511. try:
  512. # Python 2
  513. output.write(command.encode('utf8') + '\n')
  514. except TypeError:
  515. # Python 3
  516. output.write(command + '\n')
  517. @staticmethod
  518. def grassHelpPath():
  519. helpPath = ProcessingConfig.getSetting(Grass7Utils.GRASS_HELP_URL)
  520. if not helpPath:
  521. if isWindows() or isMac():
  522. if Grass7Utils.path is not None:
  523. localPath = os.path.join(Grass7Utils.path, 'docs/html')
  524. if os.path.exists(localPath):
  525. helpPath = os.path.abspath(localPath)
  526. else:
  527. searchPaths = ['/usr/share/doc/grass-doc/html',
  528. '/opt/grass/docs/html',
  529. '/usr/share/doc/grass/docs/html']
  530. for path in searchPaths:
  531. if os.path.exists(path):
  532. helpPath = os.path.abspath(path)
  533. break
  534. if helpPath:
  535. return helpPath
  536. elif Grass7Utils.version:
  537. version = Grass7Utils.version.replace('.', '')[:2]
  538. return 'https://grass.osgeo.org/grass{}/manuals/'.format(version)
  539. else:
  540. # GRASS not available!
  541. return 'https://grass.osgeo.org/grass-stable/manuals/'
  542. @staticmethod
  543. def getSupportedOutputRasterExtensions():
  544. # We use the same extensions than GDAL because:
  545. # - GRASS is also using GDAL for raster imports.
  546. # - Chances that GRASS is compiled with another version of
  547. # GDAL than QGIS are very limited!
  548. return GdalUtils.getSupportedOutputRasterExtensions()
  549. @staticmethod
  550. def getRasterFormatFromFilename(filename):
  551. """
  552. Returns Raster format name from a raster filename.
  553. :param filename: The name with extension of the raster.
  554. :return: The Gdal short format name for extension.
  555. """
  556. ext = os.path.splitext(filename)[1].lower()
  557. ext = ext.lstrip('.')
  558. if ext:
  559. supported = GdalUtils.getSupportedRasters()
  560. for name in list(supported.keys()):
  561. exts = supported[name]
  562. if ext in exts:
  563. return name
  564. return 'GTiff'