[installation] Init with inital config for global
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
var
 | 
			
		||||
*.pyc
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/Hypo71PC
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/Hypo71PC
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										82
									
								
								bin/arclink2inv
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										82
									
								
								bin/arclink2inv
									
									
									
									
									
										Executable file
									
								
							@ -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(f"{msg}\n")
 | 
			
		||||
        sys.stderr.write("for help use --help\n")
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    for o, a in opts:
 | 
			
		||||
        if o in ["-h", "--help"]:
 | 
			
		||||
            sys.stderr.write(f"{usage}\n")
 | 
			
		||||
            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))
 | 
			
		||||
							
								
								
									
										26
									
								
								bin/bindings2cfg
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										26
									
								
								bin/bindings2cfg
									
									
									
									
									
										Executable file
									
								
							@ -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())
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/caps2caps
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/caps2caps
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/capssds
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/capssds
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1245
									
								
								bin/capstool
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										1245
									
								
								bin/capstool
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/crex2caps
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/crex2caps
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										421
									
								
								bin/data2caps
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										421
									
								
								bin/data2caps
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,421 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
import getopt
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import signal
 | 
			
		||||
 | 
			
		||||
from fractions import Fraction
 | 
			
		||||
 | 
			
		||||
import numpy as np
 | 
			
		||||
 | 
			
		||||
from gempa import CAPS
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Record:
 | 
			
		||||
    net = ""
 | 
			
		||||
    sta = ""
 | 
			
		||||
    loc = ""
 | 
			
		||||
    cha = ""
 | 
			
		||||
    samples = []
 | 
			
		||||
    start = CAPS.Time.GMT()
 | 
			
		||||
    numerator = 0
 | 
			
		||||
    denominator = 1
 | 
			
		||||
    nSamp = 0
 | 
			
		||||
    unit = ""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
usage_info = f"""Usage:
 | 
			
		||||
 | 
			
		||||
  {os.path.basename(__file__)} [options]
 | 
			
		||||
 | 
			
		||||
Push data into CAPS. The input and output formats can be easily adjusted.
 | 
			
		||||
 | 
			
		||||
Options:
 | 
			
		||||
  -H, --host                   Host name of CAPS server to connect to. Default: localhost:18003.
 | 
			
		||||
  -h, --help                   Display this help message and exit.
 | 
			
		||||
  -i, --input                  Name of input file. Create a generic signal if not given.
 | 
			
		||||
  -f, --format                 Format of input data. Supported: slist, unavco.
 | 
			
		||||
  -m, --multiplier             Multiplier applied to data samples for generating integers.
 | 
			
		||||
  -n, --network                Network code of data to be sent. Read from input if not given.
 | 
			
		||||
 | 
			
		||||
Examples:
 | 
			
		||||
 | 
			
		||||
Generate generic signal assuming AM network code and send to a caps server with default port 18003
 | 
			
		||||
  {os.path.basename(__file__)} -n AM -H localhost:18003
 | 
			
		||||
 | 
			
		||||
Read data file in slist format, upscale values by 1000000000 and send in RAW format to the default caps server
 | 
			
		||||
  {os.path.basename(__file__)} -i file.slist -f slist -m 1000000000
 | 
			
		||||
 | 
			
		||||
Read data file in unavco 1.0 format and send in RAW format. The network code must be specified.
 | 
			
		||||
  {os.path.basename(__file__)} -f unavco -i unavco-pressure-v1-0.txt -n AB
 | 
			
		||||
 | 
			
		||||
Read data file in unavco 1.1 format and send in RAW format
 | 
			
		||||
  {os.path.basename(__file__)} -f unavco -i unavco-tilt-v1-1.txt
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def usage(exitcode=0):
 | 
			
		||||
    sys.stderr.write(usage_info)
 | 
			
		||||
    return exitcode
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
output = CAPS.Plugin("data2caps")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def readSlist(filePath, multiplier=1.0, network=None):
 | 
			
		||||
    # supported formats:
 | 
			
		||||
    # TIMESERIES AM_R1108_00_SHZ_R, 8226 samples, 50 sps, 2018-04-10T10:20:03.862000, SLIST, FLOAT, M/S**2
 | 
			
		||||
    # 0.000134157
 | 
			
		||||
    # ...
 | 
			
		||||
    data = Record()
 | 
			
		||||
    samples = []
 | 
			
		||||
    all_data = []
 | 
			
		||||
    try:
 | 
			
		||||
        with open(filePath, "r", encoding="utf8") as file:
 | 
			
		||||
            all_data = [line.strip() for line in file.readlines()]
 | 
			
		||||
    except FileNotFoundError:
 | 
			
		||||
        print(f"File '{filePath}' not found.", file=sys.stderr)
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    if len(all_data) == 0:
 | 
			
		||||
        print(f"No data read from file {filePath}", file=sys.stderr)
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    header = all_data[0]
 | 
			
		||||
    dataType = header.split(",")[0].split(" ")[0]
 | 
			
		||||
    if dataType != "TIMESERIES":
 | 
			
		||||
        print(
 | 
			
		||||
            f"Unsupported data type {dataType} in {filePath}. Supported: TIMESERIES",
 | 
			
		||||
            file=sys.stderr,
 | 
			
		||||
        )
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    if network:
 | 
			
		||||
        data.net = network
 | 
			
		||||
    else:
 | 
			
		||||
        data.net = header.split(",")[0].split(" ")[1].split("_")[0]
 | 
			
		||||
    data.sta = header.split(",")[0].split(" ")[1].split("_")[1]
 | 
			
		||||
    data.loc = header.split(",")[0].split(" ")[1].split("_")[2]
 | 
			
		||||
    data.cha = header.split(",")[0].split(" ")[1].split("_")[3]
 | 
			
		||||
    data.nSamp = int(header.split(",")[1].split(" ")[-2])
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        frac = Fraction(header.split(",")[2].split(" ")[-2]).limit_denominator()
 | 
			
		||||
    except Exception:
 | 
			
		||||
        print("Unknown sample rate", file=sys.stderr)
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    data.numerator = frac.numerator
 | 
			
		||||
    data.denominator = frac.denominator
 | 
			
		||||
 | 
			
		||||
    time = header.split(",")[3].strip(" ")
 | 
			
		||||
    data.start = CAPS.Time.FromString(time, "%FT%T.%f")
 | 
			
		||||
    data.unit = header.split(",")[6].strip(" ")
 | 
			
		||||
    samples = [float(x) for x in all_data[1:]]
 | 
			
		||||
    min_abs = min(abs(x) for x in samples)
 | 
			
		||||
    max_abs = max(abs(x) for x in samples)
 | 
			
		||||
    print(
 | 
			
		||||
        f"  + minimum/maximum absolute value found in data: {min_abs}/{max_abs}",
 | 
			
		||||
        file=sys.stderr,
 | 
			
		||||
    )
 | 
			
		||||
    print(f"  + applying multuplier: {multiplier}", file=sys.stderr)
 | 
			
		||||
    for sample in samples:
 | 
			
		||||
        if dataType == "TIMESERIES":
 | 
			
		||||
            data.samples.append(int(round(sample * multiplier, 0)))
 | 
			
		||||
 | 
			
		||||
    min_abs = min(abs(x) for x in data.samples)
 | 
			
		||||
    max_abs = max(abs(x) for x in data.samples)
 | 
			
		||||
    print(
 | 
			
		||||
        "  + minimum/maximum absolute value after applying multiplier: "
 | 
			
		||||
        f"{min_abs}/{max_abs}",
 | 
			
		||||
        file=sys.stderr,
 | 
			
		||||
    )
 | 
			
		||||
    return data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def readUnavco(filePath, multiplier=1.0, network=None):
 | 
			
		||||
    # supported formats:
 | 
			
		||||
    # version 1.1
 | 
			
		||||
    # # PBO Tiltmeter Data, format version 1.1
 | 
			
		||||
    # # http://pboweb.unavco.org/strain_data
 | 
			
		||||
    # #
 | 
			
		||||
    # # B201 coldwt201bwa2007
 | 
			
		||||
    # # Latitude     : +46.3033 degrees (WGS-84)
 | 
			
		||||
    # # Longitude    : -122.2648 degrees (WGS-84)
 | 
			
		||||
    # # Elevation    : 990 meters (WGS-84)
 | 
			
		||||
    # # Sensor Depth : 24.38 meters
 | 
			
		||||
    # # X Orientation: 316 degrees East of North (magnetic)
 | 
			
		||||
    # # SEED Codes   : PB B201 TT LAE
 | 
			
		||||
    # #
 | 
			
		||||
    # # Timestamp (UTC)   Tilt X (microradians)
 | 
			
		||||
    # 2023-10-05T16:00:00.58 162.981
 | 
			
		||||
 | 
			
		||||
    data = Record()
 | 
			
		||||
    version = None
 | 
			
		||||
    versionSupported = ["1.0", "1.1"]
 | 
			
		||||
    all_data = []
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        with open(filePath, "r", encoding="utf8") as file:
 | 
			
		||||
            # read header
 | 
			
		||||
            all_data = [line.strip().split(" ") for line in file.readlines()]
 | 
			
		||||
    except FileNotFoundError:
 | 
			
		||||
        print(f"File '{filePath}' not found.", file=sys.stderr)
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    if len(all_data) == 0:
 | 
			
		||||
        print(f"No data read from file {filePath}", file=sys.stderr)
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    cnt = 0
 | 
			
		||||
    for line in all_data:
 | 
			
		||||
        if not line[0].startswith("#"):
 | 
			
		||||
            break
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            if "version" in line[-2]:
 | 
			
		||||
                version = line[-1]
 | 
			
		||||
        except IndexError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        cnt += 1
 | 
			
		||||
 | 
			
		||||
    if version not in versionSupported:
 | 
			
		||||
        print(
 | 
			
		||||
            f"Unsupported format version '{version}' of file '{filePath}'. "
 | 
			
		||||
            f"Supported are: {versionSupported}",
 | 
			
		||||
            file=sys.stderr,
 | 
			
		||||
        )
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    print(f"  + unavco format version: {version}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
    # extract meta data
 | 
			
		||||
    header = all_data[:cnt]
 | 
			
		||||
    for line in header:
 | 
			
		||||
        # stream code
 | 
			
		||||
        if set(["seed", "codes"]).issubset(set(x.lower() for x in line)):
 | 
			
		||||
            if network:
 | 
			
		||||
                data.net = network
 | 
			
		||||
            else:
 | 
			
		||||
                if version == "1.0":
 | 
			
		||||
                    if not network:
 | 
			
		||||
                        print(
 | 
			
		||||
                            f"Found version {version}: Must provide network code",
 | 
			
		||||
                            file=sys.stderr,
 | 
			
		||||
                        )
 | 
			
		||||
                        return False
 | 
			
		||||
                elif version == "1.1":
 | 
			
		||||
                    data.net = line[-4]
 | 
			
		||||
                else:
 | 
			
		||||
                    print(
 | 
			
		||||
                        f"Unsupported format version {version}. Supported are: {version}",
 | 
			
		||||
                        file=sys.stderr,
 | 
			
		||||
                    )
 | 
			
		||||
                    return False
 | 
			
		||||
            data.sta = line[-3]
 | 
			
		||||
            data.loc = line[-2]
 | 
			
		||||
            data.cha = line[-1]
 | 
			
		||||
 | 
			
		||||
    # physical unit
 | 
			
		||||
    if "Timestamp" in line[1]:
 | 
			
		||||
        unit = line[-1].strip("(").strip(")")
 | 
			
		||||
        if "microradians" in unit:
 | 
			
		||||
            if multiplier == 1000.0:
 | 
			
		||||
                unit = "nRad"
 | 
			
		||||
                print(f"  + converting data in microradians to {unit}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
            elif multiplier == 1.0:
 | 
			
		||||
                unit = "nRad"
 | 
			
		||||
                print(
 | 
			
		||||
                    f"  + converting data from microradians to {unit} for high precision",
 | 
			
		||||
                    file=sys.stderr,
 | 
			
		||||
                )
 | 
			
		||||
                multiplier = 1000
 | 
			
		||||
 | 
			
		||||
        if "hPa" in unit:
 | 
			
		||||
            if multiplier == 1.0:
 | 
			
		||||
                multiplier = 100
 | 
			
		||||
                unit = "Pa"
 | 
			
		||||
                print(f"  + converting data from hPa to {unit}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
        data.unit = unit
 | 
			
		||||
 | 
			
		||||
    if version == "1.0":
 | 
			
		||||
        time1 = CAPS.Time.FromString(all_data[cnt][0] + all_data[cnt][1], "%F%T.%f")
 | 
			
		||||
        time2 = CAPS.Time.FromString(
 | 
			
		||||
            all_data[cnt + 1][0] + all_data[cnt + 1][1], "%F%T.%f"
 | 
			
		||||
        )
 | 
			
		||||
    elif version == "1.1":
 | 
			
		||||
        time1 = CAPS.Time.FromString(all_data[cnt][0], "%FT%T.%f")
 | 
			
		||||
        time2 = CAPS.Time.FromString(all_data[cnt + 1][0], "%FT%T.%f")
 | 
			
		||||
    else:
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    data.start = time1
 | 
			
		||||
    try:
 | 
			
		||||
        frac = Fraction(1.0 / (time2 - time1).length()).limit_denominator()
 | 
			
		||||
    except Exception:
 | 
			
		||||
        print("Unknown sample rate", file=sys.stderr)
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    data.numerator = frac.numerator
 | 
			
		||||
    data.denominator = frac.denominator
 | 
			
		||||
    # extract time series
 | 
			
		||||
    for i in all_data[cnt:]:
 | 
			
		||||
        if version == "1.0":
 | 
			
		||||
            data.samples.append(int(round(float(i[2]) * multiplier, 0)))
 | 
			
		||||
        elif version == "1.1":
 | 
			
		||||
            data.samples.append(int(round(float(i[1]) * multiplier, 0)))
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    data.nSamp = len(data.samples)
 | 
			
		||||
 | 
			
		||||
    return data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def signal_handler(sig, frame):  # pylint: disable=W0613
 | 
			
		||||
    print("Caught Ctrl+C!", file=sys.stderr)
 | 
			
		||||
    output.quit()
 | 
			
		||||
    sys.exit(0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    inputFormat = None
 | 
			
		||||
    inputFile = None
 | 
			
		||||
    host = "localhost"
 | 
			
		||||
    port = 18003
 | 
			
		||||
    multiplier = 1
 | 
			
		||||
    network = None
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        opts, _ = getopt.getopt(
 | 
			
		||||
            sys.argv[1:],
 | 
			
		||||
            "hH:i:f:m:n:",
 | 
			
		||||
            ["help", "host=", "input=", "format=", "multiplier=", "network="],
 | 
			
		||||
        )
 | 
			
		||||
    except getopt.GetoptError as err:
 | 
			
		||||
        # print help information and exit:
 | 
			
		||||
        print(str(err))  # will print something like "option -a not recognized"
 | 
			
		||||
        return usage(2)
 | 
			
		||||
 | 
			
		||||
    addr = None
 | 
			
		||||
 | 
			
		||||
    signal.signal(signal.SIGINT, signal_handler)
 | 
			
		||||
 | 
			
		||||
    for o, a in opts:
 | 
			
		||||
        if o in ["-h", "--help"]:
 | 
			
		||||
            return usage()
 | 
			
		||||
 | 
			
		||||
        if o in ["-H", "--host"]:
 | 
			
		||||
            addr = a
 | 
			
		||||
        elif o in ["-i", "--input"]:
 | 
			
		||||
            inputFile = a
 | 
			
		||||
        elif o in ["-f", "--format"]:
 | 
			
		||||
            inputFormat = a
 | 
			
		||||
        elif o in ["-m", "--multiplier"]:
 | 
			
		||||
            multiplier = float(a)
 | 
			
		||||
        elif o in ["-n", "--network"]:
 | 
			
		||||
            network = a
 | 
			
		||||
        else:
 | 
			
		||||
            assert False, "unhandled option"
 | 
			
		||||
 | 
			
		||||
    if addr:
 | 
			
		||||
        if ":" in addr:
 | 
			
		||||
            try:
 | 
			
		||||
                host, port = addr.split(":")
 | 
			
		||||
            except BaseException:
 | 
			
		||||
                print(f"Invalid host address given: {addr}\n", file=sys.stderr)
 | 
			
		||||
                return 1
 | 
			
		||||
        else:
 | 
			
		||||
            host = addr
 | 
			
		||||
 | 
			
		||||
    if port:
 | 
			
		||||
        try:
 | 
			
		||||
            port = int(port)
 | 
			
		||||
        except BaseException:
 | 
			
		||||
            print(f"Invalid port given: {port}", file=sys.stderr)
 | 
			
		||||
            return 1
 | 
			
		||||
    else:
 | 
			
		||||
        port = 18003
 | 
			
		||||
 | 
			
		||||
    if not host:
 | 
			
		||||
        host = "localhost"
 | 
			
		||||
 | 
			
		||||
    output.setHost(host)
 | 
			
		||||
    output.setPort(port)
 | 
			
		||||
    output.setBufferSize(1 << 30)
 | 
			
		||||
    output.enableLogging()
 | 
			
		||||
 | 
			
		||||
    res = None
 | 
			
		||||
    print("Summary", file=sys.stderr)
 | 
			
		||||
    print(f"  + input file: {inputFile}", file=sys.stderr)
 | 
			
		||||
    print(f"  + input format: {inputFormat}", file=sys.stderr)
 | 
			
		||||
    print(f"  + caps server: {host}:{port}", file=sys.stderr)
 | 
			
		||||
    if network:
 | 
			
		||||
        print(f"  + set network: {network}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
    # generic signal
 | 
			
		||||
    if not inputFormat and not inputFile:
 | 
			
		||||
        startTime = CAPS.Time.GMT()
 | 
			
		||||
        print("Sending generic test data", file=sys.stderr)
 | 
			
		||||
        x = np.array([1, 2], dtype=np.int32)
 | 
			
		||||
 | 
			
		||||
        if not network:
 | 
			
		||||
            network = "AB"
 | 
			
		||||
        print(f"Assuming network '{network}'", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
        res = output.push(
 | 
			
		||||
            network, "HMA", "", "BHZ", startTime, 1, 1, "m", x, CAPS.DT_INT32
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if res != CAPS.Plugin.Success:
 | 
			
		||||
            print("Failed to send packet", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
        output.close()
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    if not inputFile:
 | 
			
		||||
        print("Must provide input file", file=sys.stderr)
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    # data from input file
 | 
			
		||||
    data = None
 | 
			
		||||
    if inputFormat == "slist":
 | 
			
		||||
        data = readSlist(inputFile, multiplier, network)
 | 
			
		||||
    elif inputFormat == "unavco":
 | 
			
		||||
        data = readUnavco(inputFile, multiplier, network)
 | 
			
		||||
    else:
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    if not data:
 | 
			
		||||
        print(f"Error parsing {inputFile}", file=sys.stderr)
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    print(f"Sending data to CAPS server {host}:{port}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
    res = output.push(
 | 
			
		||||
        data.net,
 | 
			
		||||
        data.sta,
 | 
			
		||||
        data.loc,
 | 
			
		||||
        data.cha,
 | 
			
		||||
        data.start,
 | 
			
		||||
        data.numerator,
 | 
			
		||||
        data.denominator,
 | 
			
		||||
        data.unit,
 | 
			
		||||
        data.samples,
 | 
			
		||||
        CAPS.DT_INT32,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if res != CAPS.Plugin.Success:
 | 
			
		||||
        output.close()
 | 
			
		||||
        print("Failed to send packet", file=sys.stderr)
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    return 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    sys.exit(main())
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/dlsv2inv
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/dlsv2inv
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										764
									
								
								bin/dump_picks
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										764
									
								
								bin/dump_picks
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,764 @@
 | 
			
		||||
#!/usr/bin/env seiscomp-python
 | 
			
		||||
 | 
			
		||||
############################################################################
 | 
			
		||||
# Copyright (C) 2016 by gempa GmbH                                         #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
# All Rights Reserved.                                                     #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
# NOTICE: All information contained herein is, and remains                 #
 | 
			
		||||
# the property of gempa GmbH and its suppliers, if any. The intellectual   #
 | 
			
		||||
# and technical concepts contained herein are proprietary to gempa GmbH    #
 | 
			
		||||
# and its suppliers.                                                       #
 | 
			
		||||
# Dissemination of this information or reproduction of this material       #
 | 
			
		||||
# is strictly forbidden unless prior written permission is obtained        #
 | 
			
		||||
# from gempa GmbH.                                                         #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
#  Author: Enrico Ellguth, Dirk Roessler                                   #
 | 
			
		||||
#  Email: enrico.ellguth@gempa.de, roessler@gempa.de                       #
 | 
			
		||||
############################################################################
 | 
			
		||||
 | 
			
		||||
import datetime
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
from seiscomp import core, datamodel, io
 | 
			
		||||
from seiscomp.client import Application
 | 
			
		||||
from seiscomp import geo
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
    fmt = "%Y %m %d %H %M %S"
 | 
			
		||||
    if timestring.find(".") != -1:
 | 
			
		||||
        fmt += ".%f"
 | 
			
		||||
 | 
			
		||||
    t = core.Time()
 | 
			
		||||
    t.fromString(timestring, fmt)
 | 
			
		||||
    return t
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def utc():
 | 
			
		||||
    return datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DumpPicks(Application):
 | 
			
		||||
    def __init__(self, argc, argv):
 | 
			
		||||
        Application.__init__(self, argc, argv)
 | 
			
		||||
        self.output = "-"
 | 
			
		||||
        self.type = "0"
 | 
			
		||||
        self.margin = [300]
 | 
			
		||||
        self.originID = None
 | 
			
		||||
        self.bbox = None
 | 
			
		||||
        self.noamp = False
 | 
			
		||||
        self.automatic = False
 | 
			
		||||
        self.manual = False
 | 
			
		||||
        self.checkInventory = False
 | 
			
		||||
        self.author = None
 | 
			
		||||
        self.hours = None
 | 
			
		||||
        self.minutes = None
 | 
			
		||||
        self.start = None
 | 
			
		||||
        self.end = None
 | 
			
		||||
        self.network = None
 | 
			
		||||
        self.station = None
 | 
			
		||||
        self.tmin = str2time("1970-01-01 00:00:00")
 | 
			
		||||
        self.tmax = str2time(str(utc()))
 | 
			
		||||
        self.delay = None
 | 
			
		||||
 | 
			
		||||
        self.setMessagingEnabled(False)
 | 
			
		||||
        self.setDatabaseEnabled(True, True)
 | 
			
		||||
 | 
			
		||||
    def createCommandLineDescription(self):
 | 
			
		||||
        self.commandline().addGroup("Dump")
 | 
			
		||||
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "hours",
 | 
			
		||||
            "Start search hours before now considering object time, not creation time. "
 | 
			
		||||
            "If --minutes is given as well they will be added. "
 | 
			
		||||
            "If set, --time-window, --start, --end are ignored.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "minutes",
 | 
			
		||||
            "Start search minutes before now considering object time, not creation time. "
 | 
			
		||||
            "If --hours is given as well they will be added. "
 | 
			
		||||
            "If set, --time-window, --start, --end are ignored.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "start",
 | 
			
		||||
            "Start time of search until now considering object time, not creation time."
 | 
			
		||||
            " If set, --time-window is ignored.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "end",
 | 
			
		||||
            "End time of search considering object time, not creation time. If set, "
 | 
			
		||||
            "--time-window is ignored.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "time-window,t",
 | 
			
		||||
            "Specify time window to search picks and amplitudes by their time. Use one "
 | 
			
		||||
            "single string which must be enclosed by quotes in case of spaces in the "
 | 
			
		||||
            "time string. Times are of course in UTC and separated by a tilde '~'. "
 | 
			
		||||
            "Uses: 1970-01-01 00:00:00 to now if not set.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "maximum-delay",
 | 
			
		||||
            "Maximum allowed delay of picks or amplitudes, hence the difference between"
 | 
			
		||||
            " creation time and actual time value. Allows identifcation of picks found "
 | 
			
		||||
            "in real time.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "region,r",
 | 
			
		||||
            "Dump picks only from sensors in given region. Implies loading an "
 | 
			
		||||
            "inventory.\n"
 | 
			
		||||
            "Format: minLat,minLon,maxLat,maxLon \n"
 | 
			
		||||
            "Default: -90,-180,90,180 if not set.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "check-inventory,c",
 | 
			
		||||
            "Dump picks only when corresponding streams are found in inventory.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "origin,O",
 | 
			
		||||
            "Origin ID. Dump all "
 | 
			
		||||
            "picks associated with the origin that has the given origin ID.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addOption("Dump", "manual,m", "Dump only manual picks.")
 | 
			
		||||
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Dump", "automatic,a", "Dump only automatic picks."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "no-amp,n",
 | 
			
		||||
            "Do not dump amplitudes from picks. "
 | 
			
		||||
            "Amplitudes are not required by scanloc.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Dump", "author", "Filter picks by the given author."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "net-sta",
 | 
			
		||||
            "Filter picks and amplitudes by given network code or "
 | 
			
		||||
            "network and station code. Format: NET or NET.STA.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addGroup("Output")
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "output,o",
 | 
			
		||||
            "Name of output file. If not given, all data is written to stdout.",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "type",
 | 
			
		||||
            f"Type of output format. Default: {self.type}.\n"
 | 
			
		||||
            "0 / scml: SCML containing all objects (default if option is not used)\n"
 | 
			
		||||
            "1 / streams: Time windows and streams for all picks like in scevtstreams\n"
 | 
			
		||||
            "2 / caps: Time windows and streams in capstool format\n"
 | 
			
		||||
            "3 / fdsnws: Time windows and streams in FDSN dataselect webservice POST \
 | 
			
		||||
format\n"
 | 
			
		||||
            "Except for type 0, only picks are considered ignoring all other objects.",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "formatted,f",
 | 
			
		||||
            "Output formatted XML. Default is unformatted. Applies only for type 0.",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "margin",
 | 
			
		||||
            "Time margin applied around pick times along with --type = [1:]. Use 2 "
 | 
			
		||||
            "comma-separted values (before,after) for asymmetric margins, e.g. "
 | 
			
		||||
            f"--margin 120,300. Default: {self.margin[0]} s.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def printUsage(self):
 | 
			
		||||
        print(
 | 
			
		||||
            f"""Usage:
 | 
			
		||||
  {os.path.basename(__file__)} [options]
 | 
			
		||||
 | 
			
		||||
Read picks and amplitudes from database and dump them to a file or to standard output.\
 | 
			
		||||
"""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        Application.printUsage(self)
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            f"""Examples:
 | 
			
		||||
Dump all picks within a region and a period of time
 | 
			
		||||
  {os.path.basename(__file__)} -d localhost -t 2023-01-20T13:52:00~2023-01-20T13:57:00\
 | 
			
		||||
 -r "-10,-90,10,120"
 | 
			
		||||
 | 
			
		||||
Search 24 hours before now for automatic picks from author "scautopick" with low delay \
 | 
			
		||||
ignoring amplitudes
 | 
			
		||||
  {os.path.basename(__file__)} -d localhost --hours 24 -a -n --author "scautopick" \
 | 
			
		||||
--maximum-delay 60
 | 
			
		||||
 | 
			
		||||
Dump the streams of picks with time windows fetching the corresponding data from a \
 | 
			
		||||
local CAPS server
 | 
			
		||||
  {os.path.basename(__file__)} -d localhost --type 2 --margin 60 | capstool \
 | 
			
		||||
-H localhost -o data.mseed
 | 
			
		||||
 | 
			
		||||
Dump the streams of picks with time windows fetching the corresponding data from a \
 | 
			
		||||
local SDS archive
 | 
			
		||||
  {os.path.basename(__file__)} -d localhost --type 1 --margin 60 | scart -dsE -l - \
 | 
			
		||||
/archive -o data.mseed
 | 
			
		||||
