2018-03-29 18:25:20 +00:00
|
|
|
import logging
|
|
|
|
import shutil
|
|
|
|
import tempfile
|
|
|
|
import subprocess
|
|
|
|
import os
|
2020-03-16 18:46:22 +00:00
|
|
|
import platform
|
2018-03-29 21:28:15 +00:00
|
|
|
|
|
|
|
from webodm import settings
|
|
|
|
|
2018-03-29 18:25:20 +00:00
|
|
|
logger = logging.getLogger('app.logger')
|
|
|
|
|
|
|
|
class GrassEngine:
|
|
|
|
def __init__(self):
|
|
|
|
self.grass_binary = shutil.which('grass7') or \
|
2020-03-20 18:26:42 +00:00
|
|
|
shutil.which('grass7.bat') or \
|
2018-03-29 18:25:20 +00:00
|
|
|
shutil.which('grass72') or \
|
2020-03-20 18:26:42 +00:00
|
|
|
shutil.which('grass72.bat') or \
|
2018-03-29 22:56:15 +00:00
|
|
|
shutil.which('grass74') or \
|
2020-03-20 18:26:42 +00:00
|
|
|
shutil.which('grass74.bat') or \
|
2019-09-18 13:11:09 +00:00
|
|
|
shutil.which('grass76') or \
|
2020-03-20 18:26:42 +00:00
|
|
|
shutil.which('grass76.bat') or \
|
2019-09-18 13:11:09 +00:00
|
|
|
shutil.which('grass78') or \
|
2020-03-20 18:26:42 +00:00
|
|
|
shutil.which('grass78.bat') or \
|
|
|
|
shutil.which('grass80') or \
|
|
|
|
shutil.which('grass80.bat')
|
2018-03-29 18:25:20 +00:00
|
|
|
|
|
|
|
if self.grass_binary is None:
|
|
|
|
logger.warning("Could not find a GRASS 7 executable. GRASS scripts will not work.")
|
|
|
|
|
2018-03-29 21:28:15 +00:00
|
|
|
def create_context(self, serialized_context = {}):
|
2018-03-29 18:25:20 +00:00
|
|
|
if self.grass_binary is None: raise GrassEngineException("GRASS engine is unavailable")
|
2018-03-29 21:28:15 +00:00
|
|
|
return GrassContext(self.grass_binary, **serialized_context)
|
2018-03-29 18:25:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
class GrassContext:
|
2020-03-28 18:35:17 +00:00
|
|
|
def __init__(self, grass_binary, tmpdir = None, script_opts = {}, location = None, auto_cleanup=True, python_path=None):
|
2018-03-29 18:25:20 +00:00
|
|
|
self.grass_binary = grass_binary
|
2018-03-29 21:28:15 +00:00
|
|
|
if tmpdir is None:
|
|
|
|
tmpdir = os.path.basename(tempfile.mkdtemp('_grass_engine', dir=settings.MEDIA_TMP))
|
|
|
|
self.tmpdir = tmpdir
|
2020-03-20 18:26:42 +00:00
|
|
|
self.script_opts = script_opts.copy()
|
2018-03-29 21:28:15 +00:00
|
|
|
self.location = location
|
2019-03-30 23:07:24 +00:00
|
|
|
self.auto_cleanup = auto_cleanup
|
2020-03-28 18:35:17 +00:00
|
|
|
self.python_path = python_path
|
2018-03-29 21:28:15 +00:00
|
|
|
|
|
|
|
def get_cwd(self):
|
|
|
|
return os.path.join(settings.MEDIA_TMP, self.tmpdir)
|
2018-03-29 18:25:20 +00:00
|
|
|
|
|
|
|
def add_file(self, filename, source, use_as_location=False):
|
|
|
|
param = os.path.splitext(filename)[0] # filename without extension
|
|
|
|
|
2018-03-29 21:28:15 +00:00
|
|
|
dst_path = os.path.abspath(os.path.join(self.get_cwd(), filename))
|
|
|
|
with open(dst_path, 'w') as f:
|
2018-03-29 18:25:20 +00:00
|
|
|
f.write(source)
|
2020-03-01 03:01:15 +00:00
|
|
|
self.script_opts[param] = dst_path
|
2018-03-29 18:25:20 +00:00
|
|
|
|
|
|
|
if use_as_location:
|
2020-03-01 03:01:15 +00:00
|
|
|
self.set_location(self.script_opts[param])
|
2018-03-29 18:25:20 +00:00
|
|
|
|
|
|
|
return dst_path
|
|
|
|
|
|
|
|
def add_param(self, param, value):
|
2020-03-01 03:01:15 +00:00
|
|
|
self.script_opts[param] = value
|
2018-03-29 18:25:20 +00:00
|
|
|
|
|
|
|
def set_location(self, location):
|
|
|
|
"""
|
|
|
|
:param location: either a "epsg:XXXXX" string or a path to a geospatial file defining the location
|
|
|
|
"""
|
2018-05-13 17:30:44 +00:00
|
|
|
if not location.lower().startswith('epsg:'):
|
2018-03-29 18:25:20 +00:00
|
|
|
location = os.path.abspath(location)
|
|
|
|
self.location = location
|
|
|
|
|
|
|
|
def execute(self, script):
|
|
|
|
"""
|
|
|
|
:param script: path to .grass script
|
|
|
|
:return: script output
|
|
|
|
"""
|
|
|
|
if self.location is None: raise GrassEngineException("Location is not set")
|
|
|
|
|
|
|
|
script = os.path.abspath(script)
|
|
|
|
|
2020-03-12 15:16:32 +00:00
|
|
|
# Make sure working directory exists
|
|
|
|
if not os.path.exists(self.get_cwd()):
|
|
|
|
os.mkdir(self.get_cwd())
|
|
|
|
|
2020-03-01 03:01:15 +00:00
|
|
|
# Create param list
|
|
|
|
params = ["{}={}".format(opt,value) for opt,value in self.script_opts.items()]
|
2018-03-29 18:25:20 +00:00
|
|
|
|
2020-03-16 18:46:22 +00:00
|
|
|
# Track success, output
|
|
|
|
success = False
|
|
|
|
out = ""
|
|
|
|
err = ""
|
|
|
|
|
2020-03-28 18:35:17 +00:00
|
|
|
# Setup env
|
2020-10-21 14:44:42 +00:00
|
|
|
env = os.environ.copy()
|
|
|
|
env["LC_ALL"] = "C.UTF-8"
|
|
|
|
|
2020-03-28 18:35:17 +00:00
|
|
|
if self.python_path:
|
|
|
|
sep = ";" if platform.system() == "Windows" else ":"
|
|
|
|
env["PYTHONPATH"] = "%s%s%s" % (self.python_path, sep, env.get("PYTHONPATH", ""))
|
|
|
|
|
2018-03-29 18:25:20 +00:00
|
|
|
# Execute it
|
2021-11-15 19:23:41 +00:00
|
|
|
logger.info("Executing grass script from {}: {} -c {} location --exec python3 {} {}".format(self.get_cwd(), self.grass_binary, self.location, script, " ".join(params)))
|
2020-03-16 18:46:22 +00:00
|
|
|
|
2021-11-15 19:23:41 +00:00
|
|
|
command = [self.grass_binary, '-c', self.location, 'location', '--exec', 'python3', script] + params
|
2020-03-16 18:46:22 +00:00
|
|
|
if platform.system() == "Windows":
|
|
|
|
# communicate() hangs on Windows so we use check_output instead
|
|
|
|
try:
|
2020-03-28 18:35:17 +00:00
|
|
|
out = subprocess.check_output(command, cwd=self.get_cwd(), env=env).decode('utf-8').strip()
|
2020-03-16 18:46:22 +00:00
|
|
|
success = True
|
2020-03-20 18:26:42 +00:00
|
|
|
except:
|
2020-03-16 18:46:22 +00:00
|
|
|
success = False
|
|
|
|
err = out
|
|
|
|
else:
|
2020-03-28 18:35:17 +00:00
|
|
|
p = subprocess.Popen(command, cwd=self.get_cwd(), env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
2020-03-16 18:46:22 +00:00
|
|
|
|
|
|
|
out, err = p.communicate()
|
2018-03-29 18:25:20 +00:00
|
|
|
|
2020-03-16 18:46:22 +00:00
|
|
|
out = out.decode('utf-8').strip()
|
|
|
|
err = err.decode('utf-8').strip()
|
|
|
|
success = p.returncode == 0
|
2018-03-29 21:28:15 +00:00
|
|
|
|
2020-03-16 18:46:22 +00:00
|
|
|
if success:
|
2018-03-29 18:25:20 +00:00
|
|
|
return out
|
|
|
|
else:
|
2018-03-29 21:28:15 +00:00
|
|
|
raise GrassEngineException("Could not execute GRASS script {} from {}: {}".format(script, self.get_cwd(), err))
|
|
|
|
|
|
|
|
def serialize(self):
|
|
|
|
return {
|
|
|
|
'tmpdir': self.tmpdir,
|
2020-03-01 03:01:15 +00:00
|
|
|
'script_opts': self.script_opts,
|
2019-03-30 23:07:24 +00:00
|
|
|
'location': self.location,
|
2020-03-28 18:35:17 +00:00
|
|
|
'auto_cleanup': self.auto_cleanup,
|
|
|
|
'python_path': self.python_path,
|
2018-03-29 21:28:15 +00:00
|
|
|
}
|
2018-03-29 18:25:20 +00:00
|
|
|
|
2019-03-30 23:07:24 +00:00
|
|
|
def cleanup(self):
|
2020-03-12 15:16:32 +00:00
|
|
|
if os.path.exists(self.get_cwd()):
|
|
|
|
shutil.rmtree(self.get_cwd())
|
2018-03-29 18:25:20 +00:00
|
|
|
|
2019-03-30 23:07:24 +00:00
|
|
|
def __del__(self):
|
|
|
|
if self.auto_cleanup:
|
|
|
|
self.cleanup()
|
|
|
|
|
2018-03-29 18:25:20 +00:00
|
|
|
class GrassEngineException(Exception):
|
|
|
|
pass
|
|
|
|
|
2019-04-01 20:49:56 +00:00
|
|
|
def cleanup_grass_context(serialized_context):
|
|
|
|
ctx = grass.create_context(serialized_context)
|
|
|
|
ctx.cleanup()
|
|
|
|
|
2021-01-24 19:14:23 +00:00
|
|
|
grass = GrassEngine()
|