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.
1218 lines
43 KiB
Python
1218 lines
43 KiB
Python
#!/usr/bin/env seiscomp-python
|
|
# -*- coding: utf-8 -*-
|
|
############################################################################
|
|
# Copyright (C) GFZ Potsdam #
|
|
# All rights reserved. #
|
|
# #
|
|
# GNU Affero General Public License Usage #
|
|
# This file may be used under the terms of the GNU Affero #
|
|
# Public License version 3.0 as published by the Free Software Foundation #
|
|
# and appearing in the file LICENSE included in the packaging of this #
|
|
# file. Please review the following information to ensure the GNU Affero #
|
|
# Public License version 3.0 requirements will be met: #
|
|
# https://www.gnu.org/licenses/agpl-3.0.html. #
|
|
############################################################################
|
|
|
|
from __future__ import print_function
|
|
|
|
import sys
|
|
import seiscomp.client
|
|
import seiscomp.io
|
|
import seiscomp.math
|
|
import seiscomp.datamodel
|
|
import seiscomp.logging
|
|
import seiscomp.seismology
|
|
|
|
|
|
def time2str(time):
|
|
"""
|
|
Convert a seiscomp.core.Time to a string
|
|
"""
|
|
return time.toString("%Y-%m-%d %H:%M:%S.%f000000")[:23]
|
|
|
|
|
|
def lat2str(lat, enhanced=False):
|
|
if enhanced:
|
|
s = "%.5f " % abs(lat)
|
|
else:
|
|
s = "%.2f " % abs(lat)
|
|
if lat >= 0:
|
|
s += "N"
|
|
else:
|
|
s += "S"
|
|
return s
|
|
|
|
|
|
def lon2str(lon, enhanced=False):
|
|
if enhanced:
|
|
s = "%.5f " % abs(lon)
|
|
else:
|
|
s = "%.2f " % abs(lon)
|
|
if lon >= 0:
|
|
s += "E"
|
|
else:
|
|
s += "W"
|
|
return s
|
|
|
|
|
|
def stationCount(org, minArrivalWeight):
|
|
count = 0
|
|
for i in range(org.arrivalCount()):
|
|
arr = org.arrival(i)
|
|
# if arr.weight()> 0.5:
|
|
if arr.weight() >= minArrivalWeight:
|
|
count += 1
|
|
return count
|
|
|
|
|
|
def uncertainty(quantity):
|
|
# for convenience/readability: get uncertainty from a quantity
|
|
try:
|
|
err = 0.5*(quantity.lowerUncertainty()+quantity.upperUncertainty())
|
|
except ValueError:
|
|
try:
|
|
err = quantity.uncertainty()
|
|
except ValueError:
|
|
err = None
|
|
|
|
return err
|
|
|
|
|
|
class Bulletin(object):
|
|
|
|
def __init__(self, dbq, long=True):
|
|
self._dbq = dbq
|
|
self._long = long
|
|
self._evt = None
|
|
self.format = "autoloc1"
|
|
self.enhanced = False
|
|
self.polarities = False
|
|
self.useEventAgencyID = False
|
|
self.distInKM = False
|
|
self.minDepthPhaseCount = 3
|
|
self.minArrivalWeight = 0.5
|
|
self.minStationMagnitudeWeight = 0.5
|
|
|
|
def _getArrivalsSorted(self, org):
|
|
# returns arrival list sorted by distance
|
|
arrivals = [ org.arrival(i) for i in range(org.arrivalCount()) ]
|
|
return sorted(arrivals, key=lambda t: t.distance())
|
|
|
|
def _getPicks(self, org):
|
|
if self._dbq is None:
|
|
return {}
|
|
orid = org.publicID()
|
|
pick = {}
|
|
for obj in self._dbq.getPicks(orid):
|
|
p = seiscomp.datamodel.Pick.Cast(obj)
|
|
key = p.publicID()
|
|
pick[key] = p
|
|
return pick
|
|
|
|
def _getAmplitudes(self, org):
|
|
if self._dbq is None:
|
|
return {}
|
|
orid = org.publicID()
|
|
ampl = {}
|
|
for obj in self._dbq.getAmplitudesForOrigin(orid):
|
|
amp = seiscomp.datamodel.Amplitude.Cast(obj)
|
|
key = amp.publicID()
|
|
ampl[key] = amp
|
|
return ampl
|
|
|
|
def _printOriginAutoloc3(self, org, extra=False):
|
|
orid = org.publicID()
|
|
|
|
arrivals = self._getArrivalsSorted(org)
|
|
pick = self._getPicks(org)
|
|
ampl = self._getAmplitudes(org)
|
|
|
|
try:
|
|
depthPhaseCount = org.quality().depthPhaseCount()
|
|
except ValueError:
|
|
depthPhaseCount = 0
|
|
for arr in arrivals:
|
|
wt = arr.weight()
|
|
pha = arr.phase().code()
|
|
# if (pha[0] in ["p","s"] and wt >= 0.5 ):
|
|
if (pha[0] in ["p", "s"] and wt >= self.minArrivalWeight):
|
|
depthPhaseCount += 1
|
|
|
|
txt = ""
|
|
|
|
evt = self._evt
|
|
if not evt and self._dbq:
|
|
evt = self._dbq.getEvent(orid)
|
|
|
|
if evt:
|
|
txt += "Event:\n"
|
|
txt += " Public ID %s\n" % evt.publicID()
|
|
if extra:
|
|
txt += " Preferred Origin ID %s\n" % evt.preferredOriginID()
|
|
txt += " Preferred Magnitude ID %s\n" % evt.preferredMagnitudeID()
|
|
try:
|
|
evtType = evt.type()
|
|
txt += " Type %s\n" % \
|
|
seiscomp.datamodel.EEventTypeNames.name(evtType)
|
|
except ValueError:
|
|
seiscomp.logging.warning("%s: ignoring unknown event type" %
|
|
evt.publicID())
|
|
|
|
txt += " Description\n"
|
|
for i in range(evt.eventDescriptionCount()):
|
|
evtd = evt.eventDescription(i)
|
|
evtdtype = seiscomp.datamodel.EEventDescriptionTypeNames.name(
|
|
evtd.type())
|
|
txt += " %s: %s" % (evtdtype, evtd.text())
|
|
|
|
if extra:
|
|
try:
|
|
txt += "\n Creation time %s\n" % evt.creationInfo().creationTime().toString("%Y-%m-%d %H:%M:%S")
|
|
except ValueError:
|
|
pass
|
|
txt += "\n"
|
|
preferredMagnitudeID = evt.preferredMagnitudeID()
|
|
else:
|
|
preferredMagnitudeID = ""
|
|
|
|
tim = org.time().value()
|
|
lat = org.latitude().value()
|
|
lon = org.longitude().value()
|
|
dep = org.depth().value()
|
|
timerr = uncertainty(org.time())
|
|
laterr = uncertainty(org.latitude())
|
|
lonerr = uncertainty(org.longitude())
|
|
deperr = uncertainty(org.depth())
|
|
tstr = time2str(tim)
|
|
|
|
originHeader = "Origin:\n"
|
|
if evt:
|
|
if org.publicID() != evt.preferredOriginID():
|
|
originHeader = "Origin (NOT the preferred origin of this event):\n"
|
|
|
|
txt += originHeader
|
|
if extra:
|
|
txt += " Public ID %s\n" % org.publicID()
|
|
txt += " Date %s\n" % tstr[:10]
|
|
if timerr:
|
|
if self.enhanced:
|
|
txt += " Time %s +/- %8.3f s\n" % (
|
|
tstr[11:], timerr)
|
|
else:
|
|
txt += " Time %s +/- %6.1f s\n" % (
|
|
tstr[11:-2], timerr)
|
|
else:
|
|
if self.enhanced:
|
|
txt += " Time %s\n" % tstr[11:]
|
|
else:
|
|
txt += " Time %s\n" % tstr[11:-2]
|
|
|
|
if laterr:
|
|
if self.enhanced:
|
|
txt += " Latitude %10.5f deg +/- %8.3f km\n" % (
|
|
lat, laterr)
|
|
else:
|
|
txt += " Latitude %7.2f deg +/- %6.0f km\n" % (
|
|
lat, laterr)
|
|
else:
|
|
if self.enhanced:
|
|
txt += " Latitude %10.5f deg\n" % lat
|
|
else:
|
|
txt += " Latitude %7.2f deg\n" % lat
|
|
if lonerr:
|
|
if self.enhanced:
|
|
txt += " Longitude %10.5f deg +/- %8.3f km\n" % (
|
|
lon, lonerr)
|
|
else:
|
|
txt += " Longitude %7.2f deg +/- %6.0f km\n" % (
|
|
lon, lonerr)
|
|
else:
|
|
if self.enhanced:
|
|
txt += " Longitude %10.5f deg\n" % lon
|
|
else:
|
|
txt += " Longitude %7.2f deg\n" % lon
|
|
if self.enhanced:
|
|
txt += " Depth %11.3f km" % dep
|
|
else:
|
|
txt += " Depth %7.0f km" % dep
|
|
if deperr is None:
|
|
txt += "\n"
|
|
elif deperr == 0:
|
|
txt += " (fixed)\n"
|
|
else:
|
|
if depthPhaseCount >= self.minDepthPhaseCount:
|
|
if self.enhanced:
|
|
txt += " +/- %8.3f km (%d depth phases)\n" % (
|
|
deperr, depthPhaseCount)
|
|
else:
|
|
txt += " +/- %4.0f km (%d depth phases)\n" % (
|
|
deperr, depthPhaseCount)
|
|
else:
|
|
if self.enhanced:
|
|
txt += " +/- %8.3f km\n" % deperr
|
|
else:
|
|
txt += " +/- %4.0f km\n" % deperr
|
|
|
|
agencyID = ""
|
|
if self.useEventAgencyID:
|
|
try:
|
|
agencyID = evt.creationInfo().agencyID()
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
try:
|
|
agencyID = org.creationInfo().agencyID()
|
|
except ValueError:
|
|
pass
|
|
|
|
txt += " Agency %s\n" % agencyID
|
|
if extra:
|
|
try:
|
|
authorID = org.creationInfo().author()
|
|
except ValueError:
|
|
authorID = "NOT SET"
|
|
txt += " Author %s\n" % authorID
|
|
txt += " Mode "
|
|
try:
|
|
txt += "%s\n" % seiscomp.datamodel.EEvaluationModeNames.name(
|
|
org.evaluationMode())
|
|
except ValueError:
|
|
txt += "NOT SET\n"
|
|
txt += " Status "
|
|
try:
|
|
txt += "%s\n" % seiscomp.datamodel.EEvaluationStatusNames.name(
|
|
org.evaluationStatus())
|
|
except ValueError:
|
|
txt += "NOT SET\n"
|
|
|
|
if extra:
|
|
txt += " Creation time "
|
|
try:
|
|
txt += "%s\n" % org.creationInfo().creationTime().toString("%Y-%m-%d %H:%M:%S")
|
|
except ValueError:
|
|
txt += "NOT SET\n"
|
|
|
|
try:
|
|
if self.enhanced:
|
|
txt += " Residual RMS %9.3f s\n" % org.quality().standardError()
|
|
else:
|
|
txt += " Residual RMS %6.2f s\n" % org.quality().standardError()
|
|
except ValueError:
|
|
pass
|
|
|
|
try:
|
|
if self.enhanced:
|
|
txt += " Azimuthal gap %8.1f deg\n" % org.quality().azimuthalGap()
|
|
else:
|
|
txt += " Azimuthal gap %5.0f deg\n" % org.quality().azimuthalGap()
|
|
except ValueError:
|
|
pass
|
|
|
|
txt += "\n"
|
|
|
|
networkMagnitudeCount = org.magnitudeCount()
|
|
networkMagnitudes = {}
|
|
|
|
# Each station magnitude contributes to the network
|
|
# magnitude of the same type.
|
|
#
|
|
# We save here the StationMagnitudeContribution objects
|
|
# by publicID of the corresponding StationMagnitude object.
|
|
stationMagnitudeContributions = {}
|
|
|
|
tmptxt = txt
|
|
txt = ""
|
|
foundPrefMag = False
|
|
for i in range(networkMagnitudeCount):
|
|
mag = org.magnitude(i)
|
|
val = mag.magnitude().value()
|
|
typ = mag.type()
|
|
networkMagnitudes[typ] = mag
|
|
|
|
for k in range(mag.stationMagnitudeContributionCount()):
|
|
smc = mag.stationMagnitudeContribution(k)
|
|
smid = smc.stationMagnitudeID()
|
|
stationMagnitudeContributions[smid] = smc
|
|
|
|
err = uncertainty(mag.magnitude())
|
|
if err is not None:
|
|
err = "+/- %.2f" % err
|
|
else:
|
|
err = ""
|
|
|
|
if mag.publicID() == preferredMagnitudeID:
|
|
preferredMarker = "preferred"
|
|
foundPrefMag = True
|
|
else:
|
|
preferredMarker = " "
|
|
if extra:
|
|
try:
|
|
agencyID = mag.creationInfo().agencyID()
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
agencyID = ""
|
|
txt += " %-8s %5.2f %8s %3d %s %s\n" % \
|
|
(typ, val, err, mag.stationCount(), preferredMarker, agencyID)
|
|
|
|
if not foundPrefMag and preferredMagnitudeID != "":
|
|
mag = seiscomp.datamodel.Magnitude.Find(preferredMagnitudeID)
|
|
if mag is None and self._dbq:
|
|
o = self._dbq.loadObject(
|
|
seiscomp.datamodel.Magnitude.TypeInfo(), preferredMagnitudeID)
|
|
mag = seiscomp.datamodel.Magnitude.Cast(o)
|
|
|
|
if mag:
|
|
val = mag.magnitude().value()
|
|
typ = mag.type()
|
|
networkMagnitudes[typ] = mag
|
|
|
|
err = uncertainty(mag.magnitude())
|
|
if err is not None:
|
|
err = "+/- %.2f" % err
|
|
else:
|
|
err = ""
|
|
|
|
preferredMarker = "preferred"
|
|
if extra:
|
|
try:
|
|
agencyID = mag.creationInfo().agencyID()
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
agencyID = ""
|
|
txt += " %-8s %5.2f %8s %3d %s %s\n" % \
|
|
(typ, val, err, mag.stationCount(), preferredMarker, agencyID)
|
|
|
|
txt = tmptxt + "%d Network magnitudes:\n" % networkMagnitudeCount + txt
|
|
|
|
if not self._long:
|
|
return txt
|
|
|
|
lineFMT = " %-5s %-2s "
|
|
if self.enhanced:
|
|
lineFMT += "%9.3f" if self.distInKM else "%9.5f"
|
|
else:
|
|
lineFMT += "%5.0f" if self.distInKM else "%5.1f"
|
|
lineFMT += " %s %-7s %s %s %1s%1s %3.1f "
|
|
if self.polarities:
|
|
lineFMT += "%s "
|
|
lineFMT += "%-5s\n"
|
|
|
|
dist_azi = {}
|
|
lines = []
|
|
|
|
for arr in arrivals:
|
|
p = seiscomp.datamodel.Pick.Find(arr.pickID())
|
|
if p is None:
|
|
lines.append((180, " ## missing pick %s\n" % arr.pickID()))
|
|
continue
|
|
|
|
if self.distInKM:
|
|
dist = seiscomp.math.deg2km(arr.distance())
|
|
else:
|
|
dist = arr.distance()
|
|
|
|
wfid = p.waveformID()
|
|
net = wfid.networkCode()
|
|
sta = wfid.stationCode()
|
|
if self.enhanced:
|
|
try:
|
|
azi = "%5.1f" % arr.azimuth()
|
|
except ValueError:
|
|
azi = " N/A"
|
|
tstr = time2str(p.time().value())[11:]
|
|
try:
|
|
res = "%7.3f" % arr.timeResidual()
|
|
except ValueError:
|
|
res = " N/A"
|
|
else:
|
|
try:
|
|
azi = "%3.0f" % arr.azimuth()
|
|
except ValueError:
|
|
azi = "N/A"
|
|
tstr = time2str(p.time().value())[11:-2]
|
|
try:
|
|
res = "%5.1f" % arr.timeResidual()
|
|
except ValueError:
|
|
res = " N/A"
|
|
dist_azi[net+"_"+sta] = (dist, azi)
|
|
wt = arr.weight()
|
|
pha = arr.phase().code()
|
|
flag = "X "[wt > 0.1]
|
|
try:
|
|
status = seiscomp.datamodel.EEvaluationModeNames.name(p.evaluationMode())[
|
|
0].upper()
|
|
except ValueError:
|
|
status = "-"
|
|
if self.polarities:
|
|
try:
|
|
pol = seiscomp.datamodel.EPickPolarityNames.name(
|
|
p.polarity())
|
|
except ValueError:
|
|
pol = None
|
|
if pol:
|
|
if pol == "positive":
|
|
pol = "u"
|
|
elif pol == "negative":
|
|
pol = "d"
|
|
elif pol == "undecidable":
|
|
pol = "x"
|
|
else:
|
|
pol = "."
|
|
else:
|
|
pol = "."
|
|
line = lineFMT % (sta, net, dist, azi, pha,
|
|
tstr, res, status, flag, wt, pol, sta)
|
|
else:
|
|
line = lineFMT % (sta, net, dist, azi, pha,
|
|
tstr, res, status, flag, wt, sta)
|
|
lines.append((dist, line))
|
|
|
|
lines.sort()
|
|
|
|
txt += "\n"
|
|
txt += "%d Phase arrivals:\n" % org.arrivalCount()
|
|
if self.enhanced:
|
|
txt += " sta net dist azi phase time res wt "
|
|
else:
|
|
txt += " sta net dist azi phase time res wt "
|
|
if self.polarities:
|
|
txt += " "
|
|
txt += "sta \n"
|
|
for dist, line in lines:
|
|
txt += line
|
|
txt += "\n"
|
|
|
|
stationMagnitudeCount = org.stationMagnitudeCount()
|
|
activeStationMagnitudeCount = 0
|
|
stationMagnitudes = {}
|
|
|
|
for i in range(stationMagnitudeCount):
|
|
mag = org.stationMagnitude(i)
|
|
typ = mag.type()
|
|
if typ not in networkMagnitudes:
|
|
continue
|
|
if typ not in stationMagnitudes:
|
|
stationMagnitudes[typ] = []
|
|
|
|
# suppress unused station magnitudes
|
|
smid = mag.publicID()
|
|
if not smid in stationMagnitudeContributions:
|
|
continue
|
|
|
|
try:
|
|
w = stationMagnitudeContributions[smid].weight()
|
|
except ValueError:
|
|
w = self.minStationMagnitudeWeight
|
|
if w < self.minStationMagnitudeWeight:
|
|
continue
|
|
stationMagnitudes[typ].append(mag)
|
|
activeStationMagnitudeCount += 1
|
|
|
|
lineFMT = " %-5s %-2s "
|
|
if self.enhanced:
|
|
lineFMT += "%9.3f" if self.distInKM else "%9.5f"
|
|
else:
|
|
lineFMT += "%5.0f" if self.distInKM else "%5.1f"
|
|
lineFMT += " %s %-6s %5.2f %5.2f %8s %4s\n"
|
|
|
|
lines = []
|
|
|
|
for typ in stationMagnitudes:
|
|
for mag in stationMagnitudes[typ]:
|
|
|
|
key = mag.amplitudeID()
|
|
amp = seiscomp.datamodel.Amplitude.Find(key)
|
|
if amp is None and self._dbq:
|
|
seiscomp.logging.debug(
|
|
"missing station amplitude '%s'" % key)
|
|
|
|
# FIXME really slow!!!
|
|
obj = self._dbq.loadObject(
|
|
seiscomp.datamodel.Amplitude.TypeInfo(), key)
|
|
amp = seiscomp.datamodel.Amplitude.Cast(obj)
|
|
|
|
p = a = "N/A"
|
|
if amp:
|
|
try:
|
|
a = "%g" % amp.amplitude().value()
|
|
except ValueError:
|
|
a = "N/A"
|
|
|
|
if typ in ["mb", "Ms", "Ms(BB)"]:
|
|
try:
|
|
p = "%.2f" % amp.period().value()
|
|
except ValueError:
|
|
p = "N/A"
|
|
else:
|
|
p = ""
|
|
|
|
wfid = mag.waveformID()
|
|
net = wfid.networkCode()
|
|
sta = wfid.stationCode()
|
|
|
|
try:
|
|
dist, azi = dist_azi[net+"_"+sta]
|
|
except ValueError:
|
|
dist, azi = 0, " N/A" if self.enhanced else "N/A"
|
|
|
|
val = mag.magnitude().value()
|
|
res = val - networkMagnitudes[typ].magnitude().value()
|
|
|
|
line = lineFMT % (sta, net, dist, azi, typ, val, res, a, p)
|
|
lines.append((dist, line))
|
|
|
|
lines.sort()
|
|
|
|
if activeStationMagnitudeCount:
|
|
txt += "%d Station magnitudes:\n" % activeStationMagnitudeCount
|
|
if self.enhanced:
|
|
txt += " sta net dist azi type value res amp per\n"
|
|
else:
|
|
txt += " sta net dist azi type value res amp per\n"
|
|
for dist, line in lines:
|
|
txt += line
|
|
else:
|
|
txt += "No station magnitudes\n"
|
|
|
|
return txt
|
|
|
|
def _printOriginAutoloc1(self, org):
|
|
evt = self._evt
|
|
|
|
if not evt and self._dbq:
|
|
evt = self._dbq.getEvent(org.publicID())
|
|
|
|
if evt:
|
|
evid = evt.publicID()
|
|
pos = evid.find("#") # XXX Hack!!!
|
|
if pos != -1:
|
|
evid = evid[:pos]
|
|
prefMagID = evt.preferredMagnitudeID()
|
|
else:
|
|
evid = "..."
|
|
prefMagID = ""
|
|
|
|
txt = ""
|
|
|
|
if self.enhanced:
|
|
depth = org.depth().value()
|
|
sTime = org.time().value().toString("%Y/%m/%d %H:%M:%S.%f00")[:24]
|
|
else:
|
|
depth = int(org.depth().value()+0.5)
|
|
sTime = org.time().value().toString("%Y/%m/%d %H:%M:%S.%f")[:22]
|
|
|
|
tmp = {
|
|
"evid": evid,
|
|
"nsta": stationCount(org, self.minArrivalWeight),
|
|
"time": sTime,
|
|
"lat": lat2str(org.latitude().value(), self.enhanced),
|
|
"lon": lon2str(org.longitude().value(), self.enhanced),
|
|
"dep": depth,
|
|
"reg": seiscomp.seismology.Regions.getRegionName(org.latitude().value(), org.longitude().value()),
|
|
# changed to properly report location method. (Marco Olivieri 21/06/2010)
|
|
"method": org.methodID(),
|
|
"model": org.earthModelID(),
|
|
# end (MO)
|
|
"stat": "A"
|
|
}
|
|
|
|
try:
|
|
if org.evaluationMode() == seiscomp.datamodel.MANUAL:
|
|
tmp["stat"] = "M"
|
|
except ValueError:
|
|
pass
|
|
|
|
# dummy default
|
|
tmp["mtyp"] = "M"
|
|
tmp["mval"] = 0.
|
|
|
|
foundMag = False
|
|
networkMagnitudeCount = org.magnitudeCount()
|
|
for i in range(networkMagnitudeCount):
|
|
mag = org.magnitude(i)
|
|
if mag.publicID() == prefMagID:
|
|
tmp["mtyp"] = mag.type()
|
|
tmp["mval"] = mag.magnitude().value()
|
|
foundMag = True
|
|
break
|
|
|
|
if not foundMag and prefMagID != "":
|
|
mag = seiscomp.datamodel.Magnitude.Find(prefMagID)
|
|
if mag is None and self._dbq:
|
|
o = self._dbq.loadObject(
|
|
seiscomp.datamodel.Magnitude.TypeInfo(), prefMagID)
|
|
mag = seiscomp.datamodel.Magnitude.Cast(o)
|
|
|
|
if mag:
|
|
tmp["mtyp"] = mag.type()
|
|
tmp["mval"] = mag.magnitude().value()
|
|
|
|
# changed to properly report location method. (Marco Olivieri 21/06/2010)
|
|
# txt += """
|
|
# Autoloc alert %(evid)s: determined by %(nsta)d stations, type %(stat)s
|
|
#
|
|
# LOCSAT solution (with start solution, %(nsta)d stations used, weight %(nsta)d):
|
|
#
|
|
# %(reg)s %(mtyp)s=%(mval).1f %(time)s %(lat)s %(lon)s %(dep)d km
|
|
#
|
|
# Stat Net Date Time Amp Per Res Dist Az mb ML mB
|
|
#""" % tmp
|
|
txtFMT = """
|
|
Alert %(evid)s: determined by %(nsta)d stations, type %(stat)s
|
|
|
|
%(method)s solution with earthmodel %(model)s (with start solution, %(nsta)d stations used, weight %(nsta)d):
|
|
|
|
%(reg)s %(mtyp)s=%(mval).1f %(time)s %(lat)s %(lon)s %(dep)"""
|
|
if self.enhanced:
|
|
txtFMT += """.3f km
|
|
|
|
Stat Net Date Time Amp Per Res Dist Az mb ML mB
|
|
"""
|
|
else:
|
|
txtFMT += """d km
|
|
|
|
Stat Net Date Time Amp Per Res Dist Az mb ML mB
|
|
"""
|
|
txt += txtFMT % tmp
|
|
|
|
# end (MO)
|
|
arrivals = self._getArrivalsSorted(org)
|
|
pick = self._getPicks(org)
|
|
ampl = self._getAmplitudes(org)
|
|
|
|
stationMagnitudeCount = org.stationMagnitudeCount()
|
|
stationMagnitudes = {}
|
|
for i in range(stationMagnitudeCount):
|
|
mag = org.stationMagnitude(i)
|
|
typ = mag.type()
|
|
if typ == "MLv" or typ == "MLh":
|
|
typ = "ML"
|
|
if typ not in stationMagnitudes:
|
|
stationMagnitudes[typ] = {}
|
|
|
|
sta = mag.waveformID().stationCode()
|
|
stationMagnitudes[typ][sta] = mag
|
|
|
|
lineFMT = " %-5s %-2s %s %10.1f %4.1f %s "
|
|
if self.enhanced:
|
|
lineFMT += "%9.3f" if self.distInKM else "%9.5f"
|
|
else:
|
|
lineFMT += "%5.0f" if self.distInKM else "%5.1f"
|
|
lineFMT += " %s%s\n"
|
|
|
|
for arr in arrivals:
|
|
if arr.weight() < self.minArrivalWeight:
|
|
continue
|
|
if self.distInKM:
|
|
dist = seiscomp.math.deg2km(arr.distance())
|
|
else:
|
|
dist = arr.distance()
|
|
|
|
p = seiscomp.datamodel.Pick.Find(arr.pickID())
|
|
if p is None:
|
|
txt += " ## missing pick %s\n" % arr.pickID()
|
|
continue
|
|
|
|
wfid = p.waveformID()
|
|
net = wfid.networkCode()
|
|
sta = wfid.stationCode()
|
|
if self.enhanced:
|
|
tstr = p.time().value().toString(
|
|
"%y/%m/%d %H:%M:%S.%f00")[:22]
|
|
try:
|
|
res = "%7.3f" % arr.timeResidual()
|
|
except ValueError:
|
|
res = " N/A"
|
|
try:
|
|
azi = "%5.1f" % arr.azimuth()
|
|
except ValueError:
|
|
azi = " N/A"
|
|
else:
|
|
tstr = p.time().value().toString("%y/%m/%d %H:%M:%S.%f")[:20]
|
|
try:
|
|
res = "%5.1f" % arr.timeResidual()
|
|
except ValueError:
|
|
res = " N/A"
|
|
try:
|
|
azi = "%3.0f" % arr.azimuth()
|
|
except ValueError:
|
|
azi = "N/A"
|
|
|
|
pha = arr.phase().code()
|
|
mstr = ""
|
|
amp = per = 0.
|
|
for typ in ["mb", "ML", "mB"]:
|
|
mag = 0.
|
|
try:
|
|
m = stationMagnitudes[typ][sta]
|
|
mag = m.magnitude().value()
|
|
if typ == "mb":
|
|
ampid = m.amplitudeID()
|
|
a = seiscomp.datamodel.Amplitude.Find(ampid)
|
|
if a is None and self._dbq:
|
|
obj = self._dbq.loadObject(
|
|
seiscomp.datamodel.Amplitude.TypeInfo(), ampid)
|
|
a = seiscomp.datamodel.Amplitude.Cast(obj)
|
|
if a:
|
|
per = a.period().value()
|
|
try:
|
|
amp = a.amplitude().value()
|
|
except ValueError:
|
|
amp = -1
|
|
except KeyError:
|
|
pass
|
|
mstr += " %3.1f" % mag
|
|
|
|
txt += lineFMT % (sta, net, tstr, amp, per, res, dist, azi, mstr)
|
|
|
|
if self.enhanced:
|
|
txt += "\n RMS-ERR: %.3f\n\n" % org.quality().standardError()
|
|
else:
|
|
txt += "\n RMS-ERR: %.2f\n\n" % org.quality().standardError()
|
|
|
|
if evt:
|
|
try:
|
|
if self.enhanced:
|
|
tm = evt.creationInfo().creationTime().toString("%Y/%m/%d %H:%M:%S.%f")
|
|
else:
|
|
tm = evt.creationInfo().creationTime().toString("%Y/%m/%d %H:%M:%S")
|
|
|
|
txt += " Event created: %s\n" % tm
|
|
except ValueError:
|
|
pass
|
|
|
|
try:
|
|
if self.enhanced:
|
|
tm = org.creationInfo().creationTime().toString("%Y/%m/%d %H:%M:%S.%f")
|
|
else:
|
|
tm = org.creationInfo().creationTime().toString("%Y/%m/%d %H:%M:%S")
|
|
txt += " This origin created: %s\n" % tm
|
|
except ValueError:
|
|
pass
|
|
|
|
return txt
|
|
|
|
def _printOriginFDSN(self, org):
|
|
evt = self._evt
|
|
|
|
if not evt and self._dbq:
|
|
evt = self._dbq.getEvent(org.publicID())
|
|
|
|
author, agencyID, eType, prefMagID = '', '', '', ''
|
|
if evt:
|
|
evid = evt.publicID()
|
|
pos = evid.find("#") # XXX Hack!!!
|
|
if pos != -1:
|
|
evid = evid[:pos]
|
|
|
|
try:
|
|
prefMagID = evt.preferredMagnitudeID()
|
|
except ValueError:
|
|
pass
|
|
|
|
try:
|
|
author = org.creationInfo().author()
|
|
except ValueError:
|
|
pass
|
|
|
|
if self.useEventAgencyID:
|
|
try:
|
|
agencyID = evt.creationInfo().agencyID()
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
try:
|
|
agencyID = org.creationInfo().agencyID()
|
|
except ValueError:
|
|
pass
|
|
|
|
try:
|
|
eType = evt.type()
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
evid = ''
|
|
prefMagID = ''
|
|
|
|
if self.enhanced:
|
|
depth = "{:.3f}".format(org.depth().value())
|
|
sTime = org.time().value().toString('%FT%T.%f')[:26]
|
|
else:
|
|
depth = "{:.0f}".format(org.depth().value())
|
|
sTime = org.time().value().toString('%FT%T.%f')[:23]
|
|
|
|
lat = lat2str(org.latitude().value(), self.enhanced)
|
|
lon = lon2str(org.longitude().value(), self.enhanced)
|
|
|
|
try:
|
|
region = seiscomp.seismology.Regions.getRegionName(org.latitude().value(), org.longitude().value())
|
|
except ValueError:
|
|
region = ''
|
|
|
|
foundMag = False
|
|
mType, mVal, mAuthor = '', '', ''
|
|
networkMagnitudeCount = org.magnitudeCount()
|
|
for i in range(networkMagnitudeCount):
|
|
mag = org.magnitude(i)
|
|
if mag.publicID() == prefMagID:
|
|
mType = mag.type()
|
|
mVal = "{:.1f}".format(mag.magnitude().value())
|
|
foundMag = True
|
|
break
|
|
|
|
if not foundMag and prefMagID != "":
|
|
mag = seiscomp.datamodel.Magnitude.Find(prefMagID)
|
|
if mag is None and self._dbq:
|
|
o = self._dbq.loadObject(
|
|
seiscomp.datamodel.Magnitude.TypeInfo(), prefMagID)
|
|
mag = seiscomp.datamodel.Magnitude.Cast(o)
|
|
|
|
if mag:
|
|
mType = mag.type()
|
|
mVal = "{:.1f}".format(mag.magnitude().value())
|
|
try:
|
|
mAuthor = mag.creationInfo().author()
|
|
except ValueError:
|
|
pass
|
|
txt = "%s|%s|%s|%s|%s|%s||%s|%s|%s|%s|%s|%s|%s\n" % (
|
|
evid, sTime, lat, lon, depth, author, agencyID, evid, mType,
|
|
mVal, mAuthor, region, eType)
|
|
|
|
return txt
|
|
|
|
def printOrigin(self, origin):
|
|
org = None
|
|
if isinstance(origin, seiscomp.datamodel.Origin):
|
|
org = origin
|
|
elif isinstance(origin, str):
|
|
if self._dbq:
|
|
org = self._dbq.loadObject(
|
|
seiscomp.datamodel.Origin.TypeInfo(), origin)
|
|
org = seiscomp.datamodel.Origin.Cast(org)
|
|
if not org:
|
|
raise KeyError("Unknown origin " + origin)
|
|
else:
|
|
raise TypeError("illegal type for origin")
|
|
|
|
if self.format == "fdsnws":
|
|
return self._printOriginFDSN(org)
|
|
elif self.format == "autoloc1":
|
|
return self._printOriginAutoloc1(org)
|
|
elif self.format == "autoloc3":
|
|
return self._printOriginAutoloc3(org, extra=False)
|
|
elif self.format == "autoloc3extra":
|
|
return self._printOriginAutoloc3(org, extra=True)
|
|
else:
|
|
pass
|
|
|
|
def printEvent(self, event):
|
|
try:
|
|
evt = None
|
|
if isinstance(event, seiscomp.datamodel.Event):
|
|
self._evt = event
|
|
org = seiscomp.datamodel.Origin.Find(
|
|
event.preferredOriginID())
|
|
if not org:
|
|
org = event.preferredOriginID()
|
|
return self.printOrigin(org)
|
|
elif isinstance(event, str):
|
|
if self._dbq:
|
|
evt = self._dbq.loadObject(
|
|
seiscomp.datamodel.Event.TypeInfo(), event)
|
|
evt = seiscomp.datamodel.Event.Cast(evt)
|
|
self._evt = evt
|
|
if evt is None:
|
|
raise KeyError("unknown event " + event)
|
|
return self.printOrigin(evt.preferredOriginID())
|
|
else:
|
|
raise TypeError("illegal type for event")
|
|
finally:
|
|
self._evt = None
|
|
|
|
|
|
class BulletinApp(seiscomp.client.Application):
|
|
def __init__(self, argc, argv):
|
|
seiscomp.client.Application.__init__(self, argc, argv)
|
|
self.setMessagingEnabled(False)
|
|
self.setDatabaseEnabled(True, True)
|
|
self.setDaemonEnabled(False)
|
|
self.setLoggingToStdErr(True)
|
|
self.setLoadRegionsEnabled(True)
|
|
|
|
self.format = "autoloc1"
|
|
self.inputFile = None
|
|
|
|
def createCommandLineDescription(self):
|
|
try:
|
|
self.commandline().addGroup("Input")
|
|
self.commandline().addStringOption("Input", "format,f",
|
|
"Input format to use (xml "
|
|
"[default], zxml (zipped xml), "
|
|
"binary).")
|
|
self.commandline().addStringOption("Input", "input,i",
|
|
"Input file, default: stdin.")
|
|
|
|
self.commandline().addGroup("Dump")
|
|
self.commandline().addStringOption("Dump", "event,E",
|
|
"ID of event to dump. Separate "
|
|
"multiple IDs by comma.")
|
|
self.commandline().addStringOption("Dump", "origin,O",
|
|
"ID of origin to dump. Separate"
|
|
" multiple IDs by comma.")
|
|
self.commandline().addOption("Dump", "autoloc1,1",
|
|
"autoloc1 format.")
|
|
self.commandline().addOption("Dump", "autoloc3,3",
|
|
"autoloc3 format")
|
|
self.commandline().addOption("Dump", "enhanced,e",
|
|
"Enhanced output precision for local "
|
|
"earthquakes.")
|
|
self.commandline().addOption("Dump", "event-agency-id",
|
|
"Use agency ID information from event"
|
|
" instead of preferred origin.")
|
|
self.commandline().addOption("Dump", "fdsnws",
|
|
"Dump in FDSNWS event text format, "
|
|
"e.g., for generating catalogs.")
|
|
self.commandline().addOption("Dump", "first-only",
|
|
"Dump only the first event/origin. "
|
|
"Expects input from file or stdin.")
|
|
self.commandline().addOption("Dump", "dist-in-km,k",
|
|
"Plot distances in km instead of"
|
|
" degree.")
|
|
self.commandline().addOption("Dump", "polarities,p",
|
|
"Dump onset polarities.")
|
|
self.commandline().addStringOption("Dump", "weight,w",
|
|
"Weight threshold for printed "
|
|
"and counted picks.")
|
|
self.commandline().addOption("Dump", "extra,x",
|
|
"Extra detailed autoloc3 format.")
|
|
|
|
except RuntimeError:
|
|
seiscomp.logging.warning(
|
|
"caught unexpected error %s" % sys.exc_info())
|
|
|
|
return True
|
|
|
|
def validateParameters(self):
|
|
if not seiscomp.client.Application.validateParameters(self):
|
|
return False
|
|
|
|
try:
|
|
self.inputFile = self.commandline().optionString("input")
|
|
except RuntimeError:
|
|
self.inputFile = None
|
|
|
|
if self.inputFile:
|
|
self.setDatabaseEnabled(False, False)
|
|
|
|
if not self.commandline().hasOption("event") and not self.commandline().hasOption("origin"):
|
|
self.setDatabaseEnabled(False, False)
|
|
|
|
return True
|
|
|
|
def printUsage(self):
|
|
|
|
print('''Usage:
|
|
scbulletin [options]
|
|
|
|
Generate bulletins from events or origins in autoloc1, autoloc3 or fdsnws format.''')
|
|
|
|
seiscomp.client.Application.printUsage(self)
|
|
|
|
print('''Examples:
|
|
Create a bulletin from one event in the seiscomp database
|
|
scbulletin -d mysql://sysop:sysop@localhost/seiscomp -E gempa2012abcd
|
|
|
|
Create a bulletin from event parameters in XML
|
|
scbulletin -i gempa2012abcd.xml
|
|
''')
|
|
return True
|
|
|
|
def run(self):
|
|
evid = None
|
|
orid = None
|
|
mw = None
|
|
inputFile = None
|
|
txt = None
|
|
|
|
try:
|
|
evid = self.commandline().optionString("event")
|
|
except RuntimeError:
|
|
pass
|
|
|
|
try:
|
|
orid = self.commandline().optionString("origin")
|
|
except RuntimeError:
|
|
pass
|
|
|
|
if evid != "" or orid != "" or not self.inputFile:
|
|
dbq = seiscomp.datamodel.DatabaseQuery(self.database())
|
|
else:
|
|
dbq = None
|
|
|
|
bulletin = Bulletin(dbq)
|
|
bulletin.format = "autoloc1"
|
|
|
|
try:
|
|
mw = self.commandline().optionString("weight")
|
|
except RuntimeError:
|
|
pass
|
|
|
|
if mw != "" and mw is not None:
|
|
bulletin.minArrivalWeight = float(mw)
|
|
|
|
if self.commandline().hasOption("autoloc1"):
|
|
bulletin.format = "autoloc1"
|
|
elif self.commandline().hasOption("autoloc3"):
|
|
if self.commandline().hasOption("extra"):
|
|
bulletin.format = "autoloc3extra"
|
|
else:
|
|
bulletin.format = "autoloc3"
|
|
|
|
if self.commandline().hasOption("fdsnws"):
|
|
bulletin.format = "fdsnws"
|
|
|
|
if self.commandline().hasOption("enhanced"):
|
|
bulletin.enhanced = True
|
|
|
|
if self.commandline().hasOption("polarities"):
|
|
bulletin.polarities = True
|
|
|
|
if self.commandline().hasOption("event-agency-id"):
|
|
bulletin.useEventAgencyID = True
|
|
|
|
if self.commandline().hasOption("dist-in-km"):
|
|
bulletin.distInKM = True
|
|
|
|
inputFile = self.inputFile
|
|
|
|
if not self.inputFile:
|
|
txt = ''
|
|
try:
|
|
if evid:
|
|
for ev in evid.split(','):
|
|
try:
|
|
txt += bulletin.printEvent(ev)
|
|
except ValueError:
|
|
seiscomp.logging.error("Unknown event '%s'" % ev)
|
|
elif orid:
|
|
for org in orid.split(','):
|
|
try:
|
|
txt += bulletin.printOrigin(org)
|
|
except ValueError:
|
|
seiscomp.logging.error("Unknown origin '%s'" % org)
|
|
else:
|
|
inputFile = "-"
|
|
print("Expecting input in XML from stdin", file=sys.stderr)
|
|
|
|
except Exception as exc:
|
|
print("ERROR: {}".format(exc), file=sys.stderr)
|
|
# return False
|
|
else:
|
|
inputFile = self.inputFile
|
|
|
|
if inputFile:
|
|
inputFormat = "xml"
|
|
try:
|
|
try:
|
|
inputFormat = self.commandline().optionString("format")
|
|
except RuntimeError:
|
|
pass
|
|
|
|
if inputFormat == "xml":
|
|
ar = seiscomp.io.XMLArchive()
|
|
elif inputFormat == "zxml":
|
|
ar = seiscomp.io.XMLArchive()
|
|
ar.setCompression(True)
|
|
elif inputFormat == "binary":
|
|
ar = seiscomp.io.BinaryArchive()
|
|
else:
|
|
raise TypeError("unknown input format: " + inputFormat)
|
|
|
|
if not ar.open(inputFile):
|
|
raise IOError("Unable to open input file " + inputFile)
|
|
|
|
obj = ar.readObject()
|
|
if obj is None:
|
|
raise IOError("Invalid format in " + inputFile)
|
|
|
|
ep = seiscomp.datamodel.EventParameters.Cast(obj)
|
|
if ep is None:
|
|
raise TypeError("No event parameters found in "
|
|
+ inputFile)
|
|
|
|
if ep.eventCount() <= 0:
|
|
if ep.originCount() <= 0:
|
|
raise TypeError("No origin and no event in event "
|
|
"parameters found in " + inputFile)
|
|
|
|
if self.commandline().hasOption("first-only"):
|
|
org = ep.origin(0)
|
|
txt = bulletin.printOrigin(org)
|
|
else:
|
|
txt = ""
|
|
for i in range(ep.originCount()):
|
|
org = ep.origin(i)
|
|
if orid and org.publicID() not in orid:
|
|
seiscomp.logging.error(
|
|
"%s: Skipping origin with ID %s" %
|
|
(inputFile, org.publicID()))
|
|
continue
|
|
txt += bulletin.printOrigin(org)
|
|
else:
|
|
if self.commandline().hasOption("first-only"):
|
|
ev = ep.event(0)
|
|
if ev is None:
|
|
raise TypeError("Invalid event in " + inputFile)
|
|
|
|
try:
|
|
txt = bulletin.printEvent(ev)
|
|
except KeyError:
|
|
raise TypeError("Unknown event")
|
|
elif orid:
|
|
txt = ""
|
|
for oid in orid.split(','):
|
|
org = ep.findOrigin(oid)
|
|
if org:
|
|
txt += bulletin.printOrigin(org)
|
|
else:
|
|
seiscomp.logging.error("%s: Skipping origin with ID %s" %
|
|
(inputFile, oid))
|
|
else:
|
|
txt = ""
|
|
for i in range(ep.eventCount()):
|
|
ev = ep.event(i)
|
|
if evid and ev.publicID() not in evid:
|
|
seiscomp.logging.error("%s: Skipping event with ID %s" %
|
|
(inputFile, ev.publicID()))
|
|
continue
|
|
|
|
try:
|
|
txt += bulletin.printEvent(ev)
|
|
except KeyError:
|
|
raise TypeError("Unknown event")
|
|
|
|
except Exception as exc:
|
|
seiscomp.logging.error("%s" % str(exc))
|
|
return False
|
|
|
|
if txt:
|
|
|
|
if bulletin.format == "fdsnws":
|
|
print("#EventID|Time|Latitude|Longitude|Depth/km|Author|"
|
|
"Catalog|Contributor|ContributorID|MagType|Magnitude|"
|
|
"MagAuthor|EventLocationName|EventType")
|
|
print("{}".format(txt), file=sys.stdout)
|
|
|
|
return True
|
|
|
|
|
|
def main():
|
|
app = BulletinApp(len(sys.argv), sys.argv)
|
|
return app()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|