Commit c4365e84 authored by Peter Evans's avatar Peter Evans
Browse files

import_fdsnws_eq: Local file parsing and test code

- Allow operating on a local QuakeML event file.
- Add unit tests for this.
parent d4652715
......@@ -4,7 +4,7 @@
#
# Author: Peter L. Evans <pevans@gfz-potsdam.de>
#
# Copyright (C) 2018 Helmholtz-Zentrum Potsdam - Deutsches GeoForschungsZentrum GFZ
# Copyright (C) 2021 Helmholtz-Zentrum Potsdam - Deutsches GeoForschungsZentrum GFZ
#
# This software is free software and comes with ABSOLUTELY NO WARRANTY.
#
......@@ -18,6 +18,7 @@ Can run standalone:
"""
import datetime
import os
import sys
import urllib.request as ul
from xml.etree import ElementTree as ET
......@@ -33,9 +34,9 @@ def fetch_fm(root, ns, preferredfmID):
Extract interesting properties from the focal mechanism with
the given publicID.
root - ElementTree root element.
ns - string, its namespace.
preferredfmID - string, the focal mechanism to hunt for.
root: ElementTree root element.
ns (string): string, its namespace.
preferredfmID (string): the focal mechanism to hunt for.
"""
for fm in root[0][0].findall("ns:focalMechanism", ns):
......@@ -82,9 +83,9 @@ def fetch_origin(root, ns, preferredoriginID):
"""
Extract interesting properties from the origin with given publicID.
root - ElementTree root element.
ns - string, its namespace.
preferredoriginID - string, the origin to hunt for.
root: ElementTree root element.
ns (string): the XML object's namespace.
preferredoriginID (string): the origin to hunt for.
"""
for o in root[0][0].findall("ns:origin", ns):
if o.attrib["publicID"] != preferredoriginID:
......@@ -107,14 +108,13 @@ def fetch_origin(root, ns, preferredoriginID):
return d
def fetch_quakeml(evid):
def fetch_quakeml_ws(evid):
"""
Query fdsnws-event web service, and prepare a dictionary with the
interesting things found by reading the QuakeML file served for a given event.
Query fdsnws-event web service, and return string
to fetch_quakeml() for parsing.
If there is no focal mechanism, return None where that would otherwise go.
evid (string): the event ID to query for.
evid - string, the event ID to query for.
"""
url = (
FDSNWS_ENDPOINT
......@@ -136,10 +136,45 @@ def fetch_quakeml(evid):
print("Got", len(buf), "char(s).")
with open("tmp.xml", "w") as fid:
fid.write(str(buf))
return buf
def fetch_quakeml(path):
"""
Prepare a dictionary holding the interesting things found by
reading the QuakeML file served for a given event.
If there is no focal mechanism, return None where that would otherwise go.
path (string): Either a local file name, or a GEOFON event ID
such as 'gfz2044sqpr'.
"""
if os.path.exists(path):
with open(path) as fid:
buf = fid.read()
elif path.startswith("gfz") and len(path) == len("gfz2044sqpr"):
buf = fetch_quakeml_ws(path)
else:
raise IOError("Not a local path or a GEOFON event ID")
root = ET.fromstring(buf)
ns = QUAKEML_NS
event = root[0][0]
# .find("ns:event", ns)
if not event:
print("Couldn't get an event!")
return None
if "{" + ns["ns"] + "}event" != event.tag:
print("Got a", event.tag, "but expected an event")
return None
try:
# e.g. "smi:org.gfz-potsdam.de/geofon/gfz2021ekhv"
evid = event.attrib["publicID"].split("/")[-1]
except AttributeError:
print("Oops")
preferredoriginID = root[0][0].find("ns:preferredOriginID", ns).text
preferredmagID = root[0][0].find("ns:preferredMagnitudeID", ns).text
try:
......
<?xml version="1.0" encoding="UTF-8"?><q:quakeml xmlns="http://quakeml.org/xmlns/bed/1.2" xmlns:q="http://quakeml.org/xmlns/quakeml/1.2"><eventParameters publicID="smi:org.gfz-potsdam.de/geofon/EventParameters"><event publicID="smi:org.gfz-potsdam.de/geofon/gfz2021ekhv"><description><text>Off E. Coast of N. Island, N.Z.</text><type>region name</type></description><creationInfo><agencyID>GFZ</agencyID><creationTime>2021-03-04T13:33:25.96342Z</creationTime></creationInfo><focalMechanism publicID="smi:org.gfz-potsdam.de/geofon/FocalMechanism/20210304152924.36933.1998992"><momentTensor publicID="smi:org.gfz-potsdam.de/geofon/MomentTensor/20210304152924.369853.1998995"><scalarMoment><value>7.705753735e+19</value></scalarMoment><tensor><Mrr><value>1.744746227e+19</value></Mrr><Mtt><value>-7.84343467e+19</value></Mtt><Mpp><value>6.098688444e+19</value></Mpp><Mrt><value>-1.125507602e+19</value></Mrt><Mrp><value>2.367758042e+19</value></Mrp><Mtp><value>-1.275457308e+19</value></Mtp></tensor><varianceReduction>0.9360097745</varianceReduction><doubleCouple>0.8172941413</doubleCouple><clvd>0.1827058587</clvd><creationInfo><agencyID>GFZ</agencyID><creationTime>2021-03-04T15:29:24.368901Z</creationTime></creationInfo><derivedOriginID>smi:org.gfz-potsdam.de/geofon/Origin/20210304152924.369602.1998993</derivedOriginID><momentMagnitudeID>smi:org.gfz-potsdam.de/geofon/Magnitude/20210304152924.369708.1998994</momentMagnitudeID><greensFunctionID>smi:org.gfz-potsdam.de/geofon/saul_/gemini-prem</greensFunctionID><filterID>smi:org.gfz-potsdam.de/geofon/BP_200s-600s</filterID></momentTensor><nodalPlanes><nodalPlane1><strike><value>307.1772288</value></strike><dip><value>77.29754332</value></dip><rake><value>158.5867752</value></rake></nodalPlane1><nodalPlane2><strike><value>42.10575122</value></strike><dip><value>69.13568422</value></dip><rake><value>13.6104104</value></rake></nodalPlane2></nodalPlanes><principalAxes><tAxis><azimuth><value>263.2979461</value></azimuth><plunge><value>24.03688788</value></plunge><length><value>7.311853502e+19</value></length></tAxis><pAxis><azimuth><value>355.7725032</value></azimuth><plunge><value>5.529375306</value></plunge><length><value>-8.046967561e+19</value></length></pAxis><nAxis><azimuth><value>97.89756857</value></azimuth><plunge><value>65.25588231</value></plunge><length><value>7.351140591e+18</value></length></nAxis></principalAxes><azimuthalGap>26.47275083</azimuthalGap><misfit>0.0639902255</misfit><creationInfo><agencyID>GFZ</agencyID><creationTime>2021-03-04T15:29:24.368901Z</creationTime></creationInfo><triggeringOriginID>smi:org.gfz-potsdam.de/geofon/Origin/20210304151039.157235.2466933</triggeringOriginID></focalMechanism><magnitude publicID="smi:org.gfz-potsdam.de/geofon/Magnitude/20210304152924.369708.1998994"><stationCount>106</stationCount><azimuthalGap>26.47275083</azimuthalGap><creationInfo><agencyID>GFZ</agencyID><creationTime>2021-03-04T15:29:24.368901Z</creationTime></creationInfo><mag><value>7.191210084</value></mag><type>Mw</type><methodID>smi:org.gfz-potsdam.de/geofon/MT</methodID></magnitude><origin publicID="smi:org.gfz-potsdam.de/geofon/Origin/20210304155627.269946.2475276"><time><value>2021-03-04T13:27:38.607455Z</value><uncertainty>0.7365945187</uncertainty></time><longitude><value>179.3604736</value><uncertainty>1.763208609</uncertainty></longitude><latitude><value>-37.37611389</value><uncertainty>1.978016315</uncertainty></latitude><quality><associatedPhaseCount>564</associatedPhaseCount><usedPhaseCount>186</usedPhaseCount><associatedStationCount>561</associatedStationCount><usedStationCount>186</usedStationCount><depthPhaseCount>0</depthPhaseCount><standardError>1.592724088</standardError><azimuthalGap>23.19470215</azimuthalGap><maximumDistance>114.4872208</maximumDistance><minimumDistance>1.98769486</minimumDistance><medianDistance>77.20787048</medianDistance></quality><evaluationMode>manual</evaluationMode><creationInfo><agencyID>GFZ</agencyID><creationTime>2021-03-04T15:56:27.282369Z</creationTime></creationInfo><depth><value>34801.78452</value><uncertainty>5773.60202</uncertainty></depth><originUncertainty><minHorizontalUncertainty>3743.951082</minHorizontalUncertainty><maxHorizontalUncertainty>4381.9561</maxHorizontalUncertainty><azimuthMaxHorizontalUncertainty>21.41514397</azimuthMaxHorizontalUncertainty><preferredDescription>horizontal uncertainty</preferredDescription></originUncertainty><methodID>smi:org.gfz-potsdam.de/geofon/LOCSAT</methodID><earthModelID>smi:org.gfz-potsdam.de/geofon/iasp91</earthModelID><evaluationStatus>confirmed</evaluationStatus></origin><preferredOriginID>smi:org.gfz-potsdam.de/geofon/Origin/20210304155627.269946.2475276</preferredOriginID><preferredMagnitudeID>smi:org.gfz-potsdam.de/geofon/Magnitude/20210304152924.369708.1998994</preferredMagnitudeID><preferredFocalMechanismID>smi:org.gfz-potsdam.de/geofon/FocalMechanism/20210304152924.36933.1998992</preferredFocalMechanismID><type>earthquake</type></event></eventParameters></q:quakeml>
#
# Author: Peter L. Evans <pevans@gfz-potsdam.de>
#
# Copyright (C) 2021 Helmholtz-Zentrum Potsdam - Deutsches GeoForschungsZentrum GFZ
#
# This software is free software and comes with ABSOLUTELY NO WARRANTY.
#
# ----------------------------------------------------------------------
import os
import unittest
from shakyground2.import_fdsnws_eq import fetch_quakeml
class QuakeMLReadTestCase(unittest.TestCase):
"""Test on digesting QuakeML from GEOFON.
Beyond the basic test (read in QuakeML, export YAML), this would
also test the tricker input files: no focal mech available, focal
mech missing, multiple FMs, bad or incomplete XML, non-existent or
unreadable input files, and so on.
"""
outfile = "data/testoutput.yaml"
def test_read_good_file(self):
infile = "data/gfz2021ekhv.xml"
result = fetch_quakeml(infile)
self.assertTrue(isinstance(result, dict))
self.assertEqual(result["id"], "gfz2021ekhv")
def test_read_good_eventid(self):
"""
Might fail if the GEOFON fdsnws-event webservice is unavailable.
"""
evid = "gfz2021ekhv"
result = fetch_quakeml(evid)
self.assertTrue(isinstance(result, dict))
def test_read_no_eventid(self):
"""
The function is given a string which isn't a file and isn't an
event id other. So raise an error.
"""
infile = "/no/such/file"
with self.assertRaises(IOError):
result = fetch_quakeml(infile)
def tearDown(self):
try:
os.unlink(self.outfile)
except FileNotFoundError:
pass
if __name__ == "__main__":
unittest.main()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment