[installation] Initial commit with first config

main
Benjamin Banaskiewicz 1 year ago
commit 906e9ccf6b

5
.gitignore vendored

@ -0,0 +1,5 @@
var
*.pyc
HYPO*
RESET*
share/maps

Binary file not shown.

@ -0,0 +1,82 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import seiscomp.datamodel
import seiscomp.io
import getopt
import sys
usage = """arclink2inv [options] input=stdin output=stdout
Options:
-h [ --help ] Produce help message
-f [ --formatted ] Enable formatted XML output
"""
def main(argv):
imp = seiscomp.io.Importer.Create("arclink")
if imp is None:
sys.stderr.write("Arclink import not available\n")
return 1
formatted = False
# parse command line options
try:
opts, args = getopt.getopt(argv[1:], "hf", ["help", "formatted"])
except getopt.error as msg:
sys.stderr.write("%s\n" % msg)
sys.stderr.write("for help use --help\n")
return 1
for o, a in opts:
if o in ["-h", "--help"]:
sys.stderr.write("%s\n" % usage)
return 1
elif o in ["-f", "--formatted"]:
formatted = True
argv = args
if len(argv) > 0:
o = imp.read(argv[0])
else:
o = imp.read("-")
inv = seiscomp.datamodel.Inventory.Cast(o)
if inv is None:
sys.stderr.write("No inventory found\n")
return 1
ar = seiscomp.io.XMLArchive()
if len(argv) > 1:
res = ar.create(argv[1])
else:
res = ar.create("-")
if not res:
sys.stderr.write("Failed to open output\n")
return 1
ar.setFormattedOutput(formatted)
ar.writeObject(inv)
ar.close()
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))

@ -0,0 +1,26 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) gempa GmbH #
# All rights reserved. #
# Contact: gempa GmbH (seiscomp-dev@gempa.de) #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
# #
# Other Usage #
# Alternatively, this file may be used in accordance with the terms and #
# conditions contained in a signed written agreement between you and #
# gempa GmbH. #
############################################################################
import seiscomp.bindings2cfg
import sys
sys.exit(seiscomp.bindings2cfg.main())

Binary file not shown.

Binary file not shown.

@ -0,0 +1,28 @@
#!/usr/bin/env seiscomp-python
from __future__ import print_function
import sys
from seiscomp import mseedlite as mseed
open_files = {}
if len(sys.argv) != 2:
print("Usage: extr_file FILE")
sys.exit(1)
for rec in mseed.Input(open(sys.argv[1], "rb")):
oname = "%s.%s.%s.%s" % (rec.sta, rec.net, rec.loc, rec.cha)
if oname not in open_files:
postfix = ".D.%04d.%03d.%02d%02d" % (rec.begin_time.year,
rec.begin_time.timetuple()[7], rec.begin_time.hour,
rec.begin_time.minute)
open_files[oname] = open(oname + postfix, "ab")
ofile = open_files[oname]
ofile.write(rec.header + rec.data)
for oname in open_files:
open_files[oname].close()

File diff suppressed because it is too large Load Diff

Binary file not shown.

