1# -*- coding: utf-8 -*- 2 3""" 4*************************************************************************** 5 SagaUtils.py 6 --------------------- 7 Date : August 2012 8 Copyright : (C) 2012 by Victor Olaya 9 Email : volayaf at gmail dot com 10*************************************************************************** 11* * 12* This program is free software; you can redistribute it and/or modify * 13* it under the terms of the GNU General Public License as published by * 14* the Free Software Foundation; either version 2 of the License, or * 15* (at your option) any later version. * 16* * 17*************************************************************************** 18""" 19 20__author__ = 'Victor Olaya' 21__date__ = 'August 2012' 22__copyright__ = '(C) 2012, Victor Olaya' 23 24import os 25import platform 26import stat 27import subprocess 28import time 29 30from qgis.PyQt.QtCore import QCoreApplication 31from qgis.core import (Qgis, 32 QgsApplication, 33 QgsProcessingUtils, 34 QgsMessageLog) 35from processing.core.ProcessingConfig import ProcessingConfig 36from processing.tools.system import isWindows, isMac, userFolder 37 38SAGA_LOG_COMMANDS = 'SAGA_LOG_COMMANDS' 39SAGA_LOG_CONSOLE = 'SAGA_LOG_CONSOLE' 40SAGA_IMPORT_EXPORT_OPTIMIZATION = 'SAGA_IMPORT_EXPORT_OPTIMIZATION' 41 42_installedVersion = None 43_installedVersionFound = False 44 45 46def sagaBatchJobFilename(): 47 if isWindows(): 48 filename = 'saga_batch_job.bat' 49 else: 50 filename = 'saga_batch_job.sh' 51 52 batchfile = os.path.join(userFolder(), filename) 53 54 return batchfile 55 56 57def findSagaFolder(): 58 folder = None 59 if isMac() or platform.system() == 'FreeBSD' or platform.system() == 'DragonFly': 60 testfolder = os.path.join(QgsApplication.prefixPath(), 'bin') 61 if os.path.exists(os.path.join(testfolder, 'saga_cmd')): 62 folder = testfolder 63 else: 64 testfolder = '/usr/local/bin' 65 if os.path.exists(os.path.join(testfolder, 'saga_cmd')): 66 folder = testfolder 67 elif isWindows(): 68 folders = [] 69 folders.append(os.path.join(os.path.dirname(QgsApplication.prefixPath()), 'saga-ltr')) 70 folders.append(os.path.join(os.path.dirname(QgsApplication.prefixPath()), 'saga')) 71 if "OSGEO4W_ROOT" in os.environ: 72 folders.append(os.path.join(str(os.environ['OSGEO4W_ROOT']), "apps", "saga-ltr")) 73 folders.append(os.path.join(str(os.environ['OSGEO4W_ROOT']), "apps", "saga")) 74 75 for testfolder in folders: 76 if os.path.exists(os.path.join(testfolder, 'saga_cmd.exe')): 77 folder = testfolder 78 break 79 80 return folder 81 82 83def sagaPath(): 84 if not isWindows() and not isMac() and not platform.system() == 'FreeBSD' and platform.system() != 'DragonFly': 85 return '' 86 87 folder = findSagaFolder() 88 return folder or '' 89 90 91def sagaDescriptionPath(): 92 return os.path.join(os.path.dirname(__file__), 'description') 93 94 95def createSagaBatchJobFileFromSagaCommands(commands): 96 with open(sagaBatchJobFilename(), 'w', encoding="utf8") as fout: 97 if isWindows(): 98 fout.write('set SAGA=' + sagaPath() + '\n') 99 fout.write('set SAGA_MLB=' + os.path.join(sagaPath(), 'modules') + '\n') 100 fout.write('PATH=%PATH%;%SAGA%;%SAGA_MLB%\n') 101 elif isMac() or platform.system() == 'FreeBSD' or platform.system() == 'DragonFly': 102 fout.write('export SAGA_MLB=' + os.path.join(sagaPath(), '../lib/saga') + '\n') 103 fout.write('export PATH=' + sagaPath() + ':$PATH\n') 104 else: 105 pass 106 for command in commands: 107 if isWindows(): 108 fout.write('call saga_cmd ' + command + '\n') 109 else: 110 fout.write('saga_cmd ' + command + '\n') 111 112 fout.write('exit') 113 114 115def getInstalledVersion(runSaga=False): 116 global _installedVersion 117 global _installedVersionFound 118 119 maxRetries = 5 120 retries = 0 121 if _installedVersionFound and not runSaga: 122 return _installedVersion 123 124 if isWindows(): 125 commands = [os.path.join(sagaPath(), "saga_cmd.exe"), "-v"] 126 elif isMac() or platform.system() == 'FreeBSD' or platform.system() == 'DragonFly': 127 commands = [os.path.join(sagaPath(), "saga_cmd -v")] 128 else: 129 # for Linux use just one string instead of separated parameters as the list 130 # does not work well together with shell=True option 131 # (python docs advices to use subprocess32 instead of python2.7's subprocess) 132 commands = ["saga_cmd -v"] 133 while retries < maxRetries: 134 with subprocess.Popen( 135 commands, 136 shell=True, 137 stdout=subprocess.PIPE, 138 stdin=subprocess.DEVNULL, 139 stderr=subprocess.STDOUT, 140 universal_newlines=True, 141 ) as proc: 142 if isMac() or platform.system() == 'FreeBSD' or platform.system() == 'DragonFly': # This trick avoids having an uninterrupted system call exception if SAGA is not installed 143 time.sleep(1) 144 try: 145 lines = proc.stdout.readlines() 146 for line in lines: 147 if line.startswith("SAGA Version:"): 148 _installedVersion = line[len("SAGA Version:"):].strip().split(" ")[0] 149 _installedVersionFound = True 150 return _installedVersion 151 return None 152 except IOError: 153 retries += 1 154 except: 155 return None 156 157 return _installedVersion 158 159 160def executeSaga(feedback): 161 if isWindows(): 162 command = ['cmd.exe', '/C ', sagaBatchJobFilename()] 163 else: 164 os.chmod(sagaBatchJobFilename(), stat.S_IEXEC | 165 stat.S_IREAD | stat.S_IWRITE) 166 command = ["'" + sagaBatchJobFilename() + "'"] 167 loglines = [] 168 loglines.append(QCoreApplication.translate('SagaUtils', 'SAGA execution console output')) 169 with subprocess.Popen( 170 command, 171 shell=True, 172 stdout=subprocess.PIPE, 173 stdin=subprocess.DEVNULL, 174 stderr=subprocess.STDOUT, 175 universal_newlines=True, 176 ) as proc: 177 try: 178 for line in iter(proc.stdout.readline, ''): 179 if '%' in line: 180 s = ''.join([x for x in line if x.isdigit()]) 181 try: 182 feedback.setProgress(int(s)) 183 except: 184 pass 185 else: 186 line = line.strip() 187 if line != '/' and line != '-' and line != '\\' and line != '|': 188 loglines.append(line) 189 feedback.pushConsoleInfo(line) 190 except: 191 pass 192 193 if ProcessingConfig.getSetting(SAGA_LOG_CONSOLE): 194 QgsMessageLog.logMessage('\n'.join(loglines), 'Processing', Qgis.Info) 195