963 lines
37 KiB
Plaintext
Executable File
963 lines
37 KiB
Plaintext
Executable File
#!/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
|