diff --git a/utils/SnapPy/Snappy/EEMEP/Controller.py b/utils/SnapPy/Snappy/EEMEP/Controller.py
index fb4acac1..a8071a1b 100644
--- a/utils/SnapPy/Snappy/EEMEP/Controller.py
+++ b/utils/SnapPy/Snappy/EEMEP/Controller.py
@@ -15,11 +15,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-'''
+"""
Created on Aug 9, 2016
@author: heikok
-'''
+"""
from PyQt5 import QtWidgets
from collections import deque
@@ -34,18 +34,25 @@
from time import gmtime, strftime
import traceback
-from PyQt5.QtCore import QProcess, QProcessEnvironment, QThread, QIODevice, QThreadPool, pyqtSignal
+from PyQt5.QtCore import (
+ QProcess,
+ QProcessEnvironment,
+ QThread,
+ QIODevice,
+ QThreadPool,
+ pyqtSignal,
+)
from Snappy.BrowserWidget import BrowserWidget
from Snappy.EEMEP.Resources import Resources
from Snappy.EEMEP.ModelRunner import ModelRunner
import Snappy.Utils
+
def getFileOwner(filename):
pwuid = pwd.getpwuid(os.stat(filename).st_uid)
return pwuid.pw_name, pwuid.pw_gecos
-
def debug(*objs):
print("DEBUG: ", *objs, file=sys.stderr)
@@ -71,18 +78,15 @@ def run(self):
traceback.print_exc()
-
-
-class Controller():
- '''
+class Controller:
+ """
Controller for EEMEP Widget. Starts the browserwidget as self.main and connects it to the form handler
- '''
-
+ """
def __init__(self):
- '''
+ """
Initialize Widget and handlers
- '''
+ """
self.res = Resources()
self.main = BrowserWidget()
self.main.set_html(self.res.getStartScreen())
@@ -96,62 +100,75 @@ def __init__(self):
self.lastLog = []
self.logfile_size = 0
-
- def write_log(self, txt:str, max_lines=30, clear_log=False):
- if (clear_log):
+ def write_log(self, txt: str, max_lines=30, clear_log=False):
+ if clear_log:
self.lastLog = [txt]
else:
self.lastLog += txt.splitlines()
debug(txt)
- #Write at most 30 lines to screen
- if (len(self.lastLog) > max_lines):
+ # Write at most 30 lines to screen
+ if len(self.lastLog) > max_lines:
self.lastLog = self.lastLog[-max_lines:]
lines = None
- self.main.evaluate_javaScript('updateEemepLog({0});'.format(json.dumps("\n".join(self.lastLog))))
+ self.main.evaluate_javaScript(
+ "updateEemepLog({0});".format(json.dumps("\n".join(self.lastLog)))
+ )
def update_log_query(self, qDict):
- #MainBrowserWindow._default_form_handler(qDict)
- #self.write_log("updating...")
+ # MainBrowserWindow._default_form_handler(qDict)
+ # self.write_log("updating...")
if os.path.isfile(self.volcano_logfile):
current_size = os.path.getsize(self.volcano_logfile)
# Log overwritten - new file (this should not happen)
- if (current_size < self.logfile_size):
- self.write_log("WARNING: Logfile overwritten - someone else is running this volcano also")
+ if current_size < self.logfile_size:
+ self.write_log(
+ "WARNING: Logfile overwritten - someone else is running this volcano also"
+ )
self.logfile_size = 0
# If new content in logfile
- if (current_size > self.logfile_size):
+ if current_size > self.logfile_size:
with open(self.volcano_logfile) as lf:
lf.seek(self.logfile_size)
for line in lf:
self.write_log(line)
self.logfile_size = current_size
else:
- if (os.path.isfile(self.volcano_file)):
- self.write_log("Queue busy {:%Y-%m-%d %H:%M:%S}".format(datetime.datetime.now()))
- if (self.res.getModelRunnerLogs()):
+ if os.path.isfile(self.volcano_file):
+ self.write_log(
+ "Queue busy {:%Y-%m-%d %H:%M:%S}".format(datetime.datetime.now())
+ )
+ if self.res.getModelRunnerLogs():
self.write_log(self.res.getModelRunnerLogs())
else:
- #Check if volcano logfile exists after waiting slightly
+ # Check if volcano logfile exists after waiting slightly
# (to avoid race conditions in which volcano.xml is deleted before logfile is created)
time.sleep(1.0)
- if (os.path.isfile(self.volcano_logfile)):
- self.write_log("ERROR: Neither '{:s}' \nnor '{:s}' Exists!\nSomeone may have deleted the run.".format(self.volcano_file, self.volcano_logfile))
+ if os.path.isfile(self.volcano_logfile):
+ self.write_log(
+ "ERROR: Neither '{:s}' \nnor '{:s}' Exists!\nSomeone may have deleted the run.".format(
+ self.volcano_file, self.volcano_logfile
+ )
+ )
self.eemepRunning = "inactive"
def cancel_first_in_queue(self, qDict):
- '''Mark all currently active model-runs for abort'''
+ """Mark all currently active model-runs for abort"""
for dirpath, dirs, files in os.walk(self.res.getOutputDir()):
for file in files:
if file == ModelRunner.ABORT_FILENAME:
try:
self.write_log("trying to abort {}".format(dirpath))
- abortLogFile = datetime.datetime.now().strftime('{fname}_%Y%m%d-%H%M%S').format(fname=ModelRunner.ABORT_FILENAME)
- with open(os.path.join(dirpath, abortLogFile), 'wt') as lh:
+ abortLogFile = (
+ datetime.datetime.now()
+ .strftime("{fname}_%Y%m%d-%H%M%S")
+ .format(fname=ModelRunner.ABORT_FILENAME)
+ )
+ with open(os.path.join(dirpath, abortLogFile), "wt") as lh:
lh.write("aborted by {}".format(getpass.getuser()))
os.remove(os.path.join(dirpath, file))
except:
@@ -160,22 +177,36 @@ def cancel_first_in_queue(self, qDict):
pass
def cancel_submitted(self, qDict):
- '''Cancel the last submitted volcano-file'''
- if (os.path.isfile(self.volcano_file)):
+ """Cancel the last submitted volcano-file"""
+ if os.path.isfile(self.volcano_file):
owner, gecos = getFileOwner(self.volcano_file)
user = getpass.getuser()
- debug("Deleting {:s} owned by {:s} ({:s}) with user {:s}".format(self.volcano_file, owner, gecos, user))
- if (owner != user):
- self.write_log("WARNING: {:s}\nwas started by {:s} ({:s}). Please notify this user that you canceled the run!".format(self.volcano_file, owner, gecos))
+ debug(
+ "Deleting {:s} owned by {:s} ({:s}) with user {:s}".format(
+ self.volcano_file, owner, gecos, user
+ )
+ )
+ if owner != user:
+ self.write_log(
+ "WARNING: {:s}\nwas started by {:s} ({:s}). Please notify this user that you canceled the run!".format(
+ self.volcano_file, owner, gecos
+ )
+ )
try:
os.remove(os.path.join(self.volcano_file))
self.write_log("{} deleted".format(self.volcano_file))
self.eemepRunning = "inactive"
except Exception as e:
- self.write_log("ERROR: could not cancel the currently submitted volcano!\n Error was {:s}".format(e.msg))
+ self.write_log(
+ "ERROR: could not cancel the currently submitted volcano!\n Error was {:s}".format(
+ e.msg
+ )
+ )
pass
else:
- self.write_log("Volcano file ('{:s}') does not exist".format(self.volcano_file))
+ self.write_log(
+ "Volcano file ('{:s}') does not exist".format(self.volcano_file)
+ )
def update_log(self):
self.update_log_query({})
@@ -183,10 +214,11 @@ def update_log(self):
def _create_form_handler(self):
def handler(queryDict):
"""a form-handler with closure for self"""
- options = { 'Run' : self.run_eemep_query,
- 'Update' : self.update_log_query,
- 'Cancel+active': self.cancel_first_in_queue,
- 'Cancel+submitted': self.cancel_submitted
+ options = {
+ "Run": self.run_eemep_query,
+ "Update": self.update_log_query,
+ "Cancel+active": self.cancel_first_in_queue,
+ "Cancel+submitted": self.cancel_submitted,
}
# mapping from QList to simple dictionary
qDict = dict()
@@ -194,14 +226,19 @@ def handler(queryDict):
qDict[key] = value
# calling the correct handler depending on the module
try:
- options[qDict['action']](qDict)
+ options[qDict["action"]](qDict)
except TypeError as ex:
self.write_log("type-error: {}".format(ex))
except ValueError as ex:
self.write_log("value-error: {}".format(ex))
except:
- self.write_log("Unexpected error on {0}: {1}".format(qDict['action'],sys.exc_info()[0]))
+ self.write_log(
+ "Unexpected error on {0}: {1}".format(
+ qDict["action"], sys.exc_info()[0]
+ )
+ )
raise
+
return handler
def run_eemep_query(self, qDict):
@@ -212,64 +249,74 @@ def run_eemep_query(self, qDict):
for key, value in qDict.items():
print(str.format("{0} => {1}", key, value))
errors = ""
- match = re.search(r'(\d{4})-(\d{2})-(\d{2})[\+\s]+(\d{1,2})', qDict['startTime'])
+ match = re.search(
+ r"(\d{4})-(\d{2})-(\d{2})[\+\s]+(\d{1,2})", qDict["startTime"]
+ )
if match:
- startTime = "{0} {1} {2} {3}".format(*match.group(1,2,3,4))
- startDT = datetime.datetime(*tuple(map(int, list(match.group(1,2,3,4)))))
- modelStartDT = datetime.datetime(startDT.year, startDT.month, startDT.day, 0, 0, 0)
+ startTime = "{0} {1} {2} {3}".format(*match.group(1, 2, 3, 4))
+ startDT = datetime.datetime(*tuple(map(int, list(match.group(1, 2, 3, 4)))))
+ modelStartDT = datetime.datetime(
+ startDT.year, startDT.month, startDT.day, 0, 0, 0
+ )
else:
- errors += "Cannot interpret startTime: {0}\n".format(qDict['startTime'])
+ errors += "Cannot interpret startTime: {0}\n".format(qDict["startTime"])
try:
- runTime = int(qDict['runTime'])
+ runTime = int(qDict["runTime"])
except:
- errors += "Cannot interpret runTime: {}\n".format(qDict['runTime'])
+ errors += "Cannot interpret runTime: {}\n".format(qDict["runTime"])
restart = "false"
- if ('restart_file' in qDict and qDict['restart_file'].lower() == 'true'):
- restart = 'restart'
+ if "restart_file" in qDict and qDict["restart_file"].lower() == "true":
+ restart = "restart"
- if qDict['volcanotype'] == 'default':
- type = 'M0'
+ if qDict["volcanotype"] == "default":
+ type = "M0"
else:
- type = qDict['volcanotype']
+ type = qDict["volcanotype"]
volcanoes = self.res.readVolcanoes()
- if (qDict['volcano'] and volcanoes[qDict['volcano']]):
- tag = qDict['volcano']
- volcano = re.sub(r'[^\w.-_]','_',volcanoes[qDict['volcano']]['NAME'])
- latf = volcanoes[qDict['volcano']]['LATITUDE']
- lonf = volcanoes[qDict['volcano']]['LONGITUDE']
- altf = volcanoes[qDict['volcano']]['ELEV']
- if qDict['volcanotype'] == 'default':
- type = volcanoes[qDict['volcano']]['ERUPTIONTYPE']
+ if qDict["volcano"] and volcanoes[qDict["volcano"]]:
+ tag = qDict["volcano"]
+ volcano = re.sub(r"[^\w.-_]", "_", volcanoes[qDict["volcano"]]["NAME"])
+ latf = volcanoes[qDict["volcano"]]["LATITUDE"]
+ lonf = volcanoes[qDict["volcano"]]["LONGITUDE"]
+ altf = volcanoes[qDict["volcano"]]["ELEV"]
+ if qDict["volcanotype"] == "default":
+ type = volcanoes[qDict["volcano"]]["ERUPTIONTYPE"]
else:
- lat = qDict['latitude']
- lon = qDict['longitude']
- alt = qDict['altitude']
+ lat = qDict["latitude"]
+ lon = qDict["longitude"]
+ alt = qDict["altitude"]
try:
latf = Snappy.Utils.parseLat(lat)
lonf = Snappy.Utils.parseLon(lon)
altf = float(alt)
except:
- latf = 0.
- lonf = 0.
- altf = 0.
- errors += "Cannot interpret latitude/longitude/altitude: {0}/{1}/{2}\n".format(lat,lon,alt);
+ latf = 0.0
+ lonf = 0.0
+ altf = 0.0
+ errors += "Cannot interpret latitude/longitude/altitude: {0}/{1}/{2}\n".format(
+ lat, lon, alt
+ )
volcano = "{lat}N_{lon}E".format(lat=latf, lon=lonf)
- debug("volcano: {0} {1:.2f} {2:.2f} {3} {4}".format(volcano, latf, lonf, altf, type))
+ debug(
+ "volcano: {0} {1:.2f} {2:.2f} {3} {4}".format(
+ volcano, latf, lonf, altf, type
+ )
+ )
try:
volctype = self.res.readVolcanoType(type)
except Exception as ex:
errors += str(ex) + "\n"
- errors += 'Please select Height and Type (Advanced) manually.\n'
+ errors += "Please select Height and Type (Advanced) manually.\n"
# fine-ash fraction (particles < 63µm)
try:
- m63 = qDict['m63']
- if m63.lower() == 'mastin':
- m63f = volctype['m63']
+ m63 = qDict["m63"]
+ if m63.lower() == "mastin":
+ m63f = volctype["m63"]
else:
m63f = float(m63)
if not (0 < m63f <= 1):
@@ -277,71 +324,102 @@ def run_eemep_query(self, qDict):
except Exception as ex:
errors += str(ex) + "\n"
- self.write_log("working with {:s} (lat={:.2f}N lon={:.2f}E) starting at {:s}".format(volcano, latf, lonf, startTime))
-
+ self.write_log(
+ "working with {:s} (lat={:.2f}N lon={:.2f}E) starting at {:s}".format(
+ volcano, latf, lonf, startTime
+ )
+ )
# Get cloud height if supplied and calculate eruption rate
- if qDict['cloudheight']:
+ if qDict["cloudheight"]:
try:
- cheight = float(qDict['cloudheight'])
+ cheight = float(qDict["cloudheight"])
except:
- errors += "cannot interpret cloudheight (m): {0}\n".format(qDict['cloudheight'])
+ errors += "cannot interpret cloudheight (m): {0}\n".format(
+ qDict["cloudheight"]
+ )
- if (cheight % 1 != 0):
- self.write_log("WARNING: Ash cloud height supplied with fraction. Please check that you supplied meters, not km!")
+ if cheight % 1 != 0:
+ self.write_log(
+ "WARNING: Ash cloud height supplied with fraction. Please check that you supplied meters, not km!"
+ )
- if qDict['cloudheight_datum'] == 'mean_sea_level':
+ if qDict["cloudheight_datum"] == "mean_sea_level":
# Interpret cloud height as above sea level
# - remove volcano vent altitude to get plume height
- self.write_log("Ash cloud height measured from mean sea level: {:.2f} km".format(cheight/1000.0))
+ self.write_log(
+ "Ash cloud height measured from mean sea level: {:.2f} km".format(
+ cheight / 1000.0
+ )
+ )
cheight = cheight - altf
- elif qDict['cloudheight_datum'] == 'vent':
+ elif qDict["cloudheight_datum"] == "vent":
# Interpret cloud height as above vent
pass
else:
- errors += "cannot interpret cloud height datum: {:s}".format(qDict['cloudheight_datum'])
+ errors += "cannot interpret cloud height datum: {:s}".format(
+ qDict["cloudheight_datum"]
+ )
# rate in kg/s from Mastin et al. 2009, formular (1) and a volume (DRE) (m3) to
# mass (kg) density of 2500kg/m3
- rate = 2500.0 * ((0.5*max(0, cheight)/1000.0)**(1.0/0.241))
+ rate = 2500.0 * ((0.5 * max(0, cheight) / 1000.0) ** (1.0 / 0.241))
else:
- cheight = float(volctype['H']) * 1000 # km -> m
- rate = float(volctype['dM/dt'])
-
- #Check negative ash cloud height
- if (cheight <= 0):
- errors += "Negative cloud height {:.2f}! Please check ash cloud.".format(cheight/1000.0)
- self.write_log("Ash cloud height measured from volcano: {:.2f} km, rate: {:.0f} kg/s, volcano height: {:.2f} km.".format(cheight/1000.0, rate, altf/1000.0))
+ cheight = float(volctype["H"]) * 1000 # km -> m
+ rate = float(volctype["dM/dt"])
+
+ # Check negative ash cloud height
+ if cheight <= 0:
+ errors += "Negative cloud height {:.2f}! Please check ash cloud.".format(
+ cheight / 1000.0
+ )
+ self.write_log(
+ "Ash cloud height measured from volcano: {:.2f} km, rate: {:.0f} kg/s, volcano height: {:.2f} km.".format(
+ cheight / 1000.0, rate, altf / 1000.0
+ )
+ )
# Abort if errors
- if (len(errors) > 0):
- debug('updateLog("{0}");'.format(json.dumps("ERRORS:\n"+errors)))
+ if len(errors) > 0:
+ debug('updateLog("{0}");'.format(json.dumps("ERRORS:\n" + errors)))
self.write_log("ERRORS:\n{0}".format(errors))
return
# eEMEP runs up-to 23 km, so remove all ash above 23 km,
# See Varsling av vulkanaske i norsk luftrom - driftsfase,
# February 2020 for details
- eemep_cheight_max = 23000.0-altf
- if (cheight > eemep_cheight_max):
+ eemep_cheight_max = 23000.0 - altf
+ if cheight > eemep_cheight_max:
rate_fraction = eemep_cheight_max / cheight
- self.write_log("Cropping ash cloud to {:.2f} km from {:.2f} km using factor {:.3f}".format(eemep_cheight_max/1000.0, cheight/1000.0, rate_fraction))
+ self.write_log(
+ "Cropping ash cloud to {:.2f} km from {:.2f} km using factor {:.3f}".format(
+ eemep_cheight_max / 1000.0, cheight / 1000.0, rate_fraction
+ )
+ )
rate = rate * rate_fraction
cheight = eemep_cheight_max
eruptions = []
eruption = ''
- eruptions.append(eruption.format(start=startDT.isoformat(),
- end=(startDT + datetime.timedelta(hours=runTime)).isoformat(),
- bottom=0,
- top=cheight,
- rate=rate,
- m63=m63f))
-
- self.lastOutputDir = os.path.join(self.res.getOutputDir(), "{0}_ondemand".format(volcano))
- self.volcano_file = os.path.join(self.lastOutputDir, ModelRunner.VOLCANO_FILENAME)
+ eruptions.append(
+ eruption.format(
+ start=startDT.isoformat(),
+ end=(startDT + datetime.timedelta(hours=runTime)).isoformat(),
+ bottom=0,
+ top=cheight,
+ rate=rate,
+ m63=m63f,
+ )
+ )
+
+ self.lastOutputDir = os.path.join(
+ self.res.getOutputDir(), "{0}_ondemand".format(volcano)
+ )
+ self.volcano_file = os.path.join(
+ self.lastOutputDir, ModelRunner.VOLCANO_FILENAME
+ )
self.lastQDict = qDict
sourceTerm = """
@@ -357,34 +435,44 @@ def run_eemep_query(self, qDict):
"""
- ecModelRun = qDict['ecmodelrun'];
+ ecModelRun = qDict["ecmodelrun"]
if not ecModelRun == "best":
ecModelRun += "Z"
- self.lastSourceTerm = sourceTerm.format(lat=latf, lon=lonf,
- volcano=volcano,
- alt=altf,
- outdir=self.lastOutputDir,
- restart=restart,
- model_run=ecModelRun,
- model_start_time=modelStartDT.isoformat(),
- eruptions="\n".join(eruptions),
- runTime=runTime)
+ self.lastSourceTerm = sourceTerm.format(
+ lat=latf,
+ lon=lonf,
+ volcano=volcano,
+ alt=altf,
+ outdir=self.lastOutputDir,
+ restart=restart,
+ model_run=ecModelRun,
+ model_start_time=modelStartDT.isoformat(),
+ eruptions="\n".join(eruptions),
+ runTime=runTime,
+ )
debug("output directory: {}".format(self.lastOutputDir))
os.makedirs(self.lastOutputDir, exist_ok=True)
- self.volcano_logfile = os.path.join(self.lastOutputDir,"volcano.log")
- if (os.path.exists(self.volcano_logfile)):
- logdate = datetime.datetime.fromtimestamp(os.path.getmtime(self.volcano_logfile))
- os.rename(self.volcano_logfile, "{}_{}".format(self.volcano_logfile, logdate.strftime("%Y%m%dT%H%M%S")))
+ self.volcano_logfile = os.path.join(self.lastOutputDir, "volcano.log")
+ if os.path.exists(self.volcano_logfile):
+ logdate = datetime.datetime.fromtimestamp(
+ os.path.getmtime(self.volcano_logfile)
+ )
+ os.rename(
+ self.volcano_logfile,
+ "{}_{}".format(self.volcano_logfile, logdate.strftime("%Y%m%dT%H%M%S")),
+ )
try:
# Mode x - open for exclusive creation, failing if the file already exists
- with open(self.volcano_file,'x') as fh:
+ with open(self.volcano_file, "x") as fh:
fh.write(self.lastSourceTerm)
except FileExistsError as e:
owner = "unknown"
- if (os.path.exists(self.volcano_file)):
+ if os.path.exists(self.volcano_file):
owner, gecos = getFileOwner(self.volcano_file)
- errmsg = "ERROR: Run ({:s}) already exists!\nCreated by user {:s} ({:s}).\nPlease try again later.".format(self.volcano_file, owner, gecos)
+ errmsg = "ERROR: Run ({:s}) already exists!\nCreated by user {:s} ({:s}).\nPlease try again later.".format(
+ self.volcano_file, owner, gecos
+ )
debug('updateLog("{0}");'.format(json.dumps(errmsg)))
self.write_log(errmsg)
return
@@ -395,6 +483,7 @@ def run_eemep_query(self, qDict):
self.model_update.update_log_signal.connect(self.update_log)
self.model_update.start(QThread.LowPriority)
+
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ctr = Controller()