"""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def init(self):
 | 
			
		||||
        if not Application.init(self):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.output = self.commandline().optionString("output")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.type = self.commandline().optionString("type")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if self.type == "scml":
 | 
			
		||||
            self.type = "0"
 | 
			
		||||
        elif self.type == "streams":
 | 
			
		||||
            self.type = "1"
 | 
			
		||||
        elif self.type == "caps":
 | 
			
		||||
            self.type = "2"
 | 
			
		||||
        elif self.type == "fdsnws":
 | 
			
		||||
            self.type = "3"
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.margin = self.commandline().optionString("margin").split(",")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.originID = self.commandline().optionString("origin")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if not self.originID:
 | 
			
		||||
            try:
 | 
			
		||||
                boundingBox = self.commandline().optionString("region")
 | 
			
		||||
                self.bbox = boundingBox.split(",")
 | 
			
		||||
                if len(self.bbox) != 4:
 | 
			
		||||
                    print(
 | 
			
		||||
                        "Invalid region given, expected lat0,lon0,lat1,lon1",
 | 
			
		||||
                        file=sys.stderr,
 | 
			
		||||
                    )
 | 
			
		||||
                    return False
 | 
			
		||||
 | 
			
		||||
                self.bbox[0] = str(geo.GeoCoordinate.normalizeLat(float(self.bbox[0])))
 | 
			
		||||
                self.bbox[1] = str(geo.GeoCoordinate.normalizeLon(float(self.bbox[1])))
 | 
			
		||||
                self.bbox[2] = str(geo.GeoCoordinate.normalizeLat(float(self.bbox[2])))
 | 
			
		||||
                self.bbox[3] = str(geo.GeoCoordinate.normalizeLon(float(self.bbox[3])))
 | 
			
		||||
 | 
			
		||||
                self.checkInventory = True
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                boundingBox = "-90,-180,90,180"
 | 
			
		||||
                self.bbox = boundingBox.split(",")
 | 
			
		||||
 | 
			
		||||
            print("Settings", file=sys.stderr)
 | 
			
		||||
            print(
 | 
			
		||||
                f"  + considered region: {self.bbox[0]} - {self.bbox[2]} deg North, "
 | 
			
		||||
                f"{self.bbox[1]} - {self.bbox[3]} deg East",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                self.hours = float(self.commandline().optionString("hours"))
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                self.minutes = float(self.commandline().optionString("minutes"))
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                self.start = self.commandline().optionString("start")
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                self.end = self.commandline().optionString("end")
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            delta = 0.0
 | 
			
		||||
            if self.hours:
 | 
			
		||||
                delta = self.hours * 60
 | 
			
		||||
            if self.minutes:
 | 
			
		||||
                delta += self.minutes
 | 
			
		||||
 | 
			
		||||
            if self.hours or self.minutes:
 | 
			
		||||
                print(
 | 
			
		||||
                    "  + time window set by hours and/or minutes option: ignoring all "
 | 
			
		||||
                    "other time parameters",
 | 
			
		||||
                    file=sys.stderr,
 | 
			
		||||
                )
 | 
			
		||||
                dt = datetime.timedelta(minutes=delta)
 | 
			
		||||
 | 
			
		||||
                self.tmin = str2time(str(utc() - dt))
 | 
			
		||||
                self.tmax = str2time(str(utc()))
 | 
			
		||||
                self.start = None
 | 
			
		||||
                self.end = None
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
                if self.start:
 | 
			
		||||
                    print(
 | 
			
		||||
                        "  + time window set by start option: ignoring --time-window",
 | 
			
		||||
                        file=sys.stderr,
 | 
			
		||||
                    )
 | 
			
		||||
                    self.tmin = str2time(self.start)
 | 
			
		||||
 | 
			
		||||
                if self.end:
 | 
			
		||||
                    print(
 | 
			
		||||
                        "  + time window set by end option: ignoring --time-window",
 | 
			
		||||
                        file=sys.stderr,
 | 
			
		||||
                    )
 | 
			
		||||
                    self.tmax = str2time(self.end)
 | 
			
		||||
 | 
			
		||||
                if not self.start and not self.end:
 | 
			
		||||
                    try:
 | 
			
		||||
                        self.tmin, self.tmax = map(
 | 
			
		||||
                            str2time,
 | 
			
		||||
                            self.commandline().optionString("time-window").split("~"),
 | 
			
		||||
                        )
 | 
			
		||||
                        print(
 | 
			
		||||
                            "  + time window set by time-window option", file=sys.stderr
 | 
			
		||||
                        )
 | 
			
		||||
                    except RuntimeError:
 | 
			
		||||
                        print(
 | 
			
		||||
                            "  + no time window given exlicitly: Assuming defaults",
 | 
			
		||||
                            file=sys.stderr,
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
            print(
 | 
			
		||||
                f"  + considered time window: {str(self.tmin)} - {str(self.tmax)}",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            print(
 | 
			
		||||
                "  + searching for picks is based on originID, ignoring "
 | 
			
		||||
                "region and time window",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.delay = float(self.commandline().optionString("maximum-delay"))
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if not self.checkInventory:
 | 
			
		||||
            self.checkInventory = self.commandline().hasOption("check-inventory")
 | 
			
		||||
 | 
			
		||||
        if self.checkInventory:
 | 
			
		||||
            print(
 | 
			
		||||
                "  + dumping only picks for streams found in inventory", file=sys.stderr
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            print(
 | 
			
		||||
                "  + do not consider inventory information for dumping picks",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if self.commandline().hasOption("no-amp"):
 | 
			
		||||
            self.noamp = True
 | 
			
		||||
        else:
 | 
			
		||||
            self.noamp = False
 | 
			
		||||
 | 
			
		||||
        if self.type != "0":
 | 
			
		||||
            self.noamp = True
 | 
			
		||||
 | 
			
		||||
        if self.noamp:
 | 
			
		||||
            print("  + dumping picks without amplitudes", file=sys.stderr)
 | 
			
		||||
        else:
 | 
			
		||||
            print("  + dumping picks with amplitudes", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
        if self.commandline().hasOption("manual"):
 | 
			
		||||
            self.manual = True
 | 
			
		||||
            print("  + dumping only manual objects", file=sys.stderr)
 | 
			
		||||
        else:
 | 
			
		||||
            self.manual = False
 | 
			
		||||
            print("  + considering also manual objects", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
        if self.commandline().hasOption("automatic"):
 | 
			
		||||
            if not self.manual:
 | 
			
		||||
                self.automatic = True
 | 
			
		||||
                print("  + dumping only automatic picks", file=sys.stderr)
 | 
			
		||||
            else:
 | 
			
		||||
                print(
 | 
			
		||||
                    "EXIT - Script was started with competing options -a and -m",
 | 
			
		||||
                    file=sys.stderr,
 | 
			
		||||
                )
 | 
			
		||||
                return False
 | 
			
		||||
        else:
 | 
			
		||||
            self.automatic = False
 | 
			
		||||
            print("  + considering also automatic objects", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.author = self.commandline().optionString("author")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        networkStation = None
 | 
			
		||||
        try:
 | 
			
		||||
            networkStation = self.commandline().optionString("net-sta")
 | 
			
		||||
            print(
 | 
			
		||||
                f"  + filter objects by network / station code: {networkStation}",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if networkStation:
 | 
			
		||||
            try:
 | 
			
		||||
                self.network = networkStation.split(".")[0]
 | 
			
		||||
            except IndexError:
 | 
			
		||||
                print(
 | 
			
		||||
                    f"Error in network code '{networkStation}': Use '--net-sta' with "
 | 
			
		||||
                    "format NET or NET.STA",
 | 
			
		||||
                    file=sys.stderr,
 | 
			
		||||
                )
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                self.station = networkStation.split(".")[1]
 | 
			
		||||
            except IndexError:
 | 
			
		||||
                print(
 | 
			
		||||
                    f"  + no station code given in '--net-sta {networkStation}' - "
 | 
			
		||||
                    "using all stations from network",
 | 
			
		||||
                    file=sys.stderr,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        db = self.database()
 | 
			
		||||
 | 
			
		||||
        def _T(name):
 | 
			
		||||
            return db.convertColumnName(name)
 | 
			
		||||
 | 
			
		||||
        def _time(time):
 | 
			
		||||
            return db.timeToString(time)
 | 
			
		||||
 | 
			
		||||
        colLat, colLon = _T("latitude"), _T("longitude")
 | 
			
		||||
 | 
			
		||||
        dbq = self.query()
 | 
			
		||||
        ep = datamodel.EventParameters()
 | 
			
		||||
        picks = []
 | 
			
		||||
        noAmps = 0
 | 
			
		||||
 | 
			
		||||
        if self.originID:
 | 
			
		||||
            for p in dbq.getPicks(self.originID):
 | 
			
		||||
                picks.append(datamodel.Pick.Cast(p))
 | 
			
		||||
 | 
			
		||||
            for p in picks:
 | 
			
		||||
                dbq.loadComments(p)
 | 
			
		||||
                ep.add(p)
 | 
			
		||||
 | 
			
		||||
            if not self.noamp:
 | 
			
		||||
                for a in dbq.getAmplitudesForOrigin(self.originID):
 | 
			
		||||
                    amp = datamodel.Amplitude.Cast(a)
 | 
			
		||||
                    ep.add(amp)
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            fmt = "%Y-%m-%d %H:%M:%S"
 | 
			
		||||
            if self.checkInventory:
 | 
			
		||||
                q = (
 | 
			
		||||
                    "select distinct(PPick.%s), Pick.* "
 | 
			
		||||
                    "from PublicObject as PPick, Pick, Network, Station, SensorLocation "
 | 
			
		||||
                    "where PPick._oid=Pick._oid and Network._oid=Station._parent_oid and "
 | 
			
		||||
                    "Station._oid=SensorLocation._parent_oid and Station.%s >= %s and "
 | 
			
		||||
                    "Station.%s <= %s and Station.%s >= %s and Station.%s <= %s and "
 | 
			
		||||
                    "SensorLocation.%s=Pick.%s and SensorLocation.%s <= Pick.%s and "
 | 
			
		||||
                    "(SensorLocation.%s is null or SensorLocation.%s > Pick.%s) and "
 | 
			
		||||
                    "Station.%s=Pick.%s and Network.%s=Pick.%s and "
 | 
			
		||||
                    "Pick.%s >= '%s' and Pick.%s < '%s'"
 | 
			
		||||
                    ""
 | 
			
		||||
                    % (
 | 
			
		||||
                        _T("publicID"),
 | 
			
		||||
                        colLat,
 | 
			
		||||
                        self.bbox[0],
 | 
			
		||||
                        colLat,
 | 
			
		||||
                        self.bbox[2],
 | 
			
		||||
                        colLon,
 | 
			
		||||
                        self.bbox[1],
 | 
			
		||||
                        colLon,
 | 
			
		||||
                        self.bbox[3],
 | 
			
		||||
                        _T("code"),
 | 
			
		||||
                        _T("waveformID_locationCode"),
 | 
			
		||||
                        _T("start"),
 | 
			
		||||
                        _T("time_value"),
 | 
			
		||||
                        _T("end"),
 | 
			
		||||
                        _T("end"),
 | 
			
		||||
                        _T("time_value"),
 | 
			
		||||
                        _T("code"),
 | 
			
		||||
                        _T("waveformID_stationCode"),
 | 
			
		||||
                        _T("code"),
 | 
			
		||||
                        _T("waveformID_networkCode"),
 | 
			
		||||
                        _T("time_value"),
 | 
			
		||||
                        self.tmin.toString(fmt),
 | 
			
		||||
                        _T("time_value"),
 | 
			
		||||
                        self.tmax.toString(fmt),
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
            else:
 | 
			
		||||
                q = (
 | 
			
		||||
                    "select distinct(PPick.%s), Pick.* "
 | 
			
		||||
                    "from PublicObject as PPick, Pick "
 | 
			
		||||
                    "where PPick._oid=Pick._oid and "
 | 
			
		||||
                    "Pick.%s >= '%s' and Pick.%s < '%s'"
 | 
			
		||||
                    ""
 | 
			
		||||
                    % (
 | 
			
		||||
                        _T("publicID"),
 | 
			
		||||
                        _T("time_value"),
 | 
			
		||||
                        self.tmin.toString(fmt),
 | 
			
		||||
                        _T("time_value"),
 | 
			
		||||
                        self.tmax.toString(fmt),
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            if self.manual:
 | 
			
		||||
                q = q + f" and Pick.{_T('evaluationMode')} = 'manual' "
 | 
			
		||||
 | 
			
		||||
            if self.automatic:
 | 
			
		||||
                q = q + f" and Pick.{_T('evaluationMode')} = 'automatic' "
 | 
			
		||||
 | 
			
		||||
            if self.author:
 | 
			
		||||
                q = q + f" and Pick.{_T('creationInfo_author')} = '{self.author}' "
 | 
			
		||||
 | 
			
		||||
            if self.network:
 | 
			
		||||
                q = q + f" and Pick.{_T('waveformID_networkCode')} = '{self.network}' "
 | 
			
		||||
 | 
			
		||||
            if self.station:
 | 
			
		||||
                q = q + f" and Pick.{_T('waveformID_stationCode')} = '{self.station}' "
 | 
			
		||||
 | 
			
		||||
            for p in dbq.getObjectIterator(q, datamodel.Pick.TypeInfo()):
 | 
			
		||||
                pick = datamodel.Pick.Cast(p)
 | 
			
		||||
                if (
 | 
			
		||||
                    self.delay
 | 
			
		||||
                    and float(pick.creationInfo().creationTime() - pick.time().value())
 | 
			
		||||
                    > self.delay
 | 
			
		||||
                ):
 | 
			
		||||
                    continue
 | 
			
		||||
                picks.append(pick)
 | 
			
		||||
 | 
			
		||||
            for p in picks:
 | 
			
		||||
                dbq.loadComments(p)
 | 
			
		||||
                ep.add(p)
 | 
			
		||||
 | 
			
		||||
            if not self.noamp:
 | 
			
		||||
                if self.checkInventory:
 | 
			
		||||
                    q = (
 | 
			
		||||
                        "select distinct(PAmplitude.%s), Amplitude.* "
 | 
			
		||||
                        "from PublicObject as PAmplitude, Amplitude, PublicObject \
 | 
			
		||||
                        as PPick, Pick, Network, Station, SensorLocation "
 | 
			
		||||
                        "where PAmplitude._oid=Amplitude._oid and "
 | 
			
		||||
                        "PPick._oid=Pick._oid and Network._oid=Station._parent_oid and "
 | 
			
		||||
                        "Station._oid=SensorLocation._parent_oid and Station.%s >= %s and "
 | 
			
		||||
                        "Station.%s <= %s and Station.%s >= %s and Station.%s <= %s and "
 | 
			
		||||
                        "SensorLocation.%s=Pick.%s and SensorLocation.%s <= Pick.%s and "
 | 
			
		||||
                        "(SensorLocation.%s is null or SensorLocation.%s > Pick.%s) and "
 | 
			
		||||
                        "Station.%s=Pick.%s and Network.%s=Pick.%s and "
 | 
			
		||||
                        "Pick.%s >= '%s' and Pick.%s < '%s' and PPick.%s=Amplitude.%s"
 | 
			
		||||
                        ""
 | 
			
		||||
                        % (
 | 
			
		||||
                            _T("publicID"),
 | 
			
		||||
                            colLat,
 | 
			
		||||
                            self.bbox[0],
 | 
			
		||||
                            colLat,
 | 
			
		||||
                            self.bbox[2],
 | 
			
		||||
                            colLon,
 | 
			
		||||
                            self.bbox[1],
 | 
			
		||||
                            colLon,
 | 
			
		||||
                            self.bbox[3],
 | 
			
		||||
                            _T("code"),
 | 
			
		||||
                            _T("waveformID_locationCode"),
 | 
			
		||||
                            _T("start"),
 | 
			
		||||
                            _T("time_value"),
 | 
			
		||||
                            _T("end"),
 | 
			
		||||
                            _T("end"),
 | 
			
		||||
                            _T("time_value"),
 | 
			
		||||
                            _T("code"),
 | 
			
		||||
                            _T("waveformID_stationCode"),
 | 
			
		||||
                            _T("code"),
 | 
			
		||||
                            _T("waveformID_networkCode"),
 | 
			
		||||
                            _T("time_value"),
 | 
			
		||||
                            self.tmin.toString(fmt),
 | 
			
		||||
                            _T("time_value"),
 | 
			
		||||
                            self.tmax.toString(fmt),
 | 
			
		||||
                            _T("publicID"),
 | 
			
		||||
                            _T("pickID"),
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
                else:
 | 
			
		||||
                    q = (
 | 
			
		||||
                        "select distinct(PAmplitude.%s), Amplitude.* "
 | 
			
		||||
                        "from PublicObject as PAmplitude, Amplitude, PublicObject as PPick, Pick "
 | 
			
		||||
                        "where PAmplitude._oid=Amplitude._oid and PPick._oid=Pick._oid and "
 | 
			
		||||
                        "Pick.%s >= '%s' and Pick.%s < '%s' and PPick.%s=Amplitude.%s"
 | 
			
		||||
                        ""
 | 
			
		||||
                        % (
 | 
			
		||||
                            _T("publicID"),
 | 
			
		||||
                            _T("time_value"),
 | 
			
		||||
                            self.tmin.toString(fmt),
 | 
			
		||||
                            _T("time_value"),
 | 
			
		||||
                            self.tmax.toString(fmt),
 | 
			
		||||
                            _T("publicID"),
 | 
			
		||||
                            _T("pickID"),
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                if self.manual:
 | 
			
		||||
                    q = q + f" and Pick.{_T('evaluationMode')} = 'manual' "
 | 
			
		||||
                if self.automatic:
 | 
			
		||||
                    q = q + f" and Pick.{_T('evaluationMode')} = 'automatic' "
 | 
			
		||||
 | 
			
		||||
                if self.author:
 | 
			
		||||
                    q = q + f" and Pick.{_T('creationInfo_author')} = '{self.author}' "
 | 
			
		||||
 | 
			
		||||
                if self.network:
 | 
			
		||||
                    q = q + " and Pick.%s = '%s' " % (
 | 
			
		||||
                        _T("waveformID_networkCode"),
 | 
			
		||||
                        self.network,
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                if self.station:
 | 
			
		||||
                    q = q + " and Pick.%s = '%s' " % (
 | 
			
		||||
                        _T("waveformID_stationCode"),
 | 
			
		||||
                        self.station,
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                for a in dbq.getObjectIterator(q, datamodel.Amplitude.TypeInfo()):
 | 
			
		||||
                    amp = datamodel.Amplitude.Cast(a)
 | 
			
		||||
                    if (
 | 
			
		||||
                        self.delay
 | 
			
		||||
                        and float(
 | 
			
		||||
                            amp.creationInfo().creationTime()
 | 
			
		||||
                            - amp.timeWindow().reference()
 | 
			
		||||
                        )
 | 
			
		||||
                        > self.delay
 | 
			
		||||
                    ):
 | 
			
		||||
                        continue
 | 
			
		||||
                    ep.add(amp)
 | 
			
		||||
                    noAmps += 1
 | 
			
		||||
 | 
			
		||||
        if self.type == "0":
 | 
			
		||||
            ar = io.XMLArchive()
 | 
			
		||||
            ar.create(self.output)
 | 
			
		||||
            ar.setFormattedOutput(self.commandline().hasOption("formatted"))
 | 
			
		||||
            ar.writeObject(ep)
 | 
			
		||||
            ar.close()
 | 
			
		||||
        elif self.type in ["1", "2", "3"]:
 | 
			
		||||
            if len(picks) == 0:
 | 
			
		||||
                print(
 | 
			
		||||
                    "No picks are found and written",
 | 
			
		||||
                    file=sys.stderr,
 | 
			
		||||
                )
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            # convert times to string depending on requested output format
 | 
			
		||||
            # time and line format
 | 
			
		||||
            if self.type == "2":
 | 
			
		||||
                timeFMT = "%Y,%m,%d,%H,%M,%S"
 | 
			
		||||
                lineFMT = "{0} {1} {2} {3} {4} {5}"
 | 
			
		||||
            elif self.type == "3":
 | 
			
		||||
                timeFMT = "%FT%T"
 | 
			
		||||
                lineFMT = "{2} {3} {4} {5} {0} {1}"
 | 
			
		||||
            else:
 | 
			
		||||
                timeFMT = "%F %T"
 | 
			
		||||
                lineFMT = "{0};{1};{2}.{3}.{4}.{5}"
 | 
			
		||||
 | 
			
		||||
            lines = set()
 | 
			
		||||
            for pick in picks:
 | 
			
		||||
                net = pick.waveformID().networkCode()
 | 
			
		||||
                station = pick.waveformID().stationCode()
 | 
			
		||||
                loc = pick.waveformID().locationCode()
 | 
			
		||||
                channelGroup = f"{pick.waveformID().channelCode()[:2]}*"
 | 
			
		||||
 | 
			
		||||
                # FDSNWS requires empty location to be encoded by 2 dashes
 | 
			
		||||
                if not loc and self.type == "3":
 | 
			
		||||
                    loc = "--"
 | 
			
		||||
 | 
			
		||||
                # add some marging to picks times
 | 
			
		||||
                minTime = pick.time().value() - core.TimeSpan(float(self.margin[0]))
 | 
			
		||||
                maxTime = pick.time().value() + core.TimeSpan(float(self.margin[-1]))
 | 
			
		||||
                minTime = minTime.toString(timeFMT)
 | 
			
		||||
                maxTime = maxTime.toString(timeFMT)
 | 
			
		||||
                lines.add(
 | 
			
		||||
                    lineFMT.format(minTime, maxTime, net, station, loc, channelGroup)
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            if self.output == "-":
 | 
			
		||||
                out = sys.stdout
 | 
			
		||||
            else:
 | 
			
		||||
                print(f"Output data to file: {self.output}", file=sys.stderr)
 | 
			
		||||
                try:
 | 
			
		||||
                    out = open(self.output, "w", encoding="utf8")
 | 
			
		||||
                except Exception:
 | 
			
		||||
                    print("Cannot create output file '{self.output}'", file=sys.stderr)
 | 
			
		||||
                    return False
 | 
			
		||||
 | 
			
		||||
            for line in sorted(lines):
 | 
			
		||||
                print(line, file=out)
 | 
			
		||||
 | 
			
		||||
            if self.output != "-":
 | 
			
		||||
                out.close()
 | 
			
		||||
        else:
 | 
			
		||||
            print(
 | 
			
		||||
                f"Unspupported output format '{self.type}': No objects are written",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            f"Saved: {len(picks):d} picks, {noAmps:d} amplitudes",
 | 
			
		||||
            file=sys.stderr,
 | 
			
		||||
        )
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main(argv):
 | 
			
		||||
    app = DumpPicks(len(argv), argv)
 | 
			
		||||
    return app()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    sys.exit(main(sys.argv))
 | 
			
		||||
							
								
								
									
										28
									
								
								bin/extr_file
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										28
									
								
								bin/extr_file
									
									
									
									
									
										Executable file
									
								
							@ -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()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1620
									
								
								bin/fdsnws
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										1620
									
								
								bin/fdsnws
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/fdsnxml2inv
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/fdsnxml2inv
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/gdi2caps
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/gdi2caps
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										194
									
								
								bin/gfs2fep
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										194
									
								
								bin/gfs2fep
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,194 @@
 | 
			
		||||
#!/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 datetime
 | 
			
		||||
import getopt
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
from typing import TextIO
 | 
			
		||||
 | 
			
		||||
from seiscomp import geo
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
def printHelp():
 | 
			
		||||
    msg = """
 | 
			
		||||
gfs2fep - converts a SeisComP GeoFeatureSet file (GeoJSON or BNA) to FEP format
 | 
			
		||||
 | 
			
		||||
usage: {} [OPTIONS]
 | 
			
		||||
 | 
			
		||||
    -h, --help
 | 
			
		||||
        print this help message
 | 
			
		||||
 | 
			
		||||
    -i, --input
 | 
			
		||||
        input file (default: -)
 | 
			
		||||
 | 
			
		||||
    -o, --output
 | 
			
		||||
        output fep file (default: -)
 | 
			
		||||
 | 
			
		||||
    -a, --append
 | 
			
		||||
        append fep data to output file instead of overriding it
 | 
			
		||||
 | 
			
		||||
    -p, --precision (default: unrestricted)
 | 
			
		||||
        number of decimal places of coordintes"""
 | 
			
		||||
    print(msg.format(sys.argv[0]), file=sys.stderr)
 | 
			
		||||
    sys.exit(0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
def error(code, msg):
 | 
			
		||||
    print(f"error ({code}): {msg}", file=sys.stderr)
 | 
			
		||||
    sys.exit(code)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
def run():
 | 
			
		||||
    if len(sys.argv) == 1:
 | 
			
		||||
        printHelp()
 | 
			
		||||
 | 
			
		||||
    inFile = "-"
 | 
			
		||||
    outFile = None
 | 
			
		||||
    append = False
 | 
			
		||||
    precision = None
 | 
			
		||||
    opts, _ = getopt.getopt(
 | 
			
		||||
        sys.argv[1:], "hi:o:ap:", ["help", "input=", "output=", "append", "precision"]
 | 
			
		||||
    )
 | 
			
		||||
    for o, a in opts:
 | 
			
		||||
        if o in ("-h", "--help"):
 | 
			
		||||
            printHelp()
 | 
			
		||||
 | 
			
		||||
        if o in ("-i", "--input"):
 | 
			
		||||
            inFile = a
 | 
			
		||||
 | 
			
		||||
        if o in ("-o", "--output"):
 | 
			
		||||
            outFile = a
 | 
			
		||||
 | 
			
		||||
        if o in ("-a", "--append"):
 | 
			
		||||
            append = True
 | 
			
		||||
 | 
			
		||||
        if o in ("-p", "--precision"):
 | 
			
		||||
            precision = max(int(a), 0)
 | 
			
		||||
 | 
			
		||||
    gfs = geo.GeoFeatureSet()
 | 
			
		||||
    if not gfs.readFile(inFile, None):
 | 
			
		||||
        error(1, f"Could not read from file '{inFile}'")
 | 
			
		||||
 | 
			
		||||
    # combine features sharing the same name
 | 
			
		||||
    featureDict = {}
 | 
			
		||||
    for f in gfs.features():
 | 
			
		||||
        if not f.closedPolygon():
 | 
			
		||||
            print(
 | 
			
		||||
                f"warning: feature not a closed polygon: {f.name()}",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
        if f.name() in featureDict:
 | 
			
		||||
            featureDict[f.name()].append(f)
 | 
			
		||||
        else:
 | 
			
		||||
            featureDict[f.name()] = [f]
 | 
			
		||||
 | 
			
		||||
    # output is set to stdout or a file name if specified
 | 
			
		||||
    if outFile and outFile != "-":
 | 
			
		||||
        try:
 | 
			
		||||
            with open(outFile, "a" if append else "w", encoding="utf8") as fp:
 | 
			
		||||
                writeFEPFile(featureDict, inFile, fp, precision)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            error(2, e)
 | 
			
		||||
 | 
			
		||||
    else:
 | 
			
		||||
        writeFEPFile(featureDict, inFile, sys.stdout, precision)
 | 
			
		||||
        sys.stdout.flush()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
def writeFEPFile(featureDict: dict, inFile: str, fp: TextIO, precision: int = None):
 | 
			
		||||
    def _print(data: str):
 | 
			
		||||
        print(data, file=fp)
 | 
			
		||||
 | 
			
		||||
    if precision:
 | 
			
		||||
 | 
			
		||||
        def _printVertex(v):
 | 
			
		||||
            print(
 | 
			
		||||
                f"{v.longitude():.{precision}f} {v.latitude():.{precision}f}", file=fp
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    else:
 | 
			
		||||
 | 
			
		||||
        def _printVertex(v):
 | 
			
		||||
            print(f"{v.longitude()} {v.latitude()}", file=fp)
 | 
			
		||||
 | 
			
		||||
    _print(f"# created from file: {inFile}")
 | 
			
		||||
    _print(
 | 
			
		||||
        f"# created on {str(datetime.datetime.now())} by gfs2fep.py - (C) gempa GmbH"
 | 
			
		||||
    )
 | 
			
		||||
    _print("# LON LAT")
 | 
			
		||||
 | 
			
		||||
    # write fep
 | 
			
		||||
    for name, features in featureDict.items():
 | 
			
		||||
        # print("{}: {}".format(len(features), name))
 | 
			
		||||
        vCount = 0
 | 
			
		||||
        fStart = features[0].vertices()[0]
 | 
			
		||||
        v = fStart
 | 
			
		||||
 | 
			
		||||
        # iterate over features sharing name
 | 
			
		||||
        for f in features:
 | 
			
		||||
            # vertex array contains vertices of main land and sub features
 | 
			
		||||
            vertices = f.vertices()
 | 
			
		||||
 | 
			
		||||
            # sub feature array holds indices of starting points
 | 
			
		||||
            endIndices = list(f.subFeatures()) + [len(vertices)]
 | 
			
		||||
 | 
			
		||||
            # iterate of main land and sub features
 | 
			
		||||
            i = 0
 | 
			
		||||
            for iEnd in endIndices:
 | 
			
		||||
                vStart = vertices[i]
 | 
			
		||||
                while i < iEnd:
 | 
			
		||||
                    v = vertices[i]
 | 
			
		||||
                    _printVertex(v)
 | 
			
		||||
                    vCount += 1
 | 
			
		||||
                    i += 1
 | 
			
		||||
 | 
			
		||||
                # end sub feature on sub feature start
 | 
			
		||||
                v = vStart
 | 
			
		||||
                _printVertex(v)
 | 
			
		||||
                vCount += 1
 | 
			
		||||
 | 
			
		||||
                # go back to start of main land
 | 
			
		||||
                if v != vertices[0]:
 | 
			
		||||
                    v = vertices[0]
 | 
			
		||||
                    _printVertex(v)
 | 
			
		||||
                    vCount += 1
 | 
			
		||||
 | 
			
		||||
            # go back to start of first feature
 | 
			
		||||
            if v != fStart:
 | 
			
		||||
                v = fStart
 | 
			
		||||
                _printVertex(v)
 | 
			
		||||
                vCount += 1
 | 
			
		||||
 | 
			
		||||
        # end fep region
 | 
			
		||||
        _print(f"99.0 99.0 {vCount}")
 | 
			
		||||
        _print(f"L  {name}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    run()
 | 
			
		||||
							
								
								
									
										138
									
								
								bin/image2caps
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										138
									
								
								bin/image2caps
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,138 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import signal
 | 
			
		||||
import getopt
 | 
			
		||||
from gempa import CAPS
 | 
			
		||||
 | 
			
		||||
usage_info = f"""Usage:
 | 
			
		||||
  {os.path.basename(__file__)} [options]
 | 
			
		||||
 | 
			
		||||
Push images into CAPS.
 | 
			
		||||
 | 
			
		||||
