"""RESQML GeologicUnitInterpretation class."""
# NB: in this module, the term 'unit' refers to a geological stratigraphic unit, i.e. a layer of rock, not a unit of measure
import logging
log = logging.getLogger(__name__)
import resqpy.olio.uuid as bu
import resqpy.olio.xml_et as rqet
import resqpy.organize as rqo
import resqpy.strata
import resqpy.strata._strata_common as rqstc
from resqpy.olio.base import BaseResqpy
from resqpy.olio.xml_namespaces import curly_namespace as ns
class GeologicUnitInterpretation(BaseResqpy):
"""Class for RESQML Geologic Unit Interpretation objects.
These objects can be parts in their own right. NB: Various more specialised classes also derive from this.
RESQML documentation:
The main class for data describing an opinion of a volume-based geologic feature or unit.
"""
resqml_type = 'GeologicUnitInterpretation'
[docs] def __init__(
self,
parent_model,
uuid = None,
title = None,
domain = 'time', # or should this be depth?
geologic_unit_feature = None,
composition = None,
material_implacement = None,
extra_metadata = None):
"""Initialises an geologic unit interpretation object.
arguments:
parent_model (model.Model): the model with which the new interpretation will be associated
uuid (uuid.UUID, optional): the uuid of an existing RESQML geologic unit interpretation from which
this object will be initialised
title (str, optional): the citation title (feature name) of the new interpretation;
ignored if uuid is not None
domain (str, default 'time'): 'time', 'depth' or 'mixed', being the domain of the interpretation;
ignored if uuid is not None
geologic_unit_feature (organize.GeologicUnitFeature or StratigraphicUnitFeature, optional): the feature
which this object is an interpretation of; ignored if uuid is not None
composition (str, optional): the interpreted composition of the geologic unit; if present, must be
in valid_compositions; ignored if uuid is not None
material_implacement (str, optional): the interpeted material implacement of the geologic unit;
if present, must be in valid_implacements, ie. 'autochtonous' or 'allochtonous';
ignored if uuid is not None
extra_metadata (dict, optional): extra metadata items for the new interpretation
returns:
a new geologic unit interpretation resqpy object which may be the basis of a derived class object
note:
the RESQML 2.0.1 schema definition includes a spurious trailing space in the names of two compositions;
resqpy removes such spaces in the composition attribute as presented to calling code (but includes them
in xml)
"""
self.domain = domain
self.geologic_unit_feature = geologic_unit_feature # InterpretedFeature RESQML field
self.has_occurred_during = (None, None) # optional RESQML item
if (not title) and geologic_unit_feature is not None:
title = geologic_unit_feature.feature_name
self.composition = composition # optional RESQML item
self.material_implacement = material_implacement # optional RESQML item
super().__init__(model = parent_model, uuid = uuid, title = title, extra_metadata = extra_metadata)
if self.composition:
assert self.composition in rqstc.valid_compositions, \
f'invalid composition {self.composition} for geological unit interpretation'
self.composition = self.composition.strip()
if self.material_implacement:
assert self.material_implacement in rqstc.valid_implacements, \
f'invalid material implacement {self.material_implacement} for geological unit interpretation'
def _load_from_xml(self):
"""Loads class specific attributes from xml for an existing RESQML object; called from BaseResqpy."""
root_node = self.root
assert root_node is not None
self.domain = rqet.find_tag_text(root_node, 'Domain')
# following allows derived StratigraphicUnitInterpretation to instantiate its own interpreted feature
if self.resqml_type == 'GeologicUnitInterpretation':
feature_uuid = bu.uuid_from_string(rqet.find_nested_tags_text(root_node, ['InterpretedFeature', 'UUID']))
if feature_uuid is not None:
self.geologic_unit_feature = rqo.GeologicUnitFeature(
self.model, uuid = feature_uuid, feature_name = self.model.title(uuid = feature_uuid))
self.has_occurred_during = rqo.extract_has_occurred_during(root_node)
self.composition = rqet.find_tag_text(root_node, 'GeologicUnitComposition')
self.material_implacement = rqet.find_tag_text(root_node, 'GeologicUnitMaterialImplacement')
[docs] def is_equivalent(self, other, check_extra_metadata = True):
"""Returns True if this interpretation is essentially the same as the other; otherwise False.
arguments:
other (GeologicUnitInterpretation or StratigraphicUnitInterpretation): the other interpretation to
compare this one against
check_extra_metadata (bool, default True): if True, then extra metadata items must match for the two
interpretations to be deemed equivalent; if False, extra metadata is ignored in the comparison
returns:
bool: True if this interpretation is essentially the same as the other; False otherwise
"""
# this method is coded to allow use by the derived StratigraphicUnitInterpretation class
if other is None or not isinstance(other, type(self)):
return False
if self is other or bu.matching_uuids(self.uuid, other.uuid):
return True
if self.geologic_unit_feature is not None:
if not self.geologic_unit_feature.is_equivalent(other.geologic_unit_feature):
return False
elif other.geologic_unit_feature is not None:
return False
if self.root is not None and other.root is not None:
if rqet.citation_title_for_node(self.root) != rqet.citation_title_for_node(other.root):
return False
elif self.root is not None or other.root is not None:
return False
if check_extra_metadata and not rqo.equivalent_extra_metadata(self, other):
return False
return (self.composition == other.composition and self.material_implacement == other.material_implacement and
self.domain == other.domain and
rqo.equivalent_chrono_pairs(self.has_occurred_during, other.has_occurred_during))
[docs] def create_xml(self, add_as_part = True, add_relationships = True, originator = None, reuse = True):
"""Creates a geologic unit interpretation xml tree.
arguments:
add_as_part (bool, default True): if True, the interpretation is added to the parent model as a high level part
add_relationships (bool, default True): if True and add_as_part is True, a relationship is created with
the referenced geologic unit feature
originator (str, optional): if present, is used as the originator field of the citation block
reuse (bool, default True): if True, the parent model is inspected for any equivalent interpretation and, if found,
the uuid of this interpretation is set to that of the equivalent part
returns:
lxml.etree._Element: the root node of the newly created xml tree for the interpretation
"""
# note: related feature xml must be created first and is referenced here
# this method is coded to allow use by the derived StratigraphicUnitInterpretation class
if reuse and self.try_reuse():
return self.root
gu = super().create_xml(add_as_part = False, originator = originator)
assert self.geologic_unit_feature is not None
guf_root = self.geologic_unit_feature.root
assert guf_root is not None, 'interpreted feature not established for geologic unit interpretation'
assert self.domain in rqstc.valid_domains, 'illegal domain value for geologic unit interpretation'
dom_node = rqet.SubElement(gu, ns['resqml2'] + 'Domain')
dom_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Domain')
dom_node.text = self.domain
self.model.create_ref_node('InterpretedFeature',
self.geologic_unit_feature.title,
self.geologic_unit_feature.uuid,
content_type = self.geologic_unit_feature.resqml_type,
root = gu)
rqo.create_xml_has_occurred_during(self.model, gu, self.has_occurred_during)
if self.composition is not None:
assert self.composition in rqstc.valid_compositions, \
f'invalid composition {self.composition} for geologic unit interpretation'
comp_node = rqet.SubElement(gu, ns['resqml2'] + 'GeologicUnitComposition')
comp_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'GeologicUnitComposition')
comp_node.text = self.composition
if self.composition + ' ' in rqstc.valid_compositions: # RESQML xsd has spurious trailing space for two compositions
comp_node.text += ' '
if self.material_implacement is not None:
assert self.material_implacement in rqstc.valid_implacements, \
f'invalid material implacement {self.material_implacement} for geologic unit interpretation'
mi_node = rqet.SubElement(gu, ns['resqml2'] + 'GeologicUnitMaterialImplacement')
mi_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'GeologicUnitMaterialImplacement')
mi_node.text = self.material_implacement
if add_as_part:
self.model.add_part('obj_' + self.resqml_type, self.uuid, gu)
if add_relationships:
self.model.create_reciprocal_relationship(gu, 'destinationObject', guf_root, 'sourceObject')
return gu