Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QuakeML: fix a bug in reading QuakeML (empty stub MomentTensor added) #1896

Merged
merged 6 commits into from Sep 20, 2017
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 20 additions & 18 deletions obspy/io/quakeml/core.py
Expand Up @@ -696,11 +696,11 @@ def _nodal_plane(self, parent, name):
:param name: tag name of sub nodal plane
:rtype: :class:`~obspy.core.event.NodalPlane`
"""
obj = NodalPlane()
try:
sub_el = self._xpath(name, parent)[0]
except IndexError:
return obj
return None
obj = NodalPlane()
# required parameter
obj.strike, obj.strike_errors = self._float_value(sub_el, 'strike')
obj.dip, obj.dip_errors = self._float_value(sub_el, 'dip')
Expand All @@ -715,11 +715,11 @@ def _nodal_planes(self, parent):
:type parent: etree.Element
:rtype: :class:`~obspy.core.event.NodalPlanes`
"""
obj = NodalPlanes()
try:
sub_el = self._xpath('nodalPlanes', parent)[0]
except IndexError:
return obj
return None
obj = NodalPlanes()
# optional parameter
obj.nodal_plane_1 = self._nodal_plane(sub_el, 'nodalPlane1')
obj.nodal_plane_2 = self._nodal_plane(sub_el, 'nodalPlane2')
Expand All @@ -738,11 +738,11 @@ def _source_time_function(self, parent):
:type parent: etree.Element
:rtype: :class:`~obspy.core.event.SourceTimeFunction`
"""
obj = SourceTimeFunction()
try:
sub_el = self._xpath('sourceTimeFunction', parent)[0]
except IndexError:
return obj
return None
obj = SourceTimeFunction()
# required parameters
obj.type = self._xpath2obj('type', sub_el)
obj.duration = self._xpath2obj('duration', sub_el, float)
Expand All @@ -759,11 +759,11 @@ def _tensor(self, parent):
:type parent: etree.Element
:rtype: :class:`~obspy.core.event.Tensor`
"""
obj = Tensor()
try:
sub_el = self._xpath('tensor', parent)[0]
except IndexError:
return obj
return None
obj = Tensor()
# required parameters
obj.m_rr, obj.m_rr_errors = self._float_value(sub_el, 'Mrr')
obj.m_tt, obj.m_tt_errors = self._float_value(sub_el, 'Mtt')
Expand Down Expand Up @@ -807,11 +807,11 @@ def _moment_tensor(self, parent):
:type parent: etree.Element
:rtype: :class:`~obspy.core.event.MomentTensor`
"""
obj = MomentTensor(force_resource_id=False)
try:
mt_el = self._xpath('momentTensor', parent)[0]
except IndexError:
return obj
return None
obj = MomentTensor(force_resource_id=False)
# required parameters
obj.derived_origin_id = self._xpath2obj('derivedOriginID', mt_el)
# optional parameter
Expand Down Expand Up @@ -1525,9 +1525,11 @@ def _nodal_planes(self, obj, element):
"""
Converts a NodalPlanes into etree.Element object.

:type pick: :class:`~obspy.core.event.NodalPlanes`
:type obj: :class:`~obspy.core.event.NodalPlanes`
:rtype: etree.Element
"""
if obj is None:
return
subelement = etree.Element('nodalPlanes')
# optional
if obj.nodal_plane_1:
Expand Down Expand Up @@ -1561,7 +1563,7 @@ def _principal_axes(self, obj, element):
"""
Converts a PrincipalAxes into etree.Element object.

:type pick: :class:`~obspy.core.event.PrincipalAxes`
:type obj: :class:`~obspy.core.event.PrincipalAxes`
:rtype: etree.Element
"""
if obj is None:
Expand Down Expand Up @@ -1605,7 +1607,7 @@ def _moment_tensor(self, moment_tensor, element):
"""
Converts a MomentTensor into etree.Element object.