Options:
 | 
			
		||||
    -H, --host                   Host to connect to.
 | 
			
		||||
    -h, --help                   Display this help message and exit.
 | 
			
		||||
    -d, --directory              Input directory to use.
 | 
			
		||||
    -n, --network                Network code.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def usage(exitcode=0):
 | 
			
		||||
    print(usage_info)
 | 
			
		||||
    return exitcode
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
output = CAPS.Plugin("imageimporter")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def signal_handler(sig, frame):  # pylint: disable=W0613
 | 
			
		||||
    print("Caught Ctrl+C!")
 | 
			
		||||
    output.quit()
 | 
			
		||||
    sys.exit(0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def sendImage(netcode, stacode, timestamp, filename):
 | 
			
		||||
    f = open(filename, "rb")
 | 
			
		||||
    if not f:
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    data = f.read()
 | 
			
		||||
    f.close()
 | 
			
		||||
 | 
			
		||||
    output.push(netcode, stacode, "", "CAM", timestamp, 1, 0, "JPG", data)
 | 
			
		||||
 | 
			
		||||
    return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    try:
 | 
			
		||||
        opts, _ = getopt.getopt(
 | 
			
		||||
            sys.argv[1:], "hH:d:n:", ["help", "host=", "directory=", "network="]
 | 
			
		||||
        )
 | 
			
		||||
    except getopt.GetoptError as err:
 | 
			
		||||
        # print help information and exit:
 | 
			
		||||
        print(str(err))  # will print something like "option -a not recognized"
 | 
			
		||||
        return usage(2)
 | 
			
		||||
 | 
			
		||||
    signal.signal(signal.SIGINT, signal_handler)
 | 
			
		||||
 | 
			
		||||
    addr = None
 | 
			
		||||
    directory = None
 | 
			
		||||
    netcode = None
 | 
			
		||||
 | 
			
		||||
    for o, a in opts:
 | 
			
		||||
        if o in ["-h", "--help"]:
 | 
			
		||||
            return usage()
 | 
			
		||||
 | 
			
		||||
        if o in ["-H", "--host"]:
 | 
			
		||||
            addr = a
 | 
			
		||||
        elif o in ["-d", "--directory"]:
 | 
			
		||||
            directory = a
 | 
			
		||||
        elif o in ["-n", "--network"]:
 | 
			
		||||
            netcode = a
 | 
			
		||||
        else:
 | 
			
		||||
            assert False, "unhandled option"
 | 
			
		||||
 | 
			
		||||
    if not netcode:
 | 
			
		||||
        print("No network code given\n", file=sys.stderr)
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    if addr:
 | 
			
		||||
        try:
 | 
			
		||||
            host, port = addr.split(":")
 | 
			
		||||
        except BaseException:
 | 
			
		||||
            print(f"invalid host address given: {addr}\n", file=sys.stderr)
 | 
			
		||||
            return 1
 | 
			
		||||
    else:
 | 
			
		||||
        host = None
 | 
			
		||||
        port = None
 | 
			
		||||
 | 
			
		||||
    if port:
 | 
			
		||||
        try:
 | 
			
		||||
            port = int(port)
 | 
			
		||||
        except BaseException:
 | 
			
		||||
            print(f"invalid port given: {port}\n", file=sys.stderr)
 | 
			
		||||
            return 1
 | 
			
		||||
    else:
 | 
			
		||||
        port = 18002
 | 
			
		||||
 | 
			
		||||
    if not host:
 | 
			
		||||
        host = "localhost"
 | 
			
		||||
 | 
			
		||||
    output.setHost(host)
 | 
			
		||||
    output.setPort(port)
 | 
			
		||||
    output.setBufferSize(1 << 30)
 | 
			
		||||
 | 
			
		||||
    print(f"Looking for images in directory: {directory}\n", file=sys.stderr)
 | 
			
		||||
    # Find all images files in the given directory
 | 
			
		||||
    for path, _, files in os.walk(directory):
 | 
			
		||||
        i = 1
 | 
			
		||||
        count = len(files)
 | 
			
		||||
        for fn in files:
 | 
			
		||||
            if not fn.endswith(".jpg"):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            toks = fn.split("_")
 | 
			
		||||
            if len(toks) < 4:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            toks = toks[len(toks) - 4 :]
 | 
			
		||||
 | 
			
		||||
            stacode = toks[1]
 | 
			
		||||
            timestamp = CAPS.Time.FromString(toks[2] + toks[3], "%Y%m%d%H%M")
 | 
			
		||||
 | 
			
		||||
            filename = os.path.join(path, fn)
 | 
			
		||||
 | 
			
		||||
            timeString = timestamp.iso()
 | 
			
		||||
            print(f"Sending image {fn}, {i}/{count}, {timeString}\n", file=sys.stdout)
 | 
			
		||||
            sendImage(netcode, stacode, timestamp, filename)
 | 
			
		||||
            i = i + 1
 | 
			
		||||
 | 
			
		||||
    output.quit()
 | 
			
		||||
    return 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    sys.exit(main())
 | 
			
		||||
							
								
								
									
										141
									
								
								bin/import_inv
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										141
									
								
								bin/import_inv
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,141 @@
 | 
			
		||||
#!/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(f"unknown topic '{self._args[1]}'\n")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        fmt = self._args[0]
 | 
			
		||||
        try:
 | 
			
		||||
            prog = os.path.join(os.environ["SEISCOMP_ROOT"], "bin", f"{fmt}2inv")
 | 
			
		||||
        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(f"Format '{fmt}' is not supported\n")
 | 
			
		||||
            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(f"Generating output to {output}\n")
 | 
			
		||||
        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)
 | 
			
		||||
        formats = []
 | 
			
		||||
        for f in files:
 | 
			
		||||
            prog = os.path.basename(f)
 | 
			
		||||
            formats.append(prog[: prog.find("2inv")])
 | 
			
		||||
 | 
			
		||||
        formats.sort()
 | 
			
		||||
        sys.stdout.write("%s\n" % "\n".join(formats))
 | 
			
		||||
 | 
			
		||||
        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())
 | 
			
		||||
							
								
								
									
										278
									
								
								bin/instdb2db2
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										278
									
								
								bin/instdb2db2
									
									
									
									
									
										Executable file
									
								
							@ -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()
 | 
			
		||||
							
								
								
									
										105
									
								
								bin/inv2dlsv
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										105
									
								
								bin/inv2dlsv
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,105 @@
 | 
			
		||||
#!/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 io
 | 
			
		||||
from seiscomp.legacy.fseed import *
 | 
			
		||||
from seiscomp.legacy.db.seiscomp3 import sc3wrap
 | 
			
		||||
from seiscomp.legacy.db.seiscomp3.inventory import Inventory
 | 
			
		||||
import seiscomp.datamodel
 | 
			
		||||
