Module gplately.gpml

Tools for manipulating GPML (.gpml, .gpmlz) files and pygplates.Feature and pygplates.FeatureCollection objects.

The following functions are defined here:

- create_feature_dict
- extract_feature
- get_topological_references
- is_topological
Expand source code
r"""Tools for manipulating GPML (`.gpml`, `.gpmlz`) files and
`pygplates.Feature` and `pygplates.FeatureCollection` objects.

The following functions are defined here:

    - create_feature_dict
    - extract_feature
    - get_topological_references
    - is_topological

"""
import os

import pygplates
from .pygplates import FeatureCollection as _FeatureCollection
from .pygplates import _is_string

__all__ = [
    "create_feature_dict",
    "extract_feature",
    "get_topological_references",
    "is_topological",
]


def extract_feature(id, features):
    r"""Return the feature with the given feature ID.

    Searches through `features` and returns the feature with a feature ID
    matching `id`. The order of `features` is not preserved, so multiple
    features sharing the same feature ID should be avoided.

    Parameters
    ----------
    id : pygplates.FeatureId or str
        The feature ID (or string representation) to search for.
    features : valid argument for pygplates.FeaturesFunctionArgument
        `features` may be a single `pygplates.Feature`, a
        `pygplates.FeatureCollection`, a `str` filename,
        or a (potentially nested) sequence of any combination of the above.

    Returns
    -------
    pygplates.Feature or None
        The matching feature if found, otherwise None.

    Raises
    ------
    TypeError
        If `id` is not one of {`pygplates.FeatureId`, `str`},
        or if `features` is of an invalid type.
    OSError (including FileNotFoundError, IsADirectoryError)
        If `features` points to a non-existent or unrecognised file.
    """
    features = _parse_features_function_arguments(features)

    if isinstance(id, pygplates.FeatureId):
        id_type = "fid"
    elif isinstance(id, str):
        id_type = "str"
    else:
        raise TypeError("Invalid feature ID: `{}`".format(id))

    for feature in features:
        feature_id = feature.get_feature_id()
        if id_type == "fid":
            cond = id == feature_id
        else:
            cond = id == feature_id.get_string()
        if cond:
            return feature

    # If the requested feature is not found, either return None
    # or maybe raise a ValueError like list.index() does
    # e.g. [1, 2, 3].index(4)
    # raise ValueError("Feature ID `{}` is not in `features`".format(id))
    return None


def create_feature_dict(features, id_type=pygplates.FeatureId):
    r"""Create a dictionary mapping feature IDs to features.

    Feature IDs can be either `str` or `pygplates.FeatureId`,
    according to `id_type`.

    Parameters
    ----------
    features : valid argument for pygplates.FeaturesFunctionArgument
        `features` may be a single `pygplates.Feature`, a
        `pygplates.FeatureCollection`, a `str` filename,
        or a (potentially nested) sequence of any combination of the above.
    id_type : {`pygplates.FeatureId`, `str`}, optional
        By default, dictionary keys will be of type `pygplates.FeatureId`;
        pass `id_type=str` to use string representations instead.

    Returns
    -------
    dict
        Dictionary for looking up features by their feature ID.
        N.B. the result will be an empty dictonary  if `features` is empty.

    Raises
    ------
    TypeError
        If `id_type` is not one of {`pygplates.FeatureId`, `str`},
        or if `features` is of an invalid type.
    OSError (including FileNotFoundError, IsADirectoryError)
        If `features` points to a non-existent or unrecognised file.
    """
    if id_type not in {str, pygplates.FeatureId}:
        raise TypeError(
            "Invalid `key_type` value: `"
            + str(id_type)
            + "` (must be one of `pygplates.FeatureId`, `str`)"
        )

    features = _parse_features_function_arguments(features)
    if id_type is str:
        return {i.get_feature_id().get_string(): i for i in features}
    else:
        return {i.get_feature_id(): i for i in features}


