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