import 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:
 | 
			
		||||
        print("Usage inv2dlsv [in_xml [out_dataless]]", file=sys.stderr)
 | 
			
		||||
        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 not ar.open(inFile):
 | 
			
		||||
        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 exc:
 | 
			
		||||
                        print(
 | 
			
		||||
                            f"Error ({net.code},{sta.code},{loc.code},{strm.code}): {str(exc)}",
 | 
			
		||||
                            file=sys.stderr,
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
        print(f"Error: {str(e)}", file=sys.stderr)
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/invextr
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/invextr
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										697
									
								
								bin/licsar2caps
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										697
									
								
								bin/licsar2caps
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,697 @@
 | 
			
		||||
#!/usr/bin/env seiscomp-python
 | 
			
		||||
 | 
			
		||||
###############################################################################
 | 
			
		||||
# Copyright (C) 2024 by gempa GmbH                                            #
 | 
			
		||||
#                                                                             #
 | 
			
		||||
# All Rights Reserved.                                                        #
 | 
			
		||||
#                                                                             #
 | 
			
		||||
# NOTICE: All information contained herein is, and remains                    #
 | 
			
		||||
# the property of gempa GmbH and its suppliers, if any. The intellectual      #
 | 
			
		||||
# and technical concepts contained herein are proprietary to gempa GmbH       #
 | 
			
		||||
# and its suppliers.                                                          #
 | 
			
		||||
# Dissemination of this information or reproduction of this material          #
 | 
			
		||||
# is strictly forbidden unless prior written permission is obtained           #
 | 
			
		||||
# from gempa GmbH.                                                            #
 | 
			
		||||
###############################################################################
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import tempfile
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
import urllib.parse
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from bs4 import BeautifulSoup
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from osgeo import gdal
 | 
			
		||||
 | 
			
		||||
from seiscomp import client, logging, system
 | 
			
		||||
 | 
			
		||||
from gempa import CAPS
 | 
			
		||||
 | 
			
		||||
from licsar2caps.journal import Journal, JournalItem
 | 
			
		||||
from licsar2caps.streammap import StreamMap
 | 
			
		||||
from licsar2caps import utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def needsUpdate(item, startTime, endTime):
 | 
			
		||||
    if not item.startTime:
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    if startTime == item.startTime and endTime > item.endTime:
 | 
			
		||||
        return True
 | 
			
		||||
    if startTime > item.startTime:
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Filter:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.threshold = None
 | 
			
		||||
        self.enabled = False
 | 
			
		||||
        self.percentile = 99.9
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
###############################################################################
 | 
			
		||||
class App(client.Application):
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def __init__(self, argc, argv):
 | 
			
		||||
        client.Application.__init__(self, argc, argv)
 | 
			
		||||
 | 
			
		||||
        self.setDatabaseEnabled(False, False)
 | 
			
		||||
        self.setMessagingEnabled(False)
 | 
			
		||||
        self.baseUrl = "https://gws-access.jasmin.ac.uk/public/nceo_geohazards/LiCSAR_products"
 | 
			
		||||
        self.journalFile = "@ROOTDIR@/var/run/" + self.name() + "/journal"
 | 
			
		||||
        self.pollInterval = 60
 | 
			
		||||
        self.networks = None
 | 
			
		||||
        self.dump = False
 | 
			
		||||
        self.outputAddr = "caps://localhost:18003"
 | 
			
		||||
        self.capsLog = False
 | 
			
		||||
        self.output = None
 | 
			
		||||
        self.journal = Journal()
 | 
			
		||||
        self.print = False
 | 
			
		||||
        self.products = {}
 | 
			
		||||
        self.outputDirectory = None
 | 
			
		||||
        self.strStartTime = None
 | 
			
		||||
        self.reprocess = False
 | 
			
		||||
        self.processLatestOnly = False
 | 
			
		||||
        self.filter = Filter()
 | 
			
		||||
 | 
			
		||||
        dt = datetime.utcnow()
 | 
			
		||||
        self.startTime = CAPS.Time()
 | 
			
		||||
        self.startTime.set(dt.year, dt.month, dt.day, 0, 0, 0, 0)
 | 
			
		||||
        self.streamsFile = None
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def initConfiguration(self):
 | 
			
		||||
        if not client.Application.initConfiguration(self):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        param = ""
 | 
			
		||||
        try:
 | 
			
		||||
            param = "input.baseUrl"
 | 
			
		||||
            try:
 | 
			
		||||
                self.baseUrl = self.configGetString(param)
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            param = "input.streamsFile"
 | 
			
		||||
            try:
 | 
			
		||||
                self.streamsFile = self.configGetString(param)
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            param = "input.startTime"
 | 
			
		||||
            try:
 | 
			
		||||
                self.strStartTime = self.configGetString(param)
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            param = "input.processLatestOnly"
 | 
			
		||||
            try:
 | 
			
		||||
                self.processLatestOnly = self.configGetBool(param)
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            param = "input.reprocess"
 | 
			
		||||
            try:
 | 
			
		||||
                self.reprocess = self.configGetBool(param)
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            param = "input.pollInterval"
 | 
			
		||||
            try:
 | 
			
		||||
                self.pollInterval = self.configGetInt(param)
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            param = "input.products"
 | 
			
		||||
            try:
 | 
			
		||||
                for item in self.configGetStrings(param):
 | 
			
		||||
                    try:
 | 
			
		||||
                        key, value = item.split(":")
 | 
			
		||||
                        if not key or not value:
 | 
			
		||||
                            logging.error(
 | 
			
		||||
                                f"{param}: " "Key and value must not be empty"
 | 
			
		||||
                            )
 | 
			
		||||
                            return False
 | 
			
		||||
 | 
			
		||||
                        self.products[key.strip()] = value.strip()
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        logging.error(
 | 
			
		||||
                            f"{param}: Invalid entry: Expected: [KEY:VALUE]"
 | 
			
		||||
                        )
 | 
			
		||||
                        return False
 | 
			
		||||
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                self.products = {"geo.unw.tif": "UNW"}
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            param = "input.filter.enabled"
 | 
			
		||||
            try:
 | 
			
		||||
                self.filter.enabled = self.configGetBool(param)
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            param = "input.filter.threshold"
 | 
			
		||||
            try:
 | 
			
		||||
                self.filter.threshold = self.configGetDouble(param)
 | 
			
		||||
                param = "input.filter.percentile"
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            param = "input.filter.percentile"
 | 
			
		||||
            try:
 | 
			
		||||
                self.filter.percentile = self.configGetDouble(param)
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            param = "output.addr"
 | 
			
		||||
            try:
 | 
			
		||||
                self.outputAddr = self.configGetString(param)
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            param = "output.directory"
 | 
			
		||||
            try:
 | 
			
		||||
                self.outputDirectory = self.configGetString(param)
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            param = "journal.file"
 | 
			
		||||
            try:
 | 
			
		||||
                self.journalFile = self.configGetString(param)
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logging.error(f"Invalid parameter {param}: {e}")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def createCommandLineDescription(self):
 | 
			
		||||
        client.Application.createCommandLineDescription(self)
 | 
			
		||||
 | 
			
		||||
        self.commandline().addGroup("Input")
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Input",
 | 
			
		||||
            "base-url",
 | 
			
		||||
            f"Base URL from which data is received (default={self.baseUrl})",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Input",
 | 
			
		||||
            "interval,i",
 | 
			
		||||
            "Poll mode interval in seconds (default={self.pollInterval})",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Input",
 | 
			
		||||
            "reprocess",
 | 
			
		||||
            "Force reprocessing of the last received grid at start"
 | 
			
		||||
            f"(default={self.reprocess})",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addGroup("Output")
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "addr,a",
 | 
			
		||||
            "Data output address [[caps|capss]://][user:pass@]host[:port]",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "dir,d",
 | 
			
		||||
            "Output directory. Write grid files to this directory instead "
 | 
			
		||||
            " of sending it to CAPS.",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addBoolOption(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "caps-log",
 | 
			
		||||
            f"Enable CAPS logging (default={self.capsLog})",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Output", "print-packets", "Print packets"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addGroup("Journal")
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Journal",
 | 
			
		||||
            "journal,j",
 | 
			
		||||
            "File to store stream states. Use an "
 | 
			
		||||
            "empty string to log to standard out"
 | 
			
		||||
            "[[caps|capss]://][user:pass@]host[:port]",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def printUsage(self):
 | 
			
		||||
        print(
 | 
			
		||||
            """
 | 
			
		||||
licsar2caps: Import licsar data from web page to CAPS.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
        )
 | 
			
		||||
        client.StreamApplication.printUsage(self)
 | 
			
		||||
 | 
			
		||||
        print("Examples")
 | 
			
		||||
        print("Processing with informative debug output.")
 | 
			
		||||
        print(f"  {self.name()} --debug")
 | 
			
		||||
        print("Write output grids to directory")
 | 
			
		||||
        print(f"  {self.name()} -d /tmp")
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def getProduct(self, fullUrl, streamID, filename):
 | 
			
		||||
        logging.info(f"  + {streamID}: Downloading data product {filename}")
 | 
			
		||||
        try:
 | 
			
		||||
            res = requests.get(fullUrl, timeout=10, allow_redirects=True)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            logging.info(
 | 
			
		||||
                f"+ {streamID}: Download failed. Read operation timed out. "
 | 
			
		||||
                f"URL: {fullUrl}"
 | 
			
		||||
            )
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        if res.status_code != 200:
 | 
			
		||||
            logging.info(
 | 
			
		||||
                f"  + {streamID}: Download failed. HTTP status code "
 | 
			
		||||
                f"{res.status_code}: {res.reason}. URL: {fullUrl}"
 | 
			
		||||
            )
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        logging.info(f"  + {streamID}: Downloaded data product {filename}")
 | 
			
		||||
        with tempfile.NamedTemporaryFile() as tmp:
 | 
			
		||||
            tmp.write(res.content)
 | 
			
		||||
            tmp.flush()
 | 
			
		||||
            try:
 | 
			
		||||
                filename = "/vsimem/in_memory_output.grd"
 | 
			
		||||
                ds = gdal.Translate(
 | 
			
		||||
                    filename,
 | 
			
		||||
                    tmp.name,
 | 
			
		||||
                    format="GSBG",
 | 
			
		||||
                    outputType=gdal.GDT_Float32,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                if not ds:
 | 
			
		||||
                    logging.info(
 | 
			
		||||
                        f"  + {streamID}: Could not convert data product "
 | 
			
		||||
                        f"{filename} to Surfer6 format"
 | 
			
		||||
                    )
 | 
			
		||||
                    return None
 | 
			
		||||
 | 
			
		||||
                if self.filter.enabled:
 | 
			
		||||
                    value = utils.calculateAbsPerc(
 | 
			
		||||
                        ds.GetRasterBand(1).ReadAsArray(),
 | 
			
		||||
                        self.filter.percentile,
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                    if value <= self.filter.threshold:
 | 
			
		||||
                        logging.info(
 | 
			
		||||
                            f"  + {streamID}: Computed grid displacement is "
 | 
			
		||||
                            "less or equal the configured threshold "
 | 
			
		||||
                            f"{value} <= {self.filter.threshold}. Skipping "
 | 
			
		||||
                            "grid"
 | 
			
		||||
                        )
 | 
			
		||||
                        return None
 | 
			
		||||
 | 
			
		||||
                f = gdal.VSIFOpenL(filename, "rb")
 | 
			
		||||
                gdal.VSIFSeekL(f, 0, 2)
 | 
			
		||||
                filesize = gdal.VSIFTellL(f)
 | 
			
		||||
                gdal.VSIFSeekL(f, 0, 0)
 | 
			
		||||
 | 
			
		||||
                content = gdal.VSIFReadL(1, filesize, f)
 | 
			
		||||
                gdal.VSIFCloseL(f)
 | 
			
		||||
                gdal.Unlink("/vsimem/in_memory_output.grd")
 | 
			
		||||
 | 
			
		||||
                data = content
 | 
			
		||||
                return data
 | 
			
		||||
            except AttributeError:
 | 
			
		||||
                # Fallback for RHEL 7
 | 
			
		||||
                import subprocess
 | 
			
		||||
 | 
			
		||||
                with tempfile.NamedTemporaryFile() as outFile:
 | 
			
		||||
                    filename = outFile.name
 | 
			
		||||
                    subprocess.call(
 | 
			
		||||
                        ["gdal_translate", "-of", "GSBG", tmp.name, filename]
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                    with open(filename, "rb") as f:
 | 
			
		||||
                        return f.read()
 | 
			
		||||
 | 
			
		||||
                return None
 | 
			
		||||
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def storeProduct(self, data, streamID, item):
 | 
			
		||||
        if not os.path.exists(self.outputDirectory):
 | 
			
		||||
            try:
 | 
			
		||||
                os.makedirs(self.outputDirectory)
 | 
			
		||||
            except OSError as err:
 | 
			
		||||
                logging.error(
 | 
			
		||||
                    "Could not create output directory "
 | 
			
		||||
                    f"'{self.outputDirectory}': {err}"
 | 
			
		||||
                )
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        filename = os.path.join(self.outputDirectory, streamID + ".grd")
 | 
			
		||||
        try:
 | 
			
		||||
            with open(filename, "wb") as f:
 | 
			
		||||
                f.write(data)
 | 
			
		||||
        except IOError as err:
 | 
			
		||||
            logging.error(f"Failed to write data to file {filename}: {err}")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        logging.info(f"  + {streamID}: Stored data in '{filename}'")
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def sendProduct(self, data, item, channelCode, startTime, endTime):
 | 
			
		||||
        streamID = item.stationID + "." + channelCode
 | 
			
		||||
        if self.print:
 | 
			
		||||
            print(
 | 
			
		||||
                f"{streamID} - {startTime.iso()} ~ "
 | 
			
		||||
                f"{endTime.iso()} Size: {len(data)} bytes"
 | 
			
		||||
            )
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        ret = self.output.pushAny(
 | 
			
		||||
            item.networkCode,
 | 
			
		||||
            item.stationCode,
 | 
			
		||||
            item.locationCode,
 | 
			
		||||
            channelCode,
 | 
			
		||||
            startTime,
 | 
			
		||||
            endTime,
 | 
			
		||||
            1,
 | 
			
		||||
            0,
 | 
			
		||||
            "grd",
 | 
			
		||||
            "N/A",
 | 
			
		||||
            bytes(data),
 | 
			
		||||
        )
 | 
			
		||||
        if ret != CAPS.Plugin.Success:
 | 
			
		||||
            logging.error(
 | 
			
		||||
                f"{streamID} - Data {startTime.iso()} ~ {endTime.iso()} "
 | 
			
		||||
                "could not be sent to CAPS: Error code: {ret}"
 | 
			
		||||
            )
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        logging.info(
 | 
			
		||||
            f"  + {streamID}: Sent packet {startTime.iso()} ~ "
 | 
			
		||||
            f"{endTime.iso()} {len(data)} bytes"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def done(self):
 | 
			
		||||
        if self.output:
 | 
			
		||||
            self.output.close()
 | 
			
		||||
 | 
			
		||||
        self.journal.write(self.journalFile)
 | 
			
		||||
 | 
			
		||||
        client.Application.done(self)
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def init(self):
 | 
			
		||||
        if not client.Application.init(self):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if os.path.isfile(self.journalFile) and not self.journal.read(
 | 
			
		||||
            self.journalFile
 | 
			
		||||
        ):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        self.streamMap = StreamMap()
 | 
			
		||||
        if not self.streamMap.read(self.streamsFile):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        for key, item in self.streamMap.items.items():
 | 
			
		||||
            j = self.journal.get(key)
 | 
			
		||||
            if j:
 | 
			
		||||
                item.startTime = j.startTime
 | 
			
		||||
                item.endTime = j.endTime
 | 
			
		||||
            else:
 | 
			
		||||
                if self.startTime:
 | 
			
		||||
                    item.startTime = self.startTime
 | 
			
		||||
                    item.endTime = self.startTime
 | 
			
		||||
 | 
			
		||||
        url = urllib.parse.urlparse(self.outputAddr)
 | 
			
		||||
        if not url:
 | 
			
		||||
            logging.error("Could not parse data output address")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        self.output = CAPS.Plugin("licsar2caps")
 | 
			
		||||
        self.output.setHost(url.hostname)
 | 
			
		||||
        self.output.setPort(url.port)
 | 
			
		||||
        self.output.setBufferSize(1 << 29)
 | 
			
		||||
        if self.capsLog:
 | 
			
		||||
            self.output.enablelogging()
 | 
			
		||||
 | 
			
		||||
        protocol = url.scheme.lower()
 | 
			
		||||
        if protocol == "capss":
 | 
			
		||||
            self.output.setSSLEnabled(True)
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def run(self):
 | 
			
		||||
        if self.outputDirectory:
 | 
			
		||||
            outputStr = f"\n    Output Directory: {self.outputDirectory}"
 | 
			
		||||
        else:
 | 
			
		||||
            outputStr = f"\n    Output address  : {self.outputAddr}"
 | 
			
		||||
 | 
			
		||||
        logging.info(
 | 
			
		||||
            "\nConfiguration:"
 | 
			
		||||
            f"\n    Base URL        : {self.baseUrl}"
 | 
			
		||||
            f"\n    Poll interval   : {self.pollInterval} s"
 | 
			
		||||
            f"\n    Streams         : {self.streamsFile}"
 | 
			
		||||
            f"\n    Journal         : {self.journalFile}"
 | 
			
		||||
            f"{outputStr}"
 | 
			
		||||
            f"\n    Start time      : {self.startTime.iso()}"
 | 
			
		||||
            f"\n    Products        : {self.products}"
 | 
			
		||||
            f"\n    Filter          : {'Enabled' if self.filter.enabled else 'Disabled'}"
 | 
			
		||||
            f"\n      Threshold     : {self.filter.threshold}"
 | 
			
		||||
            f"\n      Percentile    : {self.filter.percentile}"
 | 
			
		||||
            f"\n    Force update    : {self.reprocess}"
 | 
			
		||||
            f"\n    Process latest  : {self.processLatestOnly}"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.runWatch()
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def getEpochs(self, url, item):
 | 
			
		||||
        url = url + "/interferograms/"
 | 
			
		||||
        logging.info(f"  + Checking station {item.stationID}")
 | 
			
		||||
        res = requests.get(url, timeout=10, allow_redirects=True)
 | 
			
		||||
 | 
			
		||||
        if res.status_code != 200:
 | 
			
		||||
            logging.error(f"HTTP status code {res.status_code}: {res.reason}")
 | 
			
		||||
            logging.info("  + End")
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        soup = BeautifulSoup(res.text, "html.parser")
 | 
			
		||||
        data = [
 | 
			
		||||
            node.get("href").replace("/", "")
 | 
			
		||||
            for node in soup.find_all("a")
 | 
			
		||||
            if node.get("href").endswith("/")
 | 
			
		||||
            and node.text != "Parent Directory"
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        logging.info(f"    + Found {len(data)} epochs")
 | 
			
		||||
        epochs = []
 | 
			
		||||
        for epoch in data:
 | 
			
		||||
            try:
 | 
			
		||||
                start, end = epoch.split("_")
 | 
			
		||||
            except Exception:
 | 
			
		||||
                logging.error(
 | 
			
		||||
                    f"{item.stationID}: Invalid epoch {epoch}: "
 | 
			
		||||
                    "Expected [START_END]"
 | 
			
		||||
                )
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            startTime = CAPS.Time.FromString(start, "%Y%m%d")
 | 
			
		||||
            if not startTime.valid():
 | 
			
		||||
                logging.error(f"{item.stationID}: Invalid start time {start}")
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            endTime = CAPS.Time.FromString(end, "%Y%m%d")
 | 
			
		||||
            if not endTime.valid():
 | 
			
		||||
                logging.error(f"{item.stationID}: Invalid end time {end}")
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            if needsUpdate(item, startTime, endTime):
 | 
			
		||||
                epochs.append([epoch, startTime, endTime])
 | 
			
		||||
            elif self.reprocess:
 | 
			
		||||
                if item.startTime == startTime and item.endTime == endTime:
 | 
			
		||||
                    epochs.append([epoch, startTime, endTime])
 | 
			
		||||
 | 
			
		||||
        if not epochs:
 | 
			
		||||
            logging.info("    + No new data available. Nothing todo")
 | 
			
		||||
        else:
 | 
			
		||||
            logging.info(f"    + {len(epochs)} epoch(s) must be processed")
 | 
			
		||||
 | 
			
		||||
        logging.info("  + End")
 | 
			
		||||
 | 
			
		||||
        return epochs
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def runWatch(self):
 | 
			
		||||
        while not self.isExitRequested():
 | 
			
		||||
            logging.info("Looking for new data")
 | 
			
		||||
            for stationID, item in self.streamMap.items.items():
 | 
			
		||||
                if self.isExitRequested():
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
                url = self.baseUrl + "/" + item.baseCode + "/" + item.folder
 | 
			
		||||
                epochs = self.getEpochs(url, item)
 | 
			
		||||
                if not epochs:
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if self.processLatestOnly:
 | 
			
		||||
                    epochs = [epochs[-1]]
 | 
			
		||||
 | 
			
		||||
                for epoch, startTime, endTime in epochs:
 | 
			
		||||
                    if self.isExitRequested():
 | 
			
		||||
                        break
 | 
			
		||||
 | 
			
		||||
                    for k, cha in self.products.items():
 | 
			
		||||
                        streamID = (
 | 
			
		||||
                            item.networkCode
 | 
			
		||||
                            + "."
 | 
			
		||||
                            + item.stationCode
 | 
			
		||||
                            + "."
 | 
			
		||||
                            + item.locationCode
 | 
			
		||||
                            + "."
 | 
			
		||||
                            + cha
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
                        filename = epoch + "." + k
 | 
			
		||||
                        fullUrl = (
 | 
			
		||||
                            url + "/interferograms/" + epoch + "/" + filename
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
                        data = self.getProduct(fullUrl, streamID, filename)
 | 
			
		||||
                        if data:
 | 
			
		||||
                            if self.outputDirectory:
 | 
			
		||||
                                self.storeProduct(data, streamID, item)
 | 
			
		||||
                            else:
 | 
			
		||||
                                self.sendProduct(
 | 
			
		||||
                                    data, item, cha, startTime, endTime
 | 
			
		||||
                                )
 | 
			
		||||
 | 
			
		||||
                        if self.isExitRequested():
 | 
			
		||||
                            break
 | 
			
		||||
 | 
			
		||||
                    # Update start and end time of the station
 | 
			
		||||
                    item.startTime = startTime
 | 
			
		||||
                    item.endTime = endTime
 | 
			
		||||
 | 
			
		||||
                    # Update Journal
 | 
			
		||||
                    self.journal.items[item.stationID] = JournalItem(
 | 
			
		||||
                        startTime, endTime
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
            if self.isExitRequested():
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            # Do reprocessing only once
 | 
			
		||||
            if self.reprocess:
 | 
			
		||||
                self.reprocess = False
 | 
			
		||||
 | 
			
		||||
            logging.info("End")
 | 
			
		||||
            logging.info(f"Next run in {self.pollInterval} seconds")
 | 
			
		||||
            self.wait(self.pollInterval)
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def wait(self, seconds):
 | 
			
		||||
        for _i in range(0, seconds):
 | 
			
		||||
            time.sleep(1)
 | 
			
		||||
            if self.isExitRequested():
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
    # -------------------------------------------------------------------------
 | 
			
		||||
    def validateParameters(self):
 | 
			
		||||
        if not client.Application.validateParameters(self):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        self.dump = self.commandline().hasOption("dump")
 | 
			
		||||
        if self.dump:
 | 
			
		||||
            self.setMessagingEnabled(False)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.baseUrl = self.commandline().optionString("base-url")
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.strStartTime = self.commandline().optionString("start-time")
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if self.strStartTime:
 | 
			
		||||
            self.startTime = utils.parseTime(self.strStartTime)
 | 
			
		||||
            if not self.startTime.valid():
 | 
			
		||||
                logging.error(f"Invalid start time '{self.strStartTime}")
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.journalFile = self.commandline().optionString("journal")
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.pollInterval = int(
 | 
			
		||||
                self.commandline().optionString("interval")
 | 
			
		||||
            )
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.outputAddr = self.commandline().optionString("addr")
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.outputDirectory = self.commandline().optionString("dir")
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.journalFile = self.commandline().optionString("journal")
 | 
			
		||||
        except Exception:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if self.journalFile:
 | 
			
		||||
            self.journalFile = system.Environment.Instance().absolutePath(
 | 
			
		||||
                self.journalFile
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if not self.streamsFile:
 | 
			
		||||
            logging.error("Option 'input.streamsFile' is mandatory")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        self.streamsFile = system.Environment.Instance().absolutePath(
 | 
			
		||||
            self.streamsFile
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if self.outputDirectory:
 | 
			
		||||
            self.outputDirectory = system.Environment.Instance().absolutePath(
 | 
			
		||||
                self.outputDirectory
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if self.commandline().hasOption("reprocess"):
 | 
			
		||||
            self.reprocess = True
 | 
			
		||||
 | 
			
		||||
        self.print = self.commandline().hasOption("print-packets")
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
app = App(len(sys.argv), sys.argv)
 | 
			
		||||
sys.exit(app())
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/load_timetable
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/load_timetable
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										329
									
								
								bin/msrtsimul
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										329
									
								
								bin/msrtsimul
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,329 @@
 | 
			
		||||
#!/usr/bin/env seiscomp-python
 | 
			
		||||
 | 
			
		||||
from __future__ import absolute_import, division, print_function
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import os
 | 
			
		||||
import time
 | 
			
		||||
import datetime
 | 
			
		||||
import calendar
 | 
			
		||||
import math
 | 
			
		||||
import stat
 | 
			
		||||
 | 
			
		||||
from getopt import gnu_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 = f"{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.0, jump=0.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.
 | 
			
		||||
 | 
			
		||||
Verbosity:
 | 
			
		||||
  -h, --help            Display this help message
 | 
			
		||||
  -v, --verbose         Verbose mode
 | 
			
		||||
 | 
			
		||||
Playback:
 | 
			
		||||
  -j, --jump            Minutes to skip (float).
 | 
			
		||||
  -c, --stdout          Write on standard output.
 | 
			
		||||
  -d, --delays          Seconds to add as artificial delays.
 | 
			
		||||
      --seedlink        Choose the seedlink module name. Useful if a seedlink
 | 
			
		||||
                        alias or non-standard names are used. Replaces
 | 
			
		||||
                        'seedlink' in the standard mseedfifo path.
 | 
			
		||||
  -m  --mode            Choose between 'realtime' and 'historic'.
 | 
			
		||||
  -s, --speed           Speed factor (float).
 | 
			
		||||
      --test            Test mode.
 | 
			
		||||
  -u, --unlimited       Allow miniSEED records which are not 512 bytes
 | 
			
		||||
 | 
			
		||||
Examples:
 | 
			
		||||
Play back miniSEED waveforms in real time with verbose output
 | 
			
		||||
  msrtsimul -v data.mseed
 | 
			
		||||
 | 
			
		||||
Play back miniSEED waveforms in real time skipping the first 1.5 minutes
 | 
			
		||||
  msrtsimul -j 1.5 data.mseed
 | 
			
		||||
"""
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ------------------------------------------------------------------------------
 | 
			
		||||
def main():
 | 
			
		||||
    py2 = sys.version_info < (3,)
 | 
			
		||||
 | 
			
		||||
    ifile = sys.stdin if py2 else sys.stdin.buffer
 | 
			
		||||
    verbosity = 0
 | 
			
		||||
    speed = 1.0
 | 
			
		||||
    jump = 0.0
 | 
			
		||||
    test = False
 | 
			
		||||
    ulimited = False
 | 
			
		||||
    seedlink = "seedlink"
 | 
			
		||||
    mode = "realtime"
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        opts, args = gnu_getopt(
 | 
			
		||||
            sys.argv[1:],
 | 
			
		||||
            "cd:s:j:vhm:u",
 | 
			
		||||
            [
 | 
			
		||||
                "stdout",
 | 
			
		||||
                "delays=",
 | 
			
		||||
                "speed=",
 | 
			
		||||
                "jump=",
 | 
			
		||||
                "test",
 | 
			
		||||
                "verbose",
 | 
			
		||||
                "help",
 | 
			
		||||
                "mode=",
 | 
			
		||||
                "seedlink=",
 | 
			
		||||
                "unlimited"
 | 
			
		||||
            ],
 | 
			
		||||
        )
 | 
			
		||||
    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
 | 
			
		||||
        elif flag in ("-u", "--unlimited"):
 | 
			
		||||
            ulimited = 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(
 | 
			
		||||
                    f"could not open input file '{args[0]}' for reading: {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(f"output data to {mseed_fifo}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
        if not os.path.exists(mseed_fifo):
 | 
			
		||||
            print(
 | 
			
		||||
                f"""\
 | 
			
		||||
ERROR: {mseed_fifo} does not exist.
 | 
			
		||||
In order to push the records to SeedLink, \
 | 
			
		||||
it needs to run and must be configured for real-time playback.
 | 
			
		||||
""",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
            sys.exit(1)
 | 
			
		||||
 | 
			
		||||
        if not stat.S_ISFIFO(os.stat(mseed_fifo).st_mode):
 | 
			
		||||
            print(
 | 
			
		||||
                f"""\
 | 
			
		||||
ERROR: {mseed_fifo} is not a named pipe
 | 
			
		||||
Check if SeedLink is running and configured for real-time playback.
 | 
			
		||||
""",
 | 
			
		||||
                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 = {}
 | 
			
		||||
            try:
 | 
			
		||||
                f = open(delays, "r")
 | 
			
		||||
                for line in f:
 | 
			
		||||
                    content = line.split(":")
 | 
			
		||||
                    if len(content) != 2:
 | 
			
		||||
                        raise ValueError(
 | 
			
		||||
                            f"Could not parse a line in file {delays}: {line}\n"
 | 
			
		||||
                        )
 | 
			
		||||
                    delaydict[content[0].strip()] = float(content[1].strip())
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                print(f"Error reading delay file {delays}: {e}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
        inp = rt_simul(ifile, speed=speed, jump=jump, delaydict=delaydict)
 | 
			
		||||
        stime = time.time()
 | 
			
		||||
 | 
			
		||||
        time_diff = None
 | 
			
		||||
        print(
 | 
			
		||||
            f"Starting msrtsimul at {datetime.datetime.utcnow()}",
 | 
			
		||||
            file=sys.stderr,
 | 
			
		||||
        )
 | 
			
		||||
        for rec in inp:
 | 
			
		||||
            if rec.size != 512 and not ulimited:
 | 
			
		||||
                print(
 | 
			
		||||
                    f"Skipping record of {rec.net}.{rec.sta}.{rec.loc}.{rec.cha} \
 | 
			
		||||
starting on {str(rec.begin_time)}: length != 512 Bytes.",
 | 
			
		||||
                    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:
 | 
			
		||||
                tdiff_to_start = time.time() - stime
 | 
			
		||||
                tdiff_to_current = time.time() - calendar.timegm(
 | 
			
		||||
                    rec.begin_time.timetuple()
 | 
			
		||||
                )
 | 
			
		||||
                nslc = f"{rec.net}.{rec.sta}.{rec.loc}.{rec.cha}"
 | 
			
		||||
                print(
 | 
			
		||||
                    f"{nslc: <17} \
 | 
			
		||||
{tdiff_to_start: 7.2f} {str(rec.begin_time)} {tdiff_to_current: 7.2f}",
 | 
			
		||||
                    file=sys.stderr,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            if not test:
 | 
			
		||||
                rec.write(out_channel, int(math.log2(rec.size)))
 | 
			
		||||
                out_channel.flush()
 | 
			
		||||
 | 
			
		||||
    except KeyboardInterrupt:
 | 
			
		||||
        pass
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print(f"Exception: {str(e)}", file=sys.stderr)
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    return 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ------------------------------------------------------------------------------
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    sys.exit(main())
 | 
			
		||||
							
								
								
									
										150
									
								
								bin/optodas_inventory
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										150
									
								
								bin/optodas_inventory
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,150 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
# -*- 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 json
 | 
			
		||||
import datetime
 | 
			
		||||
import argparse
 | 
			
		||||
import zmq
 | 
			
		||||
import seiscomp.datamodel, seiscomp.core, seiscomp.io
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
VERSION = "0.1 (2024.066)"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    parser = argparse.ArgumentParser()
 | 
			
		||||
 | 
			
		||||
    parser.set_defaults(
 | 
			
		||||
        address="tcp://localhost:3333",
 | 
			
		||||
        sample_rate=100,
 | 
			
		||||
        gain=1.0,
 | 
			
		||||
        network="XX",
 | 
			
		||||
        station="{channel:05d}",
 | 
			
		||||
        location="",
 | 
			
		||||
        channel="HSF"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser.add_argument("--version",
 | 
			
		||||
        action="version",
 | 
			
		||||
        version="%(prog)s " + VERSION
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser.add_argument("-a", "--address",
 | 
			
		||||
        help="ZeroMQ address (default %(default)s)"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser.add_argument("-r", "--sample-rate",
 | 
			
		||||
        type = int,
 | 
			
		||||
        help = "sample rate (default %(default)s)"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser.add_argument("-g", "--gain",
 | 
			
		||||
        type=float,
 | 
			
		||||
        help="gain (default %(default)s)"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser.add_argument("-n", "--network",
 | 
			
		||||
        help="network code (default %(default)s)"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser.add_argument("-s", "--station",
 | 
			
		||||
        help="station code template (default %(default)s)"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser.add_argument("-l", "--location",
 | 
			
		||||
        help="location code (default %(default)s)"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser.add_argument("-c", "--channel",
 | 
			
		||||
        help="channel code (default %(default)s)"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    sock = zmq.Context().socket(zmq.SUB)
 | 
			
		||||
    sock.connect(args.address)
 | 
			
		||||
    sock.setsockopt(zmq.SUBSCRIBE, b"")
 | 
			
		||||
 | 
			
		||||
    header = json.loads(sock.recv().decode("utf-8"))
 | 
			
		||||
 | 
			
		||||
    inv = seiscomp.datamodel.Inventory()
 | 
			
		||||
 | 
			
		||||
    resp = seiscomp.datamodel.ResponsePAZ_Create()
 | 
			
		||||
    resp.setType("A")
 | 
			
		||||
    resp.setGain(args.gain * header["sensitivities"][0]["factor"] / header["dataScale"])
 | 
			
		||||
    resp.setGainFrequency(0)
 | 
			
		||||
    resp.setNormalizationFactor(1)
 | 
			
		||||
    resp.setNormalizationFrequency(0)
 | 
			
		||||
    resp.setNumberOfZeros(0)
 | 
			
		||||
    resp.setNumberOfPoles(0)
 | 
			
		||||
    inv.add(resp)
 | 
			
		||||
 | 
			
		||||
    sensor = seiscomp.datamodel.Sensor_Create()
 | 
			
		||||
    sensor.setName(header["instrument"])
 | 
			
		||||
    sensor.setDescription(header["instrument"])
 | 
			
		||||
    sensor.setUnit(header["sensitivities"][0]["unit"])
 | 
			
		||||
    sensor.setResponse(resp.publicID())
 | 
			
		||||
    inv.add(sensor)
 | 
			
		||||
 | 
			
		||||
    datalogger = seiscomp.datamodel.Datalogger_Create()
 | 
			
		||||
    datalogger.setDescription(header["instrument"])
 | 
			
		||||
    datalogger.setGain(1)
 | 
			
		||||
    datalogger.setMaxClockDrift(0)
 | 
			
		||||
    deci = seiscomp.datamodel.Decimation()
 | 
			
		||||
    deci.setSampleRateNumerator(args.sample_rate)
 | 
			
		||||
    deci.setSampleRateDenominator(1)
 | 
			
		||||
    datalogger.add(deci)
 | 
			
		||||
    inv.add(datalogger)
 | 
			
		||||
 | 
			
		||||
    net = seiscomp.datamodel.Network_Create()
 | 
			
		||||
    net.setCode(args.network)
 | 
			
		||||
    net.setDescription(header["experiment"])
 | 
			
		||||
    net.setStart(seiscomp.core.Time.FromYearDay(datetime.datetime.utcnow().year, 1))
 | 
			
		||||
    inv.add(net)
 | 
			
		||||
 | 
			
		||||
    for roi in header["roiTable"]:
 | 
			
		||||
        for c in range(roi["roiStart"], roi["roiEnd"] + 1, roi["roiDec"]):
 | 
			
		||||
            sta = seiscomp.datamodel.Station_Create()
 | 
			
		||||
            sta.setCode(eval("f'''" + args.station + "'''", {"channel": c}))
 | 
			
		||||
            sta.setDescription("DAS channel %d" % c)
 | 
			
		||||
            sta.setStart(net.start())
 | 
			
		||||
            net.add(sta)
 | 
			
		||||
 | 
			
		||||
            loc = seiscomp.datamodel.SensorLocation_Create()
 | 
			
		||||
            loc.setCode(args.location)
 | 
			
		||||
            loc.setStart(net.start())
 | 
			
		||||
            sta.add(loc)
 | 
			
		||||
 | 
			
		||||
            cha = seiscomp.datamodel.Stream_Create()
 | 
			
		||||
            cha.setCode(args.channel)
 | 
			
		||||
            cha.setStart(net.start())
 | 
			
		||||
            cha.setGain(args.gain * header["sensitivities"][0]["factor"] / header["dataScale"])
 | 
			
		||||
            cha.setGainUnit(header["sensitivities"][0]["unit"])
 | 
			
		||||
            cha.setGainFrequency(0)
 | 
			
		||||
            cha.setSensor(sensor.publicID())
 | 
			
		||||
            cha.setDatalogger(datalogger.publicID())
 | 
			
		||||
            loc.add(cha)
 | 
			
		||||
 | 
			
		||||
    ar = seiscomp.io.XMLArchive()
 | 
			
		||||
    ar.create("-")
 | 
			
		||||
    ar.setFormattedOutput(True)
 | 
			
		||||
    ar.writeObject(inv)
 | 
			
		||||
    ar.close()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										938
									
								
								bin/playback_picks
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										938
									
								
								bin/playback_picks
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,938 @@
 | 
			
		||||
#!/usr/bin/env seiscomp-python
 | 
			
		||||
 | 
			
		||||
############################################################################
 | 
			
		||||
# Copyright (C) 2016 by gempa GmbH                                         #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
# All Rights Reserved.                                                     #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
# NOTICE: All information contained herein is, and remains                 #
 | 
			
		||||
# the property of gempa GmbH and its suppliers, if any. The intellectual   #
 | 
			
		||||
# and technical concepts contained herein are proprietary to gempa GmbH    #
 | 
			
		||||
# and its suppliers.                                                       #
 | 
			
		||||
# Dissemination of this information or reproduction of this material       #
 | 
			
		||||
# is strictly forbidden unless prior written permission is obtained        #
 | 
			
		||||
# from gempa GmbH.                                                         #
 | 
			
		||||
#                                                                          #
 | 
			
		||||
#  Author: Enrico Ellguth, Dirk Roessler                                   #
 | 
			
		||||
#  Email: enrico.ellguth@gempa.de, roessler@gempa.de                       #
 | 
			
		||||
############################################################################
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
import seiscomp.client
 | 
			
		||||
import seiscomp.core
 | 
			
		||||
import seiscomp.io
 | 
			
		||||
import seiscomp.datamodel
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def timing_pickTime(obj):
 | 
			
		||||
    """
 | 
			
		||||
    Sort picks, origins by their time values
 | 
			
		||||
    Sort amplitudes by their reference time
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    po = seiscomp.datamodel.Pick.Cast(obj[0])
 | 
			
		||||
    oo = seiscomp.datamodel.Origin.Cast(obj[0])
 | 
			
		||||
    if po or oo:
 | 
			
		||||
        t = obj[0].time().value()
 | 
			
		||||
    else:
 | 
			
		||||
        t = obj[0].timeWindow().reference()
 | 
			
		||||
 | 
			
		||||
    return t
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def timing_creationTime(obj):
 | 
			
		||||
    """
 | 
			
		||||
    Sort all objects by their creation time
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    ct = obj[0].creationInfo().creationTime()
 | 
			
		||||
    return ct
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def listPicks(self, objects):
 | 
			
		||||
    print(
 | 
			
		||||
        "\n#phase, time                   , streamID       , author, to previous pick "
 | 
			
		||||
        "[s], delay [s]",
 | 
			
		||||
        file=sys.stdout,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    t0 = None
 | 
			
		||||
    for obj, _ in objects:
 | 
			
		||||
        p = seiscomp.datamodel.Pick.Cast(obj)
 | 
			
		||||
        if not p:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        time = p.time().value()
 | 
			
		||||
        try:
 | 
			
		||||
            phase = p.phaseHint().code()
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            phase = "None"
 | 
			
		||||
        wfID = p.waveformID()
 | 
			
		||||
        net = wfID.networkCode()
 | 
			
		||||
        sta = wfID.stationCode()
 | 
			
		||||
        loc = wfID.locationCode()
 | 
			
		||||
        cha = wfID.channelCode()
 | 
			
		||||
        try:
 | 
			
		||||
            author = p.creationInfo().author()
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            author = "None"
 | 
			
		||||
        try:
 | 
			
		||||
            delay = f"{(p.creationInfo().creationTime() - time).toDouble():.3f}"
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            delay = "None"
 | 
			
		||||
 | 
			
		||||
        if t0 is not None:
 | 
			
		||||
            deltaT = f"{(time - t0).toDouble():.3f}"
 | 
			
		||||
        else:
 | 
			
		||||
            deltaT = "None"
 | 
			
		||||
 | 
			
		||||
        streamID = f"{net}.{sta}.{loc}.{cha}"
 | 
			
		||||
        print(
 | 
			
		||||
            f"{phase: <6}, {time.toString('%FT%T')}.{int(time.microseconds()/1000):03d}"
 | 
			
		||||
            f", {streamID: <15}, {author}, {deltaT}, {delay}",
 | 
			
		||||
            file=sys.stdout,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        t0 = time
 | 
			
		||||
    return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def printStatistics(ep):
 | 
			
		||||
    minPickTime = None
 | 
			
		||||
    maxPickTime = None
 | 
			
		||||
    minPickCTime = None
 | 
			
		||||
    maxPickCTime = None
 | 
			
		||||
    minAmplitudeTime = None
 | 
			
		||||
    maxAmplitudeTime = None
 | 
			
		||||
    minOriginTime = None
 | 
			
		||||
    maxOriginTime = None
 | 
			
		||||
    minOriginCTime = None
 | 
			
		||||
    maxOriginCTime = None
 | 
			
		||||
 | 
			
		||||
    # read picks
 | 
			
		||||
    nslc = set()
 | 
			
		||||
    authorPick = set()
 | 
			
		||||
    authorAmplitude = set()
 | 
			
		||||
    authorOrigin = set()
 | 
			
		||||
    detectionStreams = set()
 | 
			
		||||
    cntPick = ep.pickCount()
 | 
			
		||||
    for i in range(cntPick):
 | 
			
		||||
        pick = ep.pick(i)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            authorPick.add(pick.creationInfo().author())
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            print(
 | 
			
		||||
                f"Author information not found in pick {pick.publicID()}: NSLC list may"
 | 
			
		||||
                " be incomplete",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            net = pick.waveformID().networkCode()
 | 
			
		||||
            sta = pick.waveformID().stationCode()
 | 
			
		||||
            loc = pick.waveformID().locationCode()
 | 
			
		||||
            cha = pick.waveformID().channelCode()
 | 
			
		||||
            nslc.add(f"{net}.{sta}.{loc}.{cha}")
 | 
			
		||||
            detectionStreams.add(f".{loc}.{cha}")
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            print(
 | 
			
		||||
                f"Stream information not found in pick {pick.publicID()}: NSLC list "
 | 
			
		||||
                "may be incomplete",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if not minPickTime:
 | 
			
		||||
            minPickTime = pick.time().value()
 | 
			
		||||
        elif pick.time().value() < minPickTime:
 | 
			
		||||
            minPickTime = pick.time().value()
 | 
			
		||||
 | 
			
		||||
        if not maxPickTime:
 | 
			
		||||
            maxPickTime = pick.time().value()
 | 
			
		||||
        elif pick.time().value() > maxPickTime:
 | 
			
		||||
            maxPickTime = pick.time().value()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            pick.creationInfo().creationTime()
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            print(
 | 
			
		||||
                f"Creation time not found in pick {pick.publicID()}: Statistics may "
 | 
			
		||||
                "be incomplete",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        if not minPickCTime:
 | 
			
		||||
            minPickCTime = pick.creationInfo().creationTime()
 | 
			
		||||
        elif pick.creationInfo().creationTime() < minPickCTime:
 | 
			
		||||
            minPickCTime = pick.creationInfo().creationTime()
 | 
			
		||||
 | 
			
		||||
        if not maxPickCTime:
 | 
			
		||||
            maxPickCTime = pick.creationInfo().creationTime()
 | 
			
		||||
        elif pick.creationInfo().creationTime() > maxPickCTime:
 | 
			
		||||
            maxPickCTime = pick.creationInfo().creationTime()
 | 
			
		||||
 | 
			
		||||
    # read amplitudes
 | 
			
		||||
    cntAmp = ep.amplitudeCount()
 | 
			
		||||
    for i in range(cntAmp):
 | 
			
		||||
        amp = ep.amplitude(i)
 | 
			
		||||
        try:
 | 
			
		||||
            authorAmplitude.add(amp.creationInfo().author())
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            print(
 | 
			
		||||
                f"Author information not found in amplitude {amp.publicID()}: NSLC "
 | 
			
		||||
                "list may be incomplete",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
        try:
 | 
			
		||||
            net = amp.waveformID().networkCode()
 | 
			
		||||
            sta = amp.waveformID().stationCode()
 | 
			
		||||
            loc = amp.waveformID().locationCode()
 | 
			
		||||
            cha = amp.waveformID().channelCode()
 | 
			
		||||
            nslc.add(f"{net}.{sta}.{loc}.{cha}")
 | 
			
		||||
            detectionStreams.add(f".{loc}.{cha}")
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            print(
 | 
			
		||||
                f"Stream information not found in amplitude {amp.publicID()}: NSLC "
 | 
			
		||||
                "list may be incomplete",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if not minAmplitudeTime:
 | 
			
		||||
            minAmplitudeTime = amp.timeWindow().reference()
 | 
			
		||||
        elif amp.timeWindow().reference() < minAmplitudeTime:
 | 
			
		||||
            minAmplitudeTime = amp.timeWindow().reference()
 | 
			
		||||
 | 
			
		||||
        if not maxAmplitudeTime:
 | 
			
		||||
            maxAmplitudeTime = amp.timeWindow().reference()
 | 
			
		||||
        elif amp.timeWindow().reference() > maxAmplitudeTime:
 | 
			
		||||
            maxAmplitudeTime = amp.timeWindow().reference()
 | 
			
		||||
 | 
			
		||||
    # read origins
 | 
			
		||||
    cntOrg = ep.originCount()
 | 
			
		||||
    for i in range(cntOrg):
 | 
			
		||||
        oo = ep.origin(i)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            authorOrigin.add(oo.creationInfo().author())
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            print(
 | 
			
		||||
                f"Author information not found in origin {oo.publicID()}:",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if not minOriginTime:
 | 
			
		||||
            minOriginTime = oo.time().value()
 | 
			
		||||
        elif oo.time().value() < minOriginTime:
 | 
			
		||||
            minOriginTime = oo.time().value()
 | 
			
		||||
 | 
			
		||||
        if not maxOriginTime:
 | 
			
		||||
            maxOriginTime = oo.time().value()
 | 
			
		||||
        elif oo.time().value() > maxOriginTime:
 | 
			
		||||
            maxOriginTime = oo.time().value()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            oo.creationInfo().creationTime()
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            print(
 | 
			
		||||
                f"Creation time not found in oo {oo.publicID()}: Statistics may "
 | 
			
		||||
                "be incomplete",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        if not minOriginCTime:
 | 
			
		||||
            minOriginCTime = oo.creationInfo().creationTime()
 | 
			
		||||
        elif oo.creationInfo().creationTime() < minOriginCTime:
 | 
			
		||||
            minOriginCTime = oo.creationInfo().creationTime()
 | 
			
		||||
 | 
			
		||||
        if not maxOriginCTime:
 | 
			
		||||
            maxOriginCTime = oo.creationInfo().creationTime()
 | 
			
		||||
        elif oo.creationInfo().creationTime() > maxOriginCTime:
 | 
			
		||||
            maxOriginCTime = oo.creationInfo().creationTime()
 | 
			
		||||
 | 
			
		||||
    print(
 | 
			
		||||
        f"""
 | 
			
		||||
Picks
 | 
			
		||||
  + number:            {cntPick}
 | 
			
		||||
  + first pick:        {minPickTime}
 | 
			
		||||
  + last pick:         {maxPickTime}""",
 | 
			
		||||
        file=sys.stdout,
 | 
			
		||||
    )
 | 
			
		||||
    if cntPick > 0:
 | 
			
		||||
        print(
 | 
			
		||||
            f"""    + interval:        {(maxPickTime - minPickTime).toDouble():.3f} s""",
 | 
			
		||||
            file=sys.stdout,
 | 
			
		||||
        )
 | 
			
		||||
        try:
 | 
			
		||||
            print(
 | 
			
		||||
                f"""  + first created:     {minPickCTime}
 | 
			
		||||
  + last created:      {maxPickCTime}
 | 
			
		||||
    + interval:        {(maxPickCTime - minPickCTime).toDouble():.3f} s""",
 | 
			
		||||
                file=sys.stdout,
 | 
			
		||||
            )
 | 
			
		||||
        except TypeError:
 | 
			
		||||
            print(
 | 
			
		||||
                """  + first created:     no creation information
 | 
			
		||||
  + last created:      no creation information
 | 
			
		||||
    + interval:        no creation information""",
 | 
			
		||||
                file=sys.stdout,
 | 
			
		||||
            )
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        print(f"  + found {len(authorPick)} pick author(s):", file=sys.stdout)
 | 
			
		||||
        for i in authorPick:
 | 
			
		||||
            print(f"    + {i}", file=sys.stdout)
 | 
			
		||||
 | 
			
		||||
    print(
 | 
			
		||||
        f"""
 | 
			
		||||
Amplitudes
 | 
			
		||||
  + number:            {cntAmp}""",
 | 
			
		||||
        file=sys.stdout,
 | 
			
		||||
    )
 | 
			
		||||
    if cntAmp > 0:
 | 
			
		||||
        print(
 | 
			
		||||
            f"""  + first amplitude:   {minAmplitudeTime}
 | 
			
		||||
  + last amplitude:    {maxAmplitudeTime}
 | 
			
		||||
    + interval:        {(maxAmplitudeTime - minAmplitudeTime).toDouble():.3f} s""",
 | 
			
		||||
            file=sys.stdout,
 | 
			
		||||
        )
 | 
			
		||||
        print(f"  + found {len(authorAmplitude)} amplitude author(s):", file=sys.stdout)
 | 
			
		||||
        for i in authorAmplitude:
 | 
			
		||||
            print(f"    + {i}", file=sys.stdout)
 | 
			
		||||
 | 
			
		||||
    print(
 | 
			
		||||
        f"""
 | 
			
		||||
Origins
 | 
			
		||||
  + number:            {cntOrg}""",
 | 
			
		||||
        file=sys.stdout,
 | 
			
		||||
    )
 | 
			
		||||
    if cntOrg > 0:
 | 
			
		||||
        print(
 | 
			
		||||
            f"""    + first origin:    {minOriginTime}
 | 
			
		||||
    + last origin:     {maxOriginTime}
 | 
			
		||||
      + interval:        {(maxOriginTime - minOriginTime).toDouble():.3f} s""",
 | 
			
		||||
            file=sys.stdout,
 | 
			
		||||
        )
 | 
			
		||||
        try:
 | 
			
		||||
            print(
 | 
			
		||||
                f"""  + first created:     {minOriginCTime}
 | 
			
		||||
  + last created:      {maxOriginCTime}
 | 
			
		||||
    + interval:        {(maxOriginCTime - minOriginCTime).toDouble():.3f} s""",
 | 
			
		||||
                file=sys.stdout,
 | 
			
		||||
            )
 | 
			
		||||
        except TypeError:
 | 
			
		||||
            print(
 | 
			
		||||
                """  + first created:     no creation information
 | 
			
		||||
  + last created:      no creation information
 | 
			
		||||
    + interval:        no creation information""",
 | 
			
		||||
                file=sys.stdout,
 | 
			
		||||
            )
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        print(f"  + found {len(authorOrigin)} origin author(s):", file=sys.stdout)
 | 
			
		||||
        for i in authorOrigin:
 | 
			
		||||
            print(f"    + {i}", file=sys.stdout)
 | 
			
		||||
 | 
			
		||||
    # stream information
 | 
			
		||||
    print(f"\nFound {len(detectionStreams)} SensorLocation.Channel:", file=sys.stdout)
 | 
			
		||||
    for i in detectionStreams:
 | 
			
		||||
        print(f"  + {i}", file=sys.stdout)
 | 
			
		||||
 | 
			
		||||
    print(f"\nFound {len(nslc)} streams:", file=sys.stdout)
 | 
			
		||||
    for i in sorted(nslc):
 | 
			
		||||
        print(f"  + {i}", file=sys.stdout)
 | 
			
		||||
 | 
			
		||||
    return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PickPlayback(seiscomp.client.Application):
 | 
			
		||||
    def __init__(self, argc, argv):
 | 
			
		||||
        super().__init__(argc, argv)
 | 
			
		||||
 | 
			
		||||
        self.speed = 1.0
 | 
			
		||||
        self.timing = "creationTime"
 | 
			
		||||
        self.jump = 0.0
 | 
			
		||||
        self.print = False
 | 
			
		||||
        self.printList = False
 | 
			
		||||
        self.group = "PICK"
 | 
			
		||||
        self.ampGroup = "AMPLITUDE"
 | 
			
		||||
        self.orgGroup = "LOCATION"
 | 
			
		||||
        self.fileNames = None
 | 
			
		||||
        self.mode = "historic"
 | 
			
		||||
        self.authors = None
 | 
			
		||||
        self.objects = None
 | 
			
		||||
 | 
			
		||||
        self.setMessagingUsername("pbpick")
 | 
			
		||||
        self.setMessagingEnabled(True)
 | 
			
		||||
        self.setPrimaryMessagingGroup("PICK")
 | 
			
		||||
        self.setDatabaseEnabled(False, False)
 | 
			
		||||
 | 
			
		||||
    def createCommandLineDescription(self):
 | 
			
		||||
        self.commandline().addGroup("Playback")
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Playback",
 | 
			
		||||
            "authors",
 | 
			
		||||
            "Author of objects to filter before playing back. Objects from all other "
 | 
			
		||||
            "authors are ignored. Separate multiple authors by comma.",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addDoubleOption(
 | 
			
		||||
            "Playback", "jump,j", "Minutes to skip objects in the beginning."
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Playback",
 | 
			
		||||
            "list",
 | 
			
		||||
            "Just list important pick information from the read XML file and then "
 | 
			
		||||
            "exit without playing back. The sorting of the list depends on '--timing'."
 | 
			
		||||
            "Information include: phase hint, pick time, stream ID, author, time to "
 | 
			
		||||
            "previous pick, delay.",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Playback",
 | 
			
		||||
            "mode",
 | 
			
		||||
            "Playback mode: 'historic' or 'realTime'. "
 | 
			
		||||
            "'realTime' mimics current situation. Default: 'historic'.",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Playback",
 | 
			
		||||
            "object,o",
 | 
			
		||||
            "Limit the playback to the given list of objects. Supported values are: \n"
 | 
			
		||||
            "pick, amplitude, origin.",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Playback",
 | 
			
		||||
            "print",
 | 
			
		||||
            "Just print some statistics of the read XML file and then "
 | 
			
		||||
            "exit without playing back. The list of stream codes (NSLC) is printed to "
 | 
			
		||||
            "stdout. All other information is printed to stderr. The information can "
 | 
			
		||||
            "be used for filtering waveforms (scart) or inventory (invextr), for "
 | 
			
		||||
            "creating global bindings or applying author filtering, e.g., in "
 | 
			
		||||
            "dump_picks.",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addDoubleOption(
 | 
			
		||||
            "Playback", "speed", "Speed of playback.\n1: true speed."
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Playback",
 | 
			
		||||
            "timing",
 | 
			
		||||
            "Timing reference: pickTime or creationTime. Default: creationTime. "
 | 
			
		||||
            "'pickTime' plays back in order of actual times of objects, "
 | 
			
		||||
            "'creationTime' considers their creation times instead. Use 'pickTime' if "
 | 
			
		||||
            "creation times are not representative of the order of objects, e.g., when "
 | 
			
		||||
            "created in playbacks. 'creationTime' should be considered for playing "
 | 
			
		||||
            "back origins since their actual origin time values are always before "
 | 
			
		||||
            "picks and amplitudes.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def printUsage(self):
 | 
			
		||||
        print(
 | 
			
		||||
            f"""Usage:
 | 
			
		||||
  {os.path.basename(__file__)} [options] [XML file][:PICK:AMPLITUDE:LOCATION]
 | 
			
		||||
 | 
			
		||||
Play back pick, amplitude and origin objects from one or more XML files in SCML format
 | 
			
		||||
sending them to the SeisComP messaging in timely order. Default message groups:
 | 
			
		||||
  * PICK for picks,
 | 
			
		||||
  * AMPLITUDE for amplitudes.
 | 
			
		||||
  * LOCATION for origins,"""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        super().printUsage()
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            f"""Examples:
 | 
			
		||||
Play back picks and other objects in file 'pick.xml' at true speed jumping the
 | 
			
		||||
first 2 minutes
 | 
			
		||||
  {os.path.basename(__file__)} -j 2 picks.xml
 | 
			
		||||
 | 
			
		||||
Play back picks and other objects from 2 XML files sending the picks, amplitudes
 | 
			
		||||
and origins ordered by creation time to different message groups but amplitudes
 | 
			
		||||
to the same default group (AMPLITUDE).
 | 
			
		||||
  {os.path.basename(__file__)} origins.xml l1origins.xml:L1PICK:AMPLITUDE:L1LOCATION
 | 
			
		||||
 | 
			
		||||
Just print statistics and stream information
 | 
			
		||||
  {os.path.basename(__file__)} --print picks.xml
 | 
			
		||||
"""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def init(self):
 | 
			
		||||
        if not super().init():
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def validateParameters(self):
 | 
			
		||||
        if not super().validateParameters():
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.authors = self.commandline().optionString("authors").split(",")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.mode = self.commandline().optionString("mode")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.objects = self.commandline().optionString("object")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if self.mode not in ("historic", "realTime"):
 | 
			
		||||
            print(f"Unknown mode: {self.mode}", file=sys.stderr)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.print = self.commandline().hasOption("print")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.printList = self.commandline().hasOption("list")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.speed = self.commandline().optionDouble("speed")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.timing = self.commandline().optionString("timing")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.jump = self.commandline().optionDouble("jump")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if self.timing not in ("pickTime", "creationTime"):
 | 
			
		||||
            print(f"Unknown timing: {self.timing}", file=sys.stderr)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.group = self.commandline().optionString("primary-group")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        files = self.commandline().unrecognizedOptions()
 | 
			
		||||
        if not files:
 | 
			
		||||
            print("At least one XML file must be given!", file=sys.stderr)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        print(files, file=sys.stderr)
 | 
			
		||||
        self.fileNames = list(files)
 | 
			
		||||
 | 
			
		||||
        if self.print or self.printList:
 | 
			
		||||
            self.setMessagingEnabled(False)
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        seiscomp.datamodel.PublicObject.SetRegistrationEnabled(False)
 | 
			
		||||
 | 
			
		||||
        objects = []
 | 
			
		||||
        eps = []
 | 
			
		||||
 | 
			
		||||
        minTime = None
 | 
			
		||||
        maxTime = None
 | 
			
		||||
 | 
			
		||||
        print("Input:", file=sys.stdout)
 | 
			
		||||
        for fileName in self.fileNames:
 | 
			
		||||
            group = self.group
 | 
			
		||||
            ampGroup = self.ampGroup
 | 
			
		||||
            orgGroup = self.orgGroup
 | 
			
		||||
 | 
			
		||||
            toks = fileName.split(":")
 | 
			
		||||
            if len(toks) == 2:
 | 
			
		||||
                fileName = toks[0]
 | 
			
		||||
                group = toks[1]
 | 
			
		||||
 | 
			
		||||
            elif len(toks) == 3:
 | 
			
		||||
                fileName = toks[0]
 | 
			
		||||
                group = toks[1]
 | 
			
		||||
                ampGroup = toks[2]
 | 
			
		||||
 | 
			
		||||
            elif len(toks) == 4:
 | 
			
		||||
                fileName = toks[0]
 | 
			
		||||
                group = toks[1]
 | 
			
		||||
                ampGroup = toks[2]
 | 
			
		||||
                orgGroup = toks[3]
 | 
			
		||||
 | 
			
		||||
            print(
 | 
			
		||||
                f"  + file: {fileName}",
 | 
			
		||||
                file=sys.stdout,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            ar = seiscomp.io.XMLArchive()
 | 
			
		||||
            if not ar.open(fileName):
 | 
			
		||||
                print(f"Could not open {fileName}", file=sys.stderr)
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            obj = ar.readObject()
 | 
			
		||||
            ar.close()
 | 
			
		||||
 | 
			
		||||
            if obj is None:
 | 
			
		||||
                print("Empty document", file=sys.stderr)
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            ep = seiscomp.datamodel.EventParameters.Cast(obj)
 | 
			
		||||
 | 
			
		||||
            if self.print:
 | 
			
		||||
                printStatistics(ep)
 | 
			
		||||
                if not self.printList:
 | 
			
		||||
                    return True
 | 
			
		||||
 | 
			
		||||
            eps.append(ep)
 | 
			
		||||
 | 
			
		||||
            if ep is None:
 | 
			
		||||
                print(
 | 
			
		||||
                    f"Expected event parameters, got {obj.className()}", file=sys.stderr
 | 
			
		||||
                )
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            # read picks
 | 
			
		||||
            cntPick = ep.pickCount()
 | 
			
		||||
            if cntPick == 0:
 | 
			
		||||
                print(f"No picks found in file {fileName}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
            if self.objects is not None and "pick" not in self.objects:
 | 
			
		||||
                print(
 | 
			
		||||
                    f"Skipping picks. Supported objects: {self.objects}",
 | 
			
		||||
                    file=sys.stderr,
 | 
			
		||||
                )
 | 
			
		||||
                cntPick = 0
 | 
			
		||||
 | 
			
		||||
            for i in range(cntPick):
 | 
			
		||||
                pick = ep.pick(i)
 | 
			
		||||
                if self.authors is not None:
 | 
			
		||||
                    try:
 | 
			
		||||
                        if (
 | 
			
		||||
                            pick.creationInfo().author() not in self.authors
 | 
			
		||||
                            and not self.printList
 | 
			
		||||
                        ):
 | 
			
		||||
                            print(
 | 
			
		||||
                                f"Skipping pick {pick.publicID()}: "
 | 
			
		||||
                                f"{pick.creationInfo().author()} not in author list",
 | 
			
		||||
                                file=sys.stderr,
 | 
			
		||||
                            )
 | 
			
		||||
                            continue
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        if not self.printList:
 | 
			
		||||
                            print(
 | 
			
		||||
                                f"Skipping pick {pick.publicID()}: "
 | 
			
		||||
                                f"author is not available",
 | 
			
		||||
                                file=sys.stderr,
 | 
			
		||||
                            )
 | 
			
		||||
                            continue
 | 
			
		||||
 | 
			
		||||
                if self.timing == "creationTime":
 | 
			
		||||
                    try:
 | 
			
		||||
                        pick.creationInfo().creationTime()
 | 
			
		||||
                    except Exception:
 | 
			
		||||
                        if not self.printList:
 | 
			
		||||
                            print(
 | 
			
		||||
                                f"Skipping pick {pick.publicID()}: no creation time",
 | 
			
		||||
                                file=sys.stderr,
 | 
			
		||||
                            )
 | 
			
		||||
                            continue
 | 
			
		||||
 | 
			
		||||
                # filter by time
 | 
			
		||||
                if minTime and pick.time().value() < minTime:
 | 
			
		||||
                    continue
 | 
			
		||||
                if maxTime and pick.time().value() >= maxTime:
 | 
			
		||||
                    continue
 | 
			
		||||
                objects.append((pick, group))
 | 
			
		||||
 | 
			
		||||
            # read amplitudes and add to objects
 | 
			
		||||
            cntAmp = ep.amplitudeCount()
 | 
			
		||||
            if cntAmp == 0:
 | 
			
		||||
                print("No Amplitudes found", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
            if self.objects is not None and "amplitude" not in self.objects:
 | 
			
		||||
                print(
 | 
			
		||||
                    f"Skipping amplitudes. Supported objects: {self.objects}",
 | 
			
		||||
                    file=sys.stderr,
 | 
			
		||||
                )
 | 
			
		||||
                cntAmp = 0
 | 
			
		||||
 | 
			
		||||
            for i in range(cntAmp):
 | 
			
		||||
                amp = ep.amplitude(i)
 | 
			
		||||
                if self.authors is not None:
 | 
			
		||||
                    try:
 | 
			
		||||
                        if (
 | 
			
		||||
                            amp.creationInfo().author() not in self.authors
 | 
			
		||||
                            and not self.printList
 | 
			
		||||
                        ):
 | 
			
		||||
                            print(
 | 
			
		||||
                                f"Skipping amplitude {amp.publicID()}: "
 | 
			
		||||
                                f"{amp.creationInfo().author()} not in author list",
 | 
			
		||||
                                file=sys.stderr,
 | 
			
		||||
                            )
 | 
			
		||||
                            continue
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        if not self.printList:
 | 
			
		||||
                            print(
 | 
			
		||||
                                f"Skipping amplitude {amp.publicID()}: "
 | 
			
		||||
                                f"author is not available",
 | 
			
		||||
                                file=sys.stderr,
 | 
			
		||||
                            )
 | 
			
		||||
                            continue
 | 
			
		||||
 | 
			
		||||
                if self.timing == "creationTime":
 | 
			
		||||
                    try:
 | 
			
		||||
                        amp.creationInfo().creationTime()
 | 
			
		||||
                    except Exception:
 | 
			
		||||
                        print(
 | 
			
		||||
                            f"Skipping amplitude {amp.publicID()}: no creation time",
 | 
			
		||||
                            file=sys.stderr,
 | 
			
		||||
                        )
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                objects.append((amp, ampGroup))
 | 
			
		||||
 | 
			
		||||
            # read origins and add to objects
 | 
			
		||||
            cntOrgs = ep.originCount()
 | 
			
		||||
            if cntOrgs == 0:
 | 
			
		||||
                print("No Origins found", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
            if self.objects is not None and "origin" not in self.objects:
 | 
			
		||||
                print(
 | 
			
		||||
                    f"Skipping origins. Supported objects: {self.objects}",
 | 
			
		||||
                    file=sys.stderr,
 | 
			
		||||
                )
 | 
			
		||||
                cntOrgs = 0
 | 
			
		||||
 | 
			
		||||
            for i in range(cntOrgs):
 | 
			
		||||
                oo = ep.origin(i)
 | 
			
		||||
 | 
			
		||||
                if self.authors is not None:
 | 
			
		||||
                    try:
 | 
			
		||||
                        if (
 | 
			
		||||
                            oo.creationInfo().author() not in self.authors
 | 
			
		||||
                            and not self.printList
 | 
			
		||||
                        ):
 | 
			
		||||
                            print(
 | 
			
		||||
                                f"Skipping origin {oo.publicID()}: "
 | 
			
		||||
                                f"{oo.creationInfo().author()} not in author list",
 | 
			
		||||
                                file=sys.stderr,
 | 
			
		||||
                            )
 | 
			
		||||
                            continue
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        if not self.printList:
 | 
			
		||||
                            print(
 | 
			
		||||
                                f"Skipping origin {oo.publicID()}: "
 | 
			
		||||
                                f"author is not available",
 | 
			
		||||
                                file=sys.stderr,
 | 
			
		||||
                            )
 | 
			
		||||
                            continue
 | 
			
		||||
 | 
			
		||||
                if self.timing == "creationTime":
 | 
			
		||||
                    try:
 | 
			
		||||
                        oo.creationInfo().creationTime()
 | 
			
		||||
                    except Exception:
 | 
			
		||||
                        try:
 | 
			
		||||
                            string = oo.publicID().split("/")[1].split(".")[:2]
 | 
			
		||||
                            timeString = string[0] + "." + string[1]
 | 
			
		||||
                            timeFormat = "%Y%m%d%H%M%S.%f"
 | 
			
		||||
                            t = seiscomp.core.Time()
 | 
			
		||||
                            t.fromString(str(timeString), timeFormat)
 | 
			
		||||
                            ci = seiscomp.datamodel.CreationInfo()
 | 
			
		||||
                            ci.setCreationTime(t)
 | 
			
		||||
                            oo.setCreationInfo(ci)
 | 
			
		||||
                            print(
 | 
			
		||||
                                f"creation time not found in origin {oo.publicID()}: "
 | 
			
		||||
                                f"assuming {oo.creationInfo().creationTime()} from "
 | 
			
		||||
                                "originID ",
 | 
			
		||||
                                file=sys.stderr,
 | 
			
		||||
                            )
 | 
			
		||||
                        except Exception:
 | 
			
		||||
                            if not self.printList:
 | 
			
		||||
                                print(
 | 
			
		||||
                                    f"Skipping origin {oo.publicID()}: no creation time",
 | 
			
		||||
                                    file=sys.stderr,
 | 
			
		||||
                                )
 | 
			
		||||
                                continue
 | 
			
		||||
 | 
			
		||||
                objects.append((oo, orgGroup))
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            f"  + considering {cntPick} picks, {cntAmp} amplitudes, {cntOrgs} origins",
 | 
			
		||||
            file=sys.stdout,
 | 
			
		||||
        )
 | 
			
		||||
        if self.print or self.printList:
 | 
			
		||||
            print("  + do not send objects to messaging")
 | 
			
		||||
        else:
 | 
			
		||||
            print(
 | 
			
		||||
                f"""  + sending objects to groups
 | 
			
		||||
    + picks:      {group}
 | 
			
		||||
    + amplitudes: {ampGroup}
 | 
			
		||||
    + origins:    {orgGroup}""",
 | 
			
		||||
                file=sys.stdout,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if self.timing == "pickTime":
 | 
			
		||||
            try:
 | 
			
		||||
                objects.sort(key=timing_pickTime)
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                print("Time value not set in at least 1 object", file=sys.stderr)
 | 
			
		||||
                if not self.printList:
 | 
			
		||||
                    return False
 | 
			
		||||
        elif self.timing == "creationTime":
 | 
			
		||||
            try:
 | 
			
		||||
                objects.sort(key=timing_creationTime)
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                print("Creation time not set in at least 1 object", file=sys.stderr)
 | 
			
		||||
                if not self.printList:
 | 
			
		||||
                    return False
 | 
			
		||||
        else:
 | 
			
		||||
            print(f"Unknown timing: {self.timing}", file=sys.stderr)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        print("Setup:", file=sys.stdout)
 | 
			
		||||
        print(f"  + author filter: {self.authors}", file=sys.stdout)
 | 
			
		||||
        print(f"  + timing/sorting: {self.timing}", file=sys.stdout)
 | 
			
		||||
 | 
			
		||||
        if self.printList:
 | 
			
		||||
            listPicks(self, objects)
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        seiscomp.datamodel.Notifier.Enable()
 | 
			
		||||
 | 
			
		||||
        firstTime = None
 | 
			
		||||
        lastTime = None
 | 
			
		||||
        refTime = None
 | 
			
		||||
        addSeconds = 0.0
 | 
			
		||||
 | 
			
		||||
        sys.stdout.flush()
 | 
			
		||||
 | 
			
		||||
        for obj, group in objects:
 | 
			
		||||
            po = seiscomp.datamodel.Pick.Cast(obj)
 | 
			
		||||
            ao = seiscomp.datamodel.Amplitude.Cast(obj)
 | 
			
		||||
            oo = seiscomp.datamodel.Origin.Cast(obj)
 | 
			
		||||
 | 
			
		||||
            if self.isExitRequested():
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            if self.timing == "pickTime":
 | 
			
		||||
                if ao:
 | 
			
		||||
                    refTime = obj.timeWindow().reference()
 | 
			
		||||
                elif po:
 | 
			
		||||
                    refTime = obj.time().value()
 | 
			
		||||
                elif oo:
 | 
			
		||||
                    refTime = obj.time().value()
 | 
			
		||||
                else:
 | 
			
		||||
                    print(
 | 
			
		||||
                        "Object neither pick nor amplitude or origin- ignoring",
 | 
			
		||||
                        file=sys.stderr,
 | 
			
		||||
                    )
 | 
			
		||||
                    return False
 | 
			
		||||
            else:
 | 
			
		||||
                refTime = obj.creationInfo().creationTime()
 | 
			
		||||
 | 
			
		||||
            if not firstTime:
 | 
			
		||||
                firstTime = refTime
 | 
			
		||||
 | 
			
		||||
                print(f"  + first time: {firstTime}", file=sys.stderr)
 | 
			
		||||
                print(f"  + playback mode: {self.mode}", file=sys.stderr)
 | 
			
		||||
                print(f"  + speed factor: {self.speed}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
                if self.mode == "realTime":
 | 
			
		||||
                    now = seiscomp.core.Time.GMT()
 | 
			
		||||
                    addSeconds = (now - firstTime).toDouble()
 | 
			
		||||
                    print(
 | 
			
		||||
                        f"    + adding {addSeconds: .3f} s to: pick time, amplitude "
 | 
			
		||||
                        "reference time, origin time, creation time",
 | 
			
		||||
                        file=sys.stderr,
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                print("Playback progress:", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
            objectType = "pick"
 | 
			
		||||
            if ao:
 | 
			
		||||
                objectType = "amplitude"
 | 
			
		||||
            if oo:
 | 
			
		||||
                objectType = "origin"
 | 
			
		||||
 | 
			
		||||
            print(
 | 
			
		||||
                f"  + {obj.publicID()} {objectType}: {group} - reference time: {refTime}",
 | 
			
		||||
                end="",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # add addSeconds to all times in real-time mode
 | 
			
		||||
            if self.mode == "realTime":
 | 
			
		||||
                objectInfo = obj.creationInfo()
 | 
			
		||||
                creationTime = objectInfo.creationTime() + seiscomp.core.TimeSpan(
 | 
			
		||||
                    addSeconds
 | 
			
		||||
                )
 | 
			
		||||
                obj.creationInfo().setCreationTime(creationTime)
 | 
			
		||||
 | 
			
		||||
                if ao:
 | 
			
		||||
                    objectInfo = obj.timeWindow()
 | 
			
		||||
                    amplitudeTime = objectInfo.reference() + seiscomp.core.TimeSpan(
 | 
			
		||||
                        addSeconds
 | 
			
		||||
                    )
 | 
			
		||||
                    obj.timeWindow().setReference(amplitudeTime)
 | 
			
		||||
                    print(
 | 
			
		||||
                        "\n    + real-time mode - using modified reference time: "
 | 
			
		||||
                        f"{obj.timeWindow().reference()}, creation time: {creationTime}",
 | 
			
		||||
                        end="",
 | 
			
		||||
                        file=sys.stderr,
 | 
			
		||||
                    )
 | 
			
		||||
                elif po or oo:
 | 
			
		||||
                    objectTime = obj.time()
 | 
			
		||||
                    objectTime.setValue(
 | 
			
		||||
                        objectTime.value() + seiscomp.core.TimeSpan(addSeconds)
 | 
			
		||||
                    )
 | 
			
		||||
                    obj.setTime(objectTime)
 | 
			
		||||
                    print(
 | 
			
		||||
                        f"\n    + real-time mode - using modified {objectType} time: "
 | 
			
		||||
                        f"{obj.time().value()}, creation time: {creationTime}",
 | 
			
		||||
                        end="",
 | 
			
		||||
                        file=sys.stderr,
 | 
			
		||||
                    )
 | 
			
		||||
                else:
 | 
			
		||||
                    print(
 | 
			
		||||
                        "\n    + object not pick, amplitude or origin - ignoring",
 | 
			
		||||
                        file=sys.stderr,
 | 
			
		||||
                    )
 | 
			
		||||
                    return False
 | 
			
		||||
 | 
			
		||||
            delay = 0
 | 
			
		||||
            if lastTime:
 | 
			
		||||
                delay = (refTime - lastTime).toDouble() / self.speed
 | 
			
		||||
 | 
			
		||||
            if (refTime - firstTime).toDouble() / 60.0 >= self.jump:
 | 
			
		||||
                delay = max(delay, 0)
 | 
			
		||||
 | 
			
		||||
                print(f" - time to sending: {delay:.4f} s", file=sys.stderr)
 | 
			
		||||
                time.sleep(delay)
 | 
			
		||||
 | 
			
		||||
                lastTime = refTime
 | 
			
		||||
 | 
			
		||||
                nc = seiscomp.datamodel.NotifierCreator(seiscomp.datamodel.OP_ADD)
 | 
			
		||||
                obj.accept(nc)
 | 
			
		||||
                msg = seiscomp.datamodel.Notifier.GetMessage()
 | 
			
		||||
                self.connection().send(group, msg)
 | 
			
		||||
            else:
 | 
			
		||||
                print(" - skipping", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
            sys.stdout.flush()
 | 
			
		||||
 | 
			
		||||
        print("")
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main(argv):
 | 
			
		||||
    app = PickPlayback(len(argv), argv)
 | 
			
		||||
    return app()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    sys.exit(main(sys.argv))
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/rifftool
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/rifftool
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/rs2caps
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/rs2caps
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/rtpd2caps
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/rtpd2caps
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/run_with_lock
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/run_with_lock
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										226
									
								
								bin/sc2pa
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										226
									
								
								bin/sc2pa
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,226 @@
 | 
			
		||||
#!/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.0
 | 
			
		||||
        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(f"caught unexpected error {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(f"Using procalert script: {self.procAlertScript}")
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def addObject(self, parentID, obj):
 | 
			
		||||
        org = seiscomp.datamodel.Origin.Cast(obj)
 | 
			
		||||
        if org:
 | 
			
		||||
            self.cache.feed(org)
 | 
			
		||||
            seiscomp.logging.info(f"Received origin {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(f"Unable to fetch origin {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(f"Origin {orid} not published")
 | 
			
		||||
                    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(f"{sys.exc_info()}\n")
 | 
			
		||||
 | 
			
		||||
    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 = f"/tmp/yyy{evid.replace('/', '_').replace(':', '-')}"
 | 
			
		||||
            f = file(tmp, "w")
 | 
			
		||||
            f.write(f"{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.0 > 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())
 | 
			
		||||
							
								
								
									
										1
									
								
								bin/sc32inv
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/sc32inv
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
scml2inv
 | 
			
		||||
							
								
								
									
										843
									
								
								bin/scalert
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										843
									
								
								bin/scalert
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,843 @@
 | 
			
		||||
#!/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
 | 
			
		||||
import seiscomp.client
 | 
			
		||||
import seiscomp.datamodel
 | 
			
		||||
import seiscomp.math
 | 
			
		||||
import seiscomp.logging
 | 
			
		||||
import seiscomp.seismology
 | 
			
		||||
import 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._authors = []
 | 
			
		||||
        self._phaseHints = []
 | 
			
		||||
        self._phaseStreams = []
 | 
			
		||||
        self._phaseNumber = 1
 | 
			
		||||
        self._phaseInterval = 1
 | 
			
		||||
        self._cache = None
 | 
			
		||||
        self._pickCache = seiscomp.datamodel.PublicObjectTimeSpanBuffer()
 | 
			
		||||
 | 
			
		||||
    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 RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._agencyIDs = [self.configGetString("agencyID")]
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            agencyIDs = self.configGetStrings("agencyIDs")
 | 
			
		||||
            self._agencyIDs = []
 | 
			
		||||
            for item in agencyIDs:
 | 
			
		||||
                item = item.strip()
 | 
			
		||||
                if item and item not in self._agencyIDs:
 | 
			
		||||
                    self._agencyIDs.append(item)
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            authors = self.configGetStrings("authors")
 | 
			
		||||
            self._authors = []
 | 
			
		||||
            for item in authors:
 | 
			
		||||
                item = item.strip()
 | 
			
		||||
                if item not in self._authors:
 | 
			
		||||
                    self._authors.append(item)
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            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 RuntimeError:
 | 
			
		||||
            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(
 | 
			
		||||
                        f"Wrong stream ID format in `constraints.phaseStreams`: {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 RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._phaseNumber = self.configGetInt("constraints.phaseNumber")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._phaseInterval = self.configGetInt("constraints.phaseInterval")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if self._phaseNumber > 1:
 | 
			
		||||
            self._pickCache.setTimeSpan(seiscomp.core.TimeSpan(self._phaseInterval))
 | 
			
		||||
            self.enableTimer(1)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._eventDescriptionPattern = self.configGetString("poi.message")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._citiesMaxDist = self.configGetDouble("poi.maxDist")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._citiesMinPopulation = self.configGetInt("poi.minPopulation")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        # mostly command-line options
 | 
			
		||||
        try:
 | 
			
		||||
            self._citiesMaxDist = self.commandline().optionDouble("max-dist")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            if self.commandline().hasOption("first-new"):
 | 
			
		||||
                self._newWhenFirstSeen = True
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._citiesMinPopulation = self.commandline().optionInt("min-population")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._ampType = self.commandline().optionString("amp-type")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._pickScript = self.commandline().optionString("pick-script")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            try:
 | 
			
		||||
                self._pickScript = self.configGetString("scripts.pick")
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                seiscomp.logging.warning("No pick script defined")
 | 
			
		||||
 | 
			
		||||
        if self._pickScript:
 | 
			
		||||
            self._pickScript = seiscomp.system.Environment.Instance().absolutePath(
 | 
			
		||||
                self._pickScript
 | 
			
		||||
            )
 | 
			
		||||
            seiscomp.logging.info(f"Using pick script {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 RuntimeError:
 | 
			
		||||
            try:
 | 
			
		||||
                self._ampScript = self.configGetString("scripts.amplitude")
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                seiscomp.logging.warning("No amplitude script defined")
 | 
			
		||||
 | 
			
		||||
        if self._ampScript:
 | 
			
		||||
            self._ampScript = seiscomp.system.Environment.Instance().absolutePath(
 | 
			
		||||
                self._ampScript
 | 
			
		||||
            )
 | 
			
		||||
            seiscomp.logging.info(f"Using amplitude script {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 RuntimeError:
 | 
			
		||||
            try:
 | 
			
		||||
                self._alertScript = self.configGetString("scripts.alert")
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                seiscomp.logging.warning("No alert script defined")
 | 
			
		||||
 | 
			
		||||
        if self._alertScript:
 | 
			
		||||
            self._alertScript = seiscomp.system.Environment.Instance().absolutePath(
 | 
			
		||||
                self._alertScript
 | 
			
		||||
            )
 | 
			
		||||
            seiscomp.logging.info(f"Using alert script {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 RuntimeError:
 | 
			
		||||
            try:
 | 
			
		||||
                self._eventScript = self.configGetString("scripts.event")
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                seiscomp.logging.warning("No event script defined")
 | 
			
		||||
 | 
			
		||||
        if self._eventScript:
 | 
			
		||||
            self._eventScript = seiscomp.system.Environment.Instance().absolutePath(
 | 
			
		||||
                self._eventScript
 | 
			
		||||
            )
 | 
			
		||||
            seiscomp.logging.info(f"Using event script {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 self._agencyIDs:
 | 
			
		||||
            agencies = " ".join(self._agencyIDs)
 | 
			
		||||
            seiscomp.logging.info(
 | 
			
		||||
                f" + agencyIDs filter for events and picks: {agencies}"
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            seiscomp.logging.info(" + agencyIDs: no filter is applied")
 | 
			
		||||
 | 
			
		||||
        if " ".join(self._authors):
 | 
			
		||||
            authors = " ".join(self._authors)
 | 
			
		||||
            seiscomp.logging.info(f" + Authors filter for events and picks: {authors}")
 | 
			
		||||
        else:
 | 
			
		||||
            seiscomp.logging.info(" + authors: no filter is applied")
 | 
			
		||||
 | 
			
		||||
        if " ".join(self._phaseHints):
 | 
			
		||||
            phaseHints = " ".join(self._phaseHints)
 | 
			
		||||
            seiscomp.logging.info(f" + phase hint filter for picks: '{phaseHints}'")
 | 
			
		||||
        else:
 | 
			
		||||
            seiscomp.logging.info(" + phase hints: no filter is applied")
 | 
			
		||||
 | 
			
		||||
        if " ".join(self._phaseStreams):
 | 
			
		||||
            streams = " ".join(self._phaseStreams)
 | 
			
		||||
            seiscomp.logging.info(f" + phase stream ID filter for picks: '{streams}'")
 | 
			
		||||
        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 RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            return seiscomp.client.Application.run(self)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            info = traceback.format_exception(*sys.exc_info())
 | 
			
		||||
            for i in info:
 | 
			
		||||
                sys.stderr.write(i)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def done(self):
 | 
			
		||||
        self._cache = None
 | 
			
		||||
        seiscomp.client.Application.done(self)
 | 
			
		||||
 | 
			
		||||
    def runPickScript(self, pickObjectList):
 | 
			
		||||
        if not self._pickScript:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        for pickObject in pickObjectList:
 | 
			
		||||
            # parse values
 | 
			
		||||
            try:
 | 
			
		||||
                net = pickObject.waveformID().networkCode()
 | 
			
		||||
            except Exception:
 | 
			
		||||
                net = "unknown"
 | 
			
		||||
            try:
 | 
			
		||||
                sta = pickObject.waveformID().stationCode()
 | 
			
		||||
            except Exception:
 | 
			
		||||
                sta = "unknown"
 | 
			
		||||
            pickID = pickObject.publicID()
 | 
			
		||||
            try:
 | 
			
		||||
                phaseHint = pickObject.phaseHint().code()
 | 
			
		||||
            except Exception:
 | 
			
		||||
                phaseHint = "unknown"
 | 
			
		||||
 | 
			
		||||
            print(net, sta, pickID, phaseHint, file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
            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(
 | 
			
		||||
                    f"Started pick script with pid {self._pickProc.pid}"
 | 
			
		||||
                )
 | 
			
		||||
            except Exception:
 | 
			
		||||
                seiscomp.logging.error(
 | 
			
		||||
                    f"Failed to start pick script '{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, f"{amp:.2f}", ampID]
 | 
			
		||||
            )
 | 
			
		||||
            seiscomp.logging.info(
 | 
			
		||||
                f"Started amplitude script with pid {self._ampProc.pid}"
 | 
			
		||||
            )
 | 
			
		||||
        except Exception:
 | 
			
		||||
            seiscomp.logging.error(
 | 
			
		||||
                f"Failed to start amplitude script '{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, f"{lat:.1f}", f"{lon:.1f}"]
 | 
			
		||||
            )
 | 
			
		||||
            seiscomp.logging.info(
 | 
			
		||||
                f"Started alert script with pid {self._alertProc.pid}"
 | 
			
		||||
            )
 | 
			
		||||
        except Exception:
 | 
			
		||||
            seiscomp.logging.error(
 | 
			
		||||
                f"Failed to start alert script '{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 Exception:
 | 
			
		||||
                            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 Exception:
 | 
			
		||||
            info = traceback.format_exception(*sys.exc_info())
 | 
			
		||||
            for i in info:
 | 
			
		||||
                sys.stderr.write(i)
 | 
			
		||||
 | 
			
		||||
    def addObject(self, parentID, scObject):
 | 
			
		||||
        try:
 | 
			
		||||
            # pick
 | 
			
		||||
            obj = seiscomp.datamodel.Pick.Cast(scObject)
 | 
			
		||||
            if obj:
 | 
			
		||||
                self._cache.feed(obj)
 | 
			
		||||
                seiscomp.logging.debug(f"got new pick '{obj.publicID()}'")
 | 
			
		||||
                agencyID = obj.creationInfo().agencyID()
 | 
			
		||||
                author = obj.creationInfo().author()
 | 
			
		||||
                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(
 | 
			
		||||
                            f" + stream ID {waveformID} does not match constraints.phaseStreams rules"
 | 
			
		||||
                        )
 | 
			
		||||
                        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(
 | 
			
		||||
                            f" + phase hint {phaseHint} does not match '{self._phaseHints}'"
 | 
			
		||||
                        )
 | 
			
		||||
                else:
 | 
			
		||||
                    seiscomp.logging.debug(
 | 
			
		||||
                        f" + agencyID {agencyID} does not match '{self._agencyIDs}'"
 | 
			
		||||
                    )
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            # amplitude
 | 
			
		||||
            obj = seiscomp.datamodel.Amplitude.Cast(scObject)
 | 
			
		||||
            if obj:
 | 
			
		||||
                if obj.type() == self._ampType:
 | 
			
		||||
                    seiscomp.logging.debug(
 | 
			
		||||
                        f"got new {self._ampType} amplitude '{obj.publicID()}'"
 | 
			
		||||
                    )
 | 
			
		||||
                    self.notifyAmplitude(obj)
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            # origin
 | 
			
		||||
            obj = seiscomp.datamodel.Origin.Cast(scObject)
 | 
			
		||||
            if obj:
 | 
			
		||||
                self._cache.feed(obj)
 | 
			
		||||
                seiscomp.logging.debug(f"got new origin '{obj.publicID()}'")
 | 
			
		||||
 | 
			
		||||
                try:
 | 
			
		||||
                    if obj.evaluationStatus() == seiscomp.datamodel.PRELIMINARY:
 | 
			
		||||
                        self.runAlert(obj.latitude().value(), obj.longitude().value())
 | 
			
		||||
                except Exception:
 | 
			
		||||
                    pass
 | 
			
		||||
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            # magnitude
 | 
			
		||||
            obj = seiscomp.datamodel.Magnitude.Cast(scObject)
 | 
			
		||||
            if obj:
 | 
			
		||||
                self._cache.feed(obj)
 | 
			
		||||
                seiscomp.logging.debug(f"got new magnitude '{obj.publicID()}'")
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            # event
 | 
			
		||||
            obj = seiscomp.datamodel.Event.Cast(scObject)
 | 
			
		||||
            if obj:
 | 
			
		||||
                org = self._cache.get(
 | 
			
		||||
                    seiscomp.datamodel.Origin, obj.preferredOriginID()
 | 
			
		||||
                )
 | 
			
		||||
                agencyID = org.creationInfo().agencyID()
 | 
			
		||||
                author = org.creationInfo().author()
 | 
			
		||||
                seiscomp.logging.debug(f"got new event '{obj.publicID()}'")
 | 
			
		||||
                if not self._agencyIDs or agencyID in self._agencyIDs:
 | 
			
		||||
                    if not self._authors or author in self._authors:
 | 
			
		||||
                        self.notifyEvent(obj, True)
 | 
			
		||||
                return
 | 
			
		||||
        except Exception:
 | 
			
		||||
            info = traceback.format_exception(*sys.exc_info())
 | 
			
		||||
            for i in info:
 | 
			
		||||
                sys.stderr.write(i)
 | 
			
		||||
 | 
			
		||||
    def updateObject(self, parentID, scObject):
 | 
			
		||||
        try:
 | 
			
		||||
            obj = seiscomp.datamodel.Event.Cast(scObject)
 | 
			
		||||
            if obj:
 | 
			
		||||
                org = self._cache.get(
 | 
			
		||||
                    seiscomp.datamodel.Origin, obj.preferredOriginID()
 | 
			
		||||
                )
 | 
			
		||||
                agencyID = org.creationInfo().agencyID()
 | 
			
		||||
                author = org.creationInfo().author()
 | 
			
		||||
                seiscomp.logging.debug(f"update event '{obj.publicID()}'")
 | 
			
		||||
                if not self._agencyIDs or agencyID in self._agencyIDs:
 | 
			
		||||
                    if not self._authors or author in self._authors:
 | 
			
		||||
                        self.notifyEvent(obj, False)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            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(
 | 
			
		||||
                    f"unable to get origin {evt.preferredOriginID()}, ignoring event message"
 | 
			
		||||
                )
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            preliminary = False
 | 
			
		||||
            try:
 | 
			
		||||
                if org.evaluationStatus() == seiscomp.datamodel.PRELIMINARY:
 | 
			
		||||
                    preliminary = True
 | 
			
		||||
            except Exception:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            if preliminary is False:
 | 
			
		||||
                nmag = self._cache.get(
 | 
			
		||||
                    seiscomp.datamodel.Magnitude, evt.preferredMagnitudeID()
 | 
			
		||||
                )
 | 
			
		||||
                if nmag:
 | 
			
		||||
                    mag = nmag.magnitude().value()
 | 
			
		||||
                    mag = f"magnitude {mag:.1f}"
 | 
			
		||||
                else:
 | 
			
		||||
                    if len(evt.preferredMagnitudeID()) > 0:
 | 
			
		||||
                        seiscomp.logging.warning(
 | 
			
		||||
                            f"unable to get magnitude {evt.preferredMagnitudeID()}, "
 | 
			
		||||
                            "ignoring event message"
 | 
			
		||||
                        )
 | 
			
		||||
                    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 Exception:
 | 
			
		||||
                    pass
 | 
			
		||||
 | 
			
		||||
            seiscomp.logging.debug(f"desc: {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 = f"{int(dt / 3600)} hours {int((dt % 3600) / 60)} minutes ago"
 | 
			
		||||
            elif dt > 120:
 | 
			
		||||
                dt = f"{int(dt / 60)} minutes ago"
 | 
			
		||||
            else:
 | 
			
		||||
                dt = f"{int(dt)} seconds ago"
 | 
			
		||||
 | 
			
		||||
            if preliminary:
 | 
			
		||||
                message = f"earthquake, XXL, preliminary, {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 Exception:
 | 
			
		||||
                        pass
 | 
			
		||||
 | 
			
		||||
                nmag = self._cache.get(
 | 
			
		||||
                    seiscomp.datamodel.Magnitude, evt.preferredMagnitudeID()
 | 
			
		||||
                )
 | 
			
		||||
                if nmag:
 | 
			
		||||
                    param4 = f"{nmag.magnitude().value():.1f}"
 | 
			
		||||
 | 
			
		||||
                self._eventProc = subprocess.Popen(
 | 
			
		||||
                    [
 | 
			
		||||
                        self._eventScript,
 | 
			
		||||
                        message,
 | 
			
		||||
                        "%d" % param2,
 | 
			
		||||
                        evt.publicID(),
 | 
			
		||||
                        "%d" % param3,
 | 
			
		||||
                        param4,
 | 
			
		||||
                    ]
 | 
			
		||||
                )
 | 
			
		||||
                seiscomp.logging.info(
 | 
			
		||||
                    f"Started event script with pid {self._eventProc.pid}"
 | 
			
		||||
                )
 | 
			
		||||
            except Exception:
 | 
			
		||||
                seiscomp.logging.error(
 | 
			
		||||
                    f"Failed to start event script '{self._eventScript} {message} "
 | 
			
		||||
                    f"{param2} {param3} {param4}'"
 | 
			
		||||
                )
 | 
			
		||||
        except Exception:
 | 
			
		||||
            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())
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/scanloc
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scanloc
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/scardac
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scardac
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/scautoloc
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scautoloc
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/scautopick
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scautopick
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										19
									
								
								bin/scbulletin
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										19
									
								
								bin/scbulletin
									
									
									
									
									
										Executable file
									
								
							@ -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()
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/scchkcfg
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scchkcfg
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/scconfig
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scconfig
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1320
									
								
								bin/scdbstrip
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										1320
									
								
								bin/scdbstrip
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/scdispatch
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scdispatch
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										292
									
								
								bin/scdumpcfg
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										292
									
								
								bin/scdumpcfg
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,292 @@
 | 
			
		||||
#!/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.config
 | 
			
		||||
import seiscomp.system
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def readParams(sc_params):
 | 
			
		||||
    if sc_params.baseID():
 | 
			
		||||
        sc_params_base = seiscomp.datamodel.ParameterSet.Find(sc_params.baseID())
 | 
			
		||||
        if sc_params_base is None:
 | 
			
		||||
            print(
 | 
			
		||||
                f"Warning: {sc_params.baseID()}: base parameter set for "
 | 
			
		||||
                f"{sc_params.publicID()} not found",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
            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:
 | 
			
		||||
            print("scdumpcfg {modname} [options]", file=sys.stderr)
 | 
			
		||||
            raise RuntimeError
 | 
			
		||||
 | 
			
		||||
        self.appName = argv[1]
 | 
			
		||||
        self.config = seiscomp.config.Config()
 | 
			
		||||
 | 
			
		||||
        # 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)
 | 
			
		||||
 | 
			
		||||
        self.dumpBindings = False
 | 
			
		||||
        self.allowGlobal = False
 | 
			
		||||
        self.formatCfg = False
 | 
			
		||||
        self.nslc = False
 | 
			
		||||
        self.param = None
 | 
			
		||||
 | 
			
		||||
    def createCommandLineDescription(self):
 | 
			
		||||
        self.commandline().addGroup("Dump")
 | 
			
		||||
        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().addStringOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "param,P",
 | 
			
		||||
            "Specify the parameter name(s) to filter for. Use comma sepration for "
 | 
			
		||||
            "multiple parameters.",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Dump", "cfg", "Print output in .cfg format. Does not work along with -B."
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "nslc",
 | 
			
		||||
            "Print the list of channels which have bindings of the given module. "
 | 
			
		||||
            "Requires to set -B. Can be used by other modules, e.g., invextr, scart, "
 | 
			
		||||
            "scmssort, scevtstreams.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def validateParameters(self):
 | 
			
		||||
        if not seiscomp.client.Application.validateParameters(self):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        self.dumpBindings = self.commandline().hasOption("bindings")
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            param = self.commandline().optionString("param")
 | 
			
		||||
            self.param = param.split(",")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        self.allowGlobal = self.commandline().hasOption("allow-global")
 | 
			
		||||
        self.formatCfg = self.commandline().hasOption("cfg")
 | 
			
		||||
        self.nslc = self.commandline().hasOption("nslc")
 | 
			
		||||
 | 
			
		||||
        if self.dumpBindings and self.databaseURI() != "":
 | 
			
		||||
            self.setMessagingEnabled(False)
 | 
			
		||||
            self.setDatabaseEnabled(True, False)
 | 
			
		||||
 | 
			
		||||
        if not self.dumpBindings:
 | 
			
		||||
            self.setMessagingEnabled(False)
 | 
			
		||||
            self.setDatabaseEnabled(False, False)
 | 
			
		||||
            self.setLoadConfigModuleEnabled(False)
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def initConfiguration(self):
 | 
			
		||||
        if self.appName in ("-h", "--help"):
 | 
			
		||||
            self.printUsage()
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if not seiscomp.client.Application.initConfiguration(self):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        seiscomp.system.Environment.Instance().initConfig(self.config, self.appName)
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def initSubscriptions(self):
 | 
			
		||||
        # Do nothing.
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def printUsage(self):
 | 
			
		||||
        print(
 | 
			
		||||
            f"""Usage:
 | 
			
		||||
  {os.path.basename(__file__)} [options]
 | 
			
		||||
 | 
			
		||||
Dump bindings or module configurations used by a specific module or global for \
 | 
			
		||||
particular stations.""",
 | 
			
		||||
            file=sys.stderr,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        seiscomp.client.Application.printUsage(self)
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            f"""Examples:
 | 
			
		||||
Dump scautopick bindings configuration including global for all stations
 | 
			
		||||
  {os.path.basename(__file__)} scautopick -d localhost -BG
 | 
			
		||||
 | 
			
		||||
Connect to messaging for the database connection and dump scautopick bindings \
 | 
			
		||||
configuration including global for all stations
 | 
			
		||||
  {os.path.basename(__file__)} scautopick -H localhost -BG
 | 
			
		||||
 | 
			
		||||
Dump scautopick module configuration including global parameters
 | 
			
		||||
  {os.path.basename(__file__)} scautopick --cfg
 | 
			
		||||
 | 
			
		||||
Dump global bindings configuration considerd by scmv
 | 
			
		||||
  {os.path.basename(__file__)} scmv -d localhost -BG
 | 
			
		||||
 | 
			
		||||
Dump the list of streams configured with scautopick bindings
 | 
			
		||||
  {os.path.basename(__file__)} scautopick -d localhost -B --nslc
 | 
			
		||||
 | 
			
		||||
Dump specific parameters configured with scautopick bindings
 | 
			
		||||
  {os.path.basename(__file__)} scautopick -B -d localhost \
 | 
			
		||||
-P spicker.AIC.minSNR,spicker.AIC.minCnt
 | 
			
		||||
""",
 | 
			
		||||
            file=sys.stderr,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        cfg = self.config
 | 
			
		||||
        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 name not in self.param:
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                sym = symtab.get(name)
 | 
			
		||||
                if self.formatCfg:
 | 
			
		||||
                    if sym.comment:
 | 
			
		||||
                        if count > 0:
 | 
			
		||||
                            print("")
 | 
			
		||||
                        print(f"{sym.comment}")
 | 
			
		||||
                    print(f"{cfg.escapeIdentifier(sym.name)} = {sym.content}")
 | 
			
		||||
                else:
 | 
			
		||||
                    print(f"{sym.name}")
 | 
			
		||||
                    print(f"  value(s) : {', '.join(sym.values)}")
 | 
			
		||||
                    print(f"  source   : {sym.uri}")
 | 
			
		||||
 | 
			
		||||
                count = count + 1
 | 
			
		||||
 | 
			
		||||
            if self.param and count == 0:
 | 
			
		||||
                print(f"{self.param}: definition not found", file=sys.stderr)
 | 
			
		||||
        else:
 | 
			
		||||
            cfg = self.configModule()
 | 
			
		||||
            if cfg is None:
 | 
			
		||||
                print("No config module read", file=sys.stderr)
 | 
			
		||||
                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.appName
 | 
			
		||||
            # 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 += f"{cfg_sta.networkCode()}.{cfg_sta.stationCode()}{suffix}\n"
 | 
			
		||||
                    params = seiscomp.datamodel.ParameterSet.Find(
 | 
			
		||||
                        cfg_setup.parameterSetID()
 | 
			
		||||
                    )
 | 
			
		||||
                    if params is None:
 | 
			
		||||
                        print(
 | 
			
		||||
                            f"ERROR: {cfg_setup.parameterSetID()}: ParameterSet not found",
 | 
			
		||||
                            file=sys.stderr,
 | 
			
		||||
                        )
 | 
			
		||||
                        return False
 | 
			
		||||
 | 
			
		||||
                    params = readParams(params)
 | 
			
		||||
                    if self.nslc:
 | 
			
		||||
                        try:
 | 
			
		||||
                            sensorLocation = params["detecLocid"]
 | 
			
		||||
                        except KeyError:
 | 
			
		||||
                            sensorLocation = ""
 | 
			
		||||
                        try:
 | 
			
		||||
                            detecStream = params["detecStream"]
 | 
			
		||||
                        except KeyError:
 | 
			
		||||
                            detecStream = ""
 | 
			
		||||
 | 
			
		||||
                        stream = f"{cfg_sta.networkCode()}.{cfg_sta.stationCode()}.{sensorLocation}.{detecStream}"
 | 
			
		||||
                        nslc.add(stream)
 | 
			
		||||
                    count = 0
 | 
			
		||||
                    for param_name in sorted(params.keys()):
 | 
			
		||||
                        if self.param and param_name not in self.param:
 | 
			
		||||
                            continue
 | 
			
		||||
                        out += f"  {param_name}: {params[param_name]}\n"
 | 
			
		||||
                        count = count + 1
 | 
			
		||||
 | 
			
		||||
                    if not self.nslc and count > 0:
 | 
			
		||||
                        print(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 Exception:
 | 
			
		||||
    sys.exit(1)
 | 
			
		||||
 | 
			
		||||
sys.exit(app())
 | 
			
		||||
							
								
								
									
										84
									
								
								bin/scdumpobject
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										84
									
								
								bin/scdumpobject
									
									
									
									
									
										Executable file
									
								
							@ -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 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()
 | 
			
		||||
							
								
								
									
										79
									
								
								bin/sceplog
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										79
									
								
								bin/sceplog
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,79 @@
 | 
			
		||||
#!/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())
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/scevent
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scevent
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										924
									
								
								bin/scevtlog
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										924
									
								
								bin/scevtlog
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,924 @@
 | 
			
		||||
#!/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(f"caught unexpected error {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(f"Logging events to {self._directory}\n")
 | 
			
		||||
 | 
			
		||||
        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 = f"{org.depth().value():7.0f}"
 | 
			
		||||
                summary[4] = dep
 | 
			
		||||
            except:
 | 
			
		||||
                summary[4] = "%7s" % ""
 | 
			
		||||
 | 
			
		||||
            phases = "%5s" % "---"
 | 
			
		||||
            try:
 | 
			
		||||
                phases = "%5d" % org.quality().usedPhaseCount()
 | 
			
		||||
                summary[5] = phases
 | 
			
		||||
            except:
 | 
			
		||||
                summary[5] = "%5s" % ""
 | 
			
		||||
 | 
			
		||||
            summary[2] = f"{lat:7.2f}"
 | 
			
		||||
            summary[3] = f"{lon:7.2f}"
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                summary[9] = originStatusToChar(org)
 | 
			
		||||
            except:
 | 
			
		||||
                summary[9] = "-"
 | 
			
		||||
 | 
			
		||||
        if mag:
 | 
			
		||||
            summary[6] = "%12s" % mag.type()
 | 
			
		||||
            summary[7] = f"{mag.magnitude().value():5.2f}"
 | 
			
		||||
            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(f"Unable to create directory {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(
 | 
			
		||||
                        f"derived origin for MT {mt.derivedOriginID()} not found"
 | 
			
		||||
                    )
 | 
			
		||||
                    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())
 | 
			
		||||
 | 
			
		||||
                    # Load comments
 | 
			
		||||
                    if pick.commentCount() == 0:
 | 
			
		||||
                        self.query().loadComments(pick)
 | 
			
		||||
 | 
			
		||||
                    # Copy pick comments
 | 
			
		||||
                    ncmts = pick.commentCount()
 | 
			
		||||
                    for i in range(ncmts):
 | 
			
		||||
                        cmt_cloned = seiscomp.datamodel.Comment.Cast(
 | 
			
		||||
                            pick.comment(i).clone()
 | 
			
		||||
                        )
 | 
			
		||||
                        pick_cloned.add(cmt_cloned)
 | 
			
		||||
                    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(f"Unable to create directory {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(f"Unable to write file: {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())
 | 
			
		||||
							
								
								
									
										332
									
								
								bin/scevtls
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										332
									
								
								bin/scevtls
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,332 @@
 | 
			
		||||
#!/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 seiscomp.core
 | 
			
		||||
import seiscomp.client
 | 
			
		||||
import seiscomp.datamodel
 | 
			
		||||
import seiscomp.logging
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def readXML(self):
 | 
			
		||||
    ar = seiscomp.io.XMLArchive()
 | 
			
		||||
    if not ar.open(self._inputFile):
 | 
			
		||||
        print(f"Unable to open input file {self._inputFile}")
 | 
			
		||||
        return []
 | 
			
		||||
 | 
			
		||||
    obj = ar.readObject()
 | 
			
		||||
    if obj is None:
 | 
			
		||||
        raise TypeError("invalid input file format")
 | 
			
		||||
 | 
			
		||||
    ep = seiscomp.datamodel.EventParameters.Cast(obj)
 | 
			
		||||
    if ep is None:
 | 
			
		||||
        raise ValueError("no event parameters found in input file")
 | 
			
		||||
 | 
			
		||||
    eventIDs = []
 | 
			
		||||
    for i in range(ep.eventCount()):
 | 
			
		||||
        evt = ep.event(i)
 | 
			
		||||
 | 
			
		||||
        if self._modifiedAfterTime is not None:
 | 
			
		||||
            try:
 | 
			
		||||
                if evt.creationInfo().modificationTime() < self._modifiedAfterTime:
 | 
			
		||||
                    continue
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                try:
 | 
			
		||||
                    if evt.creationInfo().creationTime() < self._modifiedAfterTime:
 | 
			
		||||
                        continue
 | 
			
		||||
                except ValueError:
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
        if self._eventType:
 | 
			
		||||
            try:
 | 
			
		||||
                eventType = seiscomp.datamodel.EEventTypeNames_name(evt.type())
 | 
			
		||||
                if eventType != self._eventType:
 | 
			
		||||
                    continue
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
        prefOrgID = evt.preferredOriginID()
 | 
			
		||||
 | 
			
		||||
        # filter by origin time
 | 
			
		||||
        org = ep.findOrigin(prefOrgID)
 | 
			
		||||
        orgTime = org.time().value()
 | 
			
		||||
        if orgTime < self._startTime:
 | 
			
		||||
            continue
 | 
			
		||||
        if orgTime > self._endTime:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        outputString = evt.publicID()
 | 
			
		||||
        if self._preferredOrigin:
 | 
			
		||||
            try:
 | 
			
		||||
                outputString += " " + evt.preferredOriginID()
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                outputString += " none"
 | 
			
		||||
 | 
			
		||||
        eventIDs.append(outputString)
 | 
			
		||||
 | 
			
		||||
    return eventIDs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
        self._inputFile = None
 | 
			
		||||
        self._eventType = None
 | 
			
		||||
 | 
			
		||||
    def createCommandLineDescription(self):
 | 
			
		||||
        self.commandline().addGroup("Input")
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Input",
 | 
			
		||||
            "input,i",
 | 
			
		||||
            "Name of input XML file. Read from stdin if '-' is given. Deactivates "
 | 
			
		||||
            "reading events from database",
 | 
			
		||||
        )
 | 
			
		||||
        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().addStringOption(
 | 
			
		||||
            "Events",
 | 
			
		||||
            "event-type",
 | 
			
		||||
            "Select events whith specified " "event type.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        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 validateParameters(self):
 | 
			
		||||
        if not seiscomp.client.Application.validateParameters(self):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._inputFile = self.commandline().optionString("input")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if self._inputFile:
 | 
			
		||||
            self.setDatabaseEnabled(False, False)
 | 
			
		||||
 | 
			
		||||
        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 = seiscomp.core.Time.FromString(start)
 | 
			
		||||
            if self._startTime is None:
 | 
			
		||||
                seiscomp.logging.error(f"Wrong 'begin' format '{start}'")
 | 
			
		||||
                return False
 | 
			
		||||
            seiscomp.logging.debug(
 | 
			
		||||
                f"Setting start to {self._startTime.toString('%FT%TZ')}"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                end = self.commandline().optionString("end")
 | 
			
		||||
            except RuntimeError:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
            self._endTime = seiscomp.core.Time.FromString(end)
 | 
			
		||||
            if self._endTime is None:
 | 
			
		||||
                seiscomp.logging.error(f"Wrong 'end' format '{end}'")
 | 
			
		||||
                return False
 | 
			
		||||
            seiscomp.logging.debug(f"Setting end to {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(
 | 
			
		||||
                    f"Maximum hours exceeeded. Maximum is {int(maxSecs / 3600)}"
 | 
			
		||||
                )
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            self._startTime = seiscomp.core.Time.UTC() - seiscomp.core.TimeSpan(secs)
 | 
			
		||||
            self._endTime = seiscomp.core.Time.FromString(end)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._delimiter = self.commandline().optionString("delimiter")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            self._delimiter = "\n"
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            modifiedAfter = self.commandline().optionString("modified-after")
 | 
			
		||||
            self._modifiedAfterTime = seiscomp.core.Time.FromString(modifiedAfter)
 | 
			
		||||
            if self._modifiedAfterTime is None:
 | 
			
		||||
                seiscomp.logging.error(
 | 
			
		||||
                    f"Wrong 'modified-after' format '{modifiedAfter}'"
 | 
			
		||||
                )
 | 
			
		||||
                return False
 | 
			
		||||
            seiscomp.logging.debug(
 | 
			
		||||
                f"Setting 'modified-after' time to {self._modifiedAfterTime.toString('%FT%TZ')}"
 | 
			
		||||
            )
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._preferredOrigin = self.commandline().hasOption("preferred-origin")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._eventType = self.commandline().optionString("event-type")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if self._eventType:
 | 
			
		||||
            flagEvent = False
 | 
			
		||||
            for i in range(seiscomp.datamodel.EEventTypeQuantity):
 | 
			
		||||
                if self._eventType == seiscomp.datamodel.EEventTypeNames.name(i):
 | 
			
		||||
                    flagEvent = True
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
            if not flagEvent:
 | 
			
		||||
                seiscomp.logging.error(
 | 
			
		||||
                    f"'{self._eventType}' is not a valid SeisComP event type"
 | 
			
		||||
                )
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def printUsage(self):
 | 
			
		||||
        print(
 | 
			
		||||
            f"""Usage:
 | 
			
		||||
  {os.path.basename(__file__)} [options]
 | 
			
		||||
 | 
			
		||||
List event IDs available in a given time range and print to stdout."""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        seiscomp.client.Application.printUsage(self)
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            f"""Examples:
 | 
			
		||||
Print all event IDs from year 2022 and thereafter
 | 
			
		||||
  {os.path.basename(__file__)} -d mysql://sysop:sysop@localhost/seiscomp \
 | 
			
		||||
--begin "2022-01-01 00:00:00"
 | 
			
		||||
 | 
			
		||||
Print all event IDs with event type 'quarry blast'
 | 
			
		||||
  {os.path.basename(__file__)} -d mysql://sysop:sysop@localhost/seiscomp --event-type 'quarry blast'
 | 
			
		||||
 | 
			
		||||
Print IDs of all events in XML file
 | 
			
		||||
  {os.path.basename(__file__)} -i events.xml
 | 
			
		||||
"""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        out = []
 | 
			
		||||
        seiscomp.logging.debug(f"Search interval: {self._startTime} - {self._endTime}")
 | 
			
		||||
 | 
			
		||||
        if self._inputFile:
 | 
			
		||||
            out = readXML(self)
 | 
			
		||||
            sys.stdout.write(f"{self._delimiter.join(out)}\n")
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        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:
 | 
			
		||||
                    try:
 | 
			
		||||
                        if evt.creationInfo().creationTime() < self._modifiedAfterTime:
 | 
			
		||||
                            continue
 | 
			
		||||
                    except ValueError:
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
            if self._eventType:
 | 
			
		||||
                try:
 | 
			
		||||
                    eventType = seiscomp.datamodel.EEventTypeNames_name(evt.type())
 | 
			
		||||
                    if eventType != self._eventType:
 | 
			
		||||
                        continue
 | 
			
		||||
                except ValueError:
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
            outputString = evt.publicID()
 | 
			
		||||
            if self._preferredOrigin:
 | 
			
		||||
                try:
 | 
			
		||||
                    outputString += " " + evt.preferredOriginID()
 | 
			
		||||
                except ValueError:
 | 
			
		||||
                    outputString += " none"
 | 
			
		||||
 | 
			
		||||
            out.append(outputString)
 | 
			
		||||
 | 
			
		||||
        sys.stdout.write(f"{self._delimiter.join(out)}\n")
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    app = EventList(len(sys.argv), sys.argv)
 | 
			
		||||
    app()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
							
								
								
									
										576
									
								
								bin/scevtstreams
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										576
									
								
								bin/scevtstreams
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,576 @@
 | 
			
		||||
#!/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 re
 | 
			
		||||
 | 
			
		||||
from seiscomp import client, core, datamodel, io
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def readStreamList(listFile):
 | 
			
		||||
    """
 | 
			
		||||
    Read list of streams from file
 | 
			
		||||
 | 
			
		||||
    Parameters
 | 
			
		||||
    ----------
 | 
			
		||||
    file : file
 | 
			
		||||
        Input list file, one line per stream
 | 
			
		||||
        format: NET.STA.LOC.CHA
 | 
			
		||||
 | 
			
		||||
    Returns
 | 
			
		||||
    -------
 | 
			
		||||
    list
 | 
			
		||||
        streams.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    streams = []
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        if listFile == "-":
 | 
			
		||||
            f = sys.stdin
 | 
			
		||||
            listFile = "stdin"
 | 
			
		||||
        else:
 | 
			
		||||
            f = open(listFile, "r", encoding="utf8")
 | 
			
		||||
    except Exception:
 | 
			
		||||
        print(f"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
 | 
			
		||||
 | 
			
		||||
        if len(line.split(".")) != 4:
 | 
			
		||||
            f.close()
 | 
			
		||||
            print(
 | 
			
		||||
                f"error: {listFile} in line {lineNumber} has invalid line format, "
 | 
			
		||||
                "expecting NET.STA.LOC.CHA - 1 line per stream",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
            return []
 | 
			
		||||
        streams.append(line)
 | 
			
		||||
 | 
			
		||||
    f.close()
 | 
			
		||||
 | 
			
		||||
    if len(streams) == 0:
 | 
			
		||||
        return []
 | 
			
		||||
 | 
			
		||||
    return streams
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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 = []
 | 
			
		||||
        self.streamFilter = None
 | 
			
		||||
 | 
			
		||||
        # output format
 | 
			
		||||
        self.caps = False
 | 
			
		||||
        self.fdsnws = False
 | 
			
		||||
 | 
			
		||||
    def createCommandLineDescription(self):
 | 
			
		||||
        self.commandline().addGroup("Input")
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Input",
 | 
			
		||||
            "input,i",
 | 
			
		||||
            "Input XML file name. Reads event from the 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", "The ID of the event to consider."
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "net-sta",
 | 
			
		||||
            "Filter read picks by network code or network and station code. Format: "
 | 
			
		||||
            "NET or NET.STA.",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Dump",
 | 
			
		||||
            "nslc",
 | 
			
		||||
            "Stream list file to be used for filtering read picks by stream code. "
 | 
			
		||||
            "'--net-sta' will be ignored. One line per stream, line format: "
 | 
			
		||||
            "NET.STA.LOC.CHA.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.commandline().addGroup("Output")
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "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(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "streams,S",
 | 
			
		||||
            "Comma-separated list of streams per station to add, e.g. BH,SH,HH.",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "all-streams",
 | 
			
		||||
            "Dump all streams. If unused, just streams with picks are dumped.",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addIntOption(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "all-components,C",
 | 
			
		||||
            "All components or just the picked ones (0). Default is 1",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addIntOption(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "all-locations,L",
 | 
			
		||||
            "All locations or just the picked ones (0). Default is 1",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "all-stations",
 | 
			
		||||
            "Dump all stations from the same network. If unused, just stations "
 | 
			
		||||
            "with picks are dumped.",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "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(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "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().addOption(
 | 
			
		||||
            "Output",
 | 
			
		||||
            "caps",
 | 
			
		||||
            "Output in capstool format (Common Acquisition Protocol Server by "
 | 
			
		||||
            "gempa GmbH).",
 | 
			
		||||
        )
 | 
			
		||||
        self.commandline().addOption(
 | 
			
		||||
            "Output", "fdsnws", "Output 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 as exc:
 | 
			
		||||
            if not self.inputFile:
 | 
			
		||||
                raise ValueError(
 | 
			
		||||
                    "An eventID is mandatory if no input file is specified"
 | 
			
		||||
                ) from exc
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            nslcFile = self.commandline().optionString("nslc")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            nslcFile = None
 | 
			
		||||
 | 
			
		||||
        if nslcFile:
 | 
			
		||||
            networkStation = None
 | 
			
		||||
            self.streamFilter = readStreamList(nslcFile)
 | 
			
		||||
 | 
			
		||||
        if networkStation:
 | 
			
		||||
            try:
 | 
			
		||||
                self.network = networkStation.split(".")[0]
 | 
			
		||||
            except IndexError:
 | 
			
		||||
                print(
 | 
			
		||||
                    f"Error in network code '{networkStation}': Use '--net-sta' with "
 | 
			
		||||
                    "format NET or NET.STA",
 | 
			
		||||
                    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(
 | 
			
		||||
                    f"Could not find picks for event {self.eventID} in database"
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
        # filter picks
 | 
			
		||||
        if self.streamFilter:
 | 
			
		||||
            # # filter channel by --nslc option
 | 
			
		||||
            channels = self.streamFilter
 | 
			
		||||
            channelsRe = []
 | 
			
		||||
            for channel in channels:
 | 
			
		||||
                channel = re.sub(r"\.", r"\.", channel)  # . becomes \.
 | 
			
		||||
                channel = re.sub(r"\?", ".", channel)  # ? becomes .
 | 
			
		||||
                channel = re.sub(r"\*", ".*", channel)  # * becomes.*
 | 
			
		||||
                channel = re.compile(channel)
 | 
			
		||||
                channelsRe.append(channel)
 | 
			
		||||
 | 
			
		||||
        if self.streamFilter or self.network:
 | 
			
		||||
            pickFiltered = []
 | 
			
		||||
            for pick in picks:
 | 
			
		||||
                net = pick.waveformID().networkCode()
 | 
			
		||||
                sta = pick.waveformID().stationCode()
 | 
			
		||||
                loc = pick.waveformID().locationCode()
 | 
			
		||||
                cha = pick.waveformID().channelCode()
 | 
			
		||||
 | 
			
		||||
                filtered = False
 | 
			
		||||
                if self.streamFilter:
 | 
			
		||||
                    stream = f"{net}.{sta}.{loc}.{cha}"
 | 
			
		||||
                    for chaRe in channelsRe:
 | 
			
		||||
                        if chaRe.match(stream):
 | 
			
		||||
                            filtered = True
 | 
			
		||||
                            continue
 | 
			
		||||
 | 
			
		||||
                elif self.network:
 | 
			
		||||
                    if net != self.network:
 | 
			
		||||
                        continue
 | 
			
		||||
                    if self.station and sta != self.station:
 | 
			
		||||
                        continue
 | 
			
		||||
                    filtered = True
 | 
			
		||||
 | 
			
		||||
                if filtered:
 | 
			
		||||
                    pickFiltered.append(pick)
 | 
			
		||||
                else:
 | 
			
		||||
                    print(
 | 
			
		||||
                        f"Ignoring channel {stream}: not considered by configuration",
 | 
			
		||||
                        file=sys.stderr,
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
            picks = pickFiltered
 | 
			
		||||
 | 
			
		||||
        if not picks:
 | 
			
		||||
            raise ValueError("Info: All picks are 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(f"unknown input 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(f"Event ID {self.eventID} not found in input file")
 | 
			
		||||
 | 
			
		||||
        # 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(f"ERROR: {e}", file=sys.stderr)
 | 
			
		||||
    sys.exit(1)
 | 
			
		||||
							
								
								
									
										38
									
								
								bin/scgitinit
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										38
									
								
								bin/scgitinit
									
									
									
									
									
										Executable file
									
								
							@ -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"
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/scheli
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scheli
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/scimex
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scimex
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/scimport
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scimport
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/scmapcut
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scmapcut
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/scmaster
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scmaster
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										84
									
								
								bin/scml2inv
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										84
									
								
								bin/scml2inv
									
									
									
									
									
										Executable file
									
								
							@ -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(f"{msg}\n")
 | 
			
		||||
        sys.stderr.write("for help use --help\n")
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    for o, a in opts:
 | 
			
		||||
        if o in ["-h", "--help"]:
 | 
			
		||||
            sys.stderr.write(f"{usage}\n")
 | 
			
		||||
            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(f"Unable to parse input file: {argv[0]}\n")
 | 
			
		||||
        return 2
 | 
			
		||||
 | 
			
		||||
    obj = ar.readObject()
 | 
			
		||||
    ar.close()
 | 
			
		||||
 | 
			
		||||
    if obj is None:
 | 
			
		||||
        sys.stderr.write(f"Empty document in {argv[0]}\n")
 | 
			
		||||
        return 3
 | 
			
		||||
 | 
			
		||||
    inv = seiscomp.datamodel.Inventory.Cast(obj)
 | 
			
		||||
    if inv is None:
 | 
			
		||||
        sys.stderr.write(f"No inventory found in {argv[0]}\n")
 | 
			
		||||
        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))
 | 
			
		||||
							
								
								
									
										532
									
								
								bin/scmssort
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										532
									
								
								bin/scmssort
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,532 @@
 | 
			
		||||
#!/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 argparse
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
import sys
 | 
			
		||||
import traceback
 | 
			
		||||
 | 
			
		||||
from seiscomp import core, io
 | 
			
		||||
 | 
			
		||||
VERBOSITY = 0
 | 
			
		||||
 | 
			
		||||
INFO = 1
 | 
			
		||||
DEBUG = 2
 | 
			
		||||
TRACE = 3
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def log(level, msg):
 | 
			
		||||
    print(f"[{level}] {msg}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def info_enabled():
 | 
			
		||||
    return VERBOSITY >= INFO
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def debug_enabled():
 | 
			
		||||
    return VERBOSITY >= DEBUG
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def trace_enabled():
 | 
			
		||||
    return VERBOSITY >= TRACE
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def error(msg):
 | 
			
		||||
    log("error", msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def warning(msg):
 | 
			
		||||
    log("warning", msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def info(msg):
 | 
			
		||||
    if info_enabled():
 | 
			
		||||
        log("info", msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def debug(msg):
 | 
			
		||||
    if debug_enabled():
 | 
			
		||||
        log("debug", msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def trace(msg):
 | 
			
		||||
    if trace_enabled():
 | 
			
		||||
        log("trace", msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def parse_args():
 | 
			
		||||
    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:
 | 
			
		||||
Read data from multiple files, extract streams by time, sort records by start time, \
 | 
			
		||||
ignore duplicated and empty records
 | 
			
		||||
  cat f1.mseed f2.mseed f3.mseed | \
 | 
			
		||||
scmssort -v -t 2007-03-28T15:48~2007-03-28T16:18' -ui > sorted.mseed
 | 
			
		||||
 | 
			
		||||
Extract streams by time, stream code and sort records by end time
 | 
			
		||||
  echo CX.PB01..BH? | \
 | 
			
		||||
scmssort -v -E -t '2007-03-28T15:48~2007-03-28T16:18' \
 | 
			
		||||
-u -l - test.mseed > sorted.mseed
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
    p = argparse.ArgumentParser(
 | 
			
		||||
        description=description,
 | 
			
		||||
        epilog=epilog,
 | 
			
		||||
        formatter_class=argparse.RawDescriptionHelpFormatter,
 | 
			
		||||
    )
 | 
			
		||||
    p.add_argument(
 | 
			
		||||
        "file",
 | 
			
		||||
        nargs="*",
 | 
			
		||||
        default="-",
 | 
			
		||||
        help="miniSEED file(s) to sort. If no file name or '-' is specified then "
 | 
			
		||||
        "standard input is used.",
 | 
			
		||||
    )
 | 
			
		||||
    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(
 | 
			
		||||
        "-i",
 | 
			
		||||
        "--ignore",
 | 
			
		||||
        action="store_true",
 | 
			
		||||
        help="Ignore all records which have no data samples.",
 | 
			
		||||
    )
 | 
			
		||||
    p.add_argument(
 | 
			
		||||
        "-l",
 | 
			
		||||
        "--list",
 | 
			
		||||
        action="store",
 | 
			
		||||
        help="Filter records by a list of stream codes specified in a file or on stdin "
 | 
			
		||||
        "(-). One stream per line of format: NET.STA.LOC.CHA - wildcards and regular "
 | 
			
		||||
        "expressions are considered. Example: CX.*..BH?.",
 | 
			
		||||
    )
 | 
			
		||||
    p.add_argument(
 | 
			
		||||
        "-o",
 | 
			
		||||
        "--output",
 | 
			
		||||
        action="store",
 | 
			
		||||
        help="Name of output file for miniSEED data (default is stdout).",
 | 
			
		||||
    )
 | 
			
		||||
    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(
 | 
			
		||||
        "-t",
 | 
			
		||||
        "--time-window",
 | 
			
		||||
        action="store",
 | 
			
		||||
        help="Time window to filter the records, format: <START TIME> ~ <END TIME>. "
 | 
			
		||||
        "Time values are in UTC, must start with an ISO date and may include time "
 | 
			
		||||
        "components starting on the hour down to milliseconds. Example: "
 | 
			
		||||
        "2023-01-15T12:15",
 | 
			
		||||
    )
 | 
			
		||||
    p.add_argument(
 | 
			
		||||
        "-u",
 | 
			
		||||
        "--uniqueness",
 | 
			
		||||
        action="store_true",
 | 
			
		||||
        help="Ensure uniqueness of output by skipping duplicate records.",
 | 
			
		||||
    )
 | 
			
		||||
    p.add_argument(
 | 
			
		||||
        "-v",
 | 
			
		||||
        "--verbose",
 | 
			
		||||
        action="count",
 | 
			
		||||
        default=0,
 | 
			
		||||
        help="Run in verbose mode. This option may be repeated several time to "
 | 
			
		||||
        "increase the level of verbosity. Example: -vvv.",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    opt = p.parse_args()
 | 
			
		||||
 | 
			
		||||
    global VERBOSITY
 | 
			
		||||
    VERBOSITY += int(opt.verbose)
 | 
			
		||||
 | 
			
		||||
    if opt.rm and not opt.list:
 | 
			
		||||
        error("The '--rm' requires the '--list' option to be present as well.")
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    return opt
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def rec2id(record):
 | 
			
		||||
    return (
 | 
			
		||||
        f"{record.networkCode()}.{record.stationCode()}."
 | 
			
		||||
        f"{record.locationCode()}.{record.channelCode()}"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def str2time(timeString):
 | 
			
		||||
    return core.Time.FromString(timeString)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def time2str(time):
 | 
			
		||||
    """
 | 
			
		||||
    Convert a seiscomp.core.Time to a string
 | 
			
		||||
    """
 | 
			
		||||
    if not time:
 | 
			
		||||
        return ""
 | 
			
		||||
 | 
			
		||||
    return time.toString("%Y-%m-%dT%H:%M:%S.%f000")[:23]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def read_time_window(opt):
 | 
			
		||||
    if not opt.time_window:
 | 
			
		||||
        return None, None
 | 
			
		||||
 | 
			
		||||
    toks = opt.time_window.split("~")
 | 
			
		||||
    if len(toks) != 2:
 | 
			
		||||
        if len(toks) < 2:
 | 
			
		||||
            raise ValueError(
 | 
			
		||||
                "Time window has wrong format: Use (~) for separating start and end time"
 | 
			
		||||
            )
 | 
			
		||||
        raise ValueError("Time window has wrong format: Too many tildes (~) found")
 | 
			
		||||
 | 
			
		||||
    start = core.Time.FromString(toks[0])
 | 
			
		||||
    end = core.Time.FromString(toks[1])
 | 
			
		||||
 | 
			
		||||
    if start is None or end is None:
 | 
			
		||||
        error(f"Could not read time window: {toks}")
 | 
			
		||||
        if debug_enabled():
 | 
			
		||||
            debug(traceback.format_exc())
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    return start, end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def read_lines(file):
 | 
			
		||||
    # read from stdin
 | 
			
		||||
    if file == "-":
 | 
			
		||||
        yield from sys.stdin
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    # read from file
 | 
			
		||||
    with open(file, "r", encoding="utf-8") as f:
 | 
			
		||||
        yield from f
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def compile_stream_pattern(opt):
 | 
			
		||||
    if not opt.list:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    streams = []
 | 
			
		||||
    pattern = None
 | 
			
		||||
    try:
 | 
			
		||||
        line_number = -1
 | 
			
		||||
        for line in map(str.strip, read_lines(opt.list)):
 | 
			
		||||
            line_number += 1
 | 
			
		||||
 | 
			
		||||
            # ignore empty lines and comments
 | 
			
		||||
            if not line or line.startswith("#"):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            toks = line.split(".")
 | 
			
		||||
            if len(toks) != 4:
 | 
			
		||||
                raise ValueError(
 | 
			
		||||
                    f"Invalid stream definition at line {line_number}. Expected the 4 "
 | 
			
		||||
                    "stream components NET.STA.LOC.CHA separated by a dot, "
 | 
			
		||||
                    "got: {line}."
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            streams.append(line)
 | 
			
		||||
 | 
			
		||||
        if not streams:
 | 
			
		||||
            raise ValueError("No stream definition found.")
 | 
			
		||||
 | 
			
		||||
        pattern = re.compile("|".join(streams))
 | 
			
		||||
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        error(f"Could not compile pattern from stream list file '{opt.list}': {e}")
 | 
			
		||||
        if debug_enabled():
 | 
			
		||||
            debug(traceback.format_exc())
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    info(
 | 
			
		||||
        f"Using stream id {'DENY' if opt.rm else 'ALLOW'} list with {len(streams)} "
 | 
			
		||||
        "stream masks"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if debug_enabled():
 | 
			
		||||
        masks = "\n  + ".join(streams)
 | 
			
		||||
        debug(f"Stream masks:\n  + {masks}")
 | 
			
		||||
 | 
			
		||||
    return pattern
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def record_input(file, datatype=core.Array.INT):
 | 
			
		||||
    """
 | 
			
		||||
    Simple record iterator that reads from a file (or stdin in case of '-')
 | 
			
		||||
    """
 | 
			
		||||
    stream = io.RecordStream.Create("file")
 | 
			
		||||
    if not stream:
 | 
			
		||||
        raise IOError("Failed to create a RecordStream")
 | 
			
		||||
 | 
			
		||||
    if file != "-" and not os.path.exists(file):
 | 
			
		||||
        raise FileNotFoundError("Could not find file")
 | 
			
		||||
 | 
			
		||||
    if not stream.setSource(file):
 | 
			
		||||
        raise ValueError("Could not set record stream source")
 | 
			
		||||
 | 
			
		||||
    it = io.RecordInput(stream, datatype, core.Record.SAVE_RAW)
 | 
			
		||||
 | 
			
		||||
    if trace_enabled():
 | 
			
		||||
        while True:
 | 
			
		||||
            record = it.next()
 | 
			
		||||
            if not record:
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            trace(
 | 
			
		||||
                f"    + {time2str(record.startTime())}~{time2str(record.endTime())} "
 | 
			
		||||
                f"{rec2id(record)}"
 | 
			
		||||
            )
 | 
			
		||||
            yield record
 | 
			
		||||
    else:
 | 
			
		||||
        while True:
 | 
			
		||||
            record = it.next()
 | 
			
		||||
            if not record:
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            yield record
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def unique(sequence):
 | 
			
		||||
    seen = set()
 | 
			
		||||
    return [x for x in sequence if not (x in seen or seen.add(x))]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    # parse commandline
 | 
			
		||||
    opt = parse_args()
 | 
			
		||||
 | 
			
		||||
    # time window
 | 
			
		||||
    t_min, t_max = read_time_window(opt)
 | 
			
		||||
    if t_max and t_min and t_max <= t_min:
 | 
			
		||||
        error(
 | 
			
		||||
            f"Invalid time window: {time2str(t_min)}~{time2str(t_max)}\n"
 | 
			
		||||
            "  + end time must be greater than start time"
 | 
			
		||||
        )
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    info(f"Filtering records by time window: {time2str(t_min)}~{time2str(t_max)}")
 | 
			
		||||
 | 
			
		||||
    # stream filter
 | 
			
		||||
    pattern = compile_stream_pattern(opt)
 | 
			
		||||
 | 
			
		||||
    outputFile = None
 | 
			
		||||
    if opt.output:
 | 
			
		||||
        outputFile = opt.output
 | 
			
		||||
 | 
			
		||||
    # record buffer to be sorted later on, each item is a tuple of
 | 
			
		||||
    # (delta_time, raw_binary_record_data)
 | 
			
		||||
    rec_buf = []
 | 
			
		||||
 | 
			
		||||
    # statistics
 | 
			
		||||
    records_read = 0
 | 
			
		||||
    records_window = 0
 | 
			
		||||
    records_empty = 0
 | 
			
		||||
 | 
			
		||||
    # statistics (info mode)
 | 
			
		||||
    networks = set()
 | 
			
		||||
    stations = set()
 | 
			
		||||
    streams = set()
 | 
			
		||||
    buf_min = None
 | 
			
		||||
    buf_max = None
 | 
			
		||||
 | 
			
		||||
    # make sure to read from stdin only once
 | 
			
		||||
    files = [x for x in opt.file if x != "-"]
 | 
			
		||||
    if len(files) == len(opt.file):
 | 
			
		||||
        info(f"Reading data from {len(opt.file)} file(s)")
 | 
			
		||||
    elif not files:
 | 
			
		||||
        files = "-"
 | 
			
		||||
        info("Reading data from stdin. Use Ctrl + C to interrupt.")
 | 
			
		||||
    else:
 | 
			
		||||
        info(
 | 
			
		||||
            f"Reading data from stdin and {len(files)} files. Use Ctrl + C to "
 | 
			
		||||
            "interrupt."
 | 
			
		||||
        )
 | 
			
		||||
        files.insert(opt.file.index("-"), "-")
 | 
			
		||||
 | 
			
		||||
    # time or first valid record use as reference for sorting
 | 
			
		||||
    ref_time = None
 | 
			
		||||
 | 
			
		||||
    # read records from input file
 | 
			
		||||
    for file in files:
 | 
			
		||||
        records_file = 0
 | 
			
		||||
        records_empty_file = 0
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            for rec in record_input(file):
 | 
			
		||||
                records_file += 1
 | 
			
		||||
                stream_id = ""
 | 
			
		||||
 | 
			
		||||
                # skip record if outside time window
 | 
			
		||||
                if (t_min and rec.endTime() < t_min) or (
 | 
			
		||||
                    t_max and rec.startTime() > t_max
 | 
			
		||||
                ):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if pattern or info_enabled():
 | 
			
		||||
                    records_window += 1
 | 
			
		||||
                    stream_id = rec2id(rec)
 | 
			
		||||
 | 
			
		||||
                if pattern and bool(pattern.match(stream_id)) == bool(opt.rm):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if not rec.sampleCount():
 | 
			
		||||
                    trace(
 | 
			
		||||
                        f"    + found empty record staring at {time2str(rec.startTime())} "
 | 
			
		||||
                        f"{rec2id(rec)}"
 | 
			
		||||
                    )
 | 
			
		||||
                    records_empty_file += 1
 | 
			
		||||
                    if opt.ignore:
 | 
			
		||||
                        trace("      + ignored")
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                # record time reference set to start or end time depending on sort
 | 
			
		||||
                # option
 | 
			
		||||
                t = rec.endTime() if opt.sort_by_end_time else rec.startTime()
 | 
			
		||||
 | 
			
		||||
                if ref_time is None:
 | 
			
		||||
                    ref_time = core.Time(t)
 | 
			
		||||
                    t = 0
 | 
			
		||||
                else:
 | 
			
		||||
                    t = float(t - ref_time)  # float needs less memory
 | 
			
		||||
 | 
			
		||||
                # buffer tuple of (time delta, binary record data)
 | 
			
		||||
                rec_buf.append((t, rec.raw().str()))
 | 
			
		||||
 | 
			
		||||
                # collect statistics for debug mode
 | 
			
		||||
                if info_enabled():
 | 
			
		||||
                    networks.add(rec.networkCode())
 | 
			
		||||
                    stations.add(f"{rec.networkCode()}.{rec.stationCode()}")
 | 
			
		||||
                    streams.add(stream_id)
 | 
			
		||||
                    # copy of time object is required because record may be freed before
 | 
			
		||||
                    if not buf_min or rec.startTime() < buf_min:
 | 
			
		||||
                        buf_min = core.Time(rec.startTime())
 | 
			
		||||
                    if not buf_max or rec.startTime() > buf_max:
 | 
			
		||||
                        buf_max = core.Time(rec.endTime())
 | 
			
		||||
 | 
			
		||||
            name = "<stdin>" if file == "-" else file
 | 
			
		||||
            empty = f", empty: {records_empty_file}" if records_empty_file else ""
 | 
			
		||||
            debug(f"  + {name}: {records_file} records{empty}")
 | 
			
		||||
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            error(f"Could not read file '{file}: {e}")
 | 
			
		||||
            if debug_enabled():
 | 
			
		||||
                debug(traceback.format_exc())
 | 
			
		||||
            return 1
 | 
			
		||||
 | 
			
		||||
        records_read += records_file
 | 
			
		||||
        records_empty += records_empty_file
 | 
			
		||||
 | 
			
		||||
    # stop if no records have been read
 | 
			
		||||
    if not records_read:
 | 
			
		||||
        warning("No records found in input file(s).")
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    buf_len = len(rec_buf)
 | 
			
		||||
 | 
			
		||||
    # statistics about records read and filtered
 | 
			
		||||
    if info_enabled() and buf_len != records_read:
 | 
			
		||||
        info(
 | 
			
		||||
            f"""{records_read-buf_len}/{records_read} records filtered:
 | 
			
		||||
  + by time window: {records_read-records_window}
 | 
			
		||||
  + by stream id {'DENY' if opt.rm else 'ALLOW'} list: {records_window-buf_len}"""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    # stop if no record passed the filter
 | 
			
		||||
    if not buf_len:
 | 
			
		||||
        warning("All records filtered, nothing to write.")
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    # network, station and stream information
 | 
			
		||||
    if info_enabled():
 | 
			
		||||
        info(
 | 
			
		||||
            f"Found data for {len(networks)} networks, {len(stations)} stations "
 | 
			
		||||
            f"and {len(streams)} streams",
 | 
			
		||||
        )
 | 
			
		||||
        if debug_enabled() and streams:
 | 
			
		||||
            streamList = "\n  + ".join(streams)
 | 
			
		||||
            debug(f"streams:\n  + {streamList}")
 | 
			
		||||
 | 
			
		||||
    # sort records by time only
 | 
			
		||||
    if buf_len > 1:
 | 
			
		||||
        info(f"Sorting {buf_len} records")
 | 
			
		||||
        rec_buf.sort()
 | 
			
		||||
 | 
			
		||||
    # write sorted records, count duplicates and optional remove them
 | 
			
		||||
    info(f"Writing {buf_len} records")
 | 
			
		||||
    prev_rec = None
 | 
			
		||||
    duplicates = 0
 | 
			
		||||
 | 
			
		||||
    if outputFile:
 | 
			
		||||
        print(f"Output data to file: {outputFile}", file=sys.stderr)
 | 
			
		||||
        try:
 | 
			
		||||
            out = open(outputFile, "wb")
 | 
			
		||||
        except Exception:
 | 
			
		||||
            print("Cannot create output file {outputFile}", file=sys.stderr)
 | 
			
		||||
            return -1
 | 
			
		||||
    else:
 | 
			
		||||
        out = sys.stdout.buffer
 | 
			
		||||
 | 
			
		||||
    for _t, rec in rec_buf:
 | 
			
		||||
        if rec == prev_rec:
 | 
			
		||||
            duplicates += 1
 | 
			
		||||
            if opt.uniqueness:
 | 
			
		||||
                continue
 | 
			
		||||
        else:
 | 
			
		||||
            prev_rec = rec
 | 
			
		||||
 | 
			
		||||
        out.write(rec)
 | 
			
		||||
 | 
			
		||||
    # statistics about records written
 | 
			
		||||
    if info_enabled():
 | 
			
		||||
        records_written = buf_len - duplicates if opt.uniqueness else buf_len
 | 
			
		||||
        msg = f"""Wrote {records_written} records
 | 
			
		||||
  + time window: {time2str(buf_min)}~{time2str(buf_max)}"""
 | 
			
		||||
 | 
			
		||||
        if opt.uniqueness:
 | 
			
		||||
            msg += f"""
 | 
			
		||||
  + found and removed {duplicates} duplicate records"""
 | 
			
		||||
        elif not duplicates:
 | 
			
		||||
            msg += """
 | 
			
		||||
  + no duplicate records found"""
 | 
			
		||||
 | 
			
		||||
        if opt.ignore:
 | 
			
		||||
            msg += f"""
 | 
			
		||||
  + {records_empty} empty records found and ignored"""
 | 
			
		||||
 | 
			
		||||
        info(msg)
 | 
			
		||||
 | 
			
		||||
    # additional warning output
 | 
			
		||||
    if records_empty and not opt.ignore:
 | 
			
		||||
        warning(f"Found {records_empty} empty records - remove with: scmssort -i")
 | 
			
		||||
 | 
			
		||||
    # This is an important hint which should always be printed
 | 
			
		||||
    if duplicates > 0 and not opt.uniqueness:
 | 
			
		||||
        warning(f"Found {duplicates} duplicate records - remove with: scmssort -u")
 | 
			
		||||
 | 
			
		||||
    return 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    sys.exit(main())
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/scorg2nll
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scorg2nll
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										231
									
								
								bin/scorgls
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										231
									
								
								bin/scorgls
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,231 @@
 | 
			
		||||
#!/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 seiscomp.core
 | 
			
		||||
import seiscomp.client
 | 
			
		||||
import seiscomp.datamodel
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def readXML(self):
 | 
			
		||||
    ar = seiscomp.io.XMLArchive()
 | 
			
		||||
    if not ar.open(self._inputFile):
 | 
			
		||||
        print(f"Unable to open input file {self._inputFile}")
 | 
			
		||||
        return []
 | 
			
		||||
 | 
			
		||||
    obj = ar.readObject()
 | 
			
		||||
    if obj is None:
 | 
			
		||||
        raise TypeError("invalid input file format")
 | 
			
		||||
 | 
			
		||||
    ep = seiscomp.datamodel.EventParameters.Cast(obj)
 | 
			
		||||
    if ep is None:
 | 
			
		||||
        raise ValueError("no event parameters found in input file")
 | 
			
		||||
 | 
			
		||||
    originIDs = []
 | 
			
		||||
    for i in range(ep.originCount()):
 | 
			
		||||
        org = ep.origin(i)
 | 
			
		||||
 | 
			
		||||
        # check time requirements
 | 
			
		||||
        orgTime = org.time().value()
 | 
			
		||||
        if orgTime < self._startTime:
 | 
			
		||||
            continue
 | 
			
		||||
        if orgTime > self._endTime:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        # check author requirements
 | 
			
		||||
        if self.author:
 | 
			
		||||
            try:
 | 
			
		||||
                author = org.creationInfo().author()
 | 
			
		||||
            except Exception:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            if author != self.author:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            originIDs.append(org.publicID())
 | 
			
		||||
        except Exception:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
    return originIDs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
        self._inputFile = None
 | 
			
		||||
 | 
			
		||||
    def createCommandLineDescription(self):
 | 
			
		||||
        self.commandline().addGroup("Input")
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Input",
 | 
			
		||||
            "input,i",
 | 
			
		||||
            "Name of input XML file. Read from stdin if '-' is given. Deactivates "
 | 
			
		||||
            "reading events from database",
 | 
			
		||||
        )
 | 
			
		||||
        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 validateParameters(self):
 | 
			
		||||
        if not seiscomp.client.Application.validateParameters(self):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._inputFile = self.commandline().optionString("input")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        if self._inputFile:
 | 
			
		||||
            self.setDatabaseEnabled(False, False)
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def init(self):
 | 
			
		||||
        if not seiscomp.client.Application.init(self):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            start = self.commandline().optionString("begin")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            start = "1900-01-01T00:00:00Z"
 | 
			
		||||
 | 
			
		||||
        self._startTime = seiscomp.core.Time.FromString(start)
 | 
			
		||||
        if self._startTime is None:
 | 
			
		||||
            seiscomp.logging.error(f"Wrong 'begin' format '{start}'")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            end = self.commandline().optionString("end")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            end = "2500-01-01T00:00:00Z"
 | 
			
		||||
 | 
			
		||||
        self._endTime = seiscomp.core.Time.FromString(end)
 | 
			
		||||
        if self._endTime is None:
 | 
			
		||||
            seiscomp.logging.error(f"Wrong 'end' format '{end}'")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if self._endTime <= self._startTime:
 | 
			
		||||
            seiscomp.logging.error(
 | 
			
		||||
                f"Invalid search interval: {self._startTime} - {self._endTime}"
 | 
			
		||||
            )
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.author = self.commandline().optionString("author")
 | 
			
		||||
            seiscomp.logging.debug(f"Filtering origins by author {self.author}")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            self.author = False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._delimiter = self.commandline().optionString("delimiter")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            self._delimiter = "\n"
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def printUsage(self):
 | 
			
		||||
        print(
 | 
			
		||||
            f"""Usage:
 | 
			
		||||
  {os.path.basename(__file__)} [options]
 | 
			
		||||
 | 
			
		||||
List origin IDs available in a given time range and print to stdout."""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        seiscomp.client.Application.printUsage(self)
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            f"""Examples:
 | 
			
		||||
Print all origin IDs from year 2022 and thereafter
 | 
			
		||||
  {os.path.basename(__file__)} -d mysql://sysop:sysop@localhost/seiscomp \
 | 
			
		||||
--begin "2022-01-01 00:00:00"
 | 
			
		||||
 | 
			
		||||
Print IDs of all events in XML file
 | 
			
		||||
  {os.path.basename(__file__)} -i origins.xml
 | 
			
		||||
"""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        if self._inputFile:
 | 
			
		||||
            out = readXML(self)
 | 
			
		||||
            print(f"{self._delimiter.join(out)}\n", file=sys.stdout)
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        seiscomp.logging.debug(f"Search interval: {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(f"{self._delimiter.join(out)}\n", file=sys.stdout)
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    app = OriginList(len(sys.argv), sys.argv)
 | 
			
		||||
    app()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/scplot
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scplot
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										380
									
								
								bin/scproclat
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										380
									
								
								bin/scproclat
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,380 @@
 | 
			
		||||
#!/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(f"caught unexpected error {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(f"Logging latencies to {self._directory}\n")
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def addObject(self, parentID, obj):
 | 
			
		||||
        try:
 | 
			
		||||
            self.logObject(parentID, obj, False)
 | 
			
		||||
        except:
 | 
			
		||||
            sys.stderr.write(f"{traceback.format_exc()}\n")
 | 
			
		||||
 | 
			
		||||
    def updateObject(self, parentID, obj):
 | 
			
		||||
        try:
 | 
			
		||||
            self.logObject("", obj, True)
 | 
			
		||||
        except:
 | 
			
		||||
            sys.stderr.write(f"{traceback.format_exc()}\n")
 | 
			
		||||
 | 
			
		||||
    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()
 | 
			
		||||
                    + ";"
 | 
			
		||||
                    + f"{amp.amplitude().value():.2f}",
 | 
			
		||||
                    amp.waveformID(),
 | 
			
		||||
                    update,
 | 
			
		||||
                )
 | 
			
		||||
            except:
 | 
			
		||||
                pass
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        org = seiscomp.datamodel.Origin.Cast(obj)
 | 
			
		||||
        if org:
 | 
			
		||||
            status = ""
 | 
			
		||||
            lat = f"{org.latitude().value():.2f}"
 | 
			
		||||
            lon = f"{org.longitude().value():.2f}"
 | 
			
		||||
            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()
 | 
			
		||||
                + ";"
 | 
			
		||||
                + f"{mag.magnitude().value():.4f}"
 | 
			
		||||
                + ";"
 | 
			
		||||
                + 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(f"{timeToString(received)};{logEntry}\n")
 | 
			
		||||
 | 
			
		||||
        if nowDirectory != self._nowDirectory:
 | 
			
		||||
            if createDirectory(nowDirectory) == False:
 | 
			
		||||
                seiscomp.logging.error(f"Unable to create directory {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(
 | 
			
		||||
                    f"Unable to create directory {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(f"{timeToString(received)};{logEntry}\n")
 | 
			
		||||
 | 
			
		||||
        if nowDirectory != self._nowDirectory:
 | 
			
		||||
            if createDirectory(nowDirectory) == False:
 | 
			
		||||
                seiscomp.logging.error(f"Unable to create directory {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(
 | 
			
		||||
                        f"Unable to create directory {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())
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/scquery
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scquery
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										292
									
								
								bin/scqueryqc
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										292
									
								
								bin/scqueryqc
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,292 @@
 | 
			
		||||
#!/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                                                #
 | 
			
		||||
############################################################################
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
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:
 | 
			
		||||
  {os.path.basename(__file__)} [options]
 | 
			
		||||
 | 
			
		||||
Query a database for waveform quality control (QC) parameters.""",
 | 
			
		||||
            file=sys.stderr,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        seiscomp.client.Application.printUsage(self)
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            f"""Default QC parameters: {qcParamsDefault}\n""",
 | 
			
		||||
            file=sys.stderr,
 | 
			
		||||
        )
 | 
			
		||||
        print(
 | 
			
		||||
            f"""Examples:
 | 
			
		||||
Query rms and delay values for streams 'AU.AS18..SHZ' and 'AU.AS19..SHZ' from \
 | 
			
		||||
'2021-11-20 00:00:00' until current
 | 
			
		||||
  {os.path.basename(__file__)} -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(
 | 
			
		||||
                f"No begin time given, considering: {self._start}",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._end = self.commandline().optionString("end")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            print(
 | 
			
		||||
                f"No end time given, considering 'now': {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(
 | 
			
		||||
                    f"Wildcards in streamID are not supported: {stream}\n",
 | 
			
		||||
                    file=sys.stderr,
 | 
			
		||||
                )
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        print("Request:", file=sys.stderr)
 | 
			
		||||
        print(f"  streams:           {str(streams)}", file=sys.stderr)
 | 
			
		||||
        print(f"  number of streams: {len(streams)}", file=sys.stderr)
 | 
			
		||||
        print(f"  begin time:        {str(self._start)}", file=sys.stderr)
 | 
			
		||||
        print(f"  end time:          {str(self._end)}", file=sys.stderr)
 | 
			
		||||
        print(f"  parameters:        {str(self._parameter)}", file=sys.stderr)
 | 
			
		||||
        print("Output:", file=sys.stderr)
 | 
			
		||||
        print(f"  file:              {self._outfile}", file=sys.stderr)
 | 
			
		||||
        print(f"  formatted XML:     {self._formatted}", file=sys.stderr)
 | 
			
		||||
 | 
			
		||||
        # create archive
 | 
			
		||||
        xarc = seiscomp.io.XMLArchive()
 | 
			
		||||
        if not xarc.create(self._outfile, True, True):
 | 
			
		||||
            print(f"Unable to write XML to {self._outfile}!\n", 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:
 | 
			
		||||
                start = seiscomp.core.Time.FromString(self._start)
 | 
			
		||||
                if start is None:
 | 
			
		||||
                    seiscomp.logging.error(f"Wrong 'start' format '{self._start}'")
 | 
			
		||||
                    return False
 | 
			
		||||
 | 
			
		||||
                end = seiscomp.core.Time.FromString(self._end)
 | 
			
		||||
                if end is None:
 | 
			
		||||
                    seiscomp.logging.error(f"Wrong 'end' format '{self._end}'")
 | 
			
		||||
                    return False
 | 
			
		||||
 | 
			
		||||
                (net, sta, loc, cha) = stream.split(".")
 | 
			
		||||
                it = self.query().getWaveformQuality(
 | 
			
		||||
                    seiscomp.datamodel.WaveformStreamID(net, sta, loc, cha, ""),
 | 
			
		||||
                    parameter,
 | 
			
		||||
                    start,
 | 
			
		||||
                    end,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                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())
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/screloc
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/screloc
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/screpick
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/screpick
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/scrttv
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scrttv
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										126
									
								
								bin/scsendjournal
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										126
									
								
								bin/scsendjournal
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,126 @@
 | 
			
		||||
#!/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")
 | 
			
		||||
        self.params = None
 | 
			
		||||
        self.filename = None
 | 
			
		||||
 | 
			
		||||
    def createCommandLineDescription(self):
 | 
			
		||||
        self.commandline().addGroup("Input")
 | 
			
		||||
        self.commandline().addStringOption(
 | 
			
		||||
            "Input",
 | 
			
		||||
            "input,i",
 | 
			
		||||
            "Read parameters from given file instead of command line.",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def init(self):
 | 
			
		||||
        if seiscomp.client.Application.init(self) == False:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def printUsage(self):
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            """Usage:
 | 
			
		||||
  scsendjournal [options] {objectID} {action} [parameters]
 | 
			
		||||
 | 
			
		||||
Send journaling information to the messaging to manipulate SeisComP objects like events and origins."""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        seiscomp.client.Application.printUsage(self)
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            """Examples:
 | 
			
		||||
Set the type of the event with ID gempa2021abcd to 'earthquake'
 | 
			
		||||
  scsendjournal -H localhost gempa2021abcd EvType "earthquake"
 | 
			
		||||
 | 
			
		||||
Set the type of the event with ID gempa2021abcd and read the type from file
 | 
			
		||||
  scsendjournal -H localhost gempa2021abcd EvType -i input.txt
 | 
			
		||||
"""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    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])
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            f"Sending entry ({entry.objectID()},{entry.action()})",
 | 
			
		||||
            file=sys.stderr,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if self.filename:
 | 
			
		||||
            try:
 | 
			
		||||
                with open(self.filename, "r") as f:
 | 
			
		||||
                    entry.setParameters(f.read().rstrip())
 | 
			
		||||
            except Exception as err:
 | 
			
		||||
                print(f"{str(err)}", file=sys.stderr)
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        elif 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 validateParameters(self):
 | 
			
		||||
        if seiscomp.client.Application.validateParameters(self) == False:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.filename = self.commandline().optionString("input")
 | 
			
		||||
        except RuntimeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        self.params = self.commandline().unrecognizedOptions()
 | 
			
		||||
        if len(self.params) < 2:
 | 
			
		||||
            print(
 | 
			
		||||
                f"{self.name()} [opts] {{objectID}} {{action}} [parameters]",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main(argc, argv):
 | 
			
		||||
    app = SendJournal(argc, argv)
 | 
			
		||||
    return app()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    sys.exit(main(len(sys.argv), sys.argv))
 | 
			
		||||
							
								
								
									
										109
									
								
								bin/scsendorigin
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										109
									
								
								bin/scsendorigin
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,109 @@
 | 
			
		||||
#!/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:
 | 
			
		||||
            print(
 | 
			
		||||
                "Must specify origin using '--coord lat,lon,dep --time time'",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
            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.FromString(tstr)
 | 
			
		||||
        if time is None:
 | 
			
		||||
            seiscomp.logging.error(f"Wrong time format: '{tstr}'")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        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(f"caught unexpected error {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)
 | 
			
		||||
        seiscomp.logging.debug(
 | 
			
		||||
            f"""Origin sent with
 | 
			
		||||
  lat:   {self.origin.latitude().value()}
 | 
			
		||||
  lon:   {self.origin.longitude().value()}
 | 
			
		||||
  depth: {self.origin.depth().value()}
 | 
			
		||||
  time:  {self.origin.time().value().iso()}"""
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
app = SendOrigin(len(sys.argv), sys.argv)
 | 
			
		||||
# app.setName("scsendorigin")
 | 
			
		||||
app.setMessagingUsername("scsendorg")
 | 
			
		||||
sys.exit(app())
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/scshowevent
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scshowevent
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										414
									
								
								bin/scsohlog
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										414
									
								
								bin/scsohlog
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,414 @@
 | 
			
		||||
#!/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(f'<test name="{name}"')
 | 
			
		||||
        if self.value:
 | 
			
		||||
            try:
 | 
			
		||||
                # Try to convert to float
 | 
			
		||||
                fvalue = float(self.value)
 | 
			
		||||
                if fvalue % 1.0 >= 1e-6:
 | 
			
		||||
                    f.write(f' value="{fvalue:f}"')
 | 
			
		||||
                else:
 | 
			
		||||
                    f.write(' value="%d"' % int(fvalue))
 | 
			
		||||
            except:
 | 
			
		||||
                f.write(f' value="{self.value}"')
 | 
			
		||||
        if self.uom:
 | 
			
		||||
            f.write(f' uom="{self.uom}"')
 | 
			
		||||
        if self.update:
 | 
			
		||||
            f.write(f' updateTime="{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(f' name="{name}"')
 | 
			
		||||
        if channel:
 | 
			
		||||
            f.write(f' channel="{channel}"')
 | 
			
		||||
        if not self.count is None:
 | 
			
		||||
            f.write(f' count="{self.count}"')
 | 
			
		||||
        if not self.timeWindow is None:
 | 
			
		||||
            f.write(f' timeWindow="{self.timeWindow}"')
 | 
			
		||||
        if not self.average is None:
 | 
			
		||||
            f.write(f' average="{self.average}"')
 | 
			
		||||
        if self.last:
 | 
			
		||||
            f.write(f' lastTime="{self.last}"')
 | 
			
		||||
        f.write(f' updateTime="{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(f'<service name="{name}"')
 | 
			
		||||
        if self.host:
 | 
			
		||||
            f.write(f' host="{self.host}"')
 | 
			
		||||
        if self.pid:
 | 
			
		||||
            f.write(f' pid="{self.pid}"')
 | 
			
		||||
        if self.progname:
 | 
			
		||||
            f.write(f' prog="{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(f"caught unexpected error {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(f"Output file: {self._outputFile}")
 | 
			
		||||
 | 
			
		||||
        if self._outputScript:
 | 
			
		||||
            self._outputScript = seiscomp.system.Environment.Instance().absolutePath(
 | 
			
		||||
                self._outputScript
 | 
			
		||||
            )
 | 
			
		||||
            seiscomp.logging.info(f"Output script: {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(
 | 
			
		||||
                f"Unable to create output file: {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(f'<server name="seiscomp" host="{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())
 | 
			
		||||
							
								
								
									
										541
									
								
								bin/scvoice
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										541
									
								
								bin/scvoice
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,541 @@
 | 
			
		||||
#!/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(f"Using event script: {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(f"agencyIDs: {' '.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, f"{amp:.2f}"])
 | 
			
		||||
            logging.info("Started amplitude script with pid %d" % self._ampProc.pid)
 | 
			
		||||
        except BaseException:
 | 
			
		||||
            logging.error(f"Failed to start amplitude script '{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, f"{lat:.1f}", f"{lon:.1f}"]
 | 
			
		||||
            )
 | 
			
		||||
            logging.info("Started alert script with pid %d" % self._alertProc.pid)
 | 
			
		||||
        except BaseException:
 | 
			
		||||
            logging.error(f"Failed to start alert script '{self._alertScript}'")
 | 
			
		||||
 | 
			
		||||
    def done(self):
 | 
			
		||||
        self._cache = None
 | 
			
		||||
        client.Application.done(self)
 | 
			
		||||
 | 
			
		||||
    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(
 | 
			
		||||
                        f"got new {self._ampType} amplitude '{obj.publicID()}'"
 | 
			
		||||
                    )
 | 
			
		||||
                    self.notifyAmplitude(obj)
 | 
			
		||||
 | 
			
		||||
            obj = datamodel.Origin.Cast(arg0)
 | 
			
		||||
            if obj:
 | 
			
		||||
                self._cache.feed(obj)
 | 
			
		||||
                logging.debug(f"got new origin '{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(f"got new magnitude '{obj.publicID()}'")
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            obj = datamodel.Event.Cast(arg0)
 | 
			
		||||
            if obj:
 | 
			
		||||
                org = self._cache.get(datamodel.Origin, obj.preferredOriginID())
 | 
			
		||||
                agencyID = org.creationInfo().agencyID()
 | 
			
		||||
                logging.debug(f"got new event '{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(f"update event '{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 = f"magnitude {mag:.1f}"
 | 
			
		||||
                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(f"desc: {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" % (int(dt / 3600), int((dt % 3600) / 60))
 | 
			
		||||
            elif dt > 120:
 | 
			
		||||
                dt = "%d minutes ago" % int(dt / 60)
 | 
			
		||||
            else:
 | 
			
		||||
                dt = "%d seconds ago" % int(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(f"Suppressing repeated message '{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 = f"{nmag.magnitude().value():.1f}"
 | 
			
		||||
 | 
			
		||||
                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())
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/scwfas
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scwfas
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/scwfparam
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scwfparam
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/scxmldump
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scxmldump
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/scxmlmerge
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/scxmlmerge
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										55
									
								
								bin/seiscomp
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										55
									
								
								bin/seiscomp
									
									
									
									
									
										Executable file
									
								
							@ -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
 | 
			
		||||
							
								
								
									
										1641
									
								
								bin/seiscomp-control.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										1641
									
								
								bin/seiscomp-control.py
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										19
									
								
								bin/seiscomp-python
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										19
									
								
								bin/seiscomp-python
									
									
									
									
									
										Executable file
									
								
							@ -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 "$@"
 | 
			
		||||
							
								
								
									
										962
									
								
								bin/sh2proc
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										962
									
								
								bin/sh2proc
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,962 @@
 | 
			
		||||
#!/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                                                #
 | 
			
		||||
############################################################################
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# from time import strptime
 | 
			
		||||
import sys
 | 
			
		||||
import traceback
 | 
			
		||||
import seiscomp.client
 | 
			
		||||
import seiscomp.core
 | 
			
		||||
import seiscomp.datamodel
 | 
			
		||||
import seiscomp.io
 | 
			
		||||
import seiscomp.logging
 | 
			
		||||
import seiscomp.math
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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 f"{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 = "-"
 | 
			
		||||
        self.streams = None
 | 
			
		||||
 | 
			
		||||
    ###########################################################################
 | 
			
		||||
    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(
 | 
			
		||||
                        f"ambiguous stream id found for station {net}.{sta}"
 | 
			
		||||
                    )
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                setup = seiscomp.datamodel.findSetup(cfg, self.name(), True)
 | 
			
		||||
                if not setup:
 | 
			
		||||
                    seiscomp.logging.warning(
 | 
			
		||||
                        f"could not find station setup for {net}.{sta}"
 | 
			
		||||
                    )
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                params = seiscomp.datamodel.ParameterSet.Find(setup.parameterSetID())
 | 
			
		||||
                if not params:
 | 
			
		||||
                    seiscomp.logging.warning(
 | 
			
		||||
                        f"could not find station parameters for {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(
 | 
			
		||||
                        f"could not find detecStream for {net}.{sta}"
 | 
			
		||||
                    )
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                loc = inv.getSensorLocation(net, sta, detecLocid, now)
 | 
			
		||||
                if loc is None:
 | 
			
		||||
                    seiscomp.logging.warning(
 | 
			
		||||
                        f"could not find preferred location for {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(f"add stream {wfs2Str(wfsID)} (vertical)")
 | 
			
		||||
                if tc.firstHorizontal():
 | 
			
		||||
                    cha = tc.firstHorizontal()
 | 
			
		||||
                    wfsID = seiscomp.datamodel.WaveformStreamID(
 | 
			
		||||
                        net, sta, loc.code(), cha.code(), ""
 | 
			
		||||
                    )
 | 
			
		||||
                    components[cha.code()[-1]] = wfsID
 | 
			
		||||
                    seiscomp.logging.debug(
 | 
			
		||||
                        f"add stream {wfs2Str(wfsID)} (first horizontal)"
 | 
			
		||||
                    )
 | 
			
		||||
                if tc.secondHorizontal():
 | 
			
		||||
                    cha = tc.secondHorizontal()
 | 
			
		||||
                    wfsID = seiscomp.datamodel.WaveformStreamID(
 | 
			
		||||
                        net, sta, loc.code(), cha.code(), ""
 | 
			
		||||
                    )
 | 
			
		||||
                    components[cha.code()[-1]] = wfsID
 | 
			
		||||
                    seiscomp.logging.debug(
 | 
			
		||||
                        f"add stream {wfs2Str(wfsID)} (second horizontal)"
 | 
			
		||||
                    )
 | 
			
		||||
                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(
 | 
			
		||||
                f"network {net.code()}: loaded {net.stationCount()} stations"
 | 
			
		||||
            )
 | 
			
		||||
            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(
 | 
			
		||||
                                    f"ambiguous stream id found for station\
 | 
			
		||||
                                        {sta.code()}, ignoring {wfs2Str(wfsID)}"
 | 
			
		||||
                                )
 | 
			
		||||
                                continue
 | 
			
		||||
 | 
			
		||||
                            self.streams[sta.code()][comp] = wfsID
 | 
			
		||||
 | 
			
		||||
                        seiscomp.logging.debug(f"add stream {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"
 | 
			
		||||
        if value == "ml":
 | 
			
		||||
            return "ML"
 | 
			
		||||
        if value == "mb":
 | 
			
		||||
            return "mb"
 | 
			
		||||
        if value == "ms":
 | 
			
		||||
            return "Ms(BB)"
 | 
			
		||||
        if value == "mw":
 | 
			
		||||
            return "Mw"
 | 
			
		||||
        if 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
 | 
			
		||||
 | 
			
		||||
        ampPeriod = None
 | 
			
		||||
        ampBBPeriod = None
 | 
			
		||||
        amplitudeDisp = None
 | 
			
		||||
        amplitudeVel = None
 | 
			
		||||
        amplitudeSNR = None
 | 
			
		||||
        amplitudeBB = None
 | 
			
		||||
 | 
			
		||||
        magnitudeMB = None
 | 
			
		||||
        magnitudeML = None
 | 
			
		||||
        magnitudeMS = None
 | 
			
		||||
        magnitudeBB = None
 | 
			
		||||
 | 
			
		||||
        # To avoid undefined warning
 | 
			
		||||
        arrival = None
 | 
			
		||||
        phase = 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
 | 
			
		||||
            if keyLower == "--- end of phase ---":
 | 
			
		||||
                if pick is None:
 | 
			
		||||
                    seiscomp.logging.warning(f"Line {iLine}: found empty phase block")
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if staCode is None or compCode is None:
 | 
			
		||||
                    seiscomp.logging.warning(
 | 
			
		||||
                        f"Line {iLine}: end of phase, stream code incomplete"
 | 
			
		||||
                    )
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if not staCode in self.streams:
 | 
			
		||||
                    seiscomp.logging.warning(
 | 
			
		||||
                        f"Line {iLine}: end of phase, station code {staCode} not found in inventory"
 | 
			
		||||
                    )
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if not compCode in self.streams[staCode]:
 | 
			
		||||
                    seiscomp.logging.warning(
 | 
			
		||||
                        f"Line {iLine}: end of phase, component\
 | 
			
		||||
                            {compCode} of station {staCode} not found in inventory"
 | 
			
		||||
                    )
 | 
			
		||||
                    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
 | 
			
		||||
                ampPeriod = None
 | 
			
		||||
                ampBBPeriod = None
 | 
			
		||||
                amplitudeDisp = None
 | 
			
		||||
                amplitudeVel = None
 | 
			
		||||
                amplitudeSNR = None
 | 
			
		||||
                amplitudeBB = None
 | 
			
		||||
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            # empty key
 | 
			
		||||
            if len(a) == 1:
 | 
			
		||||
                seiscomp.logging.warning(f"Line {iLine}: key without value")
 | 
			
		||||
                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(
 | 
			
		||||
                            f"Line {iLine}: ignoring parameter: {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(
 | 
			
		||||
                            f"Line {iLine}: ignoring parameter: {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(
 | 
			
		||||
                            f"Line {iLine}: ignoring parameter: {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(
 | 
			
		||||
                            f"Line {iLine - 1}: ignoring parameter: 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(f"Line {iLine}: ignoring parameter: {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(
 | 
			
		||||
                            f"Line {iLine}: Magnitude Type not known {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(
 | 
			
		||||
                            f"Line {iLine}: Magnitude type {magType} not defined yet."
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
                # 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(f"Line {iLine}: ignoring parameter: {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(f"Line {iLine}: ignoring parameter: {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(f"Line {iLine}: ignoring parameter: {key}")
 | 
			
		||||
 | 
			
		||||
                # ignored
 | 
			
		||||
                elif keyLower == "location input params":
 | 
			
		||||
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")
 | 
			
		||||
 | 
			
		||||
                # missing keys
 | 
			
		||||
                elif keyLower == "ampl&period source":
 | 
			
		||||
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")
 | 
			
		||||
 | 
			
		||||
                elif keyLower == "location quality":
 | 
			
		||||
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")
 | 
			
		||||
 | 
			
		||||
                elif keyLower == "reference latitude":
 | 
			
		||||
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")
 | 
			
		||||
 | 
			
		||||
                elif keyLower == "reference longitude":
 | 
			
		||||
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")
 | 
			
		||||
 | 
			
		||||
                elif keyLower.startswith("amplitude time"):
 | 
			
		||||
                    seiscomp.logging.debug(f"Line {iLine}: ignoring parameter: {key}")
 | 
			
		||||
 | 
			
		||||
                # unknown key
 | 
			
		||||
                else:
 | 
			
		||||
                    seiscomp.logging.warning(
 | 
			
		||||
                        "Line {iLine}: ignoring unknown parameter: {key}"
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                seiscomp.logging.warning(f"Line {iLine}: can not parse {key} value")
 | 
			
		||||
            except Exception:
 | 
			
		||||
                seiscomp.logging.error("Line {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
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								bin/slarchive
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/slarchive
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/slink2caps
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/slink2caps
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								bin/slinktool
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/slinktool
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										486
									
								
								bin/slmon
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										486
									
								
								bin/slmon
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,486 @@
 | 
			
		||||
#!/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='#FFFFFF'><b>≤ 1 min </b></td>\n" \
 | 
			
		||||
        "<td bgcolor='#EBD6FF'><b>> 1 min </b></td>\n" \
 | 
			
		||||
        "<td bgcolor='#9470BB'><font color='#FFFFFF'><b>> 10 min </b></font></td>\n" \
 | 
			
		||||
        "<td bgcolor='#3399FF'><font color='#FFFFFF'><b>> 30 min </b></font></td>\n" \
 | 
			
		||||
        "<td bgcolor='#00FF00'><b>> 1 hour </b></td>\n" \
 | 
			
		||||
        "<td bgcolor='#FFFF00'><b>> 2 hours </b></td>\n" \
 | 
			
		||||
        "<td bgcolor='#FF9966'><b>> 6 hours </b></td>\n" \
 | 
			
		||||
        "<td bgcolor='#FF3333'><b>> 1 day </b></td>\n" \
 | 
			
		||||
        "<td bgcolor='#FFB3B3'><b>> 2 days </b></td>\n" \
 | 
			
		||||
        "<td bgcolor='#CCCCCC'><b>> 3 days </b></td>\n" \
 | 
			
		||||
        "<td bgcolor='#999999'><font color='#FFFFFF'><b>> 4 days </b></font></td>\n" \
 | 
			
		||||
        "<td bgcolor='#666666'><font color='#FFFFFF'><b>> 5 days </b></font></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'  # > 5 days
 | 
			
		||||
    elif delay > 345600: return '#999999'  # > 4 days
 | 
			
		||||
    elif delay > 259200: return '#CCCCCC'  # > 3 days
 | 
			
		||||
    elif delay > 172800: return '#FFB3B3'  # > 2 days
 | 
			
		||||
    elif delay >  86400: return '#FF3333'  # > 1 day
 | 
			
		||||
    elif delay >  21600: return '#FF9966'  # > 6 hours
 | 
			
		||||
    elif delay >   7200: return '#FFFF00'  # > 2 hours
 | 
			
		||||
    elif delay >   3600: return '#00FF00'  # > 1 hour
 | 
			
		||||
    elif delay >   1800: return '#3399FF'  # > 30 minutes
 | 
			
		||||
    elif delay >    600: return '#9470BB'  # > 10 minutes
 | 
			
		||||
    elif delay >     60: return '#EBD6FF'  # > 1 minute
 | 
			
		||||
    else:                return '#FFFFFF'  # <= 1 minute
 | 
			
		||||
 | 
			
		||||
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>  %s </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> %s </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> %s <a " \
 | 
			
		||||
               "href='%s.html'>%s</a> </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> %s %s </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'])
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user