def get_topological_references(features, id_type=pygplates.FeatureId):
    r"""Create a dictionary mapping topological feature IDs to
    referenced features.

    The resulting dictionary maps each topological
    `pygplates.FeatureId` in `features` to the set of
    `pygplates.FeatureId` referenced by its topological
    geometry or geometries.

    Parameters
    ----------
    features : valid argument for pygplates.FeaturesFunctionArgument
        `features` may be a single `pygplates.Feature`, a
        `pygplates.FeatureCollection`, a `str` filename,
        or a (potentially nested) sequence of any combination of the above.
    id_type : {`pygplates.FeatureId`, `str`}, optional
        By default, feature IDs will be of type `pygplates.FeatureId`;
        pass `id_type=str` to use string representations instead.

    Returns
    -------
    dict
        N.B. Dictionary keys include all topological features in `features`,
        and only topological features.

    Raises
    ------
    TypeError
        If `id_type` is not one of {`pygplates.FeatureId`, `str`},
        or if `features` is of an invalid type.
    OSError (including FileNotFoundError, IsADirectoryError)
        If `features` points to a non-existent or unrecognised file.
    """
    features = _parse_features_function_arguments(features)
    features_dict = create_feature_dict(features)
    results = {}
    for feature in features:
        if not is_topological(feature):
            continue
        references = _get_topological_references(feature, features_dict)
        if len(references) == 0:
            continue
        id = feature.get_feature_id()
        if id_type is str:
            id = id.get_string()
            references = set(i.get_string() for i in references)
        results[id] = references
    return results


def is_topological(feature):
    r"""Determine whether a feature contains a topological geometry.

    Parameters
    ----------
    feature : pygplates.Feature

    Returns
    -------
    bool
        True if `feature` contains a topological geometry, else False.

    Raises
    ------
    TypeError
        If `feature` is a type other than `pygplates.Feature`
        (more precisely, if its type does not implement
        `get_all_topological_geometries`).
    """
    try:
        return len(feature.get_all_topological_geometries()) > 0
    except AttributeError as e:
        raise TypeError(
            "`is_topological` not implemented for `{}`".format(type(feature))
        ) from e


def _get_topological_references(feature, features_dict):
    if not isinstance(feature, pygplates.Feature):
        raise TypeError("Invalid feature type: `{}`".format(type(feature)))
    topologies = feature.get_all_topological_geometries()
    referenced_ids = set()
    for topology in topologies:
        if isinstance(topology, pygplates.GpmlTopologicalLine):
            sections = topology.get_sections()
        else:
            sections = topology.get_boundary_sections()
        for section in sections:
            feature_id = section.get_property_delegate().get_feature_id()
            referenced_ids.add(feature_id)
    referenced_features = set()
    for id in referenced_ids:
        if id in features_dict:
            referenced_features.add(features_dict[id])
    for referenced_feature in referenced_features:
        referenced_ids = referenced_ids.union(
            _get_topological_references(referenced_feature, features_dict)
        )
    return referenced_ids


def _parse_features_function_arguments(features):
    r"""Load features using `pygplates.FeaturesFunctionArgument`.

    This function also tries to translate some of the exceptions
    raised by `pygplates.FeaturesFunctionArgument` into regular
    Python exceptions.
    """
    try:
        features = pygplates.FeatureCollection(
            pygplates.FeaturesFunctionArgument(features).get_features()
        )
    except pygplates.FileFormatNotSupportedError as e:
        print("Invalid filename: `{}`".format(features))
        if not os.path.exists(features):
            raise FileNotFoundError("File does not exist") from e
        if os.path.isdir(features):
            raise IsADirectoryError("File is a directory") from e
        raise OSError("Unrecognised file format") from e
    except pygplates.OpenFileForReadingError as e:
        raise FileNotFoundError(
            "Could not find input file(s): `{}`".format(features)
        ) from e
    except Exception as e:
        if str(type(e)) == "<class 'Boost.Python.ArgumentError'>":
            # This is the easiest way of catching Boost.Python.ArgumentError,
            # since it cannot be directly imported into Python
            raise TypeError(
                "Invalid argument type: `{}`".format(type(features))
            ) from e
        else:
            raise e
    return features

def _load_FeatureCollection(geometry):
    if isinstance(geometry, _FeatureCollection):
        return geometry
    elif isinstance(geometry, pygplates.FeatureCollection):
        geometry.filenames = []
        return geometry
    elif _is_string(geometry) and type(geometry) is list:
        fc = _FeatureCollection()
        for geom in geometry:
            fc.add( _FeatureCollection(geom) )
        return fc
    elif _is_string(geometry):
        return _FeatureCollection(geometry)
    elif geometry is None:
        return None
    else:
        raise ValueError("geometry is an invalid type", type(geometry))

