[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