:type pick: :class:`~obspy.core.event.MomentTensor`
:type moment_tensor: :class:`~obspy.core.event.MomentTensor`
:rtype: etree.Element
"""
if moment_tensor is None:
Expand Down Expand Up @@ -1671,9 +1673,11 @@ def _focal_mechanism(self, focal_mechanism):
"""
Converts a FocalMechanism into etree.Element object.

:type pick: :class:`~obspy.core.event.FocalMechanism`
:type focal_mechanism: :class:`~obspy.core.event.FocalMechanism`
:rtype: etree.Element
"""
if focal_mechanism is None:
return
element = etree.Element(
'focalMechanism',
attrib={'publicID': self._id(focal_mechanism.resource_id)})
Expand All @@ -1688,10 +1692,8 @@ def _focal_mechanism(self, focal_mechanism):
self._str(focal_mechanism.misfit, element, 'misfit')
self._str(focal_mechanism.station_distribution_ratio, element,
'stationDistributionRatio')
if focal_mechanism.nodal_planes:
self._nodal_planes(focal_mechanism.nodal_planes, element)
if focal_mechanism.principal_axes:
self._principal_axes(focal_mechanism.principal_axes, element)
self._nodal_planes(focal_mechanism.nodal_planes, element)
self._principal_axes(focal_mechanism.principal_axes, element)
self._str(focal_mechanism.method_id, element, 'methodID')
self._moment_tensor(focal_mechanism.moment_tensor, element)
self._str(focal_mechanism.evaluation_mode, element, 'evaluationMode')
Expand Down
53 changes: 53 additions & 0 deletions obspy/io/quakeml/tests/test_quakeml.py
Expand Up @@ -1009,6 +1009,59 @@ def test_read_same_file_twice_to_same_variable(self):
# No warning should have been raised.
self.assertEqual(len(w), 0)

def test_focal_mechanism_write_read(self):
"""
Test for a bug in reading a FocalMechanism without MomentTensor from
QuakeML file. Makes sure that FocalMechanism.moment_tensor stays None
if no MomentTensor is in the file.
"""
memfile = io.BytesIO()
# create virtually empty FocalMechanism
fm = FocalMechanism()
event = Event(focal_mechanisms=[fm])
cat = Catalog(events=[event])
cat.write(memfile, format="QUAKEML", validate=True)
# now read again, and make sure there's no stub MomentTensor, but
# rather `None`
memfile.seek(0)
cat = read_events(memfile, format="QUAKEML")
self.assertEqual(cat[0].focal_mechanisms[0].moment_tensor, None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also write it out again with validate=True to test the behavior because of which you discovered the bug?


def test_avoid_empty_stub_elements(self):
"""
Test for a bug in reading QuakeML. Makes sure that some subelements do
not get assigned stub elements, but rather stay None.
"""
# Test 1: Test subelements of moment_tensor
memfile = io.BytesIO()
# create virtually empty FocalMechanism
mt = MomentTensor(derived_origin_id='smi:local/abc')
fm = FocalMechanism(moment_tensor=mt)
event = Event(focal_mechanisms=[fm])
cat = Catalog(events=[event])
cat.write(memfile, format="QUAKEML", validate=True)
# now read again, and make sure there's no stub subelements on
# MomentTensor, but rather `None`
memfile.seek(0)
cat = read_events(memfile, format="QUAKEML")
self.assertEqual(cat[0].focal_mechanisms[0].moment_tensor.tensor, None)
self.assertEqual(
cat[0].focal_mechanisms[0].moment_tensor.source_time_function,
None)
# Test 2: Test subelements of focal_mechanism
memfile = io.BytesIO()
# create virtually empty FocalMechanism
fm = FocalMechanism()
event = Event(focal_mechanisms=[fm])
cat = Catalog(events=[event])
cat.write(memfile, format="QUAKEML", validate=True)
# now read again, and make sure there's no stub MomentTensor, but
# rather `None`
memfile.seek(0)
cat = read_events(memfile, format="QUAKEML")
self.assertEqual(cat[0].focal_mechanisms[0].nodal_planes, None)
self.assertEqual(cat[0].focal_mechanisms[0].principal_axes, None)


def suite():
return unittest.makeSuite(QuakeMLTestCase, 'test')
Expand Down