Functions

def create_feature_dict(features, id_type=pygplates.FeatureId)

Create a dictionary mapping feature IDs to features.

Feature IDs can be either str or pygplates.FeatureId, according to id_type.

Parameters

features : valid argument for pygplates.FeaturesFunctionArgument
features may be a single pygplates.Feature, a pygplates.FeatureCollection, a str filename, or a (potentially nested) sequence of any combination of the above.
id_type : {pygplates.FeatureId, str}, optional
By default, dictionary keys will be of type pygplates.FeatureId; pass id_type=str to use string representations instead.

Returns

dict
Dictionary for looking up features by their feature ID. N.B. the result will be an empty dictonary if features is empty.

Raises

TypeError
If id_type is not one of {pygplates.FeatureId, str}, or if features is of an invalid type.
OSError (including FileNotFoundError, IsADirectoryError)
If features points to a non-existent or unrecognised file.
Expand source code
def create_feature_dict(features, id_type=pygplates.FeatureId):
    r"""Create a dictionary mapping feature IDs to features.

    Feature IDs can be either `str` or `pygplates.FeatureId`,
    according to `id_type`.

    Parameters
    ----------
    features : valid argument for pygplates.FeaturesFunctionArgument
        `features` may be a single `pygplates.Feature`, a
        `pygplates.FeatureCollection`, a `str` filename,
        or a (potentially nested) sequence of any combination of the above.
    id_type : {`pygplates.FeatureId`, `str`}, optional
        By default, dictionary keys will be of type `pygplates.FeatureId`;
        pass `id_type=str` to use string representations instead.

    Returns
    -------
    dict
        Dictionary for looking up features by their feature ID.
        N.B. the result will be an empty dictonary  if `features` is empty.

    Raises
    ------
    TypeError
        If `id_type` is not one of {`pygplates.FeatureId`, `str`},
        or if `features` is of an invalid type.
    OSError (including FileNotFoundError, IsADirectoryError)
        If `features` points to a non-existent or unrecognised file.
    """
    if id_type not in {str, pygplates.FeatureId}:
        raise TypeError(
            "Invalid `key_type` value: `"
            + str(id_type)
            + "` (must be one of `pygplates.FeatureId`, `str`)"
        )

    features = _parse_features_function_arguments(features)
    if id_type is str:
        return {i.get_feature_id().get_string(): i for i in features}
    else:
        return {i.get_feature_id(): i for i in features}
def extract_feature(id, features)

Return the feature with the given feature ID.

Searches through features and returns the feature with a feature ID matching id. The order of features is not preserved, so multiple features sharing the same feature ID should be avoided.

Parameters

id : pygplates.FeatureId or str
The feature ID (or string representation) to search for.
features : valid argument for pygplates.FeaturesFunctionArgument
features may be a single pygplates.Feature, a pygplates.FeatureCollection, a str filename, or a (potentially nested) sequence of any combination of the above.

Returns

pygplates.Feature or None
The matching feature if found, otherwise None.

Raises

TypeError
If id is not one of {pygplates.FeatureId, str}, or if features is of an invalid type.
OSError (including FileNotFoundError, IsADirectoryError)
If features points to a non-existent or unrecognised file.
Expand source code
def extract_feature(id, features):
    r"""Return the feature with the given feature ID.

    Searches through `features` and returns the feature with a feature ID
    matching `id`. The order of `features` is not preserved, so multiple
    features sharing the same feature ID should be avoided.

    Parameters
    ----------
    id : pygplates.FeatureId or str
        The feature ID (or string representation) to search for.
    features : valid argument for pygplates.FeaturesFunctionArgument
        `features` may be a single `pygplates.Feature`, a
        `pygplates.FeatureCollection`, a `str` filename,
        or a (potentially nested) sequence of any combination of the above.

    Returns
    -------
    pygplates.Feature or None
        The matching feature if found, otherwise None.

    Raises
    ------
    TypeError
        If `id` is not one of {`pygplates.FeatureId`, `str`},
        or if `features` is of an invalid type.
    OSError (including FileNotFoundError, IsADirectoryError)
        If `features` points to a non-existent or unrecognised file.
    """
    features = _parse_features_function_arguments(features)

    if isinstance(id, pygplates.FeatureId):
        id_type = "fid"
    elif isinstance(id, str):
        id_type = "str"
    else:
        raise TypeError("Invalid feature ID: `{}`".format(id))

    for feature in features:
        feature_id = feature.get_feature_id()
        if id_type == "fid":
            cond = id == feature_id
        else:
            cond = id == feature_id.get_string()
        if cond:
            return feature

    # If the requested feature is not found, either return None
    # or maybe raise a ValueError like list.index() does
    # e.g. [1, 2, 3].index(4)
    # raise ValueError("Feature ID `{}` is not in `features`".format(id))
    return None
