"""RESQML StratigraphicColumnRankInterpretation 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
# RMS is a registered trademark of Roxar Software Solutions AS, an Emerson company
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
import resqpy.strata._binary_contact_interpretation as rqsbc
import resqpy.strata._stratigraphic_unit_interpretation as rqsui
from resqpy.olio.base import BaseResqpy
from resqpy.olio.xml_namespaces import curly_namespace as ns
class StratigraphicColumnRank(BaseResqpy):
"""Class for RESQML StratigraphicColumnRankInterpretation objects.
A list of stratigraphic unit interpretations, ordered from geologically oldest to youngest.
"""
resqml_type = 'StratigraphicColumnRankInterpretation'
# note: ordering of stratigraphic units and contacts is from geologically oldest to youngest
[docs] def __init__(
self,
parent_model,
uuid = None,
domain = 'time', # or should this be depth?
rank_index = None,
earth_model_feature_uuid = None,
strata_uuid_list = None, # ordered list of stratigraphic unit interpretations
title = None,
extra_metadata = None):
"""Initialises a Stratigraphic Column Rank resqpy object (RESQML StratigraphicColumnRankInterpretation).
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 stratigraphic column rank interpretation
from which this object will be initialised
domain (str, default 'time'): 'time', 'depth' or 'mixed', being the domain of the interpretation;
ignored if uuid is not None
rank_index (int, optional): the rank index (RESQML index) for this rank when multiple ranks are used;
will default to zero if not set; ignored if uuid is not None
earth_model_feature_uuid (uuid.UUID, optional): the uuid of an existing organization feature of kind
'earth model' which this stratigraphic column is for; ignored if uuid is not None
strata_uuid_list (list of uuid.UUID, optional): a list of uuids of existing stratigraphic unit
interpretations, ordered from geologically oldest to youngest; ignored if uuid is not None
title (str, optional): the citation title (feature name) of the new interpretation;
ignored if uuid is not None
extra_metadata (dict, optional): extra metadata items for the new stratigraphic column rank interpretation
"""
self.feature_uuid = earth_model_feature_uuid # interpreted earth model feature uuid
self.domain = domain
self.index = rank_index
self.has_occurred_during = (None, None) # optional RESQML item
self.units = [] # ordered list of (index, stratagraphic unit interpretations)
self.contacts = [] # optional ordered list of binary contact interpretations
super().__init__(model = parent_model, uuid = uuid, title = title, extra_metadata = extra_metadata)
if self.root is None and strata_uuid_list is not None:
self.set_units(strata_uuid_list)
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
assert rqet.find_tag_text(root_node, 'OrderingCriteria') == 'age', \
'stratigraphic column rank interpretation ordering criterion must be age'
self.domain = rqet.find_tag_text(root_node, 'Domain')
self.feature_uuid = bu.uuid_from_string(rqet.find_nested_tags_text(root_node, ['InterpretedFeature', 'UUID']))
self.has_occurred_during = rqo.extract_has_occurred_during(root_node)
self.index = rqet.find_tag_int(root_node, 'Index')
self.units = []
for su_node in rqet.list_of_tag(root_node, 'StratigraphicUnits'):
index = rqet.find_tag_int(su_node, 'Index')
unit_uuid = bu.uuid_from_string(rqet.find_nested_tags_text(su_node, ['Unit', 'UUID']))
assert index is not None and unit_uuid is not None
assert self.model.type_of_uuid(unit_uuid, strip_obj = True) == 'StratigraphicUnitInterpretation'
self.units.append((index, rqsui.StratigraphicUnitInterpretation(self.model, uuid = unit_uuid)))
self._sort_units()
self.contacts = []
for contact_node in rqet.list_of_tag(root_node, 'ContactInterpretation'):
self.contacts.append(rqsbc.BinaryContactInterpretation(self.model, existing_xml_node = contact_node))
self._sort_contacts()
[docs] def set_units(self, strata_uuid_list):
"""Discard any previous units and set list of units based on ordered list of uuids.
arguments:
strata_uuid_list (list of uuid.UUID): the uuids of the stratigraphic unit interpretations which constitute
this stratigraphic column, ordered from geologically oldest to youngest
"""
self.units = []
for i, uuid in enumerate(strata_uuid_list):
assert self.model.type_of_uuid(uuid, strip_obj = True) == 'StratigraphicUnitInterpretation'
self.units.append((i, rqsui.StratigraphicUnitInterpretation(self.model, uuid = uuid)))
def _sort_units(self):
"""Sorts units list, in situ, into increasing order of index values."""
self.units.sort()
def _sort_contacts(self):
"""Sorts contacts list, in situ, into increasing order of index values."""
self.contacts.sort(key = rqstc._index_attr)
[docs] def iter_units(self):
"""Yields the ordered stratigraphic unit interpretations which constitute this stratigraphic colunn."""
for _, unit in self.units:
yield unit
[docs] def unit_for_unit_index(self, index):
"""Returns the stratigraphic unit interpretation with the given index in this stratigraphic colunn."""
for i, unit in self.units:
if i == index:
return unit
return None
# todo: implement a strict validation method checking contact exists for each neighbouring unit pair
[docs] def create_xml(self, add_as_part = True, add_relationships = True, originator = None, reuse = True):
"""Creates a stratigraphic column rank 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: xml for referenced objects must be created before calling this method
assert len(self.units), 'attempting to create xml for stratigraphic column rank without any units'
if reuse and self.try_reuse():
return self.root
if self.index is None:
self.index = 0
scri = super().create_xml(add_as_part = False, originator = originator)
assert self.feature_uuid is not None
assert self.model.type_of_uuid(self.feature_uuid, strip_obj = True) == 'OrganizationFeature'
# todo: could also check that the kind is 'earth model'
self.model.create_ref_node('InterpretedFeature',
self.model.title(uuid = self.feature_uuid),
self.feature_uuid,
content_type = 'OrganizationFeature',
root = scri)
assert self.domain in rqstc.valid_domains, 'illegal domain value for stratigraphic column rank interpretation'
dom_node = rqet.SubElement(scri, ns['resqml2'] + 'Domain')
dom_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Domain')
dom_node.text = self.domain
rqo.create_xml_has_occurred_during(self.model, scri, self.has_occurred_during)
oc_node = rqet.SubElement(scri, ns['resqml2'] + 'OrderingCriteria')
oc_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'OrderingCriteria')
oc_node.text = 'age'
i_node = rqet.SubElement(scri, ns['resqml2'] + 'Index')
i_node.set(ns['xsi'] + 'type', ns['xsi'] + 'nonNegativeInteger')
i_node.text = str(self.index)
for i, unit in self.units:
su_node = rqet.SubElement(scri, ns['resqml2'] + 'StratigraphicUnits')
su_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'StratigraphicUnitInterpretationIndex')
su_node.text = ''
si_node = rqet.SubElement(su_node, ns['resqml2'] + 'Index')
si_node.set(ns['xsi'] + 'type', ns['xsi'] + 'nonNegativeInteger')
si_node.text = str(i)
self.model.create_ref_node('Unit',
unit.title,
unit.uuid,
content_type = 'StratigraphicUnitInterpretation',
root = su_node)
for contact in self.contacts:
contact.create_xml(scri)
if add_as_part:
self.model.add_part('obj_StratigraphicColumnRankInterpretation', self.uuid, scri)
if add_relationships:
emi_root = self.model.root(uuid = self.feature_uuid)
self.model.create_reciprocal_relationship(scri, 'destinationObject', emi_root, 'sourceObject')
for _, unit in self.units:
self.model.create_reciprocal_relationship(scri, 'destinationObject', unit.root, 'sourceObject')
for contact in self.contacts:
if contact.part_of_uuid is not None:
horizon_root = self.model.root(uuid = contact.part_of_uuid)
if horizon_root is not None:
self.model.create_reciprocal_relationship(scri, 'destinationObject', horizon_root,
'sourceObject')
return scri