@ -0,0 +1,134 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import sys
import os
import subprocess
import glob
import seiscomp.client
class Importer(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
self.setMessagingEnabled(False)
self.setDatabaseEnabled(False, False)
self._args = argv[1:]
def run(self):
if len(self._args) == 0:
sys.stderr.write(
"Usage: import_inv [{format}|help] <input> [output]\n")
return False
if self._args[0] == "help":
if len(self._args) < 2:
sys.stderr.write("'help' can only be used with 'formats'\n")
sys.stderr.write("import_inv help formats\n")
return False
if self._args[1] == "formats":
return self.printFormats()
sys.stderr.write("unknown topic '%s'\n" % self._args[1])
return False
fmt = self._args[0]
try:
prog = os.path.join(
os.environ['SEISCOMP_ROOT'], "bin", "%s2inv" % fmt)
except:
sys.stderr.write(
"Could not get SeisComP root path, SEISCOMP_ROOT not set?\n")
return False
if not os.path.exists(prog):
sys.stderr.write("Format '%s' is not supported\n" % fmt)
return False
if len(self._args) < 2:
sys.stderr.write("Input missing\n")
return False
input = self._args[1]
if len(self._args) < 3:
filename = os.path.basename(os.path.abspath(input))
if not filename:
filename = fmt
# Append .xml if the ending is not already .xml
if filename[-4:] != ".xml":
filename = filename + ".xml"
storage_dir = os.path.join(
os.environ['SEISCOMP_ROOT'], "etc", "inventory")
output = os.path.join(storage_dir, filename)
try:
os.makedirs(storage_dir)
except:
pass
sys.stderr.write("Generating output to %s\n" % output)
else:
output = self._args[2]
proc = subprocess.Popen([prog, input, output],
stdout=None, stderr=None, shell=False)
chans = proc.communicate()
if proc.returncode != 0:
sys.stderr.write(
"Conversion failed, return code: %d\n" % proc.returncode)
return False
return True
def printFormats(self):
try:
path = os.path.join(os.environ['SEISCOMP_ROOT'], "bin", "*2inv")
except:
sys.stderr.write(
"Could not get SeisComP root path, SEISCOMP_ROOT not set?\n")
return False
files = glob.glob(path)
for f in files:
prog = os.path.basename(f)
prog = prog[:prog.find("2inv")]
sys.stdout.write("%s\n" % prog)
return True
def printUsage(self):
print('''Usage:
import_inv [FORMAT] input [output]
import_inv help [topic]
Import inventory information from various sources.''')
seiscomp.client.Application.printUsage(self)
print('''Examples:
List all supported inventory formats
import_inv help formats
Convert from FDSN stationXML to SeisComp format
import_inv fdsnxml inventory_fdsnws.xml inventory_sc.xml
''')
if __name__ == "__main__":
app = Importer(len(sys.argv), sys.argv)
sys.exit(app())

@ -0,0 +1,278 @@
#!/usr/bin/env seiscomp-python
from __future__ import print_function
import sys, os
import csv
from optparse import OptionParser
def quote(instr):
return '"'+instr+'"'
class base(object):
def __init__(self, filename, fields):
self.att = {}
fd = open(filename)
try:
try:
fieldNames = None
for row in csv.DictReader(fd, fieldNames):
id = row['id']
if id in self.att:
print("multiple %s found in %s" % (id, filename))
continue
for key in fields:
if not row[key]:
del(row[key])
del row['id']
try:
row['low_freq'] = float(row['low_freq'])
except KeyError:
pass
try:
row['high_freq'] = float(row['high_freq'])
except KeyError:
pass
self.att[id] = row
except KeyError as e:
raise Exception("column %s missing in %s" % (str(e), filename))
except (TypeError, ValueError) as e:
raise Exception("error reading %s: %s" % (filename, str(e)))
finally:
fd.close()
def keys(self):
return list(self.att.keys())
def screname(self, what):
nc = ""
nu = True
for c in what:
if c == '_':
nu = True
continue
if nu:
nc += c.upper()
nu = False
else:
nc += c
if nc == 'LowFreq': nc = 'LowFrequency'
if nc == 'HighFreq': nc = 'HighFrequency'
return nc
def reorder(self):
att = {}
if not self.att:
return None
for (code, row) in self.att.items():
for (k, v) in row.items():
k = self.screname(k)
try:
dk = att[k]
except:
dk = {}
att[k] = dk
try:
dv = dk[str(v)]
except:
dv = []
dk[str(v)] = dv
dv.append(code)
return att
def dump(self, fdo):
att = self.reorder()
lastK=None
for (k, v) in att.items():
if not lastK: lastK = k
if lastK != k:
fdo.write("\n")
for (kv, ids) in v.items():
fdo.write("Ia: %s=%s" % (k,quote(kv)))
for id in ids:
fdo.write(" %s" % id)
fdo.write("\n")
fdo.write("\n")
class sensorAttributes(base):
def __init__(self, filename):
base.__init__(self, filename, ['id', 'type','unit', 'low_freq', 'high_freq', 'model', 'manufacturer', 'remark'])
class dataloggerAttributes(base):
def __init__(self, filename):
base.__init__(self, filename, ['id', 'digitizer_model', 'digitizer_manufacturer', 'recorder_model', 'recorder_manufacturer', 'clock_model', 'clock_manufacturer', 'clock_type', 'remark'])
class INST(object):
def cleanID(self, id):
nc = ""
for c in id:
nc += c
if c == '_':
nc = ""
return nc
def __init__(self, filename, attS, attD):
self.filename = filename
self.sensorA = sensorAttributes(attS)
self.dataloggerA = dataloggerAttributes(attD)
lines = []
f = open(filename)
for line in f:
line = line.strip()
if not line or line[0] == '#':
# Add comments line types
lines.append({ 'content': line, 'type': 'C', 'id': None})
else:
(id, line) = line.split(">", 1)
id = id.strip()
line = line.strip()
# Add undefined line types
lines.append({ 'content': line, 'type': 'U', 'id': id})
f.close()
self.lines = lines
self._filltypes()
def _filltypes(self):
for line in self.lines:
if line['type'] != 'U': continue
id = line['id']
if id.find('_FIR_') != -1:
line['type'] = 'F'
elif id.find('Sngl-gain_') != -1:
line['type'] = 'L'
line['id'] = self.cleanID(id)
elif id.find('_digipaz_') != -1:
line['type'] = 'P'
elif id.find('_iirpaz_') != -1:
line['type'] = 'I'
for line in self.lines:
if line['type'] != 'U': continue
id = self.cleanID(line['id'])
if id in list(self.sensorA.keys()):
line['type'] = 'S'
line['id'] = id
elif id in list(self.dataloggerA.keys()):
line['type'] = 'D'
line['id'] = id
# Those we are forcing !
elif id in ['OSIRIS-SC', 'Gaia', 'LE24', 'MALI', 'PSS', 'FDL', 'CMG-SAM', 'CMG-DCM', 'EDAS-24', 'SANIAC']:
line['id'] = id
line['type'] = 'D'
elif id in ['Trillium-Compact', 'Reftek-151/120', 'BBVS-60', 'CMG-3ESP/60F', 'LE-1D/1', 'L4-3D/BW', 'S13', 'GS13', 'SH-1', 'MP', 'MARKL22', 'CM-3', 'CMG-6T', 'SM-6/BW']:
line['id'] = id
line['type'] = 'S'
for line in self.lines:
if line['type'] == 'U':
print("'"+self.cleanID(line['id'])+"', ", end=' ')
def dump(self, fdo):
sa = False
da = False
dataloggerFieldSize = 0
sensorFieldSize = 0
for line in self.lines:
if line['type'] == 'C': continue
if line['type'] == 'S':
if len(line['id']) > sensorFieldSize:
sensorFieldSize = len(line['id'])
if line['type'] == 'D':
if len(line['id']) > dataloggerFieldSize:
dataloggerFieldSize = len(line['id'])
seLine = "Se: %%%ss %%s\n" % (-1*(sensorFieldSize+1))
dtLine = "Dl: %%%ss %%s\n" % (-1*(dataloggerFieldSize+1))
for line in self.lines:
if line['type'] == 'C':
fdo.write(line['content'] + "\n")
continue
if line['type'] == 'S':
if not sa:
self.sensorA.dump(fdo)
sa = True
fdo.write(seLine % (line['id'], line['content']))
continue
if line['type'] == 'D':
if not da:
self.dataloggerA.dump(fdo)
da = True
fdo.write(dtLine % (line['id'], line['content']))
continue
if line['type'] == 'L':
fdo.write("Cl: %s %s\n" % (line['id'], line['content']))
continue
if line['type'] == 'F':
fdo.write("Ff: %s %s\n" % (line['id'], line['content']))
continue
if line['type'] == 'P':
fdo.write("Pz: %s %s\n" % (line['id'], line['content']))
continue
if line['type'] == 'I':
fdo.write("If: %s %s\n" % (line['id'], line['content']))
continue
def main():
parser = OptionParser(usage="Old tab to New tab converter", version="1.0", add_help_option=True)
parser.add_option("", "--sat", type="string",
help="Indicates the sensor attribute file to use", dest="sat", default="sensor_attr.csv")
parser.add_option("", "--dat", type="string",
help="Indicates the station attribute file to use", dest="dat", default="datalogger_attr.csv")
parser.add_option("-c", "--clean", action="store_true",
help="Remove the comments and blank lines", dest="cleanFile", default=False)
# Parsing & Error check
(options, args) = parser.parse_args()
errors = []
if len(args) != 1:
errors.append("need an Input filename")
if not os.path.isfile(options.sat):
errors.append("sensor attribute file '%s' not found." % options.sat)
if not os.path.isfile(options.dat):
errors.append("datalogger attribute file '%s' not found." % options.dat)
if len(args) == 2 and os.path.isfile(args[1]):
errors.append("output file already exists, will not overwrite.")
if errors:
print("Found error while processing the command line:", file=sys.stderr)
for error in errors:
print(" %s" % error, file=sys.stderr)
return 1
inputName = args[0]
i= INST(inputName, options.sat, options.dat)
fdo = sys.stdout if len(args) < 2 else open(args[1],"w")
i.dump(fdo)
fdo.close()
if __name__ == "__main__":
main()

@ -0,0 +1,98 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
from __future__ import (absolute_import, division, print_function, unicode_literals)
import sys
import io
from seiscomp.legacy.fseed import *
from seiscomp.legacy.db.seiscomp3 import sc3wrap
from seiscomp.legacy.db.seiscomp3.inventory import Inventory
import seiscomp.datamodel, seiscomp.io
ORGANIZATION = "EIDA"
def iterinv(obj):
return (j for i in obj.values() for j in i.values())
def main():
if len(sys.argv) < 1 or len(sys.argv) > 3:
sys.stderr.write("Usage inv2dlsv [in_xml [out_dataless]]\n")
return 1
if len(sys.argv) > 1:
inFile = sys.argv[1]
else:
inFile = "-"
if len(sys.argv) > 2:
out = sys.argv[2]
else:
out = ""
sc3wrap.dbQuery = None
ar = seiscomp.io.XMLArchive()
if ar.open(inFile) == False:
raise IOError(inFile + ": unable to open")
obj = ar.readObject()
if obj is None:
raise TypeError(inFile + ": invalid format")
sc3inv = seiscomp.datamodel.Inventory.Cast(obj)
if sc3inv is None:
raise TypeError(inFile + ": invalid format")
inv = Inventory(sc3inv)
inv.load_stations("*", "*", "*", "*")
inv.load_instruments()
vol = SEEDVolume(inv, ORGANIZATION, "", resp_dict=False)
for net in iterinv(inv.network):
for sta in iterinv(net.station):
for loc in iterinv(sta.sensorLocation):
for strm in iterinv(loc.stream):
try:
vol.add_chan(net.code, sta.code, loc.code,
strm.code, strm.start, strm.end)
except SEEDError as e:
sys.stderr.write("Error (%s,%s,%s,%s): %s\n" % (
net.code, sta.code, loc.code, strm.code, str(e)))
if not out or out == "-":
output = io.BytesIO()
vol.output(output)
stdout = sys.stdout.buffer if hasattr(sys.stdout, "buffer") else sys.stdout
stdout.write(output.getvalue())
stdout.flush()
output.close()
else:
with open(sys.argv[2], "wb") as fd:
vol.output(fd)
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except Exception as e:
sys.stderr.write("Error: %s" % str(e))
sys.exit(1)

Binary file not shown.

Binary file not shown.

@ -0,0 +1,280 @@
#!/usr/bin/env seiscomp-python
from __future__ import absolute_import, division, print_function
import sys
import os
import time
import datetime
import calendar
import stat
from getopt import getopt, GetoptError
from seiscomp import mseedlite as mseed
#------------------------------------------------------------------------------
def read_mseed_with_delays(delaydict, reciterable):
"""
Create an iterator which takes into account configurable realistic delays.
This function creates an iterator which returns one miniseed record at a time.
Artificial delays can be introduced by using delaydict.
This function can be used to make simulations in real time more realistic
when e.g. some stations have a much higher delay than others due to
narrow bandwidth communication channels etc.
A delaydict has the following data structure:
keys: XX.ABC (XX: network code, ABC: station code). The key "default" is
a special value for the default delay.
values: Delay to be introduced in seconds
This function will rearrange the iterable object which has been used as
input for rt_simul() so that it can again be used by rt_simul but taking
artificial delays into account.
"""
import heapq #pylint: disable=C0415
heap = []
min_delay = 0
default_delay = 0
if 'default' in delaydict:
default_delay = delaydict['default']
for rec in reciterable:
rec_time = calendar.timegm(rec.end_time.timetuple())
delay_time = rec_time
stationname = "%s.%s" % (rec.net, rec.sta)
if stationname in delaydict:
delay_time = rec_time + delaydict[stationname]
else:
delay_time = rec_time + default_delay
heapq.heappush(heap, (delay_time, rec))
toprectime = heap[0][0]
if toprectime - min_delay < rec_time:
topelement = heapq.heappop(heap)
yield topelement
while heap:
topelement = heapq.heappop(heap)
yield topelement
#------------------------------------------------------------------------------
def rt_simul(f, speed=1., jump=0., delaydict=None):
"""
Iterator to simulate "real-time" MSeed input
At startup, the first MSeed record is read. The following records are
read in pseudo-real-time relative to the time of the first record,
resulting in data flowing at realistic speed. This is useful e.g. for
demonstrating real-time processing using real data of past events.
The data in the input file may be multiplexed, but *must* be sorted by
time, e.g. using 'mssort'.
"""
rtime = time.time()
etime = None
skipping = True
record_iterable = mseed.Input(f)
if delaydict:
record_iterable = read_mseed_with_delays(delaydict, record_iterable)
for rec in record_iterable:
if delaydict:
rec_time = rec[0]
rec = rec[1]
else:
rec_time = calendar.timegm(rec.end_time.timetuple())
if etime is None:
etime = rec_time
if skipping:
if (rec_time - etime) / 60.0 < jump:
continue
etime = rec_time
skipping = False
tmax = etime + speed * (time.time() - rtime)
ms = 1000000.0 * (rec.nsamp / rec.fsamp)
last_sample_time = rec.begin_time + datetime.timedelta(microseconds=ms)
last_sample_time = calendar.timegm(last_sample_time.timetuple())
if last_sample_time > tmax:
time.sleep((last_sample_time - tmax + 0.001) / speed)
yield rec
#------------------------------------------------------------------------------
def usage():
print('''Usage:
msrtsimul [options] file
MiniSEED real time playback and simulation
msrtsimul reads sorted (and possibly multiplexed) MiniSEED files and writes
individual records in pseudo-real-time. This is useful e.g. for testing and
simulating data acquisition. Output is
$SEISCOMP_ROOT/var/run/seedlink/mseedfifo unless --seedlink or -c is used.
Options:
-c, --stdout write on standard output
-d, --delays add artificial delays
-s, --speed speed factor (float)
-j, --jump minutes to skip (float)
--test test mode
-m --mode choose between 'realtime' and 'historic'
--seedlink choose the seedlink module name. Useful if a seedlink
alias or non-standard names are used. Replaces 'seedlink'
in the standard mseedfifo path.
-v, --verbose verbose mode
-h, --help display this help message
Examples:
Play back miniSEED waveforms in real time with verbose output
msrtsimul -v miniSEED-file
''')
#------------------------------------------------------------------------------
def main():
py2 = sys.version_info < (3,)
ifile = sys.stdin if py2 else sys.stdin.buffer
verbosity = 0
speed = 1.
jump = 0.
test = False
seedlink = 'seedlink'
mode = 'realtime'
setSystemTime = False
try:
opts, args = getopt(sys.argv[1:], "cd:s:j:vhm:",
["stdout", "delays=", "speed=", "jump=", "test",
"verbose", "help", "mode=", "seedlink="])
except GetoptError:
usage()
return 1
out_channel = None
delays = None
for flag, arg in opts:
if flag in ("-c", "--stdout"):
out_channel = sys.stdout if py2 else sys.stdout.buffer
elif flag in ("-d", "--delays"):
delays = arg
elif flag in ("-s", "--speed"):
speed = float(arg)
elif flag in ("-j", "--jump"):
jump = float(arg)
elif flag in ("-m", "--mode"):
mode = arg
elif flag == "--seedlink":
seedlink = arg
elif flag in ("-v", "--verbose"):
verbosity += 1
elif flag == "--test":
test = True
else:
usage()
if flag in ("-h", "--help"):
return 0
return 1
if len(args) == 1:
if args[0] != "-":
try:
ifile = open(args[0], "rb")
except IOError as e:
print("could not open input file '{}' for reading: {}" \
.format(args[0], e), file=sys.stderr)
sys.exit(1)
elif len(args) != 0:
usage()
return 1
if out_channel is None:
try:
sc_root = os.environ["SEISCOMP_ROOT"]
except KeyError:
print("SEISCOMP_ROOT environment variable is not set", file=sys.stderr)
sys.exit(1)
mseed_fifo = os.path.join(sc_root, "var", "run", seedlink, "mseedfifo")
if verbosity:
print("output data to %s" % mseed_fifo, file=sys.stderr)
if not os.path.exists(mseed_fifo):
print("""\
ERROR: {} does not exist.
In order to push the records to SeedLink, it needs to run and must be configured for real-time playback.
""".format(mseed_fifo), file=sys.stderr)
sys.exit(1)
if not stat.S_ISFIFO(os.stat(mseed_fifo).st_mode):
print("""\
ERROR: {} is not a named pipe
Check if SeedLink is running and configured for real-time playback.
""".format(mseed_fifo), file=sys.stderr)
sys.exit(1)
try:
out_channel = open(mseed_fifo, "wb")
except Exception as e:
print(str(e), file=sys.stderr)
sys.exit(1)
try:
delaydict = None
if delays:
delaydict = dict()
try:
f = open(delays, 'r')
for line in f:
content = line.split(':')
if len(content) != 2:
raise Exception("Could not parse a line in file %s: %s\n" % (delays, line))
delaydict[content[0].strip()] = float(content[1].strip())
except Exception as e:
print("Error reading delay file {}: {}".format(delays, e),
file=sys.stderr)
inp = rt_simul(ifile, speed=speed, jump=jump, delaydict=delaydict)
stime = time.time()
time_diff = None
print("Starting msrtsimul at {}".format(datetime.datetime.utcnow()), file=sys.stderr)
for rec in inp:
if rec.size != 512:
print("Skipping record of {}.{}.{}.{} starting on {}: length != 512 Bytes: ".format(rec.net, rec.sta, rec.loc, rec.cha, str(rec.begin_time)), file=sys.stderr)
continue
if time_diff is None:
ms = 1000000.0 * (rec.nsamp / rec.fsamp)
time_diff = datetime.datetime.utcnow() - rec.begin_time - \
datetime.timedelta(microseconds=ms)
if mode == 'realtime':
rec.begin_time += time_diff
if verbosity:
print("%s_%s %7.2f %s %7.2f" % \
(rec.net, rec.sta, (time.time() - stime), str(rec.begin_time),
time.time() - calendar.timegm(rec.begin_time.timetuple())),
file=sys.stderr)
if not test:
rec.write(out_channel, 9)
out_channel.flush()
except KeyboardInterrupt:
pass
except Exception as e:
print("Exception: {}".format(str(e)), file=sys.stderr)
return 1
return 0
#------------------------------------------------------------------------------
if __name__ == "__main__":
sys.exit(main())

Binary file not shown.

Binary file not shown.

@ -0,0 +1,217 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import time
import sys
import os
import time
import seiscomp.core, seiscomp.client, seiscomp.datamodel, seiscomp.logging
from seiscomp.scbulletin import Bulletin, stationCount
class ProcAlert(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
self.setMessagingEnabled(True)
self.setDatabaseEnabled(True, True)
self.setAutoApplyNotifierEnabled(True)
self.setInterpretNotifierEnabled(True)
self.setPrimaryMessagingGroup(seiscomp.client.Protocol.LISTENER_GROUP)
self.addMessagingSubscription("EVENT")
self.addMessagingSubscription("LOCATION")
self.addMessagingSubscription("MAGNITUDE")
self.maxAgeDays = 1.
self.minPickCount = 25
self.procAlertScript = ""
ep = seiscomp.datamodel.EventParameters()
def createCommandLineDescription(self):
try:
self.commandline().addGroup("Publishing")
self.commandline().addIntOption("Publishing", "min-arr",
"Minimum arrival count of a published origin", self.minPickCount)
self.commandline().addDoubleOption("Publishing", "max-age",
"Maximum age in days of published origins", self.maxAgeDays)
self.commandline().addStringOption("Publishing", "procalert-script",
"Specify the script to publish an event. The ProcAlert file and the event id are passed as parameter $1 and $2")
self.commandline().addOption("Publishing", "test",
"Test mode, no messages are sent")
except:
seiscomp.logging.warning(
"caught unexpected error %s" % sys.exc_info())
def initConfiguration(self):
if not seiscomp.client.Application.initConfiguration(self):
return False
try:
self.procAlertScript = self.configGetString("scripts.procAlert")
except:
pass
try:
self.minPickCount = self.configGetInt("minArrivals")
except:
pass
try:
self.maxAgeDays = self.configGetDouble("maxAgeDays")
except:
pass
return True
def init(self):
if not seiscomp.client.Application.init(self):
return False
try:
self.procAlertScript = self.commandline().optionString("procalert-script")
except:
pass
try:
self.minPickCount = self.commandline().optionInt("min-arr")
except:
pass
try:
self.maxAgeDays = self.commandline().optionDouble("max-age")
except:
pass
self.bulletin = Bulletin(self.query(), "autoloc1")
self.cache = seiscomp.datamodel.PublicObjectRingBuffer(
self.query(), 100)
if not self.procAlertScript:
seiscomp.logging.warning("No procalert script given")
else:
seiscomp.logging.info(
"Using procalert script: %s" % self.procAlertScript)
return True
def addObject(self, parentID, obj):
org = seiscomp.datamodel.Origin.Cast(obj)
if org:
self.cache.feed(org)
seiscomp.logging.info("Received origin %s" % org.publicID())
return
self.updateObject(parentID, obj)
def updateObject(self, parentID, obj):
try:
evt = seiscomp.datamodel.Event.Cast(obj)
if evt:
orid = evt.preferredOriginID()
org = self.cache.get(seiscomp.datamodel.Origin, orid)
if not org:
seiscomp.logging.error("Unable to fetch origin %s" % orid)
return
if org.arrivalCount() == 0:
self.query().loadArrivals(org)
if org.stationMagnitudeCount() == 0:
self.query().loadStationMagnitudes(org)
if org.magnitudeCount() == 0:
self.query().loadMagnitudes(org)
if not self.originMeetsCriteria(org, evt):
seiscomp.logging.warning("Origin %s not published" % orid)
return
txt = self.bulletin.printEvent(evt)
for line in txt.split("\n"):
line = line.rstrip()
seiscomp.logging.info(line)
seiscomp.logging.info("")
if not self.commandline().hasOption("test"):
self.send_procalert(txt, evt.publicID())
return
except:
sys.stderr.write("%s\n" % sys.exc_info())
def hasValidNetworkMagnitude(self, org, evt):
nmag = org.magnitudeCount()
for imag in range(nmag):
mag = org.magnitude(imag)
if mag.publicID() == evt.preferredMagnitudeID():
return True
return False
def send_procalert(self, txt, evid):
if self.procAlertScript:
tmp = "/tmp/yyy%s" % evid.replace("/", "_").replace(":", "-")
f = file(tmp, "w")
f.write("%s" % txt)
f.close()
os.system(self.procAlertScript + " " + tmp + " " + evid)
def coordinates(self, org):
return org.latitude().value(), org.longitude().value(), org.depth().value()
def originMeetsCriteria(self, org, evt):
publish = True
lat, lon, dep = self.coordinates(org)
if 43 < lat < 70 and -10 < lon < 60 and dep > 200:
seiscomp.logging.error("suspicious region/depth - ignored")
publish = False
if stationCount(org) < self.minPickCount:
seiscomp.logging.error("too few picks - ignored")
publish = False
now = seiscomp.core.Time.GMT()
if (now - org.time().value()).seconds()/86400. > self.maxAgeDays:
seiscomp.logging.error("origin too old - ignored")
publish = False
try:
if org.evaluationMode() == seiscomp.datamodel.MANUAL:
publish = True
except:
pass
try:
if org.evaluationStatus() == seiscomp.datamodel.CONFIRMED:
publish = True
except:
pass
if not self.hasValidNetworkMagnitude(org, evt):
seiscomp.logging.error("no network magnitude - ignored")
publish = False
return publish
app = ProcAlert(len(sys.argv), sys.argv)
sys.exit(app())

@ -0,0 +1 @@
scml2inv

@ -0,0 +1,717 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import os
import sys
import re
import subprocess
import traceback
import seiscomp.core, seiscomp.client, seiscomp.datamodel, seiscomp.math
import seiscomp.logging, seiscomp.seismology, seiscomp.system
class ObjectAlert(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
self.setMessagingEnabled(True)
self.setDatabaseEnabled(True, True)
self.setLoadRegionsEnabled(True)
self.setMessagingUsername("")
self.setPrimaryMessagingGroup(
seiscomp.client.Protocol.LISTENER_GROUP)
self.addMessagingSubscription("EVENT")
self.addMessagingSubscription("LOCATION")
self.addMessagingSubscription("MAGNITUDE")
self.setAutoApplyNotifierEnabled(True)
self.setInterpretNotifierEnabled(True)
self.setLoadCitiesEnabled(True)
self.setLoadRegionsEnabled(True)
self._ampType = "snr"
self._citiesMaxDist = 20
self._citiesMinPopulation = 50000
self._eventDescriptionPattern = None
self._pickScript = None
self._ampScript = None
self._alertScript = None
self._eventScript = None
self._pickProc = None
self._ampProc = None
self._alertProc = None
self._eventProc = None
self._newWhenFirstSeen = False
self._oldEvents = []
self._agencyIDs = []
self._phaseHints = []
self._phaseStreams = []
self._phaseNumber = 1
self._phaseInterval = 1
def createCommandLineDescription(self):
self.commandline().addOption("Generic", "first-new",
"calls an event a new event when it is seen the first time")
self.commandline().addGroup("Alert")
self.commandline().addStringOption("Alert", "amp-type",
"amplitude type to listen to", self._ampType)
self.commandline().addStringOption("Alert", "pick-script",
"script to be called when a pick arrived, network-, station code pick publicID are passed as parameters $1, $2, $3 and $4")
self.commandline().addStringOption("Alert", "amp-script",
"script to be called when a station amplitude arrived, network-, station code, amplitude and amplitude publicID are passed as parameters $1, $2, $3 and $4")
self.commandline().addStringOption("Alert", "alert-script",
"script to be called when a preliminary origin arrived, latitude and longitude are passed as parameters $1 and $2")
self.commandline().addStringOption("Alert", "event-script",
"script to be called when an event has been declared; the message string, a flag (1=new event, 0=update event), the EventID, the arrival count and the magnitude (optional when set) are passed as parameter $1, $2, $3, $4 and $5")
self.commandline().addGroup("Cities")
self.commandline().addStringOption("Cities", "max-dist",
"maximum distance for using the distance from a city to the earthquake")
self.commandline().addStringOption("Cities", "min-population",
"minimum population for a city to become a point of interest")
self.commandline().addGroup("Debug")
self.commandline().addStringOption("Debug", "eventid,E", "specify Event ID")
return True
def init(self):
if not seiscomp.client.Application.init(self):
return False
foundScript = False
# module configuration paramters
try:
self._newWhenFirstSeen = self.configGetBool("firstNew")
except:
pass
try:
self._agencyIDs = [self.configGetString("agencyID")]
except:
pass
try:
agencyIDs = self.configGetStrings("agencyIDs")
self._agencyIDs = []
for item in agencyIDs:
item = item.strip()
if item not in self._agencyIDs:
self._agencyIDs.append(item)
except:
pass
self._phaseHints = ['P','S']
try:
phaseHints = self.configGetStrings("constraints.phaseHints")
self._phaseHints = []
for item in phaseHints:
item = item.strip()
if item not in self._phaseHints:
self._phaseHints.append(item)
except:
pass
self._phaseStreams = []
try:
phaseStreams = self.configGetStrings("constraints.phaseStreams")
for item in phaseStreams:
rule = item.strip()
# rule is NET.STA.LOC.CHA and the special charactes ? * | ( ) are allowed
if not re.fullmatch(r'[A-Z|a-z|0-9|\?|\*|\||\(|\)|\.]+', rule):
seiscomp.logging.error("Wrong stream ID format in `constraints.phaseStreams`: %s" % item)
return False
# convert rule to a valid regular expression
rule = re.sub(r'\.', r'\.', rule)
rule = re.sub(r'\?', '.' , rule)
rule = re.sub(r'\*' , '.*' , rule)
if rule not in self._phaseStreams:
self._phaseStreams.append(rule)
except:
pass
try:
self._phaseNumber = self.configGetInt("constraints.phaseNumber")
except:
pass
try:
self._phaseInterval = self.configGetInt("constraints.phaseInterval")
except:
pass
if self._phaseNumber > 1:
self._pickCache = seiscomp.datamodel.PublicObjectTimeSpanBuffer()
self._pickCache.setTimeSpan(seiscomp.core.TimeSpan(self._phaseInterval))
self.enableTimer(1)
try:
self._eventDescriptionPattern = self.configGetString("poi.message")
except:
pass
try:
self._citiesMaxDist = self.configGetDouble("poi.maxDist")
except:
pass
try:
self._citiesMinPopulation = self.configGetInt("poi.minPopulation")
except:
pass
# mostly command-line options
try:
self._citiesMaxDist = self.commandline().optionDouble("max-dist")
except:
pass
try:
if self.commandline().hasOption("first-new"):
self._newWhenFirstSeen = True
except:
pass
try:
self._citiesMinPopulation = self.commandline().optionInt("min-population")
except:
pass
try:
self._ampType = self.commandline().optionString("amp-type")
except:
pass
try:
self._pickScript = self.commandline().optionString("pick-script")
except:
try:
self._pickScript = self.configGetString("scripts.pick")
except:
seiscomp.logging.warning("No pick script defined")
if self._pickScript:
self._pickScript = seiscomp.system.Environment.Instance().absolutePath(self._pickScript)
seiscomp.logging.info("Using pick script %s" % self._pickScript)
if not os.path.isfile(self._pickScript):
seiscomp.logging.error(" + not exising")
return False
if not os.access(self._pickScript, os.X_OK):
seiscomp.logging.error(" + not executable")
return False
foundScript = True
try:
self._ampScript = self.commandline().optionString("amp-script")
except:
try:
self._ampScript = self.configGetString("scripts.amplitude")
except:
seiscomp.logging.warning("No amplitude script defined")
if self._ampScript:
self._ampScript = seiscomp.system.Environment.Instance().absolutePath(self._ampScript)
seiscomp.logging.info("Using amplitude script %s" % self._ampScript)
if not os.path.isfile(self._ampScript):
seiscomp.logging.error(" + not exising")
return False
if not os.access(self._ampScript, os.X_OK):
seiscomp.logging.error(" + not executable")
return False
foundScript = True
try:
self._alertScript = self.commandline().optionString("alert-script")
except:
try:
self._alertScript = self.configGetString("scripts.alert")
except:
seiscomp.logging.warning("No alert script defined")
if self._alertScript:
self._alertScript = seiscomp.system.Environment.Instance(
).absolutePath(self._alertScript)
seiscomp.logging.info("Using alert script %s" % self._alertScript)
if not os.path.isfile(self._alertScript):
seiscomp.logging.error(" + not exising")
return False
if not os.access(self._alertScript, os.X_OK):
seiscomp.logging.error(" + not executable")
return False
foundScript = True
try:
self._eventScript = self.commandline().optionString("event-script")
except:
try:
self._eventScript = self.configGetString("scripts.event")
except:
seiscomp.logging.warning("No event script defined")
if self._eventScript:
self._eventScript = seiscomp.system.Environment.Instance(
).absolutePath(self._eventScript)
seiscomp.logging.info("Using event script %s" % self._eventScript)
if not os.path.isfile(self._eventScript):
seiscomp.logging.error(" + not exising")
return False
if not os.access(self._eventScript, os.X_OK):
seiscomp.logging.error(" + not executable")
return False
foundScript = True
if not foundScript:
seiscomp.logging.error("Found no valid script in configuration")
return False
seiscomp.logging.info("Creating ringbuffer for 100 objects")
if not self.query():
seiscomp.logging.warning(
"No valid database interface to read from")
self._cache = seiscomp.datamodel.PublicObjectRingBuffer(
self.query(), 100)
if self._ampScript and self.connection():
seiscomp.logging.info(
"Amplitude script defined: subscribing to AMPLITUDE message group")
self.connection().subscribe("AMPLITUDE")
if self._pickScript and self.connection():
seiscomp.logging.info(
"Pick script defined: subscribing to PICK message group")
self.connection().subscribe("PICK")
if self._newWhenFirstSeen:
seiscomp.logging.info(
"A new event is declared when I see it the first time")
seiscomp.logging.info("Filtering:")
if " ".join(self._agencyIDs):
seiscomp.logging.info(" + agencyIDs filter for events and picks: %s" % (" ".join(self._agencyIDs)))
else:
seiscomp.logging.info(" + agencyIDs: no filter is applied")
if " ".join(self._phaseHints):
seiscomp.logging.info(" + phase hint filter for picks: '%s'" % (" ".join(self._phaseHints)))
else:
seiscomp.logging.info(" + phase hints: no filter is applied")
if " ".join(self._phaseStreams):
seiscomp.logging.info(" + phase stream ID filter for picks: '%s'" % (" ".join(self._phaseStreams)))
else:
seiscomp.logging.info(" + phase stream ID: no filter is applied")
return True
def run(self):
try:
try:
eventID = self.commandline().optionString("eventid")
event = self._cache.get(seiscomp.datamodel.Event, eventID)
if event:
self.notifyEvent(event)
except:
pass
return seiscomp.client.Application.run(self)
except:
info = traceback.format_exception(*sys.exc_info())
for i in info:
sys.stderr.write(i)
return False
def runPickScript(self, pickObjectList):
if not self._pickScript:
return
for pickObject in pickObjectList:
# parse values
try:
net = pickObject.waveformID().networkCode()
except:
net = "unknown"
try:
sta = pickObject.waveformID().stationCode()
except:
sta = "unknown"
pickID = pickObject.publicID()
try:
phaseHint = pickObject.phaseHint().code()
except:
phaseHint = "unknown"
print(net, sta, pickID, phaseHint)
if self._pickProc is not None:
if self._pickProc.poll() is None:
seiscomp.logging.info(
"Pick script still in progress -> wait one second")
self._pickProc.wait(1)
if self._pickProc.poll() is None:
seiscomp.logging.warning(
"Pick script still in progress -> skipping message")
return
try:
self._pickProc = subprocess.Popen(
[self._pickScript, net, sta, pickID, phaseHint])
seiscomp.logging.info(
"Started pick script with pid %d" % self._pickProc.pid)
except:
seiscomp.logging.error(
"Failed to start pick script '%s'" % self._pickScript)
def runAmpScript(self, ampObject):
if not self._ampScript:
return
# parse values
net = ampObject.waveformID().networkCode()
sta = ampObject.waveformID().stationCode()
amp = ampObject.amplitude().value()
ampID = ampObject.publicID()
if self._ampProc is not None:
if self._ampProc.poll() is None:
seiscomp.logging.warning(
"Amplitude script still in progress -> skipping message")
return
try:
self._ampProc = subprocess.Popen(
[self._ampScript, net, sta, "%.2f" % amp, ampID])
seiscomp.logging.info(
"Started amplitude script with pid %d" % self._ampProc.pid)
except:
seiscomp.logging.error(
"Failed to start amplitude script '%s'" % self._ampScript)
def runAlert(self, lat, lon):
if not self._alertScript:
return
if self._alertProc is not None:
if self._alertProc.poll() is None:
seiscomp.logging.warning(
"AlertScript still in progress -> skipping message")
return
try:
self._alertProc = subprocess.Popen(
[self._alertScript, "%.1f" % lat, "%.1f" % lon])
seiscomp.logging.info(
"Started alert script with pid %d" % self._alertProc.pid)
except:
seiscomp.logging.error(
"Failed to start alert script '%s'" % self._alertScript)
def handleMessage(self, msg):
try:
dm = seiscomp.core.DataMessage.Cast(msg)
if dm:
for att in dm:
org = seiscomp.datamodel.Origin.Cast(att)
if org:
try:
if org.evaluationStatus() == seiscomp.datamodel.PRELIMINARY:
self.runAlert(org.latitude().value(),
org.longitude().value())
except:
pass
#ao = seiscomp.datamodel.ArtificialOriginMessage.Cast(msg)
# if ao:
# org = ao.origin()
# if org:
# self.runAlert(org.latitude().value(), org.longitude().value())
# return
seiscomp.client.Application.handleMessage(self, msg)
except:
info = traceback.format_exception(*sys.exc_info())
for i in info:
sys.stderr.write(i)
def addObject(self, parentID, object):
try:
# pick
obj = seiscomp.datamodel.Pick.Cast(object)
if obj:
self._cache.feed(obj)
seiscomp.logging.debug("got new pick '%s'" % obj.publicID())
agencyID = obj.creationInfo().agencyID()
phaseHint = obj.phaseHint().code()
if self._phaseStreams:
waveformID = "%s.%s.%s.%s" % (
obj.waveformID().networkCode(), obj.waveformID().stationCode(),
obj.waveformID().locationCode(), obj.waveformID().channelCode())
matched = False
for rule in self._phaseStreams:
if re.fullmatch(rule, waveformID):
matched = True
break
if not matched:
seiscomp.logging.debug(
" + stream ID %s does not match constraints.phaseStreams rules"
% (waveformID))
return
if not self._agencyIDs or agencyID in self._agencyIDs:
if not self._phaseHints or phaseHint in self._phaseHints:
self.notifyPick(obj)
else:
seiscomp.logging.debug(" + phase hint %s does not match '%s'"
% (phaseHint, self._phaseHints))
else:
seiscomp.logging.debug(" + agencyID %s does not match '%s'"
% (agencyID, self._agencyIDs))
return
# amplitude
obj = seiscomp.datamodel.Amplitude.Cast(object)
if obj:
if obj.type() == self._ampType:
seiscomp.logging.debug("got new %s amplitude '%s'" % (
self._ampType, obj.publicID()))
self.notifyAmplitude(obj)
return
# origin
obj = seiscomp.datamodel.Origin.Cast(object)
if obj:
self._cache.feed(obj)
seiscomp.logging.debug("got new origin '%s'" % obj.publicID())
try:
if obj.evaluationStatus() == seiscomp.datamodel.PRELIMINARY:
self.runAlert(obj.latitude().value(),
obj.longitude().value())
except:
pass
return
# magnitude
obj = seiscomp.datamodel.Magnitude.Cast(object)
if obj:
self._cache.feed(obj)
seiscomp.logging.debug(
"got new magnitude '%s'" % obj.publicID())
return
# event
obj = seiscomp.datamodel.Event.Cast(object)
if obj:
org = self._cache.get(
seiscomp.datamodel.Origin, obj.preferredOriginID())
agencyID = org.creationInfo().agencyID()
seiscomp.logging.debug("got new event '%s'" % obj.publicID())
if not self._agencyIDs or agencyID in self._agencyIDs:
self.notifyEvent(obj, True)
return
except:
info = traceback.format_exception(*sys.exc_info())
for i in info:
sys.stderr.write(i)
def updateObject(self, parentID, object):
try:
obj = seiscomp.datamodel.Event.Cast(object)
if obj:
org = self._cache.get(
seiscomp.datamodel.Origin, obj.preferredOriginID())
agencyID = org.creationInfo().agencyID()
seiscomp.logging.debug("update event '%s'" % obj.publicID())
if not self._agencyIDs or agencyID in self._agencyIDs:
self.notifyEvent(obj, False)
except:
info = traceback.format_exception(*sys.exc_info())
for i in info:
sys.stderr.write(i)
def handleTimeout(self):
self.checkEnoughPicks()
def checkEnoughPicks(self):
if self._pickCache.size() >= self._phaseNumber:
# wait until self._phaseInterval has elapsed before calling the
# script (more picks might come)
timeWindowLength = (seiscomp.core.Time.GMT() - self._pickCache.oldest()).length()
if timeWindowLength >= self._phaseInterval:
picks = [seiscomp.datamodel.Pick.Cast(o) for o in self._pickCache]
self.runPickScript(picks)
self._pickCache.clear()
def notifyPick(self, pick):
if self._phaseNumber <= 1:
self.runPickScript([pick])
else:
self.checkEnoughPicks()
self._pickCache.feed(pick)
def notifyAmplitude(self, amp):
self.runAmpScript(amp)
def notifyEvent(self, evt, newEvent=True, dtmax=3600):
try:
org = self._cache.get(
seiscomp.datamodel.Origin, evt.preferredOriginID())
if not org:
seiscomp.logging.warning(
"unable to get origin %s, ignoring event message" % evt.preferredOriginID())
return
preliminary = False
try:
if org.evaluationStatus() == seiscomp.datamodel.PRELIMINARY:
preliminary = True
except:
pass
if preliminary == False:
nmag = self._cache.get(
seiscomp.datamodel.Magnitude, evt.preferredMagnitudeID())
if nmag:
mag = nmag.magnitude().value()
mag = "magnitude %.1f" % mag
else:
if len(evt.preferredMagnitudeID()) > 0:
seiscomp.logging.warning(
"unable to get magnitude %s, ignoring event message" % evt.preferredMagnitudeID())
else:
seiscomp.logging.warning(
"no preferred magnitude yet, ignoring event message")
return
# keep track of old events
if self._newWhenFirstSeen:
if evt.publicID() in self._oldEvents:
newEvent = False
else:
newEvent = True
self._oldEvents.append(evt.publicID())
dsc = seiscomp.seismology.Regions.getRegionName(
org.latitude().value(), org.longitude().value())
if self._eventDescriptionPattern:
try:
city, dist, azi = self.nearestCity(org.latitude().value(), org.longitude(
).value(), self._citiesMaxDist, self._citiesMinPopulation)
if city:
dsc = self._eventDescriptionPattern
region = seiscomp.seismology.Regions.getRegionName(
org.latitude().value(), org.longitude().value())
distStr = str(int(seiscomp.math.deg2km(dist)))
dsc = dsc.replace("@region@", region).replace(
"@dist@", distStr).replace("@poi@", city.name())
except:
pass
seiscomp.logging.debug("desc: %s" % dsc)
dep = org.depth().value()
now = seiscomp.core.Time.GMT()
otm = org.time().value()
dt = (now - otm).seconds()
# if dt > dtmax:
# return
if dt > 3600:
dt = "%d hours %d minutes ago" % (dt/3600, (dt % 3600)/60)
elif dt > 120:
dt = "%d minutes ago" % (dt/60)
else:
dt = "%d seconds ago" % dt
if preliminary:
message = "earthquake, XXL, preliminary, %s, %s" % (dt, dsc)
else:
message = "earthquake, %s, %s, %s, depth %d kilometers" % (
dt, dsc, mag, int(dep+0.5))
seiscomp.logging.info(message)
if not self._eventScript:
return
if self._eventProc is not None:
if self._eventProc.poll() is None:
seiscomp.logging.warning(
"EventScript still in progress -> skipping message")
return
try:
param2 = 0
param3 = 0
param4 = ""
if newEvent:
param2 = 1
org = self._cache.get(
seiscomp.datamodel.Origin, evt.preferredOriginID())
if org:
try:
param3 = org.quality().associatedPhaseCount()
except:
pass
nmag = self._cache.get(
seiscomp.datamodel.Magnitude, evt.preferredMagnitudeID())
if nmag:
param4 = "%.1f" % nmag.magnitude().value()
self._eventProc = subprocess.Popen(
[self._eventScript, message, "%d" % param2, evt.publicID(), "%d" % param3, param4])
seiscomp.logging.info(
"Started event script with pid %d" % self._eventProc.pid)
except:
seiscomp.logging.error("Failed to start event script '%s %s %d %d %s'" % (
self._eventScript, message, param2, param3, param4))
except:
info = traceback.format_exception(*sys.exc_info())
for i in info:
sys.stderr.write(i)
def printUsage(self):
print('''Usage:
scalert [options]
Execute custom scripts upon arrival of objects or updates''')
seiscomp.client.Application.printUsage(self)
print('''Examples:
Execute scalert on command line with debug output
scalert --debug
''')
app = ObjectAlert(len(sys.argv), sys.argv)
sys.exit(app())

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

@ -0,0 +1,19 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import seiscomp.scbulletin
if __name__ == "__main__":
seiscomp.scbulletin.main()

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

@ -0,0 +1,238 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
from __future__ import division, print_function
import sys
import os
import seiscomp.client
import seiscomp.datamodel
import seiscomp.config
def readParams(sc_params):
if sc_params.baseID():
sc_params_base = seiscomp.datamodel.ParameterSet.Find(
sc_params.baseID())
if sc_params_base is None:
sys.stderr.write("Warning: %s: base parameter set for %s not found\n" % (
sc_params.baseID(), sc_params.publicID()))
params = {}
else:
params = readParams(sc_params_base)
else:
params = {}
for i in range(sc_params.parameterCount()):
p = sc_params.parameter(i)
params[p.name()] = p.value()
return params
class DumpCfg(seiscomp.client.Application):
def __init__(self, argc, argv):
if argc < 2:
sys.stderr.write("scdumpcfg {modname} [options]\n")
raise RuntimeError
self.appName = argv[1]
# Remove first parameter to replace appname with passed module name
argc = argc-1
argv = argv[1:]
seiscomp.client.Application.__init__(self, argc, argv)
self.setMessagingEnabled(True)
self.setMessagingUsername("")
self.setDatabaseEnabled(True, True)
self.setLoadConfigModuleEnabled(True)
self.setDaemonEnabled(False)
def createCommandLineDescription(self):
self.commandline().addGroup("Dump")
self.commandline().addStringOption("Dump", "param,P",
"Specify parameter name to filter for.")
self.commandline().addOption("Dump", "bindings,B",
"Dump bindings instead of module configuration.")
self.commandline().addOption("Dump", "allow-global,G",
"Print global bindings if no module binding is avaible.")
self.commandline().addOption("Dump", "cfg",
"Print output in .cfg format.")
self.commandline().addOption("Dump", "nslc",
"Print the list of streams which have bindings of the given module.")
def validateParameters(self):
if not seiscomp.client.Application.validateParameters(self):
return False
self.dumpBindings = self.commandline().hasOption("bindings")
try:
self.param = self.commandline().optionString("param")
except:
self.param = None
self.allowGlobal = self.commandline().hasOption("allow-global")
self.formatCfg = self.commandline().hasOption("cfg")
self.nslc = self.commandline().hasOption("nslc")
if not self.dumpBindings:
self.setMessagingEnabled(False)
self.setDatabaseEnabled(False, False)
self.setLoadConfigModuleEnabled(False)
return True
def initConfiguration(self):
if self.appName == "-h" or self.appName == "--help":
self.printUsage()
return False
return seiscomp.client.Application.initConfiguration(self)
# Do nothing.
def initSubscriptions(self):
return True
def printUsage(self):
print('''Usage:
{} [options]
Dump bindings or module configurations used by a specific module or global for
particular stations.'''.format(os.path.basename(__file__)), file=sys.stderr)
seiscomp.client.Application.printUsage(self)
print('''Examples:
Dump global bindings configuration for all stations
{} global -d localhost -B > config.xml
'''.format(os.path.basename(__file__)), file=sys.stderr)
def run(self):
cfg = self.configuration()
if self.nslc:
nslc = set()
if not self.dumpBindings:
symtab = cfg.symbolTable()
names = cfg.names()
count = 0
for name in names:
if self.param and self.param != name:
continue
sym = symtab.get(name)
if self.formatCfg:
if sym.comment:
if count > 0:
sys.stdout.write("\n")
sys.stdout.write("%s\n" % sym.comment)
sys.stdout.write("%s = %s\n" % (sym.name, sym.content))
else:
sys.stdout.write("%s\n" % sym.name)
sys.stdout.write(" value(s) : %s\n" %
", ".join(sym.values))
sys.stdout.write(" source : %s\n" % sym.uri)
count = count + 1
if self.param and count == 0:
sys.stderr.write("%s: definition not found\n." % self.param)
else:
cfg = self.configModule()
if cfg is None:
sys.stderr.write("No config module read\n")
return False
tmp = {}
for i in range(cfg.configStationCount()):
cfg_sta = cfg.configStation(i)
tmp[(cfg_sta.networkCode(), cfg_sta.stationCode())] = cfg_sta
name = self.name()
# For backward compatibility rename global to default
if name == "global":
name = "default"
for item in sorted(tmp.keys()):
cfg_sta = tmp[item]
sta_enabled = cfg_sta.enabled()
cfg_setup = seiscomp.datamodel.findSetup(
cfg_sta, name, self.allowGlobal)
if not cfg_setup is None:
suffix = ""
if sta_enabled and cfg_setup.enabled():
out = "+ "
else:
suffix = " ("
if not sta_enabled:
suffix += "station disabled"
if not cfg_setup.enabled():
if suffix:
suffix += ", "
suffix += "setup disabled"
suffix += ")"
out = "- "
out += "%s.%s%s\n" % (cfg_sta.networkCode(),
cfg_sta.stationCode(), suffix)
params = seiscomp.datamodel.ParameterSet.Find(
cfg_setup.parameterSetID())
if params is None:
sys.stderr.write(
"ERROR: %s: ParameterSet not found\n" %
cfg_setup.parameterSetID())
return False
params = readParams(params)
if self.nslc:
try:
sensorLocation = params["detecLocid"]
except:
sensorLocation = ""
try:
detecStream = params["detecStream"]
except:
detecStream = ""
stream = "%s.%s.%s.%s" % \
(cfg_sta.networkCode(), cfg_sta.stationCode(),
sensorLocation, detecStream)
nslc.add(stream)
count = 0
for param_name in sorted(params.keys()):
if self.param and self.param != param_name:
continue
out += " %s: %s\n" % (param_name, params[param_name])
count = count + 1
if not self.nslc and count > 0:
sys.stdout.write(out)
if self.nslc:
for stream in sorted(nslc):
print(stream, file=sys.stdout)
return True
try:
app = DumpCfg(len(sys.argv), sys.argv)
except:
sys.exit(1)
sys.exit(app())

@ -0,0 +1,75 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import sys
import seiscomp.client, seiscomp.datamodel, seiscomp.io
class ObjectDumper(seiscomp.client.Application):
def __init__(self):
seiscomp.client.Application.__init__(self, len(sys.argv), sys.argv)
self.setMessagingEnabled(True)
self.setDatabaseEnabled(True, False)
self.setMessagingUsername("")
def createCommandLineDescription(self):
seiscomp.client.Application.createCommandLineDescription(self)
self.commandline().addGroup("Dump")
self.commandline().addStringOption("Dump", "public-id,P", "publicID")
def loadEventParametersObject(self, publicID):
for tp in \
seiscomp.datamodel.Pick, seiscomp.datamodel.Amplitude, seiscomp.datamodel.Origin, \
seiscomp.datamodel.Event, seiscomp.datamodel.FocalMechanism, \
seiscomp.datamodel.Magnitude, seiscomp.datamodel.StationMagnitude:
obj = self.query().loadObject(tp.TypeInfo(), publicID)
obj = tp.Cast(obj)
if obj:
ep = seiscomp.datamodel.EventParameters()
ep.add(obj)
return ep
def loadInventoryObject(self, publicID):
for tp in \
seiscomp.datamodel.Network, seiscomp.datamodel.Station, seiscomp.datamodel.Sensor, \
seiscomp.datamodel.SensorLocation, seiscomp.datamodel.Stream:
obj = self.query().loadObject(tp.TypeInfo(), publicID)
obj = tp.Cast(obj)
if obj:
return obj
def run(self):
publicID = self.commandline().optionString("public-id")
obj = self.loadEventParametersObject(publicID)
if obj is None:
obj = self.loadInventoryObject(publicID)
if obj is None:
raise ValueError("unknown object '" + publicID + "'")
# dump formatted XML archive to stdout
ar = seiscomp.io.XMLArchive()
ar.setFormattedOutput(True)
ar.create("-")
ar.writeObject(obj)
ar.close()
return True
if __name__ == "__main__":
app = ObjectDumper()
app()

@ -0,0 +1,76 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import sys
import os
import seiscomp.client
import seiscomp.datamodel
import seiscomp.io
class EventParameterLog(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
self.setMessagingEnabled(True)
self.setDatabaseEnabled(False, False)
self.setMessagingUsername("")
self.setPrimaryMessagingGroup(
seiscomp.client.Protocol.LISTENER_GROUP)
self.addMessagingSubscription("EVENT")
self.addMessagingSubscription("LOCATION")
self.addMessagingSubscription("MAGNITUDE")
self.addMessagingSubscription("AMPLITUDE")
self.addMessagingSubscription("PICK")
self.setAutoApplyNotifierEnabled(True)
self.setInterpretNotifierEnabled(True)
# EventParameter object
self._eventParameters = seiscomp.datamodel.EventParameters()
def printUsage(self):
print('''Usage:
sceplog [options]
Receive event parameters from messaging and write them to stdout in SCML''')
seiscomp.client.Application.printUsage(self)
print('''Examples:
Execute sceplog with debug output
sceplog --debug
''')
def run(self):
if not seiscomp.client.Application.run(self):
return False
ar = seiscomp.io.XMLArchive()
ar.setFormattedOutput(True)
if ar.create("-"):
ar.writeObject(self._eventParameters)
ar.close()
# Hack to avoid the "close failed in file object destructor"
# exception
# print ""
sys.stdout.write("\n")
return True
app = EventParameterLog(len(sys.argv), sys.argv)
sys.exit(app())

Binary file not shown.

Binary file not shown.

@ -0,0 +1,850 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import sys
import os
import traceback
import re
import seiscomp.core
import seiscomp.client
import seiscomp.datamodel
import seiscomp.io
import seiscomp.logging
import seiscomp.system
def time2str(time):
"""
Convert a seiscomp.core.Time to a string
"""
return time.toString("%Y-%m-%d %H:%M:%S.%f000000")[:23]
def createDirectory(dir):
if os.access(dir, os.W_OK):
return True
try:
os.makedirs(dir)
return True
except:
return False
def originStatusToChar(org):
# Manual origin are always tagged as M
try:
if org.evaluationMode() == seiscomp.datamodel.MANUAL:
return 'M'
except:
pass
try:
if org.evaluationStatus() == seiscomp.datamodel.PRELIMINARY:
return 'P'
elif org.evaluationStatus() == seiscomp.datamodel.CONFIRMED or \
org.evaluationStatus() == seiscomp.datamodel.REVIEWED or \
org.evaluationStatus() == seiscomp.datamodel.FINAL:
return 'C'
elif org.evaluationStatus() == seiscomp.datamodel.REJECTED:
return 'X'
elif org.evaluationStatus() == seiscomp.datamodel.REPORTED:
return 'R'
except:
pass
return 'A'
class CachePopCallback(seiscomp.datamodel.CachePopCallback):
def __init__(self, target):
seiscomp.datamodel.CachePopCallback.__init__(self)
self.target = target
def handle(self, obj):
self.target.objectAboutToPop(obj)
class EventHistory(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
seiscomp.datamodel.Notifier.SetEnabled(False)
self.setMessagingEnabled(True)
self.setDatabaseEnabled(True, True)
self.setMessagingUsername("scevtlog")
self.setPrimaryMessagingGroup(
seiscomp.client.Protocol.LISTENER_GROUP)
self.addMessagingSubscription("EVENT")
self.addMessagingSubscription("LOCATION")
self.addMessagingSubscription("MAGNITUDE")
self.setAutoApplyNotifierEnabled(True)
self.setInterpretNotifierEnabled(True)
# Create a callback object that gets called when an object
# is going to be removed from the cache
self._popCallback = CachePopCallback(self)
# Create an object cache of half an hour
self._cache = seiscomp.datamodel.PublicObjectTimeSpanBuffer(
self.query(), seiscomp.core.TimeSpan(30.0*60.0))
self._cache.setPopCallback(self._popCallback)
# Event progress counter
self._eventProgress = dict()
# Event-Origin mapping
self._eventToOrg = dict()
self._orgToEvent = dict()
# Event-Magnitude mapping
self._eventToMag = dict()
self._magToEvent = dict()
self._directory = "@LOGDIR@/events"
self._format = "xml"
self._currentDirectory = ""
self._revisionFileExt = ".zip"
self._useGZIP = False
def createCommandLineDescription(self):
try:
self.commandline().addGroup("Storage")
self.commandline().addStringOption(
"Storage", "directory,o", "Specify the storage directory. "
"Default: @LOGDIR@/events.")
self.commandline().addStringOption("Storage", "format,f",
"Specify storage format (autoloc1, autoloc3, xml [default])")
except:
seiscomp.logging.warning(
"caught unexpected error %s" % sys.exc_info())
return True
def initConfiguration(self):
if not seiscomp.client.Application.initConfiguration(self):
return False
try:
self._directory = self.configGetString("directory")
except:
pass
try:
self._format = self.configGetString("format")
except:
pass
try:
if self.configGetBool("gzip"):
self._useGZIP = True
self._revisionFileExt = ".gz"
except:
pass
return True
def printUsage(self):
print('''Usage:
scevtlog [options]
Save event history into files''')
seiscomp.client.Application.printUsage(self)
print('''Examples:
Execute on command line with debug output
scevtlog --debug
''')
def init(self):
if not seiscomp.client.Application.init(self):
return False
try:
self._directory = self.commandline().optionString("directory")
except:
pass
try:
self._format = self.commandline().optionString("format")
except:
pass
if self._format != "autoloc1" and self._format != "autoloc3" and self._format != "xml":
self._format = "xml"
try:
if self._directory[-1] != "/":
self._directory = self._directory + "/"
except:
pass
if self._directory:
self._directory = seiscomp.system.Environment.Instance().absolutePath(self._directory)
sys.stderr.write("Logging events to %s\n" % self._directory)
self._cache.setDatabaseArchive(self.query())
return True
# def run(self):
# obj = self._cache.get(seiscomp.datamodel.Magnitude, "or080221153929#16#netMag.mb")
# self.updateObject(obj)
# return True
def done(self):
seiscomp.client.Application.done(self)
self._cache.setDatabaseArchive(None)
def printEvent(self, evt, newEvent):
if self._format != "xml":
self.printEventProcAlert(evt, newEvent)
else:
self.printEventXML(evt, newEvent)
self.advanceEventProgress(evt.publicID())
def getSummary(self, time, org, mag):
strTime = time.toString("%Y-%m-%d %H:%M:%S")
summary = [strTime, "", "", "", "", "", "", "", "", ""]
if org:
tim = org.time().value()
latency = time - tim
summary[1] = "%5d.%02d" % (
latency.seconds() / 60, (latency.seconds() % 60) * 100 / 60)
lat = org.latitude().value()
lon = org.longitude().value()
dep = "%7s" % "---"
try:
dep = "%7.0f" % org.depth().value()
summary[4] = dep
except:
summary[4] = "%7s" % ""
phases = "%5s" % "---"
try:
phases = "%5d" % org.quality().usedPhaseCount()
summary[5] = phases
except:
summary[5] = "%5s" % ""
summary[2] = "%7.2f" % lat
summary[3] = "%7.2f" % lon
try:
summary[9] = originStatusToChar(org)
except:
summary[9] = "-"
if mag:
summary[6] = "%12s" % mag.type()
summary[7] = "%5.2f" % mag.magnitude().value()
try:
summary[8] = "%5d" % mag.stationCount()
except:
summary[8] = " "
else:
summary[6] = "%12s" % ""
summary[7] = " "
summary[8] = " "
return summary
def printEventProcAlert(self, evt, newEvent):
now = seiscomp.core.Time.GMT()
org = self._cache.get(seiscomp.datamodel.Origin,
evt.preferredOriginID())
prefmag = self._cache.get(
seiscomp.datamodel.Magnitude, evt.preferredMagnitudeID())
summary = self.getSummary(now, org, prefmag)
# Load arrivals
if org.arrivalCount() == 0:
self.query().loadArrivals(org)
# Load station magnitudes
if org.stationMagnitudeCount() == 0:
self.query().loadStationMagnitudes(org)
# Load magnitudes
if org.magnitudeCount() == 0:
self.query().loadMagnitudes(org)
picks = []
amps = []
if org:
narr = org.arrivalCount()
for i in range(narr):
picks.append(self._cache.get(
seiscomp.datamodel.Pick, org.arrival(i).pickID()))
nstamags = org.stationMagnitudeCount()
for i in range(nstamags):
amps.append(self._cache.get(
seiscomp.datamodel.Amplitude, org.stationMagnitude(i).amplitudeID()))
netmag = {}
nmag = org.magnitudeCount()
bulletin = seiscomp.scbulletin.Bulletin(None, self._format)
try:
txt = bulletin.printEvent(evt)
except:
txt = ""
if self._directory is None:
sys.stdout.write("%s" % ("#<\n" + txt + "#>\n"))
sys.stdout.flush()
else:
# Use created time to look up the proper directory
try:
arNow = evt.creationInfo().creationTime().get()
# Otherwise use now (in case that event.created has not been set
# which is always valid within the SC3 distribution
except:
arNow = now.get()
seiscomp.logging.error("directory is " + self._directory + "/".join(
["%.2d" % i for i in arNow[1:4]]) + "/" + evt.publicID() + "/")
directory = self._directory + \
"/".join(["%.2d" % i for i in arNow[1:4]]) + \
"/" + evt.publicID() + "/"
if directory != self._currentDirectory:
if createDirectory(directory) == False:
seiscomp.logging.error(
"Unable to create directory %s" % directory)
return
self._currentDirectory = directory
self.writeLog(self._currentDirectory + self.convertID(evt.publicID()) +
"." + ("%06d" % self.eventProgress(evt.publicID(), directory)), txt, "w")
self.writeLog(self._currentDirectory +
self.convertID(evt.publicID()) + ".last", txt, "w")
self.writeLog(self._directory + "last", txt, "w")
self.writeLog(self._currentDirectory + self.convertID(evt.publicID()) + ".summary",
"|".join(summary), "a",
"# Layout: Timestamp, +OT (minutes, decimal), Latitude, Longitude, Depth, PhaseCount, MagType, Magnitude, MagCount")
seiscomp.logging.info("cache size = %d" % self._cache.size())
def printEventXML(self, evt, newEvent):
now = seiscomp.core.Time.GMT()
# Load comments
if evt.commentCount() == 0:
self.query().loadComments(evt)
# Load origin references
if evt.originReferenceCount() == 0:
self.query().loadOriginReferences(evt)
# Load event descriptions
if evt.eventDescriptionCount() == 0:
self.query().loadEventDescriptions(evt)
org = self._cache.get(seiscomp.datamodel.Origin,
evt.preferredOriginID())
if evt.preferredFocalMechanismID():
fm = self._cache.get(
seiscomp.datamodel.FocalMechanism, evt.preferredFocalMechanismID())
else:
fm = None
# Load comments
if org.commentCount() == 0:
self.query().loadComments(org)
# Load arrivals
if org.arrivalCount() == 0:
self.query().loadArrivals(org)
prefmag = self._cache.get(
seiscomp.datamodel.Magnitude, evt.preferredMagnitudeID())
wasEnabled = seiscomp.datamodel.PublicObject.IsRegistrationEnabled()
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
ep = seiscomp.datamodel.EventParameters()
evt_cloned = seiscomp.datamodel.Event.Cast(evt.clone())
ep.add(evt_cloned)
summary = self.getSummary(now, org, prefmag)
if fm:
ep.add(fm)
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(wasEnabled)
# Load focal mechainsm references
if evt.focalMechanismReferenceCount() == 0:
self.query().loadFocalMechanismReferences(evt)
# Load moment tensors
if fm.momentTensorCount() == 0:
self.query().loadMomentTensors(fm)
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
# Copy focal mechanism reference
fm_ref = evt.focalMechanismReference(
seiscomp.datamodel.FocalMechanismReferenceIndex(fm.publicID()))
if fm_ref:
fm_ref_cloned = seiscomp.datamodel.FocalMechanismReference.Cast(
fm_ref.clone())
if fm_ref_cloned is None:
fm_ref_cloned = seiscomp.datamodel.FocalMechanismReference(
fm.publicID())
evt_cloned.add(fm_ref_cloned)
nmt = fm.momentTensorCount()
for i in range(nmt):
mt = fm.momentTensor(i)
if not mt.derivedOriginID():
continue
# Origin already added
if ep.findOrigin(mt.derivedOriginID()) is not None:
continue
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(
wasEnabled)
derivedOrigin = self._cache.get(
seiscomp.datamodel.Origin, mt.derivedOriginID())
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
if derivedOrigin is None:
seiscomp.logging.warning(
"derived origin for MT %s not found" % mt.derivedOriginID())
continue
# Origin has been read from database -> read all childs
if not self._cache.cached():
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(
wasEnabled)
self.query().load(derivedOrigin)
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(
False)
# Add it to the event parameters
ep.add(derivedOrigin)
if org:
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(wasEnabled)
# Load magnitudes
if org.magnitudeCount() == 0:
self.query().loadMagnitudes(org)
if org.stationMagnitudeCount() == 0:
self.query().loadStationMagnitudes(org)
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
# Copy event comments
ncmts = evt.commentCount()
for i in range(ncmts):
cmt_cloned = seiscomp.datamodel.Comment.Cast(
evt.comment(i).clone())
evt_cloned.add(cmt_cloned)
# Copy origin references
org_ref = evt.originReference(
seiscomp.datamodel.OriginReferenceIndex(org.publicID()))
if org_ref:
org_ref_cloned = seiscomp.datamodel.OriginReference.Cast(
org_ref.clone())
if org_ref_cloned is None:
org_ref_cloned = seiscomp.datamodel.OriginReference(
org.publicID())
evt_cloned.add(org_ref_cloned)
# Copy event descriptions
for i in range(evt.eventDescriptionCount()):
ed_cloned = seiscomp.datamodel.EventDescription.Cast(
evt.eventDescription(i).clone())
evt_cloned.add(ed_cloned)
org_cloned = seiscomp.datamodel.Origin.Cast(org.clone())
ep.add(org_cloned)
# Copy origin comments
ncmts = org.commentCount()
for i in range(ncmts):
cmt_cloned = seiscomp.datamodel.Comment.Cast(
org.comment(i).clone())
org_cloned.add(cmt_cloned)
# Copy arrivals
narr = org.arrivalCount()
for i in range(narr):
arr_cloned = seiscomp.datamodel.Arrival.Cast(
org.arrival(i).clone())
org_cloned.add(arr_cloned)
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(
wasEnabled)
pick = self._cache.get(
seiscomp.datamodel.Pick, arr_cloned.pickID())
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
if pick:
pick_cloned = seiscomp.datamodel.Pick.Cast(pick.clone())
ep.add(pick_cloned)
# Copy network magnitudes
nmag = org.magnitudeCount()
for i in range(nmag):
mag = org.magnitude(i)
mag_cloned = seiscomp.datamodel.Magnitude.Cast(mag.clone())
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(
wasEnabled)
if mag.stationMagnitudeContributionCount() == 0:
self.query().loadStationMagnitudeContributions(mag)
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
# Copy magnitude references
nmagref = mag.stationMagnitudeContributionCount()
for j in range(nmagref):
mag_ref_cloned = seiscomp.datamodel.StationMagnitudeContribution.Cast(
mag.stationMagnitudeContribution(j).clone())
mag_cloned.add(mag_ref_cloned)
org_cloned.add(mag_cloned)
# Copy station magnitudes and station amplitudes
smag = org.stationMagnitudeCount()
amp_map = dict()
for i in range(smag):
mag_cloned = seiscomp.datamodel.StationMagnitude.Cast(
org.stationMagnitude(i).clone())
org_cloned.add(mag_cloned)
if (mag_cloned.amplitudeID() in amp_map) == False:
amp_map[mag_cloned.amplitudeID()] = True
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(
wasEnabled)
amp = self._cache.get(
seiscomp.datamodel.Amplitude, mag_cloned.amplitudeID())
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(
False)
if amp:
amp_cloned = seiscomp.datamodel.Amplitude.Cast(
amp.clone())
ep.add(amp_cloned)
seiscomp.datamodel.PublicObject.SetRegistrationEnabled(wasEnabled)
# archive.create(event.publicID() + )
ar = seiscomp.io.XMLArchive()
ar.setFormattedOutput(True)
if self._directory is None:
sys.stdout.write("#<\n")
ar.create("-")
ar.writeObject(ep)
ar.close()
sys.stdout.write("#>\n")
sys.stdout.flush()
else:
# Use created time to look up the proper directory
try:
arNow = evt.creationInfo().creationTime().get()
# Otherwise use now (in case that event.created has not been set
# which is always valid within the SC3 distribution
except:
arNow = now.get()
directory = self._directory + \
"/".join(["%.2d" % i for i in arNow[1:4]]) + \
"/" + evt.publicID() + "/"
if directory != self._currentDirectory:
if createDirectory(directory) == False:
seiscomp.logging.error(
"Unable to create directory %s" % directory)
return
self._currentDirectory = directory
# self.writeLog(self._currentDirectory + evt.publicID(), "#<\n" + txt + "#>\n")
#self.writeLog(self._currentDirectory + evt.publicID() + ".last", txt, "w")
ar.create(self._currentDirectory + self.convertID(evt.publicID()) + "." + ("%06d" %
self.eventProgress(evt.publicID(), directory)) + ".xml" + self._revisionFileExt)
ar.setCompression(True)
if self._useGZIP:
ar.setCompressionMethod(seiscomp.io.XMLArchive.GZIP)
ar.writeObject(ep)
ar.close()
# Write last file to root
ar.create(self._directory + "last.xml" + self._revisionFileExt)
ar.setCompression(True)
if self._useGZIP:
ar.setCompressionMethod(seiscomp.io.XMLArchive.GZIP)
ar.writeObject(ep)
ar.close()
# Write last xml
ar.create(self._currentDirectory +
self.convertID(evt.publicID()) + ".last.xml")
ar.setCompression(False)
ar.writeObject(ep)
ar.close()
self.writeLog(self._currentDirectory + self.convertID(evt.publicID()) + ".summary",
"|".join(summary), "a",
"# Layout: Timestamp, +OT (minutes, decimal), Latitude, Longitude, Depth, PhaseCount, MagType, Magnitude, MagCount")
del ep
def convertID(self, id):
'''Converts an ID containing slashes to one without slashes'''
p = re.compile('/')
return p.sub('_', id)
def writeLog(self, file, text, mode="a", header=None):
of = open(file, mode)
if of:
if of.tell() == 0 and not header is None:
of.write(header+"\n")
of.write(text+"\n")
of.close()
else:
seiscomp.logging.error("Unable to write file: %s" % file)
def objectAboutToPop(self, obj):
try:
evt = seiscomp.datamodel.Event.Cast(obj)
if evt:
try:
self._orgToEvent.pop(evt.preferredOriginID())
self._eventToOrg.pop(evt.publicID())
self._magToEvent.pop(evt.preferredMagnitudeID())
self._eventToMag.pop(evt.publicID())
self._eventProgress.pop(evt.publicID())
return
except:
pass
org = seiscomp.datamodel.Origin.Cast(obj)
if org:
try:
self._orgToEvent.pop(org.publicID())
except:
pass
return
mag = seiscomp.datamodel.Magnitude.Cast(obj)
if mag:
try:
self._magToEvent.pop(mag.publicID())
except:
pass
return
except:
info = traceback.format_exception(*sys.exc_info())
for i in info:
sys.stderr.write(i)
sys.exit(-1)
def eventProgress(self, evtID, directory):
# The progress is already stored
if evtID in self._eventProgress:
return self._eventProgress[evtID]
# Find the maximum file counter
maxid = -1
files = os.listdir(directory)
for file in files:
if os.path.isfile(directory + file) == False:
continue
fid = file[len(evtID + '.'):len(file)]
sep = fid.find('.')
if sep == -1:
sep = len(fid)
fid = fid[0:sep]
try:
nid = int(fid)
except:
continue
if nid > maxid:
maxid = nid
maxid = maxid + 1
self._eventProgress[evtID] = maxid
return maxid
def advanceEventProgress(self, evtID):
try:
self._eventProgress[evtID] = self._eventProgress[evtID] + 1
except:
pass
def addObject(self, parentID, object):
try:
obj = seiscomp.datamodel.Event.Cast(object)
if obj:
self._cache.feed(obj)
self._eventProgress[obj.publicID()] = 0
self.printEvent(obj, True)
self.updateCache(obj)
return
# New Magnitudes or Origins are not important for
# the history update but we feed it into the cache to
# access them faster later on in case they will become
# preferred entities
obj = seiscomp.datamodel.Magnitude.Cast(object)
if obj:
self._cache.feed(obj)
return
obj = seiscomp.datamodel.Origin.Cast(object)
if obj:
self._cache.feed(obj)
return
obj = seiscomp.datamodel.Pick.Cast(object)
if obj:
self._cache.feed(obj)
return
obj = seiscomp.datamodel.Amplitude.Cast(object)
if obj:
self._cache.feed(obj)
return
except:
info = traceback.format_exception(*sys.exc_info())
for i in info:
sys.stderr.write(i)
sys.exit(-1)
def updateObject(self, parentID, object):
try:
obj = seiscomp.datamodel.Event.Cast(object)
if obj:
self._cache.feed(obj)
self.printEvent(obj, False)
self.updateCache(obj)
return
# Updates of a Magnitude are only imported when it is
# the preferred one.
obj = seiscomp.datamodel.Magnitude.Cast(object)
if obj:
try:
evtID = self._magToEvent[obj.publicID()]
if evtID:
self._cache.feed(obj)
evt = self._cache.get(seiscomp.datamodel.Event, evtID)
if evt:
self.printEvent(evt, False)
else:
sys.stderr.write("Unable to fetch event for ID '%s' while update of magnitude '%s'\n" % (
evtID, obj.publicID()))
else:
# Magnitude has not been associated to an event yet
pass
except:
# Search the corresponding event from the database
evt = self.query().getEventByPreferredMagnitudeID(obj.publicID())
# Associate the event (even if None) with the magnitude ID
if evt:
self._magToEvent[obj.publicID()] = evt.publicID()
self._cache.feed(obj)
self.printEvent(evt, False)
else:
self._magToEvent[obj.publicID()] = None
return
# Usually we do not update origins. To have it complete,
# this case will be supported as well
obj = seiscomp.datamodel.Origin.Cast(object)
if obj:
try:
evtID = self._orgToEvent[obj.publicID()]
if evtID:
self._cache.feed(obj)
evt = self._cache.get(seiscomp.datamodel.Event, evtID)
if evt:
self.printEvent(evt, False)
else:
sys.stderr.write("Unable to fetch event for ID '%s' while update of origin '%s'\n" % (
evtID, obj.publicID()))
else:
# Origin has not been associated to an event yet
pass
except:
# Search the corresponding event from the database
evt = self.query().getEvent(obj.publicID())
if evt:
if evt.preferredOriginID() != obj.publicID():
evt = None
# Associate the event (even if None) with the origin ID
if evt:
self._orgToEvent[obj.publicID()] = evt.publicID()
self._cache.feed(obj)
self.printEvent(evt, False)
else:
self._orgToEvent[obj.publicID()] = None
return
return
except:
info = traceback.format_exception(*sys.exc_info())
for i in info:
sys.stderr.write(i)
sys.exit(-1)
def updateCache(self, evt):
# Event-Origin update
try:
orgID = self._eventToOrg[evt.publicID()]
if orgID != evt.preferredOriginID():
self._orgToEvent.pop(orgID)
except:
# origin not yet registered
pass
# Bind the current preferred origin ID to the event and
# vice versa
self._orgToEvent[evt.preferredOriginID()] = evt.publicID()
self._eventToOrg[evt.publicID()] = evt.preferredOriginID()
# Event-Magnitude update
try:
magID = self._eventToMag[evt.publicID()]
if magID != evt.preferredMagnitudeID():
self._magToEvent.pop(magID)
except:
# not yet registered
pass
# Bind the current preferred magnitude ID to the event and
# vice versa
self._magToEvent[evt.preferredMagnitudeID()] = evt.publicID()
self._eventToMag[evt.publicID()] = evt.preferredMagnitudeID()
app = EventHistory(len(sys.argv), sys.argv)
sys.exit(app())

@ -0,0 +1,197 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import sys
import seiscomp.core
import seiscomp.client
import seiscomp.datamodel
import seiscomp.logging
def _parseTime(timestring):
t = seiscomp.core.Time()
if t.fromString(timestring, "%F %T"):
return t
if t.fromString(timestring, "%FT%T"):
return t
if t.fromString(timestring, "%FT%TZ"):
return t
return None
class EventList(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
self.setMessagingEnabled(False)
self.setDatabaseEnabled(True, False)
self.setDaemonEnabled(False)
self._startTime = None
self._endTime = None
self.hours = None
self._delimiter = None
self._modifiedAfterTime = None
self._preferredOrigin = False
def createCommandLineDescription(self):
self.commandline().addGroup("Events")
self.commandline().addStringOption("Events", "begin",
"Specify the lower bound of the "
"time interval.")
self.commandline().addStringOption("Events", "end",
"Specify the upper bound of the "
"time interval.")
self.commandline().addStringOption("Events", "hours",
"Start searching given hours before"
" now. If set, --begin and --end "
"are ignored.")
self.commandline().addStringOption("Events", "modified-after",
"Select events modified after the "
"specified time.")
self.commandline().addGroup("Output")
self.commandline().addStringOption("Output", "delimiter,D",
"Specify the delimiter of the "
"resulting event IDs. "
"Default: '\\n')")
self.commandline().addOption("Output", "preferred-origin,p",
"Print the ID of the preferred origin "
"along with the event ID.")
return True
def init(self):
if not seiscomp.client.Application.init(self):
return False
try:
self.hours = float(self.commandline().optionString("hours"))
except RuntimeError:
pass
end = "2500-01-01T00:00:00Z"
if self.hours is None:
try:
start = self.commandline().optionString("begin")
except RuntimeError:
start = "1900-01-01T00:00:00Z"
self._startTime = _parseTime(start)
if self._startTime is None:
seiscomp.logging.error("Wrong 'begin' format '%s'" % start)
return False
seiscomp.logging.debug("Setting start to %s"
% self._startTime.toString("%FT%TZ"))
try:
end = self.commandline().optionString("end")
except RuntimeError:
pass
self._endTime = _parseTime(end)
if self._endTime is None:
seiscomp.logging.error("Wrong 'end' format '%s'" % end)
return False
seiscomp.logging.debug("Setting end to %s"
% self._endTime.toString("%FT%TZ"))
else:
seiscomp.logging.debug("Time window set by hours option: ignoring "
"all other time parameters")
secs = self.hours*3600
maxSecs = 596523 * 3600
if secs > maxSecs:
seiscomp.logging.error("Maximum hours exceeeded. Maximum is %i" % (maxSecs / 3600))
return False
self._startTime = seiscomp.core.Time.UTC() - seiscomp.core.TimeSpan(secs)
self._endTime = _parseTime(end)
try:
self._delimiter = self.commandline().optionString("delimiter")
except RuntimeError:
self._delimiter = "\n"
try:
modifiedAfter = self.commandline().optionString("modified-after")
self._modifiedAfterTime = _parseTime(modifiedAfter)
if self._modifiedAfterTime is None:
seiscomp.logging.error("Wrong 'modified-after' format '%s'"
% modifiedAfter)
return False
seiscomp.logging.debug(
"Setting 'modified-after' time to %s" %
self._modifiedAfterTime.toString("%FT%TZ"))
except RuntimeError:
pass
try:
self._preferredOrigin = self.commandline().hasOption("preferred-origin")
except RuntimeError:
pass
return True
def printUsage(self):
print('''Usage:
scevtls [options]
List event IDs available in a given time range and print to stdout.''')
seiscomp.client.Application.printUsage(self)
print('''Examples:
Print all event IDs from year 2022 and thereafter
scevtls -d mysql://sysop:sysop@localhost/seiscomp --begin "2022-01-01 00:00:00"
''')
def run(self):
out = []
seiscomp.logging.debug("Search interval: %s - %s" %
(self._startTime, self._endTime))
for obj in self.query().getEvents(self._startTime, self._endTime):
evt = seiscomp.datamodel.Event.Cast(obj)
if not evt:
continue
if self._modifiedAfterTime is not None:
try:
if evt.creationInfo().modificationTime() < self._modifiedAfterTime:
continue
except ValueError:
continue
outputString = evt.publicID()
if self._preferredOrigin:
try:
outputString += " " + evt.preferredOriginID()
except ValueError:
outputString += " none"
out.append(outputString)
sys.stdout.write("%s\n" % self._delimiter.join(out))
return True
def main():
app = EventList(len(sys.argv), sys.argv)
app()
if __name__ == "__main__":
main()

@ -0,0 +1,432 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
from __future__ import absolute_import, division, print_function
import sys
from seiscomp import client, core, datamodel, io
class EventStreams(client.Application):
def __init__(self, argc, argv):
client.Application.__init__(self, argc, argv)
self.setMessagingEnabled(False)
self.setDatabaseEnabled(True, False)
self.setDaemonEnabled(False)
self.eventID = None
self.inputFile = None
self.inputFormat = "xml"
self.margin = [300]
self.allNetworks = True
self.allStations = True
self.allLocations = True
self.allStreams = True
self.allComponents = True
# filter
self.network = None
self.station = None
self.streams = []
# output format
self.caps = False
self.fdsnws = False
def createCommandLineDescription(self):
self.commandline().addGroup("Input")
self.commandline().addStringOption(
"Input", "input,i",
"read event from XML file instead of database. Use '-' to read "
"from stdin.")
self.commandline().addStringOption(
"Input", "format,f",
"input format to use (xml [default], zxml (zipped xml), binary). "
"Only relevant with --input.")
self.commandline().addGroup("Dump")
self.commandline().addStringOption("Dump", "event,E", "event id")
self.commandline().addStringOption(
"Dump", "margin,m",
"time margin around the picked time window, default is 300. Added "
"before the first and after the last pick, respectively. Use 2 "
"comma-separted values (before,after) for asymmetric margins, e.g. "
"-m 120,300.")
self.commandline().addStringOption(
"Dump", "streams,S",
"comma separated list of streams per station to add, e.g. BH,SH,HH")
self.commandline().addOption(
"Dump", "all-streams",
"dump all streams. If unused, just streams with picks are dumped.")
self.commandline().addIntOption(
"Dump", "all-components,C",
"all components or just the picked ones (0). Default is 1")
self.commandline().addIntOption(
"Dump", "all-locations,L",
"all locations or just the picked ones (0). Default is 1")
self.commandline().addOption(
"Dump", "all-stations",
"dump all stations from the same network. If unused, just stations "
"with picks are dumped.")
self.commandline().addOption(
"Dump", "all-networks",
"dump all networks. If unused, just networks with picks are dumped."
" This option implies all-stations, all-locations, all-streams, "
"all-components and will only provide the time window.")
self.commandline().addOption(
"Dump", "resolve-wildcards,R",
"if all components are used, use inventory to resolve stream "
"components instead of using '?' (important when Arclink should be "
"used)")
self.commandline().addStringOption(
"Dump", "net-sta", "Filter streams by network code or network and "
"station code. Format: NET or NET.STA")
self.commandline().addOption(
"Dump", "caps",
"dump in capstool format (Common Acquisition Protocol Server by "
"gempa GmbH)")
self.commandline().addOption(
"Dump", "fdsnws",
"dump in FDSN dataselect webservice POST format")
return True
def validateParameters(self):
if not client.Application.validateParameters(self):
return False
if self.commandline().hasOption("resolve-wildcards"):
self.setLoadStationsEnabled(True)
try:
self.inputFile = self.commandline().optionString("input")
self.setDatabaseEnabled(False, False)
except BaseException:
pass
return True
def init(self):
if not client.Application.init(self):
return False
try:
self.inputFormat = self.commandline().optionString("format")
except BaseException:
pass
try:
self.eventID = self.commandline().optionString("event")
except BaseException:
if not self.inputFile:
raise ValueError("An eventID is mandatory if no input file is "
"specified")
try:
self.margin = self.commandline().optionString("margin").split(",")
except BaseException:
pass
try:
self.streams = self.commandline().optionString("streams").split(",")
except BaseException:
pass
try:
self.allComponents = self.commandline().optionInt("all-components") != 0
except BaseException:
pass
try:
self.allLocations = self.commandline().optionInt("all-locations") != 0
except BaseException:
pass
self.allStreams = self.commandline().hasOption("all-streams")
self.allStations = self.commandline().hasOption("all-stations")
self.allNetworks = self.commandline().hasOption("all-networks")
try:
networkStation = self.commandline().optionString("net-sta")
except RuntimeError:
networkStation = None
if networkStation:
try:
self.network = networkStation.split('.')[0]
except IndexError:
print("Error in network code '{}': Use '--net-sta' with "
"format NET or NET.STA".format(networkStation), file=sys.stderr)
return False
try:
self.station = networkStation.split('.')[1]
except IndexError:
pass
self.caps = self.commandline().hasOption("caps")
self.fdsnws = self.commandline().hasOption("fdsnws")
return True
def printUsage(self):
print('''Usage:
scevtstreams [options]
Extract stream information and time windows from an event''')
client.Application.printUsage(self)
print('''Examples:
Get the time windows for an event in the database:
scevtstreams -E gfz2012abcd -d mysql://sysop:sysop@localhost/seiscomp
Create lists compatible with fdsnws:
scevtstreams -E gfz2012abcd -i event.xml -m 120,500 --fdsnws
''')
def run(self):
resolveWildcards = self.commandline().hasOption("resolve-wildcards")
picks = []
# read picks from input file
if self.inputFile:
picks = self.readXML()
if not picks:
raise ValueError("Could not find picks in input file")
# read picks from database
else:
for obj in self.query().getEventPicks(self.eventID):
pick = datamodel.Pick.Cast(obj)
if pick is None:
continue
picks.append(pick)
if not picks:
raise ValueError("Could not find picks for event {} in "
"database".format(self.eventID))
# filter picks
pickFiltered = []
if self.network:
for pick in picks:
if pick.waveformID().networkCode() != self.network:
continue
if self.station and self.station != pick.waveformID().stationCode():
continue
pickFiltered.append(pick)
picks = pickFiltered
if not picks:
raise ValueError("All picks filtered out")
# calculate minimum and maximum pick time
minTime = None
maxTime = None
for pick in picks:
if minTime is None or minTime > pick.time().value():
minTime = pick.time().value()
if maxTime is None or maxTime < pick.time().value():
maxTime = pick.time().value()
# add time margin(s), no need for None check since pick time is
# mandatory and at least on pick exists
minTime = minTime - core.TimeSpan(float(self.margin[0]))
maxTime = maxTime + core.TimeSpan(float(self.margin[-1]))
# convert times to string dependend on requested output format
if self.caps:
timeFMT = "%Y,%m,%d,%H,%M,%S"
elif self.fdsnws:
timeFMT = "%FT%T"
else:
timeFMT = "%F %T"
minTime = minTime.toString(timeFMT)
maxTime = maxTime.toString(timeFMT)
inv = client.Inventory.Instance().inventory()
lines = set()
for pick in picks:
net = pick.waveformID().networkCode()
station = pick.waveformID().stationCode()
loc = pick.waveformID().locationCode()
streams = [pick.waveformID().channelCode()]
rawStream = streams[0][:2]
if self.allComponents:
if resolveWildcards:
iloc = datamodel.getSensorLocation(inv, pick)
if iloc:
tc = datamodel.ThreeComponents()
datamodel.getThreeComponents(
tc, iloc, rawStream, pick.time().value())
streams = []
if tc.vertical():
streams.append(tc.vertical().code())
if tc.firstHorizontal():
streams.append(tc.firstHorizontal().code())
if tc.secondHorizontal():
streams.append(tc.secondHorizontal().code())
else:
streams = [rawStream + "?"]
if self.allLocations:
loc = "*"
if self.allStations:
station = "*"
if self.allNetworks:
net = "*"
station = "*"
loc = "*"
# FDSNWS requires empty location to be encoded by 2 dashes
if not loc and self.fdsnws:
loc = "--"
# line format
if self.caps:
lineFMT = "{0} {1} {2} {3} {4} {5}"
elif self.fdsnws:
lineFMT = "{2} {3} {4} {5} {0} {1}"
else:
lineFMT = "{0};{1};{2}.{3}.{4}.{5}"
for s in streams:
if self.allStreams or self.allNetworks:
s = "*"
lines.add(lineFMT.format(
minTime, maxTime, net, station, loc, s))
for s in self.streams:
if s == rawStream:
continue
if self.allStreams or self.allNetworks:
s = "*"
lines.add(lineFMT.format(
minTime, maxTime, net, station, loc, s + streams[0][2]))
for line in sorted(lines):
print(line, file=sys.stdout)
return True
def readXML(self):
if self.inputFormat == "xml":
ar = io.XMLArchive()
elif self.inputFormat == "zxml":
ar = io.XMLArchive()
ar.setCompression(True)
elif self.inputFormat == "binary":
ar = io.VBinaryArchive()
else:
raise TypeError("unknown input format '{}'".format(
self.inputFormat))
if not ar.open(self.inputFile):
raise IOError("unable to open input file")
obj = ar.readObject()
if obj is None:
raise TypeError("invalid input file format")
ep = datamodel.EventParameters.Cast(obj)
if ep is None:
raise ValueError("no event parameters found in input file")
# we require at least one origin which references to picks via arrivals
if ep.originCount() == 0:
raise ValueError("no origin found in input file")
originIDs = []
# search for a specific event id
if self.eventID:
ev = datamodel.Event.Find(self.eventID)
if ev:
originIDs = [ev.originReference(i).originID() \
for i in range(ev.originReferenceCount())]
else:
raise ValueError("event id {} not found in input file".format(
self.eventID))
# use first event/origin if no id was specified
else:
# no event, use first available origin
if ep.eventCount() == 0:
if ep.originCount() > 1:
print("WARNING: Input file contains no event but more than "
"1 origin. Considering only first origin",
file=sys.stderr)
originIDs.append(ep.origin(0).publicID())
# use origin references of first available event
else:
if ep.eventCount() > 1:
print("WARNING: Input file contains more than 1 event. "
"Considering only first event", file=sys.stderr)
ev = ep.event(0)
originIDs = [ev.originReference(i).originID() \
for i in range(ev.originReferenceCount())]
# collect pickIDs
pickIDs = set()
for oID in originIDs:
o = datamodel.Origin.Find(oID)
if o is None:
continue
for i in range(o.arrivalCount()):
pickIDs.add(o.arrival(i).pickID())
# lookup picks
picks = []
for pickID in pickIDs:
pick = datamodel.Pick.Find(pickID)
if pick:
picks.append(pick)
return picks
if __name__ == '__main__':
try:
app = EventStreams(len(sys.argv), sys.argv)
sys.exit(app())
except (ValueError, TypeError) as e:
print("ERROR: {}".format(e), file=sys.stderr)
sys.exit(1)

@ -0,0 +1,38 @@
#!/bin/bash
# Initializes a GIT repository in $SEISCOMP_ROOT and adds important
# configuration files from 'etc' and 'share' directory
#
# Author: Stephan Herrnkind <herrnkind@gempa.de>
# search for SeisComP path
if [ x"$SEISCOMP_ROOT" = x ]; then
echo "SEISCOMP_ROOT not set"
exit 1
fi
# search git binary
which git > /dev/null
if [ $? -ne 0 ]; then
echo "git binary not found"
exit 2
fi
cd $SEISCOMP_ROOT || exit 3
# initialize git if necessary
[ -d .git ] || git rev-parse --git-dir > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "GIT repository in $SEISCOMP_ROOT already initialized"
else
git init || exit 4
fi
# add files
git add etc
find share -type f -regex \
".*\.\(bna\|cfg\|conf\|htaccess\|kml\|py\|sh\|tpl\|tvel\|txt\|xml\)" \
-execdir git add {} +
echo "files added to GIT, use 'git status' to get an overview and " \
"'git commit' to commit them"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,84 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import sys
import getopt
import seiscomp.io
import seiscomp.datamodel
usage = """scml2inv [options] input output=stdout
Options:
-h [ --help ] Produce help message
-f Enable formatted XML output
"""
def main(argv):
formatted = False
# parse command line options
try:
opts, args = getopt.getopt(argv[1:], "hf", ["help"])
except getopt.error as msg:
sys.stderr.write("%s\n" % msg)
sys.stderr.write("for help use --help\n")
return 1
for o, a in opts:
if o in ["-h", "--help"]:
sys.stderr.write("%s\n" % usage)
return 1
elif o in ["-f"]:
formatted = True
argv = args
if len(argv) < 1:
sys.stderr.write("Missing input file\n")
return 1
ar = seiscomp.io.XMLArchive()
if not ar.open(argv[0]):
sys.stderr.write("Unable to parse input file: %s\n" % argv[0])
return 2
obj = ar.readObject()
ar.close()
if obj is None:
sys.stderr.write("Empty document in %s\n" % argv[0])
return 3
inv = seiscomp.datamodel.Inventory.Cast(obj)
if inv is None:
sys.stderr.write("No inventory found in %s\n" % argv[0])
return 4
if len(argv) < 2:
output_file = "-"
else:
output_file = argv[1]
ar.create(output_file)
ar.setFormattedOutput(formatted)
ar.writeObject(inv)
ar.close()
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))

Binary file not shown.

@ -0,0 +1,416 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
from __future__ import absolute_import, division, print_function
import sys
import os
import re
import argparse
import seiscomp.core
import seiscomp.io
class MyArgumentParser(argparse.ArgumentParser):
def format_epilog(self):
return self.epilog
def str2time(timestring):
"""
Liberally accept many time string formats and convert them to a
seiscomp.core.Time
"""
timestring = timestring.strip()
for c in ["-", "/", ":", "T", "Z"]:
timestring = timestring.replace(c, " ")
timestring = timestring.split()
assert 3 <= len(timestring) <= 6
timestring.extend((6 - len(timestring)) * ["0"])
timestring = " ".join(timestring)
timeFormat = "%Y %m %d %H %M %S"
if timestring.find(".") != -1:
timeFormat += ".%f"
time = seiscomp.core.Time()
time.fromString(timestring, timeFormat)
return time
def time2str(time):
"""
Convert a seiscomp.core.Time to a string
"""
return time.toString("%Y-%m-%d %H:%M:%S.%f000000")[:23]
def recordInput(filename=None, datatype=seiscomp.core.Array.INT):
"""
Simple Record iterator that reads from a file (to be specified by
filename) or -- if no filename was specified -- reads from standard input
"""
stream = seiscomp.io.RecordStream.Create("file")
if not stream:
raise IOError("failed to create a RecordStream")
if not filename:
filename = "-"
if filename == "-":
print(
"Waiting for data input from stdin. Use Ctrl + C to interrupt.",
file=sys.stderr,
)
else:
if not os.path.exists(filename):
print("Cannot find file {}".format(filename), file=sys.stderr)
sys.exit()
if not stream.setSource(filename):
print(" + failed to assign source file to RecordStream", file=sys.stderr)
sys.exit()
records = seiscomp.io.RecordInput(stream, datatype, seiscomp.core.Record.SAVE_RAW)
while True:
try:
record = next(records)
except Exception:
print("Received invalid or no input", file=sys.stderr)
sys.exit()
if not record:
return
yield record
tmin = str2time("1970-01-01 00:00:00")
tmax = str2time("2500-01-01 00:00:00")
ifile = "-"
description = (
"Read unsorted and possibly multiplexed miniSEED files. "
"Sort data by time (multiplexing) and filter the individual "
"records by time and/or streams. Apply this before playbacks "
"and waveform archiving."
)
epilog = (
"Examples:\n"
"Read data from multiple files, extract streams by time, sort records by start "
"time, remove duplicate records\n"
" cat f1.mseed f2.mseed f3.mseed |\\\n"
" scmssort -v -t '2007-03-28 15:48~2007-03-28 16:18' -u > sorted.mseed\n"
"\n"
"Extract streams by time, stream code and sort records by end time\n"
" echo CX.PB01..BH? |\\ \n"
" scmssort -v -E -t '2007-03-28 15:48~2007-03-28 16:18' -u -l - test.mseed > "
"sorted.mseed"
)
# p = MyArgumentParser(
# usage="\n %prog [options] [files | < ] > ", description=description, epilog=epilog
# )
p = MyArgumentParser(
description=description,
epilog=epilog,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument(
"-E",
"--sort-by-end-time",
action="store_true",
help="Sort according to record end time; default is start time.",
)
p.add_argument(
"-r",
"--rm",
action="store_true",
help="Remove all traces in stream list given by --list instead of keeping them.",
)
p.add_argument(
"-l",
"--list",
action="store",
help="File with stream list to filter the records. "
"One stream per line. Instead of a file read the from stdin (-). "
"Line format: NET.STA.LOC.CHA - wildcards and regular expressions "
"are considered. Example: CX.*..BH?.",
)
p.add_argument(
"-t",
"--time-window",
action="store",
help="Specify time window (as one -properly quoted- string). Times "
"are of course UTC and separated by a tilde '~'.",
)
p.add_argument(
"-u",
"--uniqueness",
action="store_true",
help="Ensure uniqueness of output, i.e. skip duplicate records.",
)
p.add_argument("-v", "--verbose", action="store_true", help="Run in verbose mode.")
p.add_argument(
"filenames",
nargs="+",
help="Names of input files in miniSEED format.",
)
opt = p.parse_args()
filenames = opt.filenames
if opt.time_window:
tmin, tmax = list(map(str2time, opt.time_window.split("~")))
if opt.verbose:
print(
"Considered time window: %s~%s" % (time2str(tmin), time2str(tmax)),
file=sys.stderr,
)
listFile = None
removeStreams = False
if opt.list:
listFile = opt.list
print("Considered stream list from: %s" % (listFile), file=sys.stderr)
if opt.rm:
removeStreams = True
print("Removing listed streams", file=sys.stderr)
def _time(record):
if opt.sort_by_end_time:
return seiscomp.core.Time(record.endTime())
return seiscomp.core.Time(record.startTime())
def _in_time_window(record, tMin, tMax):
return record.endTime() >= tMin and record.startTime() <= tMax
def readStreamList(file):
streamList = []
try:
if file == "-":
f = sys.stdin
file = "stdin"
else:
f = open(listFile, "r", encoding="utf-8")
except FileNotFoundError:
print("%s: error: unable to open" % listFile, file=sys.stderr)
return []
lineNumber = -1
for line in f:
lineNumber = lineNumber + 1
line = line.strip()
# ignore comments
if len(line) > 0 and line[0] == "#":
continue
if len(line) == 0:
continue
toks = line.split(".")
if len(toks) != 4:
f.close()
print(
"error: %s in line %d has invalid line format, expected "
"stream list: NET.STA.LOC.CHA - 1 line per stream including "
"regular expressions" % (listFile, lineNumber),
file=sys.stderr,
)
return []
streamList.append(line)
f.close()
if len(streamList) == 0:
return []
return streamList
if not filenames:
filenames = ["-"]
streams = None
if listFile:
streams = readStreamList(listFile)
if not streams and not removeStreams:
print(" + cannot extract data", file=sys.stderr)
sys.exit()
if opt.verbose:
string = " + streams: "
for stream in streams:
string += stream + " "
print("%s" % (string), file=sys.stderr)
pattern = re.compile("|".join(streams))
readRecords = 0
networks = set()
stations = set()
locations = set()
channels = set()
readStreams = set()
outEnd = None
outStart = None
if filenames:
first = None
time_raw = []
for fileName in filenames:
if opt.verbose:
print("Reading file '%s'" % fileName, file=sys.stderr)
for rec in recordInput(fileName):
if not rec:
continue
if not _in_time_window(rec, tmin, tmax):
continue
raw = rec.raw().str()
streamCode = "%s.%s.%s.%s" % (
rec.networkCode(),
rec.stationCode(),
rec.locationCode(),
rec.channelCode(),
)
if listFile:
foundStream = False
if pattern.match(streamCode):
foundStream = True
if removeStreams:
foundStream = not foundStream
if not foundStream:
continue
# collect statistics for verbosity mode
if opt.verbose:
networks.add(rec.networkCode())
stations.add(rec.stationCode())
locations.add(rec.locationCode())
channels.add(rec.channelCode())
readStreams.add(streamCode)
readRecords += 1
start = rec.startTime()
end = rec.endTime()
if (outStart is None) or (start < outStart):
outStart = seiscomp.core.Time(start)
if (outEnd is None) or (end > outEnd):
outEnd = seiscomp.core.Time(end)
t = _time(rec)
if first is None:
first = t
t = float(t - first) # float needs less memory
time_raw.append((t, raw))
if opt.verbose:
print(
" + %d networks, %d stations, %d sensor locations, "
"%d channel codes, %d streams, %d records"
% (
len(networks),
len(stations),
len(locations),
len(channels),
len(readStreams),
readRecords,
),
file=sys.stderr,
)
print("Sorting records", file=sys.stderr)
time_raw.sort()
if opt.verbose:
print("Writing output", file=sys.stderr)
previous = None
out = sys.stdout
try:
# needed in Python 3, fails in Python 2
out = out.buffer
except AttributeError:
# assuming this is Python 2, nothing to be done
pass
duplicates = 0
for item in time_raw:
if item == previous:
duplicates += 1
if opt.uniqueness:
continue
t, raw = item
out.write(raw)
previous = item
if opt.verbose:
print("Finished", file=sys.stderr)
if opt.uniqueness:
print(
" + found and removed {} duplicate records".format(duplicates),
file=sys.stderr,
)
else:
if duplicates > 0:
print(
" + found {} duplicate records - remove with: scmssort -u".format(
duplicates
),
file=sys.stderr,
)
else:
print(" + found 0 duplicate records", file=sys.stderr)
print("Output:", file=sys.stderr)
if outStart and outEnd:
print(
" + time window: %s~%s"
% (seiscomp.core.Time(outStart), seiscomp.core.Time(outEnd)),
file=sys.stderr,
)
else:
print("No data found in time window", file=sys.stderr)
else:
# This is an important hint which should always be printed
if duplicates > 0 and not opt.uniqueness:
print(
"Found {} duplicate records - remove with: scmssort -u".format(
duplicates
),
file=sys.stderr,
)

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,131 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import sys
import seiscomp.core
import seiscomp.client
import seiscomp.datamodel
class OriginList(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
self.setMessagingEnabled(False)
self.setDatabaseEnabled(True, False)
self.setDaemonEnabled(False)
self._startTime = seiscomp.core.Time()
self._endTime = seiscomp.core.Time.GMT()
self._delimiter = None
def createCommandLineDescription(self):
self.commandline().addGroup("Origins")
self.commandline().addStringOption("Origins", "begin",
"The lower bound of the time interval. Format: '1970-01-01 00:00:00'.")
self.commandline().addStringOption("Origins", "end",
"The upper bound of the time interval. Format: '1970-01-01 00:00:00'.")
self.commandline().addStringOption("Origins", "author",
"The author of the origins.")
self.commandline().addGroup("Output")
self.commandline().addStringOption("Output", "delimiter,D",
"The delimiter of the resulting "
"origin IDs. Default: '\\n')")
return True
def init(self):
if not seiscomp.client.Application.init(self):
return False
try:
start = self.commandline().optionString("begin")
if not self._startTime.fromString(start, "%F %T"):
print("Wrong 'begin' given -> assuming {}"
.format(self._startTime), file=sys.stderr)
except RuntimeError:
print("No 'begin' given -> assuming {}".format(self._startTime),
file=sys.stderr)
try:
end = self.commandline().optionString("end")
if not self._endTime.fromString(end, "%F %T"):
print("Wrong 'end' given -> assuming {}"
.format(self._endTime), file=sys.stderr)
except RuntimeError:
print("No 'end' given -> assuming {}".format(self._endTime),
file=sys.stderr)
try:
self.author = self.commandline().optionString("author")
sys.stderr.write("%s author used for output\n" % (self.author))
except RuntimeError:
self.author = False
try:
self._delimiter = self.commandline().optionString("delimiter")
except RuntimeError:
self._delimiter = "\n"
# sys.stderr.write("Setting end to %s\n" % self._endTime.toString("%F %T"))
return True
def printUsage(self):
print('''Usage:
scorgls [options]
List origin IDs available in a given time range and print to stdout.''')
seiscomp.client.Application.printUsage(self)
print('''Examples:
Print all origin IDs from year 2022 and thereafter
scorgls -d mysql://sysop:sysop@localhost/seiscomp --begin "2022-01-01 00:00:00"
''')
def run(self):
seiscomp.logging.debug("Search interval: %s - %s" %
(self._startTime, self._endTime))
out = []
q = "select PublicObject.%s, Origin.* from Origin, PublicObject where Origin._oid=PublicObject._oid and Origin.%s >= '%s' and Origin.%s < '%s'" %\
(self.database().convertColumnName("publicID"),
self.database().convertColumnName("time_value"),
self.database().timeToString(self._startTime),
self.database().convertColumnName("time_value"),
self.database().timeToString(self._endTime))
if self.author:
q += " and Origin.%s = '%s' " %\
(self.database().convertColumnName("creationInfo_author"),
self.query().toString(self.author))
for obj in self.query().getObjectIterator(q, seiscomp.datamodel.Origin.TypeInfo()):
org = seiscomp.datamodel.Origin.Cast(obj)
if org:
out.append(org.publicID())
print("{}\n".format(self._delimiter.join(out)), file=sys.stdout)
return True
def main():
app = OriginList(len(sys.argv), sys.argv)
app()
if __name__ == "__main__":
main()

Binary file not shown.

@ -0,0 +1,328 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import time, sys, os, traceback
import seiscomp.core, seiscomp.client, seiscomp.datamodel
import seiscomp.logging, seiscomp.system
def createDirectory(dir):
if os.access(dir, os.W_OK):
return True
try:
os.makedirs(dir)
return True
except:
return False
def timeToString(t):
return t.toString("%T.%6f")
def timeSpanToString(ts):
neg = ts.seconds() < 0 or ts.microseconds() < 0
secs = abs(ts.seconds())
days = secs / 86400
daySecs = secs % 86400
hours = daySecs / 3600
hourSecs = daySecs % 3600
mins = hourSecs / 60
secs = hourSecs % 60
usecs = abs(ts.microseconds())
if neg:
return "-%.2d:%.2d:%.2d:%.2d.%06d" % (days, hours, mins, secs, usecs)
else:
return "%.2d:%.2d:%.2d:%.2d.%06d" % (days, hours, mins, secs, usecs)
class ProcLatency(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
self.setMessagingEnabled(True)
self.setDatabaseEnabled(False, False)
self.setAutoApplyNotifierEnabled(False)
self.setInterpretNotifierEnabled(True)
self.addMessagingSubscription("PICK")
self.addMessagingSubscription("AMPLITUDE")
self.addMessagingSubscription("LOCATION")
self.addMessagingSubscription("MAGNITUDE")
self.addMessagingSubscription("EVENT")
self.setPrimaryMessagingGroup(seiscomp.client.Protocol.LISTENER_GROUP)
self._directory = ""
self._nowDirectory = ""
self._triggeredDirectory = ""
self._logCreated = False
def createCommandLineDescription(self):
try:
self.commandline().addGroup("Storage")
self.commandline().addStringOption(
"Storage", "directory,o", "Specify the storage directory")
except:
seiscomp.logging.warning(
"caught unexpected error %s" % sys.exc_info())
def initConfiguration(self):
if not seiscomp.client.Application.initConfiguration(self):
return False
try:
self._directory = self.configGetString("directory")
except:
pass
try:
self._logCreated = self.configGetBool("logMsgLatency")
except:
pass
return True
def init(self):
if not seiscomp.client.Application.init(self):
return False
try:
self._directory = self.commandline().optionString("directory")
except:
pass
try:
if self._directory[-1] != "/":
self._directory = self._directory + "/"
except:
pass
if self._directory:
self._directory = seiscomp.system.Environment.Instance().absolutePath(self._directory)
sys.stderr.write("Logging latencies to %s\n" % self._directory)
return True
def addObject(self, parentID, obj):
try:
self.logObject(parentID, obj, False)
except:
sys.stderr.write("%s\n" % traceback.format_exc())
def updateObject(self, parentID, obj):
try:
self.logObject("", obj, True)
except:
sys.stderr.write("%s\n" % traceback.format_exc())
def logObject(self, parentID, obj, update):
now = seiscomp.core.Time.GMT()
time = None
pick = seiscomp.datamodel.Pick.Cast(obj)
if pick:
phase = ""
try:
phase = pick.phaseHint().code()
except:
pass
created = None
if self._logCreated:
try:
created = pick.creationInfo().creationTime()
except:
pass
self.logStation(now, created, pick.time().value(
), pick.publicID() + ";P;" + phase, pick.waveformID(), update)
return
amp = seiscomp.datamodel.Amplitude.Cast(obj)
if amp:
created = None
if self._logCreated:
try:
created = amp.creationInfo().creationTime()
except:
pass
try:
self.logStation(now, created, amp.timeWindow().reference(), amp.publicID(
) + ";A;" + amp.type() + ";" + "%.2f" % amp.amplitude().value(), amp.waveformID(), update)
except:
pass
return
org = seiscomp.datamodel.Origin.Cast(obj)
if org:
status = ""
lat = "%.2f" % org.latitude().value()
lon = "%.2f" % org.longitude().value()
try:
depth = "%d" % org.depth().value()
except:
pass
try:
status = seiscomp.datamodel.EOriginStatusNames.name(
org.status())
except:
pass
self.logFile(now, org.time().value(), org.publicID(
) + ";O;" + status + ";" + lat + ";" + lon + ";" + depth, update)
return
mag = seiscomp.datamodel.Magnitude.Cast(obj)
if mag:
count = ""
try:
count = "%d" % mag.stationCount()
except:
pass
self.logFile(now, None, mag.publicID() + ";M;" + mag.type() +
";" + "%.4f" % mag.magnitude().value() + ";" + count, update)
return
orgref = seiscomp.datamodel.OriginReference.Cast(obj)
if orgref:
self.logFile(now, None, parentID + ";OR;" +
orgref.originID(), update)
return
evt = seiscomp.datamodel.Event.Cast(obj)
if evt:
self.logFile(now, None, evt.publicID(
) + ";E;" + evt.preferredOriginID() + ";" + evt.preferredMagnitudeID(), update)
return
def logStation(self, received, created, triggered, text, waveformID, update):
streamID = waveformID.networkCode() + "." + waveformID.stationCode() + "." + \
waveformID.locationCode() + "." + waveformID.channelCode()
aNow = received.get()
aTriggered = triggered.get()
nowDirectory = self._directory + \
"/".join(["%.2d" % i for i in aNow[1:4]]) + "/"
triggeredDirectory = self._directory + \
"/".join(["%.2d" % i for i in aTriggered[1:4]]) + "/"
logEntry = timeSpanToString(received - triggered) + ";"
if created is not None:
logEntry = logEntry + timeSpanToString(received - created) + ";"
else:
logEntry = logEntry + ";"
if update:
logEntry = logEntry + "U"
else:
logEntry = logEntry + "A"
logEntry = logEntry + ";" + text
sys.stdout.write("%s;%s\n" % (timeToString(received), logEntry))
if nowDirectory != self._nowDirectory:
if createDirectory(nowDirectory) == False:
seiscomp.logging.error(
"Unable to create directory %s" % nowDirectory)
return False
self._nowDirectory = nowDirectory
self.writeLog(self._nowDirectory + streamID + ".rcv",
timeToString(received) + ";" + logEntry)
if triggeredDirectory != self._triggeredDirectory:
if createDirectory(triggeredDirectory) == False:
seiscomp.logging.error(
"Unable to create directory %s" % triggeredDirectory)
return False
self._triggeredDirectory = triggeredDirectory
self.writeLog(self._triggeredDirectory + streamID +
".trg", timeToString(triggered) + ";" + logEntry)
return True
def logFile(self, received, triggered, text, update):
aNow = received.get()
nowDirectory = self._directory + \
"/".join(["%.2d" % i for i in aNow[1:4]]) + "/"
triggeredDirectory = None
#logEntry = timeToString(received)
logEntry = ""
if not triggered is None:
aTriggered = triggered.get()
triggeredDirectory = self._directory + \
"/".join(["%.2d" % i for i in aTriggered[1:4]]) + "/"
logEntry = logEntry + timeSpanToString(received - triggered)
logEntry = logEntry + ";"
if update:
logEntry = logEntry + "U"
else:
logEntry = logEntry + "A"
logEntry = logEntry + ";" + text
sys.stdout.write("%s;%s\n" % (timeToString(received), logEntry))
if nowDirectory != self._nowDirectory:
if createDirectory(nowDirectory) == False:
seiscomp.logging.error(
"Unable to create directory %s" % nowDirectory)
return False
self._nowDirectory = nowDirectory
self.writeLog(self._nowDirectory + "objects.rcv",
timeToString(received) + ";" + logEntry)
if triggeredDirectory:
if triggeredDirectory != self._triggeredDirectory:
if createDirectory(triggeredDirectory) == False:
seiscomp.logging.error(
"Unable to create directory %s" % triggeredDirectory)
return False
self._triggeredDirectory = triggeredDirectory
self.writeLog(self._triggeredDirectory + "objects.trg",
timeToString(triggered) + ";" + logEntry)
return True
def writeLog(self, file, text):
of = open(file, "a")
if of:
of.write(text)
of.write("\n")
of.close()
app = ProcLatency(len(sys.argv), sys.argv)
sys.exit(app())

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,252 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) 2021 by gempa GmbH #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
# #
# adopted from scqcquery #
# Author: Dirk Roessler, gempa GmbH #
# Email: roessler@gempa.de #
############################################################################
from __future__ import absolute_import, division, print_function
import sys
import re
import seiscomp.core
import seiscomp.client
import seiscomp.io
import seiscomp.datamodel
qcParamsDefault = "latency,delay,timing,offset,rms,availability,"\
"'gaps count','gaps interval','gaps length',"\
"'overlaps count','overlaps interval','overlaps length',"\
"'spikes count','spikes interval','spikes amplitude'"
def getStreamsFromInventory(self):
try:
dbr = seiscomp.datamodel.DatabaseReader(self.database())
inv = seiscomp.datamodel.Inventory()
dbr.loadNetworks(inv)
streamList = set()
for inet in range(inv.networkCount()):
network = inv.network(inet)
dbr.load(network)
for ista in range(network.stationCount()):
station = network.station(ista)
try:
start = station.start()
except Exception:
continue
try:
end = station.end()
if not start <= self._end <= end and end >= self._start:
continue
except Exception:
pass
for iloc in range(station.sensorLocationCount()):
location = station.sensorLocation(iloc)
for istr in range(location.streamCount()):
stream = location.stream(istr)
streamID = network.code() + "." + station.code() \
+ "." + location.code() + "." + stream.code()
streamList.add(streamID)
return list(streamList)
except Exception:
return False
class WfqQuery(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
self.setMessagingEnabled(False)
self.setDatabaseEnabled(True, False)
self.setLoggingToStdErr(True)
self.setDaemonEnabled(False)
self._streams = False
self._fromInventory = False
self._outfile = '-'
self._parameter = qcParamsDefault
self._start = "1900-01-01T00:00:00Z"
self._end = str(seiscomp.core.Time.GMT())
self._formatted = False
def createCommandLineDescription(self):
self.commandline().addGroup("Output")
self.commandline().addStringOption("Output", "output,o",
"output file name for XML. Writes "
"to stdout if not given.")
self.commandline().addOption("Output", "formatted,f",
"write formatted XML")
self.commandline().addGroup("Query")
self.commandline().addStringOption(
"Query", "begin,b", "Begin time of query: 'YYYY-MM-DD hh:mm:ss'")
self.commandline().addStringOption(
"Query", "end,e", "End time of query: 'YYYY-MM-DD hh:mm:ss'")
self.commandline().addStringOption(
"Query", "stream-id,i",
"Waveform stream ID to search for QC parameters: net.sta.loc.cha -"
" [networkCode].[stationCode].[sensorLocationCode].[channelCode]. "
"Provide a single ID or a comma-separated list. Overrides "
"--streams-from-inventory")
self.commandline().addStringOption(
"Query", "parameter,p",
"QC parameter to output: (e.g. delay, rms, 'gaps count' ...). "
"Provide a single parameter or a comma-separated list. Defaults "
"apply if parameter is not given.")
self.commandline().addOption("Query", "streams-from-inventory",
"Read streams from inventory. Superseded"
" by stream-id.")
return True
def printUsage(self):
print('''Usage:
scqueryqc [options]
Query a database for waveform quality control (QC) parameters.''', file=sys.stderr)
seiscomp.client.Application.printUsage(self)
print('''Default QC parameters: {}
'''.format(qcParamsDefault), file=sys.stderr)
print('''Examples:
Query rms and delay values for streams 'AU.AS18..SHZ' and 'AU.AS19..SHZ' from '2021-11-20 00:00:00' until current
scqueryqc -d localhost -b '2021-11-20 00:00:00' -p rms,delay -i AU.AS18..SHZ,AU.AS19..SHZ
''', file=sys.stderr)
def validateParameters(self):
if not seiscomp.client.Application.validateParameters(self):
return False
try:
self._streams = self.commandline().optionString("stream-id").split(",")
except RuntimeError:
pass
try:
self._fromInventory = self.commandline().hasOption("streams-from-inventory")
except RuntimeError:
pass
if not self._streams and not self._fromInventory:
print("Provide streamID(s): --stream-id or --streams-from-inventory",
file=sys.stderr)
return False
try:
self._outfile = self.commandline().optionString("output")
except RuntimeError:
print("No output file name given: Sending to stdout",
file=sys.stderr)
try:
self._start = self.commandline().optionString("begin")
except RuntimeError:
print("No begin time given, considering: {}".format(self._start),
file=sys.stderr)
try:
self._end = self.commandline().optionString("end")
except RuntimeError:
print("No end time given, considering 'now': {}".format(self._end),
file=sys.stderr)
try:
self._parameter = self.commandline().optionString("parameter")
except RuntimeError:
print("No QC parameter given, using default", file=sys.stderr)
try:
self._formatted = self.commandline().hasOption("formatted")
except RuntimeError:
pass
return True
def run(self):
if not self.query():
print("No database connection!\n", file=sys.stderr)
return False
streams = self._streams
if not streams and self._fromInventory:
try:
streams = getStreamsFromInventory(self)
except RuntimeError:
print("No streams read from database!\n", file=sys.stderr)
return False
if not streams:
print("Empty stream list")
return False
for stream in streams:
if re.search("[*?]", stream):
print("Wildcards in streamID are not supported: {}\n"
.format(stream), file=sys.stderr)
return False
print("Request:", file=sys.stderr)
print(" streams: {}".format(str(streams)), file=sys.stderr)
print(" number of streams: {}".format(len(streams)), file=sys.stderr)
print(" begin time: {}".format(str(self._start)), file=sys.stderr)
print(" end time: {}".format(str(self._end)), file=sys.stderr)
print(" parameters: {}".format(str(self._parameter)),
file=sys.stderr)
print("Output:", file=sys.stderr)
print(" file: {}".format(self._outfile), file=sys.stderr)
print(" formatted XML: {}".format(self._formatted), file=sys.stderr)
# create archive
xarc = seiscomp.io.XMLArchive()
if not xarc.create(self._outfile, True, True):
print("Unable to write XML to {}!\n".format(self._outfile),
file=sys.stderr)
return False
xarc.setFormattedOutput(self._formatted)
qc = seiscomp.datamodel.QualityControl()
# write parameters
for parameter in self._parameter.split(","):
for stream in streams:
(net, sta, loc, cha) = stream.split(".")
it = self.query().getWaveformQuality(seiscomp.datamodel.WaveformStreamID(net, sta, loc, cha, ""),
parameter,
seiscomp.core.Time.FromString(
self._start, "%Y-%m-%d %H:%M:%S"),
seiscomp.core.Time.FromString(self._end, "%Y-%m-%d %H:%M:%S"))
while it.get():
try:
wfq = seiscomp.datamodel.WaveformQuality.Cast(it.get())
qc.add(wfq)
except Exception:
pass
it.step()
xarc.writeObject(qc)
xarc.close()
return True
app = WfqQuery(len(sys.argv), sys.argv)
sys.exit(app())

Binary file not shown.

Binary file not shown.

@ -0,0 +1,83 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import sys
import seiscomp.core
import seiscomp.client
import seiscomp.datamodel
class SendJournal(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
self.setDatabaseEnabled(False, False)
self.setMessagingEnabled(True)
self.setMessagingUsername("")
self.setPrimaryMessagingGroup("EVENT")
def init(self):
if not seiscomp.client.Application.init(self):
return False
self.params = self.commandline().unrecognizedOptions()
if len(self.params) < 2:
sys.stderr.write(
self.name() + " [opts] {objectID} {action} [parameters]\n")
return False
return True
def printUsage(self):
print('''Usage:
scsendjournal [options]
Send journaling information to the messaging to manipulate event parameters''')
seiscomp.client.Application.printUsage(self)
print('''Examples:
Set the type of the event with ID gempa2021abcd to 'earthquake'
scsendjournal -H localhost gempa2021abcd EvType "earthquake"
''')
def run(self):
msg = seiscomp.datamodel.NotifierMessage()
entry = seiscomp.datamodel.JournalEntry()
entry.setCreated(seiscomp.core.Time.GMT())
entry.setObjectID(self.params[0])
entry.setSender(self.author())
entry.setAction(self.params[1])
sys.stderr.write(
"Sending entry (" + entry.objectID() + "," + entry.action() + ")\n")
if len(self.params) > 2:
entry.setParameters(self.params[2])
n = seiscomp.datamodel.Notifier(
seiscomp.datamodel.Journaling.ClassName(), seiscomp.datamodel.OP_ADD, entry)
msg.attach(n)
self.connection().send(msg)
return True
def main(argc, argv):
app = SendJournal(argc, argv)
return app()
if __name__ == "__main__":
sys.exit(main(len(sys.argv), sys.argv))

@ -0,0 +1,94 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import sys
import seiscomp.core
import seiscomp.datamodel
import seiscomp.client
import seiscomp.logging
class SendOrigin(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
self.setDatabaseEnabled(False, False)
self.setMessagingEnabled(True)
self.setPrimaryMessagingGroup("GUI")
def init(self):
if not seiscomp.client.Application.init(self):
return False
try:
cstr = self.commandline().optionString("coord")
tstr = self.commandline().optionString("time")
except:
sys.stderr.write(
"Must specify origin using '--coord lat,lon,dep --time time'\n")
return False
self.origin = seiscomp.datamodel.Origin.Create()
ci = seiscomp.datamodel.CreationInfo()
ci.setAgencyID(self.agencyID())
ci.setCreationTime(seiscomp.core.Time.GMT())
self.origin.setCreationInfo(ci)
lat, lon, dep = list(map(float, cstr.split(",")))
self.origin.setLongitude(seiscomp.datamodel.RealQuantity(lon))
self.origin.setLatitude(seiscomp.datamodel.RealQuantity(lat))
self.origin.setDepth(seiscomp.datamodel.RealQuantity(dep))
time = seiscomp.core.Time()
time.fromString(tstr.replace("/", "-") + ":0:0", "%F %T")
self.origin.setTime(seiscomp.datamodel.TimeQuantity(time))
return True
def createCommandLineDescription(self):
try:
self.commandline().addGroup("Parameters")
self.commandline().addStringOption("Parameters",
"coord",
"Latitude,longitude,depth of origin")
self.commandline().addStringOption("Parameters",
"time", "time of origin")
except:
seiscomp.logging.warning("caught unexpected error %s" % sys.exc_info())
def printUsage(self):
print('''Usage:
scsendorigin [options]
Create an artificial origin and send to the messaging''')
seiscomp.client.Application.printUsage(self)
print('''Examples:
Send an artificial origin with hypocenter parameters to the messaging
scsendorigin --time "2022-05-01 10:00:00" --coord 52,12,10
''')
def run(self):
msg = seiscomp.datamodel.ArtificialOriginMessage(self.origin)
self.connection().send(msg)
return True
app = SendOrigin(len(sys.argv), sys.argv)
# app.setName("scsendorigin")
app.setMessagingUsername("scsendorg")
sys.exit(app())

Binary file not shown.

@ -0,0 +1,395 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import sys, os, re
import seiscomp.core, seiscomp.client, seiscomp.logging, seiscomp.system
"""
Monitor application that connects to the messaging and collects all
information on the STATUS_GROUP to create an XML file ever N seconds.
It can furthermore call a configured script to trigger processing of the
produced XML file.
"""
inputRegEx = re.compile("in\((?P<params>[^\)]*)\)")
outputRegEx = re.compile("out\((?P<params>[^\)]*)\)")
# Define all units of measure for available system SOH tags. Tags that are
# not given here are not processed.
Tests = {
"cpuusage": "%",
"clientmemoryusage": "kB",
"sentmessages": "cnt",
"receivedmessages": "cnt",
"messagequeuesize": "cnt",
"objectcount": "cnt",
"uptime": "s",
"dbadds": "row/s",
"dbupdates": "row/s",
"dbdeletes": "row/s"
}
#----------------------------------------------------------------------------
# Class TestLog to hold the properties of a test. It also creates XML.
#----------------------------------------------------------------------------
class TestLog:
def __init__(self):
self.value = None
self.uom = None
self.update = None
def toXML(self, f, name):
f.write('<test name="%s"' % name)
if self.value:
try:
# Try to convert to float
fvalue = float(self.value)
if fvalue % 1.0 >= 1E-6:
f.write(' value="%f"' % fvalue)
else:
f.write(' value="%d"' % int(fvalue))
except:
f.write(' value="%s"' % self.value)
if self.uom:
f.write(' uom="%s"' % self.uom)
if self.update:
f.write(' updateTime="%s"' % self.update)
f.write('/>')
#----------------------------------------------------------------------------
# Class ObjectLog to hold the properties of a object log. It also creates
# XML.
#----------------------------------------------------------------------------
class ObjectLog:
def __init__(self):
self.count = None
self.average = None
self.timeWindow = None
self.last = None
self.update = None
def toXML(self, f, name, channel):
f.write('<object')
if name:
f.write(' name="%s"' % name)
if channel:
f.write(' channel="%s"' % channel)
if not self.count is None:
f.write(' count="%s"' % self.count)
if not self.timeWindow is None:
f.write(' timeWindow="%s"' % self.timeWindow)
if not self.average is None:
f.write(' average="%s"' % self.average)
if self.last:
f.write(' lastTime="%s"' % self.last)
f.write(' updateTime="%s"' % self.update)
f.write('/>')
#----------------------------------------------------------------------------
# Class Client that holds all tests and object logs of a particular client
# (messaging user name).
#----------------------------------------------------------------------------
class Client:
def __init__(self):
self.pid = None
self.progname = None
self.host = None
self.inputLogs = dict()
self.outputLogs = dict()
self.tests = dict()
#----------------------------------------------------------------------------
# Update/add (system) tests based on the passed tests dictionary retrieved
# from a status message.
#----------------------------------------------------------------------------
def updateTests(self, updateTime, tests):
for name, value in list(tests.items()):
if name == "pid":
self.pid = value
elif name == "programname":
self.progname = value
elif name == "hostname":
self.host = value
if name not in Tests:
continue
# Convert d:h:m:s to seconds
if name == "uptime":
try:
t = [int(v) for v in value.split(":")]
except:
continue
if len(t) != 4:
continue
value = str(t[0]*86400+t[1]*3600+t[2]*60+t[3])
if name not in self.tests:
log = TestLog()
log.uom = Tests[name]
self.tests[name] = log
else:
log = self.tests[name]
log.value = value
log.update = updateTime
#----------------------------------------------------------------------------
# Update/add object logs based on the passed log text. The content is parsed.
#----------------------------------------------------------------------------
def updateObjects(self, updateTime, log):
# Check input structure
v = inputRegEx.search(log)
if not v:
# Check out structure
v = outputRegEx.search(log)
if not v:
return
logs = self.outputLogs
else:
logs = self.inputLogs
try:
tmp = v.group('params').split(',')
except:
return
params = dict()
for p in tmp:
try:
param, value = p.split(':', 1)
except:
continue
params[param] = value
name = params.get("name", "")
channel = params.get("chan", "")
if (name, channel) not in logs:
logObj = ObjectLog()
logs[(name, channel)] = logObj
else:
logObj = logs[(name, channel)]
logObj.update = updateTime
logObj.count = params.get("cnt")
logObj.average = params.get("avg")
logObj.timeWindow = params.get("tw")
logObj.last = params.get("last")
def toXML(self, f, name):
f.write('<service name="%s"' % name)
if self.host:
f.write(' host="%s"' % self.host)
if self.pid:
f.write(' pid="%s"' % self.pid)
if self.progname:
f.write(' prog="%s"' % self.progname)
f.write('>')
for name, log in list(self.tests.items()):
log.toXML(f, name)
if len(self.inputLogs) > 0:
f.write('<input>')
for id, log in list(self.inputLogs.items()):
log.toXML(f, id[0], id[1])
f.write('</input>')
if len(self.outputLogs) > 0:
f.write('<output>')
for id, log in list(self.outputLogs.items()):
log.toXML(f, id[0], id[1])
f.write('</output>')
f.write("</service>")
#----------------------------------------------------------------------------
# SC3 application class Monitor
#----------------------------------------------------------------------------
class Monitor(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
self.setDatabaseEnabled(False, False)
self.setMembershipMessagesEnabled(True);
self.addMessagingSubscription(seiscomp.client.Protocol.STATUS_GROUP)
self.setMessagingUsername("")
self.setPrimaryMessagingGroup(seiscomp.client.Protocol.LISTENER_GROUP)
self._clients = dict()
self._outputScript = None
self._outputFile = "@LOGDIR@/server.xml"
self._outputInterval = 60
def createCommandLineDescription(self):
try:
self.commandline().addGroup("Output")
self.commandline().addStringOption("Output", "file,o",
"Specify the output file to create")
self.commandline().addIntOption("Output", "interval,i",
"Specify the output interval in seconds (default: 60)")
self.commandline().addStringOption("Output", "script",
"Specify an output script to be called after the output file is generated")
except:
seiscomp.logging.warning(
"caught unexpected error %s" % sys.exc_info())
return True
def initConfiguration(self):
if not seiscomp.client.Application.initConfiguration(self):
return False
try:
self._outputFile = self.configGetString("monitor.output.file")
except:
pass
try:
self._outputInterval = self.configGetInt("monitor.output.interval")
except:
pass
try:
self._outputScript = self.configGetString("monitor.output.script")
except:
pass
return True
def init(self):
if not seiscomp.client.Application.init(self):
return False
try:
self._outputFile = self.commandline().optionString("file")
except:
pass
try:
self._outputInterval = self.commandline().optionInt("interval")
except:
pass
try:
self._outputScript = self.commandline().optionString("script")
except:
pass
self._outputFile = seiscomp.system.Environment.Instance().absolutePath(self._outputFile)
seiscomp.logging.info("Output file: %s" % self._outputFile)
if self._outputScript:
self._outputScript = seiscomp.system.Environment.Instance().absolutePath(self._outputScript)
seiscomp.logging.info("Output script: %s" % self._outputScript)
self._monitor = self.addInputObjectLog("status", seiscomp.client.Protocol.STATUS_GROUP)
self.enableTimer(self._outputInterval)
seiscomp.logging.info("Starting output timer with %d secs" % self._outputInterval)
return True
def printUsage(self):
print('''Usage:
scsohlog [options]
Connect to the messaging collecting information sent from connected clients''')
seiscomp.client.Application.printUsage(self)
print('''Examples:
Create an output XML file every 60 seconds and execute a custom script to process the XML file
scsohlog -o stat.xml -i 60 --script process-stat.sh
''')
def handleNetworkMessage(self, msg):
# A state of health message
if msg.type == seiscomp.client.Packet.Status:
data = filter(None, msg.payload.split("&"))
self.updateStatus(msg.subject, data)
# If a client disconnected, remove it from the list
elif msg.type == seiscomp.client.Packet.Disconnected:
if msg.subject in self._clients:
del self._clients[msg.subject]
def handleDisconnect(self):
# If we got disconnected all client states are deleted
self._clients = dict()
#----------------------------------------------------------------------------
# Timeout handler called by the Application class.
# Write XML to configured output file and trigger configured script.
#----------------------------------------------------------------------------
def handleTimeout(self):
if self._outputFile == "-":
self.toXML(sys.stdout)
sys.stdout.write("\n")
return
try:
f = open(self._outputFile, "w")
except:
seiscomp.logging.error(
"Unable to create output file: %s" % self._outputFile)
return
self.toXML(f)
f.close()
if self._outputScript:
os.system(self._outputScript + " " + self._outputFile)
#----------------------------------------------------------------------------
# Write XML to stream f
#----------------------------------------------------------------------------
def toXML(self, f):
f.write('<?xml version="1.0" encoding="UTF-8"?>')
f.write('<server name="seiscomp" host="%s">' % self.messagingURL())
for name, client in list(self._clients.items()):
client.toXML(f, name)
f.write('</server>')
def updateStatus(self, name, items):
if name not in self._clients:
self._clients[name] = Client()
now = seiscomp.core.Time.GMT()
client = self._clients[name]
self.logObject(self._monitor, now)
params = dict()
objs = []
for t in items:
try:
param, value = t.split("=", 1)
params[param] = value
except:
objs.append(t)
if "time" in params:
update = params["time"]
del params["time"]
else:
update = now.iso()
client.updateTests(update, params)
for o in objs:
client.updateObjects(update, o)
#client.toXML(sys.stdout, name)
app = Monitor(len(sys.argv), sys.argv)
sys.exit(app())

@ -0,0 +1,502 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
############################################################################
import sys
import subprocess
import traceback
from seiscomp import (client, core, datamodel, logging, seismology, system,
math)
class VoiceAlert(client.Application):
def __init__(self, argc, argv):
client.Application.__init__(self, argc, argv)
self.setMessagingEnabled(True)
self.setDatabaseEnabled(True, True)
self.setLoadRegionsEnabled(True)
self.setMessagingUsername("")
self.setPrimaryMessagingGroup(client.Protocol.LISTENER_GROUP)
self.addMessagingSubscription("EVENT")
self.addMessagingSubscription("LOCATION")
self.addMessagingSubscription("MAGNITUDE")
self.setAutoApplyNotifierEnabled(True)
self.setInterpretNotifierEnabled(True)
self.setLoadCitiesEnabled(True)
self.setLoadRegionsEnabled(True)
self._ampType = "snr"
self._citiesMaxDist = 20
self._citiesMinPopulation = 50000
self._cache = None
self._eventDescriptionPattern = None
self._ampScript = None
self._alertScript = None
self._eventScript = None
self._ampProc = None
self._alertProc = None
self._eventProc = None
self._newWhenFirstSeen = False
self._prevMessage = {}
self._agencyIDs = []
def createCommandLineDescription(self):
self.commandline().addOption(
"Generic", "first-new", "calls an event a new event when it is "
"seen the first time")
self.commandline().addGroup("Alert")
self.commandline().addStringOption(
"Alert", "amp-type", "specify the amplitude type to listen to",
self._ampType)
self.commandline().addStringOption(
"Alert", "amp-script", "specify the script to be called when a "
"stationamplitude arrived, network-, stationcode and amplitude are "
"passed as parameters $1, $2 and $3")
self.commandline().addStringOption(
"Alert", "alert-script", "specify the script to be called when a "
"preliminary origin arrived, latitude and longitude are passed as "
"parameters $1 and $2")
self.commandline().addStringOption(
"Alert", "event-script", "specify the script to be called when an "
"event has been declared; the message string, a flag (1=new event, "
"0=update event), the EventID, the arrival count and the magnitude "
"(optional when set) are passed as parameter $1, $2, $3, $4 and $5")
self.commandline().addGroup("Cities")
self.commandline().addStringOption(
"Cities", "max-dist", "maximum distance for using the distance "
"from a city to the earthquake")
self.commandline().addStringOption(
"Cities", "min-population", "minimum population for a city to "
"become a point of interest")
self.commandline().addGroup("Debug")
self.commandline().addStringOption(
"Debug", "eventid,E", "specify Event ID")
return True
def init(self):
if not client.Application.init(self):
return False
try:
self._newWhenFirstSeen = self.configGetBool("firstNew")
except BaseException:
pass
try:
agencyIDs = self.configGetStrings("agencyIDs")
for item in agencyIDs:
item = item.strip()
if item not in self._agencyIDs:
self._agencyIDs.append(item)
except BaseException:
pass
try:
if self.commandline().hasOption("first-new"):
self._newWhenFirstSeen = True
except BaseException:
pass
try:
self._eventDescriptionPattern = self.configGetString("poi.message")
except BaseException:
pass
try:
self._citiesMaxDist = self.configGetDouble("poi.maxDist")
except BaseException:
pass
try:
self._citiesMaxDist = self.commandline().optionDouble("max-dist")
except BaseException:
pass
try:
self._citiesMinPopulation = self.configGetInt("poi.minPopulation")
except BaseException:
pass
try:
self._citiesMinPopulation = self.commandline().optionInt("min-population")
except BaseException:
pass
try:
self._ampType = self.commandline().optionString("amp-type")
except BaseException:
pass
try:
self._ampScript = self.commandline().optionString("amp-script")
except BaseException:
try:
self._ampScript = self.configGetString("scripts.amplitude")
except BaseException:
logging.warning("No amplitude script defined")
if self._ampScript:
self._ampScript = system.Environment.Instance().absolutePath(self._ampScript)
try:
self._alertScript = self.commandline().optionString("alert-script")
except BaseException:
try:
self._alertScript = self.configGetString("scripts.alert")
except BaseException:
logging.warning("No alert script defined")
if self._alertScript:
self._alertScript = system.Environment.Instance(
).absolutePath(self._alertScript)
try:
self._eventScript = self.commandline().optionString("event-script")
except BaseException:
try:
self._eventScript = self.configGetString("scripts.event")
logging.info(
"Using event script: %s" % self._eventScript)
except BaseException:
logging.warning("No event script defined")
if self._eventScript:
self._eventScript = system.Environment.Instance() \
.absolutePath(self._eventScript)
logging.info("Creating ringbuffer for 100 objects")
if not self.query():
logging.warning(
"No valid database interface to read from")
self._cache = datamodel.PublicObjectRingBuffer(
self.query(), 100)
if self._ampScript and self.connection():
self.connection().subscribe("AMPLITUDE")
if self._newWhenFirstSeen:
logging.info(
"A new event is declared when I see it the first time")
if not self._agencyIDs:
logging.info("agencyIDs: []")
else:
logging.info(
"agencyIDs: %s" % (" ".join(self._agencyIDs)))
return True
def printUsage(self):
print('''Usage:
scvoice [options]
Alert the user acoustically in real time.
''')
client.Application.printUsage(self)
print('''Examples:
Execute scvoice on command line with debug output
scvoice --debug
''')
def run(self):
try:
try:
eventID = self.commandline().optionString("eventid")
event = self._cache.get(datamodel.Event, eventID)
if event:
self.notifyEvent(event)
except BaseException:
pass
return client.Application.run(self)
except BaseException:
info = traceback.format_exception(*sys.exc_info())
for i in info:
sys.stderr.write(i)
return False
def runAmpScript(self, net, sta, amp):
if not self._ampScript:
return
if self._ampProc is not None:
if self._ampProc.poll() is None:
logging.warning(
"AmplitudeScript still in progress -> skipping message")
return
try:
self._ampProc = subprocess.Popen(
[self._ampScript, net, sta, "%.2f" % amp])
logging.info(
"Started amplitude script with pid %d" % self._ampProc.pid)
except BaseException:
logging.error(
"Failed to start amplitude script '%s'" % self._ampScript)
def runAlert(self, lat, lon):
if not self._alertScript:
return
if self._alertProc is not None:
if self._alertProc.poll() is None:
logging.warning(
"AlertScript still in progress -> skipping message")
return
try:
self._alertProc = subprocess.Popen(
[self._alertScript, "%.1f" % lat, "%.1f" % lon])
logging.info(
"Started alert script with pid %d" % self._alertProc.pid)
except BaseException:
logging.error(
"Failed to start alert script '%s'" % self._alertScript)
def handleMessage(self, msg):
try:
dm = core.DataMessage.Cast(msg)
if dm:
for att in dm:
org = datamodel.Origin.Cast(att)
if not org:
continue
try:
if org.evaluationStatus() == datamodel.PRELIMINARY:
self.runAlert(org.latitude().value(),
org.longitude().value())
except BaseException:
pass
#ao = datamodel.ArtificialOriginMessage.Cast(msg)
# if ao:
# org = ao.origin()
# if org:
# self.runAlert(org.latitude().value(), org.longitude().value())
# return
client.Application.handleMessage(self, msg)
except BaseException:
info = traceback.format_exception(*sys.exc_info())
for i in info:
sys.stderr.write(i)
def addObject(self, parentID, arg0):
#pylint: disable=W0622
try:
obj = datamodel.Amplitude.Cast(arg0)
if obj:
if obj.type() == self._ampType:
logging.debug("got new %s amplitude '%s'" % (
self._ampType, obj.publicID()))
self.notifyAmplitude(obj)
obj = datamodel.Origin.Cast(arg0)
if obj:
self._cache.feed(obj)
logging.debug("got new origin '%s'" % obj.publicID())
try:
if obj.evaluationStatus() == datamodel.PRELIMINARY:
self.runAlert(obj.latitude().value(),
obj.longitude().value())
except BaseException:
pass
return
obj = datamodel.Magnitude.Cast(arg0)
if obj:
self._cache.feed(obj)
logging.debug(
"got new magnitude '%s'" % obj.publicID())
return
obj = datamodel.Event.Cast(arg0)
if obj:
org = self._cache.get(
datamodel.Origin, obj.preferredOriginID())
agencyID = org.creationInfo().agencyID()
logging.debug("got new event '%s'" % obj.publicID())
if not self._agencyIDs or agencyID in self._agencyIDs:
self.notifyEvent(obj, True)
except BaseException:
info = traceback.format_exception(*sys.exc_info())
for i in info:
sys.stderr.write(i)
def updateObject(self, parentID, arg0):
try:
obj = datamodel.Event.Cast(arg0)
if obj:
org = self._cache.get(datamodel.Origin, obj.preferredOriginID())
agencyID = org.creationInfo().agencyID()
logging.debug("update event '%s'" % obj.publicID())
if not self._agencyIDs or agencyID in self._agencyIDs:
self.notifyEvent(obj, False)
except BaseException:
info = traceback.format_exception(*sys.exc_info())
for i in info:
sys.stderr.write(i)
def notifyAmplitude(self, amp):
self.runAmpScript(amp.waveformID().networkCode(),
amp.waveformID().stationCode(),
amp.amplitude().value())
def notifyEvent(self, evt, newEvent=True):
try:
org = self._cache.get(datamodel.Origin, evt.preferredOriginID())
if not org:
logging.warning("unable to get origin %s, ignoring event "
"message" % evt.preferredOriginID())
return
preliminary = False
try:
if org.evaluationStatus() == datamodel.PRELIMINARY:
preliminary = True
except BaseException:
pass
if not preliminary:
nmag = self._cache.get(
datamodel.Magnitude, evt.preferredMagnitudeID())
if nmag:
mag = nmag.magnitude().value()
mag = "magnitude %.1f" % mag
else:
if len(evt.preferredMagnitudeID()) > 0:
logging.warning(
"unable to get magnitude %s, ignoring event "
"message" % evt.preferredMagnitudeID())
else:
logging.warning(
"no preferred magnitude yet, ignoring event message")
return
# keep track of old events
if self._newWhenFirstSeen:
if evt.publicID() in self._prevMessage:
newEvent = False
else:
newEvent = True
dsc = seismology.Regions.getRegionName(
org.latitude().value(), org.longitude().value())
if self._eventDescriptionPattern:
try:
city, dist, _ = self.nearestCity(
org.latitude().value(), org.longitude().value(),
self._citiesMaxDist, self._citiesMinPopulation)
if city:
dsc = self._eventDescriptionPattern
region = seismology.Regions.getRegionName(
org.latitude().value(), org.longitude().value())
distStr = str(int(math.deg2km(dist)))
dsc = dsc.replace("@region@", region).replace(
"@dist@", distStr).replace("@poi@", city.name())
except BaseException:
pass
logging.debug("desc: %s" % dsc)
dep = org.depth().value()
now = core.Time.GMT()
otm = org.time().value()
dt = (now - otm).seconds()
# if dt > dtmax:
# return
if dt > 3600:
dt = "%d hours %d minutes ago" % (dt/3600, (dt % 3600)/60)
elif dt > 120:
dt = "%d minutes ago" % (dt/60)
else:
dt = "%d seconds ago" % dt
if preliminary:
message = "earthquake, preliminary, %%s, %s" % dsc
else:
message = "earthquake, %%s, %s, %s, depth %d kilometers" % (
dsc, mag, int(dep+0.5))
# at this point the message lacks the "ago" part
if evt.publicID() in self._prevMessage and \
self._prevMessage[evt.publicID()] == message:
logging.info("Suppressing repeated message '%s'" % message)
return
self._prevMessage[evt.publicID()] = message
message = message % dt # fill the "ago" part
logging.info(message)
if not self._eventScript:
return
if self._eventProc is not None:
if self._eventProc.poll() is None:
logging.warning(
"EventScript still in progress -> skipping message")
return
try:
param2 = 0
param3 = 0
param4 = ""
if newEvent:
param2 = 1
org = self._cache.get(
datamodel.Origin, evt.preferredOriginID())
if org:
try:
param3 = org.quality().associatedPhaseCount()
except BaseException:
pass
nmag = self._cache.get(
datamodel.Magnitude, evt.preferredMagnitudeID())
if nmag:
param4 = "%.1f" % nmag.magnitude().value()
self._eventProc = subprocess.Popen(
[self._eventScript, message, "%d" % param2, evt.publicID(),
"%d" % param3, param4])
logging.info(
"Started event script with pid %d" % self._eventProc.pid)
except BaseException:
logging.error(
"Failed to start event script '%s %s %d %d %s'" % (
self._eventScript, message, param2, param3, param4))
except BaseException:
info = traceback.format_exception(*sys.exc_info())
for i in info:
sys.stderr.write(i)
app = VoiceAlert(len(sys.argv), sys.argv)
sys.exit(app())

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,55 @@
#!/bin/sh -e
# Resolve softlink to seiscomp executable first
if test -L "$0"
then
# $0 is a link
target="$(readlink "$0")"
case "$target" in
/*)
d="$target"
;;
*)
d="$(dirname "$0")/$target"
;;
esac
else
# $0 is NOT a link
case "$0" in
*/* | /*)
d="$0"
;;
*)
d="$(command -v "$0")"
;;
esac
fi
normalized_dirname() {
# Normalize directory name without following symlinks.
# Brute-force but portable.
cd "${1%/*}" && pwd || exit 1
}
# Determine the root directory of the 'seiscomp' utility.
d="$(normalized_dirname "$d")"
SEISCOMP_ROOT="$(realpath "${d%/bin}")"
export SEISCOMP_ROOT
export PATH="$SEISCOMP_ROOT/bin:$PATH"
export LD_LIBRARY_PATH="$SEISCOMP_ROOT/lib:$LD_LIBRARY_PATH"
export PYTHONPATH="$SEISCOMP_ROOT/lib/python:$PYTHONPATH"
export MANPATH="$SEISCOMP_ROOT/share/man:$MANPATH"
HOSTENV=$SEISCOMP_ROOT/etc/env/by-hostname/$(hostname)
test -f $HOSTENV && . $HOSTENV
case $1 in
exec)
shift
exec "$@"
;;
*)
exec $SEISCOMP_ROOT/bin/seiscomp-python "$SEISCOMP_ROOT/bin/seiscomp-control.py" "$@"
;;
esac

File diff suppressed because it is too large Load Diff

@ -0,0 +1,19 @@
#!/bin/sh
#
# This is a shell script that executes the Python interpreter as
# configured using cmake.
#
# In order to use this in your Python programs use this
# shebang line:
#!/usr/bin/env seiscomp-python
# Please note that this wrapper does *not* set the environment
# variables for you. To ensure that you run your script in the
# proper environment, please use 'seiscomp exec'. Alternatively
# you can also set your environment variables according to the
# output of 'seiscomp print env'.
python_executable="/usr/bin/python3"
exec $python_executable "$@"

@ -0,0 +1,884 @@
#!/usr/bin/env seiscomp-python
# -*- coding: utf-8 -*-
############################################################################
# Copyright (C) GFZ Potsdam #
# All rights reserved. #
# #
# GNU Affero General Public License Usage #
# This file may be used under the terms of the GNU Affero #
# Public License version 3.0 as published by the Free Software Foundation #
# and appearing in the file LICENSE included in the packaging of this #
# file. Please review the following information to ensure the GNU Affero #
# Public License version 3.0 requirements will be met: #
# https://www.gnu.org/licenses/agpl-3.0.html. #
# #
# Author: Alexander Jaeger, Stephan Herrnkind, #
# Lukas Lehmann, Dirk Roessler# #
# Email: herrnkind@gempa.de #
############################################################################
import seiscomp.client, seiscomp.core, seiscomp.datamodel, seiscomp.io, seiscomp.logging, seiscomp.math
from time import strptime
import sys
import traceback
TimeFormats = [
'%d-%b-%Y_%H:%M:%S.%f',
'%d-%b-%Y_%H:%M:%S'
]
# SC3 has more event types available in the datamodel
EventTypes = {
'teleseismic quake': seiscomp.datamodel.EARTHQUAKE,
'local quake': seiscomp.datamodel.EARTHQUAKE,
'regional quake': seiscomp.datamodel.EARTHQUAKE,
'quarry blast': seiscomp.datamodel.QUARRY_BLAST,
'nuclear explosion': seiscomp.datamodel.NUCLEAR_EXPLOSION,
'mining event': seiscomp.datamodel.MINING_EXPLOSION
}
def wfs2Str(wfsID):
return '%s.%s.%s.%s' % (wfsID.networkCode(), wfsID.stationCode(),
wfsID.locationCode(), wfsID.channelCode())
###############################################################################
class SH2Proc(seiscomp.client.Application):
###########################################################################
def __init__(self):
seiscomp.client.Application.__init__(self, len(sys.argv), sys.argv)
self.setMessagingEnabled(True)
self.setDatabaseEnabled(True, True)
self.setLoadInventoryEnabled(True)
self.setLoadConfigModuleEnabled(True)
self.setDaemonEnabled(False)
self.inputFile = '-'
###########################################################################
def initConfiguration(self):
if not seiscomp.client.Application.initConfiguration(self):
return False
# If the database connection is passed via command line or configuration
# file then messaging is disabled. Messaging is only used to get
# the configured database connection URI.
if self.databaseURI() != '':
self.setMessagingEnabled(False)
else:
# A database connection is not required if the inventory is loaded
# from file
if not self.isInventoryDatabaseEnabled():
self.setMessagingEnabled(False)
self.setDatabaseEnabled(False, False)
return True
##########################################################################
def printUsage(self):
print('''Usage:
sh2proc [options]
Convert Seismic Handler event data to SeisComP XML format''')
seiscomp.client.Application.printUsage(self)
print('''Examples:
Convert the Seismic Handler file shm.evt to SCML. Receive the database
connection to read inventory and configuration information from messaging
sh2proc shm.evt
Read Seismic Handler data from stdin. Provide inventory and configuration in XML
cat shm.evt | sh2proc --inventory-db=inventory.xml --config-db=config.xml
''')
##########################################################################
def validateParameters(self):
if not seiscomp.client.Application.validateParameters(self):
return False
for opt in self.commandline().unrecognizedOptions():
if len(opt) > 1 and opt.startswith('-'):
continue
self.inputFile = opt
break
return True
###########################################################################
def loadStreams(self):
now = seiscomp.core.Time.GMT()
inv = seiscomp.client.Inventory.Instance()
self.streams = {}
# try to load streams by detecLocid and detecStream
mod = self.configModule()
if mod is not None and mod.configStationCount() > 0:
seiscomp.logging.info('loading streams using detecLocid and detecStream')
for i in range(mod.configStationCount()):
cfg = mod.configStation(i)
net = cfg.networkCode()
sta = cfg.stationCode()
if sta in self.streams:
seiscomp.logging.warning(
'ambiguous stream id found for station %s.%s' % (net, sta))
continue
setup = seiscomp.datamodel.findSetup(cfg, self.name(), True)
if not setup:
seiscomp.logging.warning(
'could not find station setup for %s.%s' % (net, sta))
continue
params = seiscomp.datamodel.ParameterSet.Find(setup.parameterSetID())
if not params:
seiscomp.logging.warning(
'could not find station parameters for %s.%s' % (net, sta))
continue
detecLocid = ''
detecStream = None
for j in range(params.parameterCount()):
param = params.parameter(j)
if param.name() == 'detecStream':
detecStream = param.value()
elif param.name() == 'detecLocid':
detecLocid = param.value()
if detecStream is None:
seiscomp.logging.warning(
'could not find detecStream for %s.%s' % (net, sta))
continue
loc = inv.getSensorLocation(net, sta, detecLocid, now)
if loc is None:
seiscomp.logging.warning(
'could not find preferred location for %s.%s' % (net, sta))
continue
components = {}
tc = seiscomp.datamodel.ThreeComponents()
seiscomp.datamodel.getThreeComponents(tc, loc, detecStream[:2], now)
if tc.vertical():
cha = tc.vertical()
wfsID = seiscomp.datamodel.WaveformStreamID(net, sta, loc.code(),
cha.code(), '')
components[cha.code()[-1]] = wfsID
seiscomp.logging.debug('add stream %s (vertical)' % wfs2Str(wfsID))
if tc.firstHorizontal():
cha = tc.firstHorizontal()
wfsID = seiscomp.datamodel.WaveformStreamID(net, sta, loc.code(),
cha.code(), '')
components[cha.code()[-1]] = wfsID
seiscomp.logging.debug('add stream %s (first horizontal)' % wfs2Str(wfsID))
if tc.secondHorizontal():
cha = tc.secondHorizontal()
wfsID = seiscomp.datamodel.WaveformStreamID(net, sta, loc.code(),
cha.code(), '')
components[cha.code()[-1]] = wfsID
seiscomp.logging.debug('add stream %s (second horizontal)' % wfs2Str(wfsID))
if len(components) > 0:
self.streams[sta] = components
return
# fallback loading streams from inventory
seiscomp.logging.warning(
'no configuration module available, loading streams '
'from inventory and selecting first available stream '
'matching epoch')
for iNet in range(inv.inventory().networkCount()):
net = inv.inventory().network(iNet)
seiscomp.logging.debug('network %s: loaded %i stations' % (net.code(), net.stationCount()))
for iSta in range(net.stationCount()):
sta = net.station(iSta)
try:
start = sta.start()
if not start <= now:
continue
except:
continue
try:
end = sta.end()
if not now <= end:
continue
except:
pass
for iLoc in range(sta.sensorLocationCount()):
loc = sta.sensorLocation(iLoc)
for iCha in range(loc.streamCount()):
cha = loc.stream(iCha)
wfsID = seiscomp.datamodel.WaveformStreamID(net.code(),
sta.code(), loc.code(), cha.code(), '')
comp = cha.code()[2]
if sta.code() not in self.streams:
components = {}
components[comp] = wfsID
self.streams[sta.code()] = components
else:
# Seismic Handler does not support network,
# location and channel code: make sure network and
# location codes match first item in station
# specific steam list
oldWfsID = list(self.streams[sta.code()].values())[0]
if net.code() != oldWfsID.networkCode() or \
loc.code() != oldWfsID.locationCode() or \
cha.code()[:2] != oldWfsID.channelCode()[:2]:
seiscomp.logging.warning(
'ambiguous stream id found for station %s, ignoring %s'
% (sta.code(), wfs2Str(wfsID)))
continue
self.streams[sta.code()][comp] = wfsID
seiscomp.logging.debug('add stream %s' % wfs2Str(wfsID))
###########################################################################
def parseTime(self, timeStr):
time = seiscomp.core.Time()
for fmt in TimeFormats:
if time.fromString(timeStr, fmt):
break
return time
###########################################################################
def parseMagType(self, value):
if value == 'm':
return 'M'
elif value == 'ml':
return 'ML'
elif value == 'mb':
return 'mb'
elif value == 'ms':
return 'Ms(BB)'
elif value == 'mw':
return 'Mw'
elif value == 'bb':
return 'mB'
return ''
###########################################################################
def sh2proc(self, file):
ep = seiscomp.datamodel.EventParameters()
origin = seiscomp.datamodel.Origin.Create()
event = seiscomp.datamodel.Event.Create()
origin.setCreationInfo(seiscomp.datamodel.CreationInfo())
origin.creationInfo().setCreationTime(seiscomp.core.Time.GMT())
originQuality = None
originCE = None
latFound = False
lonFound = False
depthError = None
originComments = {}
# variables, reset after 'end of phase'
pick = None
stationMag = None
staCode = None
compCode = None
stationMagBB = None
amplitudeDisp = None
amplitudeVel = None
amplitudeSNR = None
amplitudeBB = None
magnitudeMB = None
magnitudeML = None
magnitudeMS = None
magnitudeBB = None
km2degFac = 1.0 / seiscomp.math.deg2km(1.0)
# read file line by line, split key and value at colon
iLine = 0
for line in file:
iLine += 1
a = line.split(':', 1)
key = a[0].strip()
keyLower = key.lower()
value = None
# empty line
if len(keyLower) == 0:
continue
# end of phase
elif keyLower == '--- end of phase ---':
if pick is None:
seiscomp.logging.warning(
'Line %i: found empty phase block' % iLine)
continue
if staCode is None or compCode is None:
seiscomp.logging.warning(
'Line %i: end of phase, stream code incomplete' % iLine)
continue
if not staCode in self.streams:
seiscomp.logging.warning(
'Line %i: end of phase, station code %s not found in inventory' % (iLine, staCode))
continue
if not compCode in self.streams[staCode]:
seiscomp.logging.warning(
'Line %i: end of phase, component %s of station %s not found in inventory' % (iLine, compCode, staCode))
continue
streamID = self.streams[staCode][compCode]
pick.setWaveformID(streamID)
ep.add(pick)
arrival.setPickID(pick.publicID())
arrival.setPhase(phase)
origin.add(arrival)
if amplitudeSNR is not None:
amplitudeSNR.setPickID(pick.publicID())
amplitudeSNR.setWaveformID(streamID)
ep.add(amplitudeSNR)
if amplitudeBB is not None:
amplitudeBB.setPickID(pick.publicID())
amplitudeBB.setWaveformID(streamID)
ep.add(amplitudeBB)
if stationMagBB is not None:
stationMagBB.setWaveformID(streamID)
origin.add(stationMagBB)
stationMagContrib = seiscomp.datamodel.StationMagnitudeContribution()
stationMagContrib.setStationMagnitudeID(
stationMagBB.publicID())
if magnitudeBB is None:
magnitudeBB = seiscomp.datamodel.Magnitude.Create()
magnitudeBB.add(stationMagContrib)
if stationMag is not None:
if stationMag.type() in ['mb', 'ML'] and amplitudeDisp is not None:
amplitudeDisp.setPickID(pick.publicID())
amplitudeDisp.setWaveformID(streamID)
amplitudeDisp.setPeriod(
seiscomp.datamodel.RealQuantity(ampPeriod))
amplitudeDisp.setType(stationMag.type())
ep.add(amplitudeDisp)
if stationMag.type() in ['Ms(BB)'] and amplitudeVel is not None:
amplitudeVel.setPickID(pick.publicID())
amplitudeVel.setWaveformID(streamID)
amplitudeVel.setPeriod(
seiscomp.datamodel.RealQuantity(ampPeriod))
amplitudeVel.setType(stationMag.type())
ep.add(amplitudeVel)
stationMag.setWaveformID(streamID)
origin.add(stationMag)
stationMagContrib = seiscomp.datamodel.StationMagnitudeContribution()
stationMagContrib.setStationMagnitudeID(
stationMag.publicID())
magType = stationMag.type()
if magType == 'ML':
if magnitudeML is None:
magnitudeML = seiscomp.datamodel.Magnitude.Create()
magnitudeML.add(stationMagContrib)
elif magType == 'Ms(BB)':
if magnitudeMS is None:
magnitudeMS = seiscomp.datamodel.Magnitude.Create()
magnitudeMS.add(stationMagContrib)
elif magType == 'mb':
if magnitudeMB is None:
magnitudeMB = seiscomp.datamodel.Magnitude.Create()
magnitudeMB.add(stationMagContrib)
pick = None
staCode = None
compCode = None
stationMag = None
stationMagBB = None
amplitudeDisp = None
amplitudeVel = None
amplitudeSNR = None
amplitudeBB = None
continue
# empty key
elif len(a) == 1:
seiscomp.logging.warning('Line %i: key without value' % iLine)
continue
value = a[1].strip()
if pick is None:
pick = seiscomp.datamodel.Pick.Create()
arrival = seiscomp.datamodel.Arrival()
try:
##############################################################
# station parameters
# station code
if keyLower == 'station code':
staCode = value
# pick time
elif keyLower == 'onset time':
pick.setTime(seiscomp.datamodel.TimeQuantity(self.parseTime(value)))
# pick onset type
elif keyLower == 'onset type':
found = False
for onset in [seiscomp.datamodel.EMERGENT, seiscomp.datamodel.IMPULSIVE,
seiscomp.datamodel.QUESTIONABLE]:
if value == seiscomp.datamodel.EPickOnsetNames_name(onset):
pick.setOnset(onset)
found = True
break
if not found:
raise Exception('Unsupported onset value')
# phase code
elif keyLower == 'phase name':
phase = seiscomp.datamodel.Phase()
phase.setCode(value)
pick.setPhaseHint(phase)
# event type
elif keyLower == 'event type':
evttype = EventTypes[value]
event.setType(evttype)
originComments[key] = value
# filter ID
elif keyLower == 'applied filter':
pick.setFilterID(value)
# channel code, prepended by configured Channel prefix if only
# one character is found
elif keyLower == 'component':
compCode = value
# pick evaluation mode
elif keyLower == 'pick type':
found = False
for mode in [seiscomp.datamodel.AUTOMATIC, seiscomp.datamodel.MANUAL]:
if value == seiscomp.datamodel.EEvaluationModeNames_name(mode):
pick.setEvaluationMode(mode)
found = True
break
if not found:
raise Exception('Unsupported evaluation mode value')
# pick author
elif keyLower == 'analyst':
creationInfo = seiscomp.datamodel.CreationInfo()
creationInfo.setAuthor(value)
pick.setCreationInfo(creationInfo)
# pick polarity
# isn't tested
elif keyLower == 'sign':
if value == 'positive':
sign = '0' # positive
elif value == 'negative':
sign = '1' # negative
else:
sign = '2' # unknown
pick.setPolarity(float(sign))
# arrival weight
elif keyLower == 'weight':
arrival.setWeight(float(value))
# arrival azimuth
elif keyLower == 'theo. azimuth (deg)':
arrival.setAzimuth(float(value))
# pick theo backazimuth
elif keyLower == 'theo. backazimuth (deg)':
if pick.slownessMethodID() == 'corrected':
seiscomp.logging.debug('Line %i: ignoring parameter: %s' % (iLine, key))
else:
pick.setBackazimuth(
seiscomp.datamodel.RealQuantity(float(value)))
pick.setSlownessMethodID('theoretical')
# pick beam slowness
elif keyLower == 'beam-slowness (sec/deg)':
if pick.slownessMethodID() == 'corrected':
seiscomp.logging.debug('Line %i: ignoring parameter: %s' % (iLine, key))
else:
pick.setHorizontalSlowness(
seiscomp.datamodel.RealQuantity(float(value)))
pick.setSlownessMethodID('Array Beam')
# pick beam backazimuth
elif keyLower == 'beam-azimuth (deg)':
if pick.slownessMethodID() == 'corrected':
seiscomp.logging.debug('Line %i: ignoring parameter: %s' % (iLine, key))
else:
pick.setBackazimuth(
seiscomp.datamodel.RealQuantity(float(value)))
# pick epi slowness
elif keyLower == 'epi-slowness (sec/deg)':
pick.setHorizontalSlowness(
seiscomp.datamodel.RealQuantity(float(value)))
pick.setSlownessMethodID('corrected')
# pick epi backazimuth
elif keyLower == 'epi-azimuth (deg)':
pick.setBackazimuth(seiscomp.datamodel.RealQuantity(float(value)))
# arrival distance degree
elif keyLower == 'distance (deg)':
arrival.setDistance(float(value))
# arrival distance km, recalculates for degree
elif keyLower == 'distance (km)':
if isinstance(arrival.distance(), float):
seiscomp.logging.debug('Line %i: ignoring parameter: %s' % (iLine-1, 'distance (deg)'))
arrival.setDistance(float(value) * km2degFac)
# arrival time residual
elif keyLower == 'residual time':
arrival.setTimeResidual(float(value))
# amplitude snr
elif keyLower == 'signal/noise':
amplitudeSNR = seiscomp.datamodel.Amplitude.Create()
amplitudeSNR.setType('SNR')
amplitudeSNR.setAmplitude(
seiscomp.datamodel.RealQuantity(float(value)))
# amplitude period
elif keyLower.startswith('period'):
ampPeriod = float(value)
# amplitude value for displacement
elif keyLower == 'amplitude (nm)':
amplitudeDisp = seiscomp.datamodel.Amplitude.Create()
amplitudeDisp.setAmplitude(
seiscomp.datamodel.RealQuantity(float(value)))
amplitudeDisp.setUnit('nm')
# amplitude value for velocity
elif keyLower.startswith('vel. amplitude'):
amplitudeVel = seiscomp.datamodel.Amplitude.Create()
amplitudeVel.setAmplitude(
seiscomp.datamodel.RealQuantity(float(value)))
amplitudeVel.setUnit('nm/s')
elif keyLower == 'bb amplitude (nm/sec)':
amplitudeBB = seiscomp.datamodel.Amplitude.Create()
amplitudeBB.setAmplitude(
seiscomp.datamodel.RealQuantity(float(value)))
amplitudeBB.setType('mB')
amplitudeBB.setUnit('nm/s')
amplitudeBB.setPeriod(seiscomp.datamodel.RealQuantity(ampBBPeriod))
elif keyLower == 'bb period (sec)':
ampBBPeriod = float(value)
elif keyLower == 'broadband magnitude':
magType = self.parseMagType('bb')
stationMagBB = seiscomp.datamodel.StationMagnitude.Create()
stationMagBB.setMagnitude(
seiscomp.datamodel.RealQuantity(float(value)))
stationMagBB.setType(magType)
stationMagBB.setAmplitudeID(amplitudeBB.publicID())
# ignored
elif keyLower == 'quality number':
seiscomp.logging.debug('Line %i: ignoring parameter: %s' % (iLine, key))
# station magnitude value and type
elif keyLower.startswith('magnitude '):
magType = self.parseMagType(key[10:])
stationMag = seiscomp.datamodel.StationMagnitude.Create()
stationMag.setMagnitude(
seiscomp.datamodel.RealQuantity(float(value)))
if len(magType) > 0:
stationMag.setType(magType)
if magType == 'mb':
stationMag.setAmplitudeID(amplitudeDisp.publicID())
elif magType == 'MS(BB)':
stationMag.setAmplitudeID(amplitudeVel.publicID())
else:
seiscomp.logging.debug('Line %i: Magnitude Type not known %s.' % (iLine, magType))
###############################################################
# origin parameters
# event ID, added as origin comment later on
elif keyLower == 'event id':
originComments[key] = value
# magnitude value and type
elif keyLower == 'mean bb magnitude':
magType = self.parseMagType('bb')
if magnitudeBB is None:
magnitudeBB = seiscomp.datamodel.Magnitude.Create()
magnitudeBB.setMagnitude(
seiscomp.datamodel.RealQuantity(float(value)))
magnitudeBB.setType(magType)
elif keyLower.startswith('mean magnitude '):
magType = self.parseMagType(key[15:])
if magType == 'ML':
if magnitudeML is None:
magnitudeML = seiscomp.datamodel.Magnitude.Create()
magnitudeML.setMagnitude(
seiscomp.datamodel.RealQuantity(float(value)))
magnitudeML.setType(magType)
elif magType == 'Ms(BB)':
if magnitudeMS is None:
magnitudeMS = seiscomp.datamodel.Magnitude.Create()
magnitudeMS.setMagnitude(
seiscomp.datamodel.RealQuantity(float(value)))
magnitudeMS.setType(magType)
elif magType == 'mb':
if magnitudeMB is None:
magnitudeMB = seiscomp.datamodel.Magnitude.Create()
magnitudeMB.setMagnitude(
seiscomp.datamodel.RealQuantity(float(value)))
magnitudeMB.setType(magType)
else:
seiscomp.logging.warning('Line %i: Magnitude type %s not defined yet.' % (iLine, magType))
# latitude
elif keyLower == 'latitude':
origin.latitude().setValue(float(value))
latFound = True
elif keyLower == 'error in latitude (km)':
origin.latitude().setUncertainty(float(value))
# longitude
elif keyLower == 'longitude':
origin.longitude().setValue(float(value))
lonFound = True
elif keyLower == 'error in longitude (km)':
origin.longitude().setUncertainty(float(value))
# depth
elif keyLower == 'depth (km)':
origin.setDepth(seiscomp.datamodel.RealQuantity(float(value)))
if depthError is not None:
origin.depth().setUncertainty(depthError)
elif keyLower == 'depth type':
seiscomp.logging.debug('Line %i: ignoring parameter: %s' % (iLine, key))
elif keyLower == 'error in depth (km)':
depthError = float(value)
try:
origin.depth().setUncertainty(depthError)
except seiscomp.core.ValueException:
pass
# time
elif keyLower == 'origin time':
origin.time().setValue(self.parseTime(value))
elif keyLower == 'error in origin time':
origin.time().setUncertainty(float(value))
# location method
elif keyLower == 'location method':
origin.setMethodID(str(value))
# region table, added as origin comment later on
elif keyLower == 'region table':
originComments[key] = value
# region table, added as origin comment later on
elif keyLower == 'region id':
originComments[key] = value
# source region, added as origin comment later on
elif keyLower == 'source region':
originComments[key] = value
# used station count
elif keyLower == 'no. of stations used':
if originQuality is None:
originQuality = seiscomp.datamodel.OriginQuality()
originQuality.setUsedStationCount(int(value))
# ignored
elif keyLower == 'reference location name':
seiscomp.logging.debug('Line %i: ignoring parameter: %s' % (iLine, key))
# confidence ellipsoid major axis
elif keyLower == 'error ellipse major':
if originCE is None:
originCE = seiscomp.datamodel.ConfidenceEllipsoid()
originCE.setSemiMajorAxisLength(float(value))
# confidence ellipsoid minor axis
elif keyLower == 'error ellipse minor':
if originCE is None:
originCE = seiscomp.datamodel.ConfidenceEllipsoid()
originCE.setSemiMinorAxisLength(float(value))
# confidence ellipsoid rotation
elif keyLower == 'error ellipse strike':
if originCE is None:
originCE = seiscomp.datamodel.ConfidenceEllipsoid()
originCE.setMajorAxisRotation(float(value))
# azimuthal gap
elif keyLower == 'max azimuthal gap (deg)':
if originQuality is None:
originQuality = seiscomp.datamodel.OriginQuality()
originQuality.setAzimuthalGap(float(value))
# creation info author
elif keyLower == 'author':
origin.creationInfo().setAuthor(value)
# creation info agency
elif keyLower == 'source of information':
origin.creationInfo().setAgencyID(value)
# earth model id
elif keyLower == 'velocity model':
origin.setEarthModelID(value)
# standard error
elif keyLower == 'rms of residuals (sec)':
if originQuality is None:
originQuality = seiscomp.datamodel.OriginQuality()
originQuality.setStandardError(float(value))
# ignored
elif keyLower == 'phase flags':
seiscomp.logging.debug('Line %i: ignoring parameter: %s' % (iLine, key))
# ignored
elif keyLower == 'location input params':
seiscomp.logging.debug('Line %i: ignoring parameter: %s' % (iLine, key))
# missing keys
elif keyLower == 'ampl&period source':
seiscomp.logging.debug('Line %i: ignoring parameter: %s' % (iLine, key))
elif keyLower == 'location quality':
seiscomp.logging.debug('Line %i: ignoring parameter: %s' % (iLine, key))
elif keyLower == 'reference latitude':
seiscomp.logging.debug('Line %i: ignoring parameter: %s' % (iLine, key))
elif keyLower == 'reference longitude':
seiscomp.logging.debug('Line %i: ignoring parameter: %s' % (iLine, key))
elif keyLower.startswith('amplitude time'):
seiscomp.logging.debug('Line %i: ignoring parameter: %s' % (iLine, key))
# unknown key
else:
seiscomp.logging.warning('Line %i: ignoring unknown parameter: %s' % (iLine, key))
except ValueError as ve:
seiscomp.logging.warning('Line %i: can not parse %s value' % (iLine, key))
except Exception:
seiscomp.logging.error('Line %i: %s' % (iLine, str(traceback.format_exc())))
return None
# check
if not latFound:
seiscomp.logging.warning('could not add origin, missing latitude parameter')
elif not lonFound:
seiscomp.logging.warning('could not add origin, missing longitude parameter')
elif not origin.time().value().valid():
seiscomp.logging.warning('could not add origin, missing origin time parameter')
else:
if magnitudeMB is not None:
origin.add(magnitudeMB)
if magnitudeML is not None:
origin.add(magnitudeML)
if magnitudeMS is not None:
origin.add(magnitudeMS)
if magnitudeBB is not None:
origin.add(magnitudeBB)
ep.add(event)
ep.add(origin)
if originQuality is not None:
origin.setQuality(originQuality)
if originCE is not None:
uncertainty = seiscomp.datamodel.OriginUncertainty()
uncertainty.setConfidenceEllipsoid(originCE)
origin.setUncertainty(uncertainty)
for k, v in originComments.items():
comment = seiscomp.datamodel.Comment()
comment.setId(k)
comment.setText(v)
origin.add(comment)
return ep
###########################################################################
def run(self):
self.loadStreams()
try:
if self.inputFile == '-':
f = sys.stdin
else:
f = open(self.inputFile)
except IOError as e:
seiscomp.logging.error(str(e))
return False
ep = self.sh2proc(f)
if ep is None:
return False
ar = seiscomp.io.XMLArchive()
ar.create('-')
ar.setFormattedOutput(True)
ar.writeObject(ep)
ar.close()
return True
###############################################################################
def main():
try:
app = SH2Proc()
return app()
except:
sys.stderr.write(str(traceback.format_exc()))
return 1
if __name__ == '__main__':
sys.exit(main())
# vim: ts=4 et

Binary file not shown.

Binary file not shown.

@ -0,0 +1,483 @@
#!/usr/bin/env seiscomp-python
from __future__ import print_function
from getopt import getopt, GetoptError
from time import time, gmtime
from datetime import datetime
import os, sys, signal, glob, re
from seiscomp.myconfig import MyConfig
import seiscomp.slclient
import seiscomp.kernel, seiscomp.config
usage_info = """
Usage:
slmon [options]
SeedLink monitor creating static web pages
Options:
-h, --help display this help message
-c ini_setup = arg
-s ini_stations = arg
-t refresh = float(arg) # XXX not yet used
-v verbose = 1
Examples:
Start slmon from the command line
slmon -c $SEISCOMP_ROOT/var/lib/slmon/config.ini
Restart slmon in order to update the web pages. Use crontab entries for
automatic restart, e.g.:
*/3 * * * * /home/sysop/seiscomp/bin/seiscomp check slmon >/dev/null 2>&1
"""
def usage(exitcode=0):
sys.stderr.write(usage_info)
exit(exitcode)
try:
seiscompRoot=os.environ["SEISCOMP_ROOT"]
except:
print("\nSEISCOMP_ROOT must be defined - EXIT\n", file=sys.stderr)
usage(exitcode=2)
ini_stations = os.path.join(seiscompRoot,'var/lib/slmon/stations.ini')
ini_setup = os.path.join(seiscompRoot,'var/lib/slmon/config.ini')
regexStreams = re.compile("[SLBVEH][HNLG][ZNE123]")
verbose = 0
class Module(seiscomp.kernel.Module):
def __init__(self, env):
seiscomp.kernel.Module.__init__(self, env, env.moduleName(__file__))
def printCrontab(self):
print("3 * * * * %s/bin/seiscomp check slmon >/dev/null 2>&1" % (self.env.SEISCOMP_ROOT))
class Status:
def __repr__(self):
return "%2s %-5s %2s %3s %1s %s %s" % \
(self.net, self.sta, self.loc, self.cha, self.typ, \
str(self.last_data), str(self.last_feed))
class StatusDict(dict):
def __init__(self, source=None):
if source:
self.read(source)
def fromSlinkTool(self,server="",stations=["GE_MALT","GE_MORC","GE_IBBN"]):
# later this shall use XML
cmd = "slinktool -nd 10 -nt 10 -Q %s" % server
print(cmd)
f = os.popen(cmd)
# regex = re.compile("[SLBVEH][HNLG][ZNE123]")
regex = regexStreams
for line in f:
net_sta = line[:2].strip() + "_" + line[3:8].strip()
if not net_sta in stations:
continue
typ = line[16]
if typ != "D":
continue
cha = line[12:15].strip()
if not regex.match(cha):
continue
d = Status()
d.net = line[ 0: 2].strip()
d.sta = line[ 3: 8].strip()
d.loc = line[ 9:11].strip()
d.cha = line[12:15]
d.typ = line[16]
d.last_data = seiscomp.slclient.timeparse(line[47:70])
d.last_feed = d.last_data
sec = "%s_%s" % (d.net, d.sta)
sec = "%s.%s.%s.%s.%c" % (d.net, d.sta, d.loc, d.cha, d.typ)
self[sec] = d
def read(self, source):
if type(source) == str:
source = file(source)
if type(source) == file:
source = source.readlines()
if type(source) != list:
raise TypeError('cannot read from %s' % str(type(source)))
for line in source:
d = Status()
d.net = line[ 0: 2]
d.sta = line[ 3: 8].strip()
d.loc = line[ 9:11].strip()
d.cha = line[12:15]
d.typ = line[16]
d.last_data = seiscomp.slclient.timeparse(line[18:41])
d.last_feed = seiscomp.slclient.timeparse(line[42:65])
if d.last_feed < d.last_data:
d.last_feed = d.last_data
sec = "%s_%s:%s.%s.%c" % (d.net, d.sta, d.loc, d.cha, d.typ)
self[sec] = d
def write(self, f):
if type(f) is str:
f = file(f, "w")
lines = []
for key in list(self.keys()):
lines.append(str(self[key]))
lines.sort()
f.write('\n'.join(lines)+'\n')
def colorLegend(htmlfile):
htmlfile.write("<p><center>Latencies:<br>\n" \
"<table cellpadding='2' cellspacing='1' border='0'" \
" bgcolor='#000000'>\n<tr>\n" \
"<td bgcolor='#cc99ff'>&nbsp;&lt;30 m&nbsp;</td>\n" \
"<td bgcolor='#3399ff'>&nbsp;&lt; 1 h&nbsp;</td>\n" \
"<td bgcolor='#00ff00'>&nbsp;&lt; 2 h&nbsp;</td>\n" \
"<td bgcolor='#ffff00'>&nbsp;&lt; 6 h&nbsp;</td>\n" \
"<td bgcolor='#ff9966'>&nbsp;&lt; 1 d&nbsp;</td>\n" \
"<td bgcolor='#ff3333'>&nbsp;&lt; 2 d&nbsp;</td>\n" \
"<td bgcolor='#ffcccc'>&nbsp;&lt; 3 d&nbsp;</td>\n" \
"<td bgcolor='#cccccc'>&nbsp;&lt; 4 d&nbsp;</td>\n" \
"<td bgcolor='#999999'>&nbsp;&lt; 5 d&nbsp;</td>\n" \
"<td bgcolor='#666666'>&nbsp;&gt; 5 d&nbsp;</td>\n" \
"</tr>\n</table>\n</center></p>\n")
# encodes an email address so that it cannot (easily) be extracted
# from the web page. This is meant to be a spam protection.
def encode(txt): return ''.join(["&#%d;" % ord(c) for c in txt])
def total_seconds(td): return td.seconds + (td.days*86400)
def pageTrailer(htmlfile, config):
htmlfile.write("<hr>\n" \
"<table width='99%%' cellpaddding='2' cellspacing='1' border='0'>\n" \
"<tr>\n<td>Last updated %04d/%02d/%02d %02d:%02d:%02d UTC</td>\n" \
" <td align='right'><a href='%s' " \
"target='_top'>%s</a></td>\n</tr>\n" \
"</table>\n</body></html>\n" % (gmtime()[:6] + (config['setup']['linkurl'],) + (config['setup']['linkname'],)) )
def getColor(delta):
delay = total_seconds(delta)
if delay >432000: return '#666666'
if delay >345600: return '#999999'
if delay >259200: return '#cccccc'
if delay >172800: return '#ffcccc'
if delay > 86400: return '#ff3333'
elif delay > 21600: return '#ff9966'
elif delay > 7200: return '#ffff00'
elif delay > 3600: return '#00ff00'
elif delay > 1800: return '#3399ff'
else: return '#cc99ff'
TDdummy = "<td align='center' bgcolor='%s'><tt>n/a</tt></td>"
def TDf(delta, col="#ffffff"):
if delta is None: return TDdummy % col
t = total_seconds(delta)
if t > 86400: x = "%.1f d" % (t/86400.)
elif t > 7200: x = "%.1f h" % (t/3600.)
elif t > 120: x = "%.1f m" % (t/60.)
else: x = "%.1f s" % (t)
return "<td align='right' bgcolor='%s'><tt> &nbsp;%s&nbsp;</tt></td>" % \
(col,x)
def TDt(t, col="#ffffff"):
if t is None: return TDdummy % col
x = t.strftime("%Y/%m/%d %H:%M:%S")
return "<td align='center' bgcolor='%s'><tt>&nbsp;%s&nbsp;</tt></td>" % \
(col,x)
def myrename(name1, name2):
# fault-tolerant rename that doesn't cause an exception if it fails, which
# may happen e.g. if the target is on a non-reachable NFS directory
try:
os.rename(name1, name2)
except OSError:
print("failed to rename(%s,%s)" % (name1, name2), file=sys.stderr)
def makeMainHTML(config):
global status
now = datetime.utcnow()
stations = []
streams = [ x for x in list(status.keys()) if regexStreams.search(x) ]
streams.sort()
tmp_rt = []
tmp_du = []
for label in streams:
lat1 = now - status[label].last_data # XXX
lat2 = now - status[label].last_feed # XXX
lat3 = lat1-lat2 # XXX
if lat3 == 0.: lat3 = lat2 = None
if label[-2]=='.' and label[-1] in "DE":
label = label[:-2]
n,s,x,x = label.split(".")
if s in stations: continue # avoid duplicates for different locations
stations.append(s)
net_sta = "%s_%s" % (n,s)
line = "<tr bgcolor='#ffffff'><td><tt>&nbsp;%s <a " \
"href='%s.html'>%s</a>&nbsp;</td>%s%s%s</tr>" \
% (n, net_sta, s, TDf(lat1, getColor(lat1)),
TDf(lat2, getColor(lat2)),
TDf(lat3, getColor(lat3)))
if config.station[net_sta]['type'][:4] == 'real':
tmp_rt.append(line)
else: tmp_du.append(line)
makeStatHTML(net_sta, config)
try: os.makedirs(config['setup']['wwwdir'])
except: pass
temp = "%s/tmp.html" % config['setup']['wwwdir']
dest = "%s/index.html" % config['setup']['wwwdir']
table_begin = """
<table cellpaddding='2' cellspacing='1' border='0' bgcolor='#000000'>
<tr>
<th bgcolor='#ffffff' rowspan='2' align='center'>Station</th>
<th bgcolor='#ffffff' colspan='3' align='center'>Latencies</th>
</tr>
<tr>
<th bgcolor='#ffffff' align='center'>Data</th>
<th bgcolor='#ffffff' align='center'>Feed</th>
<th bgcolor='#ffffff' align='center'>Diff.</th>
</tr>
"""
table_end = """
</table>
"""
htmlfile = open(temp, "w")
htmlfile.write("""<html>
<head>
<title>%s</title>
<meta http-equiv='refresh' content='%d'>
<link rel='SHORTCUT ICON' href='%s'>
</head>
<body bgcolor='#ffffff'>
<center><font size='+2'>%s</font></center>\n""" % \
( config['setup']['title'], int(config['setup']['refresh']),
config['setup']['icon'], config['setup']['title']))
htmlfile.write("<center><table cellpaddding='5' cellspacing='5'><tr>\n")
if len(tmp_rt):
htmlfile.write("<td valign='top' align='center'>\n" \
"<font size='+1'>Real-time stations<font>\n</td>\n")
if len(tmp_du):
htmlfile.write("<td valign='top' align='center'>\n" \
"<font size='+1'>Dial-up stations<font>\n</td>\n")
htmlfile.write("</tr><tr>")
if len(tmp_rt):
htmlfile.write("<td valign='top' align='center'>\n")
htmlfile.write(table_begin)
htmlfile.write("\n".join(tmp_rt))
htmlfile.write(table_end)
htmlfile.write("</td>\n")
if len(tmp_du):
htmlfile.write("<td valign='top' align='center'>\n")
htmlfile.write(table_begin)
htmlfile.write("\n".join(tmp_du))
htmlfile.write(table_end)
htmlfile.write("</td>\n")
htmlfile.write("</tr></table></center>\n")
colorLegend(htmlfile)
pageTrailer(htmlfile, config)
htmlfile.close()
myrename(temp, dest)
def makeStatHTML(net_sta, config):
global status
try: os.makedirs(config['setup']['wwwdir'])
except: pass
temp = "%s/tmp2.html" % config['setup']['wwwdir']
dest = "%s/%s.html" % ( config['setup']['wwwdir'], net_sta)
htmlfile = open(temp, "w")
htmlfile.write("""<html>
<head>
<title>%s - Station %s</title>
<meta http-equiv='refresh' content='%d'>
<link rel='SHORTCUT ICON' href='%s'>
</head>
<body bgcolor='#ffffff'>
<center><font size='+2'>%s - Station %s</font>\n""" % \
( config['setup']['title'], net_sta, int(config['setup']['refresh']),
config['setup']['icon'],
config['setup']['title'], net_sta.split("_")[-1]))
try:
name = config.station[net_sta]['info']
htmlfile.write("<br><font size='+1'>%s</font>" % name)
except: pass
htmlfile.write("</center>\n")
if 'text' in config.station[net_sta]:
htmlfile.write("<P>%s</P>\n" % config.station[net_sta]['text'])
htmlfile.write("""<p><center>
<table cellpadding='2' cellspacing='1' border='0' bgcolor='#000000'>
<tr>
<th bgcolor='#ffffff' align='center' rowspan='2'>Station/<br>Channel</th>
<th bgcolor='#ffffff' align='center' colspan='2'>Data</th>
<th bgcolor='#ffffff' align='center' colspan='2'>Feed</th>
<th bgcolor='#ffffff' align='center' rowspan='2'>Diff.</th>
</tr>
<tr>
<th bgcolor='#ffffff' align='center'>Last Sample</th>
<th bgcolor='#ffffff' align='center'>Latency</th>
<th bgcolor='#ffffff' align='center'>Last Received</th>
<th bgcolor='#ffffff' align='center'>Latency</th>
</tr>""")
now = datetime.utcnow()
netsta2=net_sta.replace("_",".")
streams = [ x for x in list(status.keys()) if x.find(netsta2)==0 ]
streams.sort()
for label in streams:
tim1 = status[label].last_data
tim2 = status[label].last_feed
lat1, lat2, lat3 = now-tim1, now-tim2, tim2-tim1
col1, col2, col3 = getColor(lat1), getColor(lat2), getColor(lat3)
if lat1==lat2: lat2 = lat3 = None
if label[-2]=='.' and label[-1] in "DE":
label = label[:-2]
n,s,loc,c = label.split(".")
c = ("%s.%s" % (loc,c)).strip(".")
htmlfile.write("<tr bgcolor='#ffffff'><td>" \
"<tt>&nbsp;%s %s&nbsp;</td>%s%s%s%s%s</tr>\n" \
% (s, c, TDt(tim1, col1), TDf(lat1, col1),
TDt(tim2, col2), TDf(lat2, col2),
TDf(lat3, col3)))
htmlfile.write("</table></p>\n")
colorLegend(htmlfile)
htmlfile.write("<p>\nHow to <a href='http://geofon.gfz-potsdam.de/waveform/status/latency.php' target='_blank'>interpret</a> " \
"these numbers?<br>\n")
if 'liveurl' in config['setup']:
# substitute '%s' in live_url by station name
url = config['setup']['liveurl'] % s
htmlfile.write("View a <a href='%s' target='_blank'>live seismogram</a> of "
"station %s</center>\n" % (url, s))
htmlfile.write("</p>\n")
pageTrailer(htmlfile, config)
htmlfile.close()
myrename(temp, dest)
def read_ini():
global config, ini_setup, ini_stations
print("\nreading setup config from '%s'" % ini_setup)
if not os.path.isfile(ini_setup):
print("[error] setup config '%s' does not exist" % ini_setup, file=sys.stderr)
usage(exitcode=2)
config = MyConfig(ini_setup)
print("reading station config from '%s'" % ini_stations)
if not os.path.isfile(ini_stations):
print("[error] station config '%s' does not exist" % ini_stations, file=sys.stderr)
usage(exitcode=2)
config.station = MyConfig(ini_stations)
def SIGINT_handler(signum, frame):
global status
print("received signal #%d => will write status file and exit" % signum)
# status.write("status.tab")
sys.exit(0)
try:
opts, args = getopt(sys.argv[1:], "c:s:t:hv")
except GetoptError:
print("\nUnknown option in "+str(sys.argv[1:])+" - EXIT.", file=sys.stderr)
usage(exitcode=2)
for flag, arg in opts:
if flag == "-c": ini_setup = arg
if flag == "-s": ini_stations = arg
if flag == "-t": refresh = float(arg) # XXX not yet used
if flag == "-h": usage(exitcode=0)
if flag == "-v": verbose = 1
signal.signal(signal.SIGHUP, SIGINT_handler)
signal.signal(signal.SIGINT, SIGINT_handler)
signal.signal(signal.SIGQUIT, SIGINT_handler)
signal.signal(signal.SIGTERM, SIGINT_handler)
read_ini()
cha = "???"
loc = ""
s = config.station
net_sta = ["%s_%s" % (s[k]['net'],s[k]['sta']) for k in s]
s_arg = ','.join(net_sta)
streams = [ (s[k]['net'],s[k]['sta'],loc,cha) for k in s ]
if 'server' in config['setup']:
server = config['setup']['server']
else: server = "localhost"
#def read_initial(config):
#
# for s in config.station:
# print s,glob.glob("/home/dcop/seedlink/%s/segments/*" % s)
# for f in glob.glob("/home/dcop/seedlink/%s/segments/*" % s):
# print f
#
#read_initial(config)
#print "reading initial time windows from file 'status.tab'"
#status = StatusDict("status.tab")
status = StatusDict()
#if verbose: status.write(sys.stderr)
print("generating output to '%s'" % config['setup']['wwwdir'])
print("getting initial time windows from SeedLink server '%s'" % server)
status.fromSlinkTool(server, stations=net_sta)
if verbose: status.write(sys.stderr)
nextTimeGenerateHTML = time()
print("setting up connection to SeedLink server '%s'" % server)
input = seiscomp.slclient.Input(server, streams)
for rec in input:
id = '.'.join([rec.net, rec.sta, rec.loc, rec.cha, rec.rectype])
# if not id in status: continue # XXX XXX XXX
try:
status[id].last_data = rec.end_time
status[id].last_feed = datetime.utcnow()
except:
continue
if time() > nextTimeGenerateHTML:
makeMainHTML(config)
nextTimeGenerateHTML = time() + int(config['setup']['refresh'])

@ -0,0 +1,88 @@
#!/usr/bin/env seiscomp-python
from __future__ import print_function
import sys
from optparse import OptionParser
from nettab.tab import Tab
import seiscomp.io
def main():
# Creating the parser
parser = OptionParser(usage="Tab to Inventory (sc3) converter", version="1.0", add_help_option=True)
parser.add_option("-i", "--ip", type="string",
help="Prefix to be added to each instrument generated.", dest="instrumentPrefix", default=None)
parser.add_option("-f", "--filterf", type="string",
help="Indicates a folder containing the filters coefficients files", dest="ffolder", default=None)
parser.add_option("-x", "--xmlf", type="string",
help="Indicates a folder containing the XML inventory files (needed for station group support)", dest="xfolder", default=None)
parser.add_option("-D", "--database", type="string",
help="Database URL for inventory (needed for station group support)", dest="database", default=None)
parser.add_option("", "--force", action="store_true",
help="Don't stop on error of individual files", dest="force", default=False)
parser.add_option("-g", "--generate", action="store_true",
help="Generate XML file at the end", dest="generate", default=False)
parser.add_option("-c", "--check", action="store_true",
help="Check the loaded files", dest="check", default=False)
parser.add_option("-d", "--default", type="string",
help="Indicates the default file", dest="defaultFile", default=None)
parser.add_option("-o", "--output", type="string",
help="Indicates the output file", dest="outFile", default="-")
# Parsing & Error check
(options, args) = parser.parse_args()
error = False
if len(args) < 1:
print("No input file(s) to digest", file=sys.stderr)
error = True
if error:
print("Use -h for help on usage", file=sys.stderr)
return 1
# Execution
try:
inv = None
t=Tab(options.instrumentPrefix, options.defaultFile, options.ffolder, options.xfolder, options.database)
for f in args:
try:
t.digest(f)
except Exception as e:
print("Error digesting %s:\n %s" % (f, e), file=sys.stderr)
if not options.force:
raise e
if options.check:
t.check()
return
if options.generate:
inv = t.sc3Obj()
if inv:
ar = seiscomp.io.XMLArchive()
print("Generating file: %s" % options.outFile, file=sys.stderr)
ar.create(options.outFile)
ar.setFormattedOutput(True)
ar.setCompression(False)
ar.writeObject(inv)
ar.close()
except Exception as e:
print("Error: " + str(e), file=sys.stderr)
return 1
finally:
print("Ending.", file=sys.stderr)
return 0
if __name__ == "__main__":
ret = main()
sys.exit(ret)

@ -0,0 +1,526 @@
#!/usr/bin/env seiscomp-python
from __future__ import print_function
import os
import sys
from datetime import datetime
from nettab.convertUtils import StationAttributes, NetworkAttributes, StationMappings, parseDate, formatDate, quote, hummanStr
from nettab.tab import Tab
from optparse import OptionParser
from nettab.nodesi import Instruments
class TabConverter:
def __init__(self, networkCode):
self.__fmt__ = None
self.takeSugestions = None
self.filename = None
self.networkCode = networkCode
self.stationList = None
self.nat = None
self.sat = None
self.sma = None
self.inst = None
self.defaultEpoch = parseDate("1980/001")
self.start=0
self.code=0
self.description=0
self.datalogger=0
self.sensor=0
self.channel=0
self.gaind = 0
self.longitude=0
self.latitude=0
self.elevation=0
self.end=0
self.depth=0
self.orientation=0
## default dates
self.startDate = parseDate("1980/001")
self.endDate = parseDate(None)
def loadStationMapping(self, filename):
if self.networkCode is None: raise Exception("Cannot load Station mapping without network code")
if self.stationList is None: raise Exception("Cannot load Station mapping without station list")
try:
sm = StationMappings(self.networkCode, self.stationList, filename)
self.sma = sm
except Exception as e:
raise e
def loadStationAttribute(self, filename):
if self.networkCode is None: raise Exception("Cannot load Station att without network code")
if self.stationList is None: raise Exception("Cannot load Station att without station list")
try:
sa = StationAttributes(self.networkCode, self.stationList, filename)
self.sat = sa
except Exception as e:
raise e
def loadNetworkAttribute(self, filename):
if self.networkCode is None: raise Exception("Cannot load Network att without network code")
if self.stationList is None: raise Exception("Cannot load Network att without station list")
try:
na = NetworkAttributes(self.networkCode, filename)
self.nat = na
except Exception as e:
raise e
def loadInstrumentsFile(self, filename, filterFolder):
tab = Tab(filterFolder=filterFolder)
tab.digest(filename)
if tab.i:
self.inst = tab.i
def __fmtline__(self):
if not self.__fmt__:
fmt = "Sl: "
fmt += "%%-%ds" % self.code
fmt += " %%-%ds" % self.description
fmt += " %%-%ds" % self.datalogger
fmt += " %%-%ds" % self.sensor
fmt += " %%-%ds" % self.channel
fmt += " %%-%ds" % self.orientation
fmt += " %%-%ds" % self.latitude
fmt += " %%-%ds" % self.longitude
fmt += " %%-%ds" % self.elevation
fmt += " %%-%ds" % self.depth
fmt += " %%-%ds" % self.start
fmt += " %%-%ds" % self.end
self.__fmt__ = fmt
return self.__fmt__
def __analyseLine__(self, items):
inputLine = " ".join(items)
if len(items) < 4:
raise Exception("Invalid items count on line %s" % inputLine)
if len(items) <= 5:
netCode = items[2]
if netCode != self.networkCode:
raise Exception("Tab file (%s) doesn't match class (%s) -- %s" % (netCode,self.networkCode,inputLine))
return [None, None, None]
else:
if len(items) < 6:
raise Exception("Invalid Station line %s" % inputLine)
stationCode = items.pop(0)
code = len(stationCode)
self.code=max(self.code,code)
description = len(quote(hummanStr(items.pop(0))))
self.description=max(self.description, description)
datalogger = len(items.pop(0))
self.datalogger=max(self.datalogger, datalogger)
sensor = len(items.pop(0))
self.sensor=max(self.sensor, sensor)
# Gain
gaind = items.pop(0)
if float(gaind) != 1.0:
self.datalogger = max (self.datalogger, datalogger + len(gaind))
channel = len(items.pop(0))
self.channel=max(self.channel, channel)
latitude = len(items.pop(0))
self.latitude=max(self.latitude, latitude)
longitude = len(items.pop(0))
self.longitude=max(self.longitude, longitude)
elevation = len(items.pop(0))
self.elevation=max(self.elevation, elevation)
#Orientation
depth = items.pop(0)
try:
float(depth)
orientation="ZNE"
except:
orientation = "Z"
(depth,a1,a2) = depth.split("/")
a1n = float(a1)
a2n = float(a2)
orientation+="1"
if a1n != 0.0: orientation += "(0.0,%s)"%a1
orientation+="2"
if a2n != 90.0: orientation+="(0.0,%s)"%a1
orientation = len(orientation)
self.orientation=max(self.orientation, orientation)
depth = len(depth)
self.depth=max(self.depth, depth)
# Start
try:
start = parseDate(items.pop(0))
self.start = max (self.start, len(formatDate(start)))
except:
raise Exception ("Invalid Station line start date %s" % inputLine)
# End
try:
end = parseDate(items.pop(0))
except:
end=parseDate("")
pass
self.end = max (self.end, len(formatDate(end)))
return [stationCode, start, end]
def preload(self, filename, takeSugestions):
self.takeSugestions = takeSugestions
sugestedStart = datetime.now()
sugestedEnd = self.defaultEpoch
stationList = []
error = []
# Some initialization
if self.filename is not None:
raise Exception("Cannot pre-load two different files (current one is %s)" % self.filename)
print("Analysing ... ", file=sys.stderr)
fd = open(filename)
for line in fd:
line = line.strip()
if not line or line[0] == "#": continue
try:
(stationCode, start, end) = self.__analyseLine__(line.split())
except Exception as e:
error.append(str(e))
continue
if not stationCode: continue
if stationCode not in stationList:
stationList.append(stationCode)
sugestedStart = min(sugestedStart, start)
if end and sugestedEnd:
sugestedEnd = max(sugestedEnd, end)
else:
sugestedEnd = None
fd.close()
if len(error):
raise Exception("\n".join(error))
print(" Loaded %d different stations" % len(stationList), file=sys.stderr)
if takeSugestions:
print(" Taking suggestion start date of %s " % formatDate(self.startDate), file=sys.stderr)
self.startDate = sugestedStart
print(" Taking suggestion end date of %s " % formatDate(self.endDate), file=sys.stderr)
self.endDate = sugestedEnd
self.filename = filename
self.stationList = stationList
print("Done.", file=sys.stderr)
def __convertHeader__(self, line, fdo):
# Split line
items = line.split()
if not self.takeSugestions:
if self.nat.hasStart:
print(" Using start from attribute.", file=sys.stderr)
self.startDate = self.nat.startDate
if self.nat.hasEnd:
print(" Using end from attribute.", file=sys.stderr)
self.endDate = self.nat.endDate
nCode = items[2].strip()
if nCode != self.networkCode:
raise Exception("Wrong network code found: %s != %s" % (self.networkCode, nCode))
fdo.write("Nw: %s %s %s" % (nCode, formatDate(self.startDate), formatDate(self.endDate)) + "\n")
self.nat.dump(fdo)
def __convertLine__(self, line, fdo, atFront):
lnfmt = self.__fmtline__()
# Split line
items = line.split()
try:
code = items.pop(0)
except Exception as e:
raise Exception ("Missing Code on %s" % line)
if code not in self.stationList:
raise Exception("Unknow station code $s" % code)
try:
hummanStr(items.pop(0))
except Exception as e:
raise Exception ("Missing Gain on %s" % line)
try:
datalogger = items.pop(0)
except Exception as e:
raise Exception ("Missing Datalogger on %s" % line)
try:
sensor = items.pop(0)
except Exception as e:
raise Exception ("Missing Sensor on %s" % line)
try:
gaind = items.pop(0)
if float(gaind) != 1.0:
if not self.inst:
raise Exception("Instrument database needed to convert gain")
try:
dte = self.inst.dls[str(datalogger).split("%")[0]]
except Exception as e:
print(e, file=sys.stderr)
raise Exception("Datalogger %s not found" % str(datalogger).split("%")[0])
datalogger += "%%%s" % (float(dte.gain) * float(gaind))
print(" Converting gain multiplier to real gain using instrument DB on %s" % code, file=sys.stderr)
except Exception as e:
raise Exception ("Missing Gain on %s (%s)" % (line,str(e)))
try:
channel = items.pop(0)
except Exception as e:
raise Exception ("Missing Channel on %s" % line)
try:
latitude = items.pop(0)
except Exception as e:
raise Exception ("Missing Latitude on %s" % line)
try:
longitude = items.pop(0)
except Exception as e:
raise Exception ("Missing Longitude on %s" % line)
try:
elevation = items.pop(0)
except Exception as e:
raise Exception ("Missing Elevation on %s" % line)
try:
depth = items.pop(0)
except Exception as e:
raise Exception ("Missing Depth on %s" % line)
#Orientation
try:
float(depth)
orientation = "ZNE"
except:
orientation = "Z"
(depth,a1,a2) = depth.split("/")
a1n = float(a1)
if a1n == 0.0:
orientation+="1"
else:
orientation+="1(0.0,%s)"%a1
a2n = float(a2)
if a2n == 90.0:
orientation+="2"
else:
orientation+="2(0.0,%s)"%a2
# Start
try:
start = items.pop(0)
except Exception:
raise Exception ("Missing Start on %s" % line)
try:
start = parseDate(start)
except Exception as e:
raise Exception("Invalide Start date: %s (%s) on %s" % (start, e, line))
#End
try:
end = items.pop(0)
except:
end = ""
try:
end = parseDate(end)
except Exception as e:
raise Exception("Invalide End date: %s (%s) on %s" % (end, e, line))
[place, country] = self.sat.parseStationLine(line.split())
description = "%s/%s" % (place, country)
## Prepare necessary output
if not atFront:
self.sma.dump(fdo, code)
self.sat.dump(fdo, code)
for (start, end) in self.sma.getMappings(code, start, end):
fdo.write(lnfmt % (code, quote(description), datalogger, sensor, channel, orientation, latitude, longitude, elevation, depth, formatDate(start), formatDate(end)) + "\n")
return code
def convert(self, fdo, keepcomments = False, atFront = True):
if self.filename is None:
raise Exception("You should pre-load a tab file before before converting.")
## Obtain additional attribute classes if needed
if not self.nat:
self.nat = NetworkAttributes(self.networkCode, None)
if not self.sat:
self.sat = StationAttributes(self.networkCode, self.stationList, None)
if not self.sma:
self.sma = StationMappings(self.networkCode, self.stationList, None)
# Parse in again the station lines and network header by the additional classes
print("Pre-Parsing Station/Network lines ... ", file=sys.stderr)
fd = open(self.filename)
for line in fd:
line = line.strip()
if not line or line[0] == "#":
continue
items = line.split()
if len(items) <= 5:
self.nat.parseNetworkLine(items)
elif len(items) <= 12:
self.sma.parseStationLine(items)
self.sat.parseStationLine(items)
fd.close()
fd = open(self.filename)
oldcode="" # Station code of the last printed line
last="" # Type of the last printed line
print("Converting ... ", file=sys.stderr)
for line in fd:
line = line.strip()
if not line or line[0] == "#":
if last == "l" or last == "a" or last == "h": fdo.write("\n")
if keepcomments: fdo.write(line + "\n")
last = "c"
continue
items = line.split()
if len(items) <= 5:
self.__convertHeader__(line, fdo)
last = "h"
if (atFront):
fdo.write("\n")
self.sma.dump(fdo, None)
self.sat.dump(fdo, None)
last = "a"
fdo.write("\n")
elif len(items) <= 12:
if (last == "l" and items[0].strip() != oldcode) or last == "h": fdo.write("\n")
oldcode = self.__convertLine__(line, fdo, atFront)
last = "l"
pass
else:
print("input at %s" % line, file=sys.stderr)
fd.close()
def main():
# Creating the parser
parser = OptionParser(usage="Old tab to New tab converter", version="1.0", add_help_option=True)
parser.add_option("", "--instdb", type="string",
help="Indicates the instrument databases file to use", dest="inst", default=None)
parser.add_option("", "--smap", type="string",
help="Indicates the station attribute file to use", dest="smap", default=None)
parser.add_option("", "--sat", type="string",
help="Indicates the station attribute file to use", dest="sat", default=None)
parser.add_option("", "--nat", type="string",
help="Indicates the station attribute file to use", dest="nat", default=None)
parser.add_option("-t", "--tab", type="string",
help="Indicates the tab file to convert", dest="tabFile", default=None)
parser.add_option("-f", "--filterf", type="string",
help="Indicates a folder containing the filters coefficients files", dest="ffolder", default=None)
parser.add_option("-n", "--net", type="string",
help="Indicates a two leter station code", dest="netCode", default=None)
parser.add_option("-g", "--globalsa", action="store_true",
help="Indicate that we should put a condensed version of the station attributes just below the network definition", dest="globalSa", default=False)
parser.add_option("-a", "--autotime", action="store_true",
help="Guess the start and end times for a network from the channel times", dest="autoTime", default=False)
parser.add_option("-c", "--clean", action="store_true",
help="Remove the comments and blank lines", dest="cleanFile", default=False)
# Parsing & Error check
(options, args) = parser.parse_args()
error = False
if len(args) != 1:
print("need an Output Filename or '-' for stdout", file=sys.stderr)
error = True
if not options.tabFile:
print("tab file name not supplied", file=sys.stderr)
error = True
if options.inst and not options.ffolder:
print("Filter folder not supplied.", file=sys.stderr)
error = True
if options.tabFile and not os.path.isfile(options.tabFile):
print("supplied tab file (%s) is not a file" % options.tabFile, file=sys.stderr)
error = True
if not options.netCode:
print("network code not supplied", file=sys.stderr)
error = True
#if options.autoTime and (options.netStart or options.netEnd):
# print >> sys.stderr, "options Auto Time and Network Start/End times are exclusive"
# return
if error:
print("use -h for getting a help on usage", file=sys.stderr)
return
if args[0] != "-":
fdo = open(args[0], "w")
else:
fdo = sys.stdout
# Execution
try:
cnv = TabConverter(options.netCode.upper())
cnv.preload(options.tabFile, options.autoTime)
if options.inst or options.smap or options.nat or options.sat:
print("Loading optional files: ", file=sys.stderr)
if options.inst and os.path.isfile(options.inst):
cnv.loadInstrumentsFile(options.inst, options.ffolder)
if options.smap and os.path.isfile(options.smap):
cnv.loadStationMapping(options.smap)
if options.nat and os.path.isfile(options.nat):
cnv.loadNetworkAttribute(options.nat)
if options.sat and os.path.isfile(options.sat):
cnv.loadStationAttribute(options.sat)
print("Done.", file=sys.stderr)
cnv.convert(fdo, not options.cleanFile, options.globalSa)
except Exception as e:
print("", file=sys.stderr)
print("Error on processing: %s" % e, file=sys.stderr)
fdo.close()
if __name__ == "__main__":
main()

@ -0,0 +1,380 @@
#!/usr/bin/env seiscomp-python
################################################################################
# Copyright (C) 2012-2013, 2020 Helmholtz-Zentrum Potsdam - Deutsches GeoForschungsZentrum GFZ
#
# tabinvmodifier -- Tool for inventory modification using nettab files.
#
# This software is free software and comes with ABSOLUTELY NO WARRANTY.
#
# Author: Marcelo Bianchi
# Email: mbianchi@gfz-potsdam.de
################################################################################
from __future__ import print_function
import os
import sys
import datetime, time
from nettab.lineType import Nw, Sa, Na, Ia
from nettab.basesc3 import sc3
import seiscomp.datamodel, seiscomp.io, seiscomp.client, seiscomp.core, seiscomp.logging
class Rules(object):
def __init__(self, relaxed = False):
self.relaxed = relaxed
self.attributes = {}
self.iattributes = []
return
@staticmethod
def _overlaps(pstart, pend, cstart, cend):
if pend:
if pend > cstart:
if not cend or pstart < cend:
return True
else:
if not cend or pstart < cend:
return True
return False
def Nw(self, nw):
key = (nw.code, nw.start, nw.end)
if key in self.attributes:
raise Exception("Nw (%s/%s-%s) is already defined." % key)
self.attributes[key] = {}
self.attributes[key]["Sa"] = []
self.attributes[key]["Na"] = []
return key
def Sa(self, key, sa):
try:
items = self.attributes[key]["Sa"]
except KeyError:
raise Exception ("Nw %s/%s-%s not found in Ruleset" % key)
items.append(sa)
def Na(self, key, na):
try:
items = self.attributes[key]["Na"]
except KeyError:
raise Exception ("Nw %s/%s-%s not found in Ruleset" % key)
items.append(na)
def Ia(self, ia):
self.iattributes.append(ia);
def findKey(self, ncode, nstart, nend):
for (code, start, end) in self.attributes:
if code == ncode and self._overlaps(start, end, nstart, nend):
return (code, start, end)
return None
def getInstrumentsAttributes(self, elementId, elementType):
att = {}
for item in self.iattributes:
if item.match(elementId, elementType):
att[item.Key] = item.Value
return att
def getNetworkAttributes(self, key):
att = {}
for item in self.attributes[key]["Na"]:
att[item.Key] = item.Value
return att
def getStationAttributes(self, key, ncode, scode, lcode, ccode, start, end):
att = {}
for item in self.attributes[key]["Sa"]:
if item.match(scode, lcode, ccode, start, end, self.relaxed):
att[item.Key] = item.Value
return att
class InventoryModifier(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
self.setMessagingUsername("iModify")
self.rules = None
self.relaxed = False
self.outputFile = None
def _digest(self, tabFilename, rules = None):
if not tabFilename or not os.path.isfile(tabFilename):
raise Exception("Supplied filename is invalid.")
if not rules:
rules = Rules(self.relaxed)
try:
fd = open(tabFilename)
for line in fd:
obj = None
line = line.strip()
if not line or line[0] == "#": continue
if str(line).find(":") == -1:
raise Exception("Invalid line format '%s'" % line)
(Type, Content) = line.split(":",1)
if Type == "Nw":
nw = Nw(Content)
key = rules.Nw(nw)
elif Type == "Sg":
raise Exception("Type not supported.")
elif Type == "Na":
na = Na(Content)
rules.Na(key, na)
elif Type == "Sa":
sa = Sa(Content)
rules.Sa(key, sa)
elif Type == "Sr":
raise Exception("Type not supported.")
elif Type == "Ia":
ia = Ia(Content)
rules.Ia(ia)
elif Type == "Se":
raise Exception("Type not supported.")
elif Type == "Dl":
raise Exception("Type not supported.")
elif Type == "Cl":
raise Exception("Type not supported.")
elif Type == "Ff":
raise Exception("Type not supported.")
elif Type == "If":
raise Exception("Type not supported.")
elif Type == "Pz":
raise Exception("Type not supported.")
except Exception as e:
raise e
finally:
if fd:
fd.close()
return rules
def validateParameters(self):
outputFile = None
rulesFile = None
if self.commandline().hasOption("rules"):
rulesFile = self.commandline().optionString("rules")
if self.commandline().hasOption("output"):
outputFile = self.commandline().optionString("output")
if self.commandline().hasOption("relaxed"):
self.relaxed = True
if self.commandline().hasOption("inventory-db") and outputFile is None:
print("Cannot send notifiers when loading inventory from file.", file=sys.stderr)
return False
if self.commandline().unrecognizedOptions():
print("Invalid options: ", end=' ', file=sys.stderr)
for i in self.commandline().unrecognizedOptions():
print(i, end=' ', file=sys.stderr)
print("", file=sys.stderr)
return False
if not rulesFile:
print("No rule file was supplied for processing", file=sys.stderr)
return False
if not os.path.isfile(rulesFile):
argv0 = os.path.basename(self.arguments()[0])
print("%s: %s: No such file or directory" % (argv0, rulesFile), file=sys.stderr)
return False
if self.commandline().hasOption("inventory-db"):
self.setDatabaseEnabled(False, False)
self.setMessagingEnabled(False)
self.rules = self._digest(rulesFile, self.rules)
self.outputFile = outputFile
return True
def createCommandLineDescription(self):
seiscomp.client.Application.createCommandLineDescription(self)
self.commandline().addGroup("Rules")
self.commandline().addStringOption("Rules", "rules,r", "Input XML filename")
self.commandline().addOption("Rules", "relaxed,e", "Relax rules for matching NSLC items")
self.commandline().addGroup("Dump")
self.commandline().addStringOption("Dump", "output,o", "Output XML filename")
def initConfiguration(self):
value = seiscomp.client.Application.initConfiguration(self)
self.setLoggingToStdErr(True)
self.setDatabaseEnabled(True, True)
self.setMessagingEnabled(True)
self.setLoadInventoryEnabled(True)
return value
def send(self, *args):
while not self.connection().send(*args):
seiscomp.logging.warning("send failed, retrying")
time.sleep(1)
def send_notifiers(self, group):
Nsize = seiscomp.datamodel.Notifier.Size()
if Nsize > 0:
seiscomp.logging.info("trying to apply %d change%s" % (Nsize,"s" if Nsize != 1 else "" ))
else:
seiscomp.logging.info("no changes to apply")
return 0
Nmsg = seiscomp.datamodel.Notifier.GetMessage(True)
it = Nmsg.iter()
msg = seiscomp.datamodel.NotifierMessage()
maxmsg = 100
sent = 0
mcount = 0
try:
try:
while it.get():
msg.attach(seiscomp.datamodel.Notifier_Cast(it.get()))
mcount += 1
if msg and mcount == maxmsg:
sent += mcount
seiscomp.logging.debug("sending message (%5.1f %%)" % (sent / float(Nsize) * 100.0))
self.send(group, msg)
msg.clear()
mcount = 0
next(it)
except:
pass
finally:
if msg.size():
seiscomp.logging.debug("sending message (%5.1f %%)" % 100.0)
self.send(group, msg)
msg.clear()
seiscomp.logging.info("done")
return mcount
@staticmethod
def _loop(obj, count):
return [ obj(i) for i in range(count) ]
@staticmethod
def _collect(obj):
code = obj.code()
start = datetime.datetime.strptime(obj.start().toString("%Y %m %d %H %M %S"), "%Y %m %d %H %M %S")
try:
end = obj.end()
end = datetime.datetime.strptime(end.toString("%Y %m %d %H %M %S"), "%Y %m %d %H %M %S")
except:
end = None
return (code, start, end)
@staticmethod
def _modifyInventory(mode, obj, att):
valid = sc3._findValidOnes(mode)
if not att:
return
# Why repeat the code in basesc3.py (sc3::_fillSc3())?
# What about if there are existing comments/pids - won't
# this code get the count wrong?? *FIXME*
commentNum = 0
for (k,p) in att.items():
try:
if k == 'Comment':
# print('DEBUG: Adding comment', p)
if p.startswith('Grant'):
# 2020: These belong in DOI metadata, not here.
continue
c = seiscomp.datamodel.Comment()
c.setText(p)
c.setId(str(commentNum))
commentNum += 1
obj.add(c)
continue
if k == 'Pid':
print('DEBUG: Adding Pid as comment', p)
c = seiscomp.datamodel.Comment()
(typ, val) = p.split(':', 1)
s = '{"type":"%s", "value":"%s"}' % (typ.upper(), val)
c.setText(s)
c.setId('FDSNXML:Identifier/' + str(commentNum))
commentNum += 1
obj.add(c)
continue
p = valid['attributes'][k]['validator'](p)
getattr(obj, 'set'+k)(p)
except KeyError:
import string
hint = ''
if k[0] in string.lowercase:
hint = " (try '%s' instead)" % ( k[0].upper() + k[1:])
print('Modifying %s: \'%s\' is not a valid key%s' % (mode, k, hint), file=sys.stderr)
obj.update()
return
def run(self):
rules = self.rules
iv = seiscomp.client.Inventory.Instance().inventory()
if not rules:
return False
if not iv:
return False
seiscomp.logging.debug("Loaded %d networks" % iv.networkCount())
if self.outputFile is None:
seiscomp.datamodel.Notifier.Enable()
self.setInterpretNotifierEnabled(True)
for net in self._loop(iv.network, iv.networkCount()):
(ncode, nstart, nend) = self._collect(net)
key = rules.findKey(ncode, nstart, nend)
if not key: continue
att = rules.getNetworkAttributes(key)
self._modifyInventory("network", net, att)
seiscomp.logging.info("%s %s" % (ncode, att))
for sta in self._loop(net.station, net.stationCount()):
(scode, sstart, send) = self._collect(sta)
att = rules.getStationAttributes(key, ncode, scode, None, None, sstart, send)
self._modifyInventory("station", sta, att)
if att: seiscomp.logging.info(" %s %s" % (scode, att))
for loc in self._loop(sta.sensorLocation, sta.sensorLocationCount()):
(lcode, lstart, lend) = self._collect(loc)
att = rules.getStationAttributes(key, ncode, scode, lcode, None, lstart, lend)
self._modifyInventory("location", loc, att)
if att: seiscomp.logging.info(" %s %s" % (lcode, att))
for cha in self._loop(loc.stream, loc.streamCount()):
(ccode, cstart, cend) = self._collect(cha)
att = rules.getStationAttributes(key, ncode, scode, lcode, ccode, cstart, cend)
self._modifyInventory("channel", cha, att)
if att: seiscomp.logging.info(" %s %s" % (ccode, att))
for sensor in self._loop(iv.sensor, iv.sensorCount()):
att = rules.getInstrumentsAttributes(sensor.name(), "Se")
self._modifyInventory("sensor", sensor, att)
for datalogger in self._loop(iv.datalogger, iv.dataloggerCount()):
att = rules.getInstrumentsAttributes(datalogger.name(), "Dl")
self._modifyInventory("datalogger", datalogger, att)
return True
def done(self):
if self.outputFile:
ar = seiscomp.io.XMLArchive()
ar.create(self.outputFile)
ar.setFormattedOutput(True)
ar.writeObject(seiscomp.client.Inventory.Instance().inventory())
ar.close()
else:
self.send_notifiers("INVENTORY")
seiscomp.client.Application.done(self)
if __name__ == "__main__":
app = InventoryModifier(len(sys.argv), sys.argv)
sys.exit(app())

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,17 @@
# Defines a list of modules loaded at startup.
plugins = ${plugins}, fdsnxml
# SeisComP applications access waveform data through the RecordStream
# interface. Please consult the SeisComP documentation for a list of supported
# services and their configuration.
# This parameter configures the RecordStream URL, format:
# [service://]location[#type]. "service" is the name of the recordstream
# implementation. If "service" is not given "file://" is implied.
recordstream = sdsarchive://@ROOTDIR@/var/lib/archive
# Set the number of bytes to buffer for each chunk of waveform data served
# to the client. The lower the buffer the higher the overhead of Python Twisted.
# The higher the buffer the higher the memory usage per request. 100kB seems
# to be a good trade-off.
recordBulkSize = 102400

@ -0,0 +1,5 @@
# UDP port for receiving GDRT messages. By default port 9999 will be used.
plugins.gdrt.udpport = 9999
# Location of station list file.
plugins.gdrt.stationsFrom = stations.txt

@ -0,0 +1,93 @@
# Default plugins to load. Application specific configuration
# files should use the 'plugins' entry to specify additional
# plugins otherwise when using 'core.plugins' also these
# default values are going to be overwritten.
#
# To be able to read from all supported databases all available
# database plugins are loaded as 'core'.
# All currently supported db backends: dbmysql, dbpostgresql, dbsqlite3
core.plugins = dbmysql
# Use log level 2 (error and warning)
logging {
level = 2
# Use logfiles. It is commented by default to allow applications to define
# console output with their hard coded defaults. If this setting is enabled
# it would otherwise always override the applications default logging
# backend.
#file = true
# Rotate the logfiles
file {
rotator = true
# Rotate each 86400 seconds (1 day)
rotator.timeSpan = 86400
# Keep 7 rotated log files
rotator.archiveSize = 7
}
}
# Server connection
connection.server = localhost/production
# The connection timeout
connection.timeout = 3
# How to transfer messages (binary, xml)?
connection.encoding = binary
# Use slink (seedlink) as record source service.
recordstream = slink://localhost:18000
# The agencyID to use when tagging processing results
agencyID = GFZ
# Organization name used mainly by ArcLink and SeedLink.
organization = Unset
# Configures the default filters selectable in manual picker.
# The entry with a leading "@" is selected as default filter.
picker.filters = \
"BP 0.1 - 1 Hz 3rd order;RMHP(10)>>ITAPER(30)>>BW(3,0.1,1)", \
"BP 0.1 - 2 Hz 3rd order;RMHP(10)>>ITAPER(30)>>BW(3,0.1,2)", \
"BP 0.4 - 1 Hz 3rd order;RMHP(10)>>ITAPER(30)>>BW(3,0.4,1)", \
"@BP 0.7 - 2 Hz 3rd order;RMHP(10)>>ITAPER(30)>>BW(3,0.7,2)", \
"BP 1 - 3 Hz 3rd order;RMHP(5)>>ITAPER(10)>>BW(3,1.0,3)", \
"BP 1 - 5 Hz 3rd order;RMHP(5)>>ITAPER(10)>>BW(3,1.0,5)", \
"BP 2 - 4 Hz 3rd order;RMHP(5)>>ITAPER(10)>>BW(3,2.0,4)", \
"BP 3 - 6 Hz 3rd order;RMHP(5)>>ITAPER(10)>>BW(3,3.0,6)", \
"BP 4 - 8 Hz 3rd order;RMHP(5)>>ITAPER(10)>>BW(3,4.0,8)", \
"HP 3 Hz 3rd order;RMHP(1)>>ITAPER(2)>>BW_HP(3,3)", \
"BP 0.7 - 2 Hz + STA/LTA(1,50);RMHP(10)->ITAPER(30)->BW(3,0.7,2)->STALTA(1,50)"
# Configure the columns of the event list that are visible initially.
# The first column containing the origin time is always visible and cannot
# be hidden.
# Possible values are:
# * Type
# * M
# * MType
# * Phases
# * Lat
# * Lon
# * Depth
# * Stat
# * Agency
# * Region
# * ID
eventlist.visibleColumns = M, MType, Phases, RMS, Lat, Lon, Depth, Stat, Agency, Region, ID
# Default travel time table configuration. Plugins can be added to for custom
# travel time table implementations.
# This configuration can be used by applications that need to know which
# interfaces are activated and which tables they define.
ttt {
libtau.tables = iasp91, ak135
LOCSAT.tables = iasp91, tab
homogeneous.tables = ""
}

@ -0,0 +1,22 @@
# Send journals and event specific updates to the EVENT group.
connection.primaryGroup = EVENT
# Receive objects from EVENT group. This is necessary to wait for event
# association of imported origins.
connection.subscriptions = EVENT
# Number of seconds to fetch missed updates on start up.
backLog = 1800
# Number of public objects to cache.
cacheSize = 5000
# Maximum number of notifiers to batch in one message. If set to 0 no size
# limit is enforced. Make sure to not hit the overall message size limited of
# 16MiB which is enforced by the messaging system.
batchSize = 2000
# If event synchronisation is enabled and an incoming origin is not yet
# associated with an event on the target machine then this timeout defines
# the maximum number of seconds to wait for an association.
eventAssociationTimeout = 10

@ -0,0 +1,3 @@
# Defines a list of message groups to subscribe to. The default is usually
# given by the application and does not need to be changed.
connection.subscriptions = EVENT, LOCATION, MAGNITUDE

@ -0,0 +1,18 @@
# Send to the AMPLITUDE group
connection.primaryGroup = AMPLITUDE
# Receive objects from PICK, AMPLITUDE and LOCATION group
connection.subscriptions = PICK, AMPLITUDE, LOCATION
# The amplitudes to compute triggered by an incoming Origin
amplitudes = MLv, mb, mB, Mwp
# The minimum arrival weight within an origin to compute amplitudes
# for the associated pick.
amptool.minimumPickWeight = 0.5
# Timeout in seconds of the first data packet of waveform data acquisition.
amptool.initialAcquisitionTimeout = 30
# Timeout in seconds of any subsequent data packet of waveform data acquisition.
amptool.runningAcquisitionTimeout = 2

@ -0,0 +1,6 @@
archive = @ROOTDIR@/var/lib/archive
batchSize = 100
threads = 1
jitter = 0.5
deepScan = false

@ -0,0 +1,84 @@
## Send to the LOCATION group
connection.primaryGroup = LOCATION
## Receive objects from PICK and AMPLITUDE groups
connection.subscriptions = PICK, AMPLITUDE
## max. permissible RMS for a location to be reported
#autoloc.maxRMS = 3.5
## max. individual residual (unweighted) for a pick to
## be used in location
#autoloc.maxResidual = 7.0
## Max. secondary azimuth gap for an origin to be reported by.
## Default is 360 degrees, i.e. no restriction based on this parameter.
#autoloc.maxSGAP = 360
## Arrivals with exceptionally large amplitudes may be
## flagged as XXL, allowing (in future) faster, preliminary
## "heads-up" alerts.
#autoloc.thresholdXXL = 10000.
#autoloc.maxStationDistance = 180
#autoloc.maxDistanceXXL = 10
#autoloc.minPhaseCount = 6
#autoloc.minPhaseCountXXL = 4
## If the station count for stations at < 105 degrees
## distance exceeds this number, no picks at > 105 degrees will be
## used in location. They will be loosely associated, though.
#autoloc.minStaCountIgnorePKP = 30
## Clean-up interval for removing old/unused objects, in seconds
## Don't change.
#autoloc.cleanupInterval = 3600
## max. age for objects kept in memory, in seconds
## Default is 6 hours - don't change.
#autoloc.maxAge = 21600
## Don't change.
#autoloc.wakeupInterval = 5
## Grid configuration
#autoloc.grid = @DATADIR@/scautoloc/grid.conf
## Station configuration
#autoloc.stationConfig = @DATADIR@/scautoloc/station.conf
## This is only relevant in offline/testing mode
#locator.stationLocations = @DATADIR@/scautoloc/station-locations.conf
## Manual picks/origins can be fed back into autoloc for two purposes:
## * passive association to a solution from a "trusted" source so that we
## avoid fake or wrong locations due to events outside our area of interest
## * use the manual origins in further processing, especially the manual picks.
## Possibly also honor an operator specified fixed depth.
## Currently we only permit use of manual picks which are then used
## instead of the corresponding automatic picks (if existing)
# autoloc.useManualPicks = false
## Log all picks received by scautoloc to this file
autoloc.pickLog = @LOGDIR@/autoloc-picklog
# Amplitude type to be used as SNR amplitude
# Don't change unless you know exactly what you are doing.
autoloc.amplTypeSNR = snr
# Amplitude type to be used as absolute amplitude
# Don't change unless you know exactly what you are doing.
autoloc.amplTypeAbs = mb
# Use manual origins from our own agency. Essentially it means to
# use manual picks from manual origins, which is assumed to be
# better than using only automatic picks.
autoloc.useManualOrigins = false
# NOTE: If you set the above to true, then make sure to add the
# LOCATION group to connection.subscriptions!
# If autoloc.useManualOrigins is true, adopt the depth from manual
# origins, which is especially important if it was fixed by the analyst.
autoloc.adoptManualDepth = false

@ -0,0 +1,87 @@
# Send to the PICK group
connection.primaryGroup = PICK
# Send amplitudes to this group
connection.amplitudeGroup = AMPLITUDE
# Receive objects from CONFIG group
connection.subscriptions = CONFIG
# The filter used to trigger
filter = "RMHP(10)>>ITAPER(30)>>BW(4,0.7,2)>>STALTA(2,80)"
# The time correction applied to a detected pick
timeCorrection = -0.8
# The record ringbuffer size in seconds
ringBufferSize = 300
# The leadTime defines the time in seconds to
# start picking on the streams before current
# time
leadTime = 60
# The initTime defines a timespan in seconds
# for that the picker is blind after initialization
# This time is needed to initialize the filter and
# depends on it
initTime = 60
# Interpolate gaps linearly? This is valid for gaps
# short than thresholds.maxGapLength
gapInterpolation = false
# For which value on a filtered stream is
# a pick detected
thresholds.triggerOn = 3
# The value the filtered stream must reach to
# enable detection again
thresholds.triggerOff = 1.5
# The maximum gap length to handle. Gaps larger
# than this size reset the picker
thresholds.maxGapLength = 4.5
# The timeWindow used to compute a maximum (snr)
# amplitude on the filtered stream
thresholds.amplMaxTimeWindow = 10
thresholds.deadTime = 30
thresholds.minAmplOffset = 3
# The amplitudes to compute triggered by
# a new P Pick continuously without having
# an Origin
amplitudes = MLv, mb, mB
# Configures the picker to use. By default only simple
# STALTA detections are emitted as picks. To enable "repicking"
# define a picker algorithm here.
picker = ""
# Configures the secondary picker to be used.
spicker = ""
# Configures the feature extraction type to be used
fx = ""
# If enabled the all streams are used for picking that are received by the
# picker. This option has only effect if a file is used as input which contains
# more data than the picker requests or if amplitudes are enabled which are using
# the horizontal components.
useAllStreams = false
# If enabled the all secondary pickers that were triggered by a previous pick
# will be terminated when a new detection or pick has been found. This aims to
# avoid the case where an S phase is wrongly picked as P but would also be
# picked as S by the secondary picker. But suppressing the S pick can lead to
# undesired results. It might be better in some situations to have two picks
# (P and S) instead only a wrong P.
killPendingSPickers = true
# If enabled and a picker is configured then detections are sent as well.
# To distinguish between detections and picks the evaluation mode of the pick
# is set to manual. This is meant to be a debug option which can be used to
# compare detections and picks by their evaluation mode.
sendDetections = false

@ -0,0 +1,2 @@
# Messaging subscriptions
connection.subscriptions = EVENT, MAGNITUDE, LOCATION, FOCMECH

@ -0,0 +1,203 @@
# Send to the EVENT group
connection.primaryGroup = EVENT
# Receive objects from LOCATION, MAGNITUDE and FOCMECH group
connection.subscriptions = LOCATION, MAGNITUDE, FOCMECH, EVENT
# A magnitudes needs at least 4 stationmagnitudes
# to become preferred
eventAssociation.minimumMagnitudes = 4
# An automatic origin will be associated to an
# event when it has at least 10 phases
eventAssociation.minimumDefiningPhases = 10
# Minimum score of an automatic origin to be allowed to
# form an new Event. This requires an activated score
# plugin. See parameter score.
# If set the minimumDefiningPhases has no effect at as
# this check will be superseded by the score check. It is
# the task of the score processor to evaluate a proper
# score for all input origins.
# By default this option is deactivated.
#eventAssociation.minimumScore = 1
# An automatic origin will be associated to an
# event when it falls inside this region.
# Format: min-lat, min-lon, max-lat, max-lon
#eventAssociation.region.rect = -90,-180,90,180
# Search 1800 seconds BEFORE origin time of a
# new location for matching events
eventAssociation.eventTimeBefore = 1800
# Search 1800 seconds AFTER origin time of a
# new location for matching events
eventAssociation.eventTimeAfter = 1800
# An origin will be associated to an existing
# event when at least 3 picks matches with
# former associated origins
eventAssociation.minimumMatchingArrivals = 3
# If this time window in seconds is negative, pickIDs
# are compared to find matching arrivals. A non negative
# value (including 0) compares pick times regardless
# of the pickID.
# Pass: |pick1.time - pick2.time| <= threshold
eventAssociation.maximumMatchingArrivalTimeDiff = -1
# This parameter is only used in conjunction with
# eventAssociation.maximumMatchingArrivalTimeDiff. If a station
# has multiple associated arrivals for a particular event, this
# flag defines if the time distance of a new pick to all arrivals
# must be within eventAssociation.maximumMatchingArrivalTimeDiff
# or if one matching arrival is enough.
eventAssociation.compareAllArrivalTimes = true
# Associates an origin with an existing event
# if the origin time differs not more
# than 60 seconds unless the minimumMatchingArrivals
# criteria matches.
eventAssociation.maximumTimeSpan = 60
# Associates an origin to an existing event
# when the location differs not more
# than 5 degrees unless the minimumMatchingArrivals
# criteria matches
eventAssociation.maximumDistance = 5
# Minimum number of station magnitudes required for Mw(mB) to be considered as
# preferred magnitude.
eventAssociation.minMwCount = 8
# If false then the station count rules out the magnitude priority
# which is only taken into account if two magnitudes have the
# same station count.
#
# If true then the priority rules out the station count
# which is only taken into account if two magnitudes have the
# same priority.
eventAssociation.magPriorityOverStationCount = false
# Minimum number of station magnitudes which ensures that Mw(mB) will be
# preferred and not mb.
eventAssociation.mbOverMwCount = 30
# Average between mb and Mw(mB) which must be exceeded to become Mw(mB)
# preferred.
eventAssociation.mbOverMwValue = 6
# The magnitude type priority list
# Magnitudes with other types cannot become
# preferred magnitudes
eventAssociation.magTypes = M
# The agencyID priority list
# When the eventtool comes to the point to select a preferred
# origin it orders all origins by its
# agency priority and selects then the best one among the
# highest priority agency.
# It also defines the agency priority for custom priority
# checks (eventAssociation.priorities)
#eventAssociation.agencies = GFZ
# The author priority list
# When the eventtool comes to the point to select a preferred
# origin it orders all origins by its
# author priority and selects then the best one among the
# highest priority author.
# It also defines the author priority for custom priority
# checks (eventAssociation.priorities)
#eventAssociation.authors = scautoloc@localhost
# The general priority list to decide if an origin becomes preferred. The
# priority decreases in the order of the parameters. This list is not used
# unless this parameter is activated.
# Empty priority list: scevent replicates the default hard wired behaviour:
# AGENCY, STATUS, PHASES_AUTOMATIC, TIME_AUTOMATIC
# Each item in the list corresponds to a check that is performed. Each check
# computes a score of the incoming origin (s1) and the current preferred origin
# (s2). If the s1 is lower than s2, the incoming origin is rejected and does
# not become preferred. All subsequent checks are ignored. If s1 is equal to
# s2, the next check in the list is performed. If s1 is larger than s2, the
# origin becomes preferred and all subsequent checks are ignored.
# Available tokens:
# AGENCY: check based on agency priorities
# AUTHOR: check based on author priorities
# MODE: evaluation mode priority: 0 = unset, 1 = automatic, 2 = manual, manual
# over-rules automatic
# STATUS: priority combined from evaluation status and evaluation mode: -100 =
# status is rejected, -1 = status is reported, 0 = status is preliminary or
# status is unset and mode is automatic, 1 = status is confirmed or status is
# unset and mode is manual, 2 = status is reviewed, 3 = status is final,
# METHOD: check based on the method priorities
# PHASES: higher phase count = higher priority
# PHASES_AUTOMATIC: only checks phase priorities for incoming automatic origins
# RMS: lower rms = higher priority
# RMS_AUTOMATIC: only check RMS on incoming automatic origins
# TIME: more recent origins (creationTime) have higher priorities
# TIME_AUTOMATIC: only check creationTime priority on incoming automatic
# origins
# SCORE: evaluates the score according to a configured ScoreProcessor and
# prefers the origin/focalmechanism with the highest score
#eventAssociation.priorities = AGENCY, STATUS, PHASES_AUTOMATIC, TIME_AUTOMATIC
# If true, one magnitude will be preferred even if magnitude criteria are
# not fullfilled.
eventAssociation.enableFallbackMagnitude = false
# The eventID prefix
# The eventID format is [prefix][year][code], e.g. gfz2008fdvg
eventIDPrefix = "gfz"
# Defines the pattern to generate an event ID.
# %p : prefix
# %Y : year
# %[w]c: alpha character
# %[w]C: upper case alpha character
# %[w]d: decimal
# %[w]x: hexadecimal
# %[w]X: upper case hexadecimal
eventIDPattern = "%p%Y%04c"
# Configures the number of event ID slots to look back and forth when an event
# ID is already taken. The default in previous versions was 5. Now -1 means
# that the margin is determined automatically based on
# "eventAssociation.eventTimeBefore" and "eventAssociation.eventTimeAfter".
# According to the configured "eventIDPattern" a fixed time range per slot can
# be computed and with that width the number of look ahead slots and look back
# slots can be computed based on the given time ranges for event association.
eventIDLookupMargin = -1
# Configures a timespan in seconds to delay origin association
#eventAssociation.delayTimeSpan = 0
# AgencyID filter used to delay origin association if
# eventAssociation.delayTimeSpan > 0
#eventAssociation.delayFilter.agencyID = agency
# Author filter used to delay origin association if
# eventAssociation.delayTimeSpan > 0
#eventAssociation.delayFilter.author = author
# evaluationMode filter used to delay origin association if
# eventAssociation.delayTimeSpan > 0. Allowed values are "manual" or "automatic"
#eventAssociation.delayFilter.evaluationMode = automatic
# Defines whether to associate or to ignore origins derived from CMT/MT
# inversions.
eventAssociation.ignoreFMDerivedOrigins = true
# If the preferred origin has evaluation status 'rejected' the event type will
# be set as 'not existing' unless the event type has been fixed by an operator
# or the preferred origin has been fixed.
eventAssociation.declareFakeEventForRejectedOrigin = false
# Allows to match picks that are associated with weight 0
eventAssociation.allowLooseAssociatedArrivals = false
# If enabled then the EventDescription with type 'Flinn-Engdahl region'
# will be populated with the Flinn-Engdahl region name.
populateFERegion = false

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save