def get_topological_references(features, id_type=pygplates.FeatureId)

Create a dictionary mapping topological feature IDs to referenced features.

The resulting dictionary maps each topological pygplates.FeatureId in features to the set of pygplates.FeatureId referenced by its topological geometry or geometries.

Parameters

features : valid argument for pygplates.FeaturesFunctionArgument
features may be a single pygplates.Feature, a pygplates.FeatureCollection, a str filename, or a (potentially nested) sequence of any combination of the above.
id_type : {pygplates.FeatureId, str}, optional
By default, feature IDs will be of type pygplates.FeatureId; pass id_type=str to use string representations instead.

Returns

dict
N.B. Dictionary keys include all topological features in features, and only topological features.

Raises

TypeError
If id_type is not one of {pygplates.FeatureId, str}, or if features is of an invalid type.
OSError (including FileNotFoundError, IsADirectoryError)
If features points to a non-existent or unrecognised file.
Expand source code
def get_topological_references(features, id_type=pygplates.FeatureId):
    r"""Create a dictionary mapping topological feature IDs to
    referenced features.

    The resulting dictionary maps each topological
    `pygplates.FeatureId` in `features` to the set of
    `pygplates.FeatureId` referenced by its topological
    geometry or geometries.

    Parameters
    ----------
    features : valid argument for pygplates.FeaturesFunctionArgument
        `features` may be a single `pygplates.Feature`, a
        `pygplates.FeatureCollection`, a `str` filename,
        or a (potentially nested) sequence of any combination of the above.
    id_type : {`pygplates.FeatureId`, `str`}, optional
        By default, feature IDs will be of type `pygplates.FeatureId`;
        pass `id_type=str` to use string representations instead.

    Returns
    -------
    dict
        N.B. Dictionary keys include all topological features in `features`,
        and only topological features.

    Raises
    ------
    TypeError
        If `id_type` is not one of {`pygplates.FeatureId`, `str`},
        or if `features` is of an invalid type.
    OSError (including FileNotFoundError, IsADirectoryError)
        If `features` points to a non-existent or unrecognised file.
    """
    features = _parse_features_function_arguments(features)
    features_dict = create_feature_dict(features)
    results = {}
    for feature in features:
        if not is_topological(feature):
            continue
        references = _get_topological_references(feature, features_dict)
        if len(references) == 0:
            continue
        id = feature.get_feature_id()
        if id_type is str:
            id = id.get_string()
            references = set(i.get_string() for i in references)
        results[id] = references
    return results
def is_topological(feature)

Determine whether a feature contains a topological geometry.

Parameters

feature : pygplates.Feature
 

Returns

bool
True if feature contains a topological geometry, else False.

Raises

TypeError
If feature is a type other than pygplates.Feature (more precisely, if its type does not implement get_all_topological_geometries).
Expand source code
def is_topological(feature):
    r"""Determine whether a feature contains a topological geometry.

    Parameters
    ----------
    feature : pygplates.Feature

    Returns
    -------
    bool
        True if `feature` contains a topological geometry, else False.

    Raises
    ------
    TypeError
        If `feature` is a type other than `pygplates.Feature`
        (more precisely, if its type does not implement
        `get_all_topological_geometries`).
    """
    try:
        return len(feature.get_all_topological_geometries()) > 0
    except AttributeError as e:
        raise TypeError(
            "`is_topological` not implemented for `{}`".format(type(feature))
        ) from e