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 @@ ...@@ -4,7 +4,7 @@
# #
# Author: Peter L. Evans <pevans@gfz-potsdam.de> # 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. # This software is free software and comes with ABSOLUTELY NO WARRANTY.
# #
...@@ -18,6 +18,7 @@ Can run standalone: ...@@ -18,6 +18,7 @@ Can run standalone:
""" """
import datetime import datetime
import os
import sys import sys
import urllib.request as ul import urllib.request as ul
from xml.etree import ElementTree as ET from xml.etree import ElementTree as ET
...@@ -33,9 +34,9 @@ def fetch_fm(root, ns, preferredfmID): ...@@ -33,9 +34,9 @@ def fetch_fm(root, ns, preferredfmID):
Extract interesting properties from the focal mechanism with Extract interesting properties from the focal mechanism with
the given publicID. the given publicID.
root - ElementTree root element. root: ElementTree root element.
ns - string, its namespace. ns (string): string, its namespace.
preferredfmID - string, the focal mechanism to hunt for. preferredfmID (string): the focal mechanism to hunt for.
""" """
for fm in root[0][0].findall("ns:focalMechanism", ns): for fm in root[0][0].findall("ns:focalMechanism", ns):
...@@ -82,9 +83,9 @@ def fetch_origin(root, ns, preferredoriginID): ...@@ -82,9 +83,9 @@ def fetch_origin(root, ns, preferredoriginID):
""" """
Extract interesting properties from the origin with given publicID. Extract interesting properties from the origin with given publicID.
root - ElementTree root element. root: ElementTree root element.
ns - string, its namespace. ns (string): the XML object's namespace.
preferredoriginID - string, the origin to hunt for. preferredoriginID (string): the origin to hunt for.
""" """
for o in root[0][0].findall("ns:origin", ns): for o in root[0][0].findall("ns:origin", ns):
if o.attrib["publicID"] != preferredoriginID: if o.attrib["publicID"] != preferredoriginID:
...@@ -107,14 +108,13 @@ def fetch_origin(root, ns, preferredoriginID): ...@@ -107,14 +108,13 @@ def fetch_origin(root, ns, preferredoriginID):
return d return d
def fetch_quakeml(evid): def fetch_quakeml_ws(evid):
""" """
Query fdsnws-event web service, and prepare a dictionary with the Query fdsnws-event web service, and return string
interesting things found by reading the QuakeML file served for a given event. 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 = ( url = (
FDSNWS_ENDPOINT FDSNWS_ENDPOINT
...@@ -136,10 +136,45 @@ def fetch_quakeml(evid): ...@@ -136,10 +136,45 @@ def fetch_quakeml(evid):
print("Got", len(buf), "char(s).") print("Got", len(buf), "char(s).")
with open("tmp.xml", "w") as fid: with open("tmp.xml", "w") as fid:
fid.write(str(buf)) 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) root = ET.fromstring(buf)
ns = QUAKEML_NS 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 preferredoriginID = root[0][0].find("ns:preferredOriginID", ns).text
preferredmagID = root[0][0].find("ns:preferredMagnitudeID", ns).text preferredmagID = root[0][0].find("ns:preferredMagnitudeID", ns).text
try: 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