def resolve_topologies(
    rotation_features_or_model,
    topology_features,
    time,
    output_filename_prefix,
    output_filename_extension,
    transform_segment_deviation_in_radians=separate_ridge_transform_segments.
    DEFAULT_TRANSFORM_SEGMENT_DEVIATION_RADIANS):
    """
    Resolves topologies at specified time and saves (to separate files) the resolved topologies, and their boundary sections as subduction zones,
    mid-ocean ridges (ridge/transform) and others (not subduction zones or mid-ocean ridges).
    
    rotation_features_or_model: Rotation model or feature collection(s), or list of features, or filename(s).
    
    topology_features: Topology feature collection(s), or list of features, or filename(s) or any combination of those.
    
    time: Reconstruction time to resolved topologies.
    
    transform_segment_deviation_in_radians: How much a mid-ocean ridge segment can deviate from the stage pole before
                                            it's considered a transform segment (in radians).
    
    Writes output files containing the following features...
            - resolved topology features (topological plates and networks)
            - ridge and transform boundary sections (resolved features)
            - ridge boundary sections (resolved features)
            - transform boundary sections (resolved features)
            - subduction boundary sections (resolved features)
            - left subduction boundary sections (resolved features)
            - right subduction boundary sections (resolved features)
            - other boundary sections (resolved features) that are not subduction zones or mid-ocean ridges (ridge/transform)
    """

    # Turn rotation data into a RotationModel (if not already).
    rotation_model = pygplates.RotationModel(rotation_features_or_model)

    # Get topology features (could include filenames).
    topology_features = pygplates.FeaturesFunctionArgument(
        topology_features).get_features()

    # Resolve our topological plate polygons (and deforming networks) to the current 'time'.
    # We generate both the resolved topology boundaries and the boundary sections between them.
    (resolved_topology_features, ridge_transform_boundary_section_features,
     ridge_boundary_section_features, transform_boundary_section_features,
     subduction_boundary_section_features,
     left_subduction_boundary_section_features,
     right_subduction_boundary_section_features,
     other_boundary_section_features) = resolve_topologies_into_features(
         rotation_model, topology_features, time,
         transform_segment_deviation_in_radians)

    # Write each list of features to a separate file.
    _write_resolved_topologies(
        time, output_filename_prefix, output_filename_extension,
        resolved_topology_features, ridge_transform_boundary_section_features,
        ridge_boundary_section_features, transform_boundary_section_features,
        subduction_boundary_section_features,
        left_subduction_boundary_section_features,
        right_subduction_boundary_section_features,
        other_boundary_section_features)
def spreading_rates(
        rotation_features_or_model,
        topology_features,
        time,
        threshold_sampling_distance_radians,
        spreading_feature_types=None,
        transform_segment_deviation_in_radians=separate_ridge_transform_segments
    .DEFAULT_TRANSFORM_SEGMENT_DEVIATION_RADIANS,
        velocity_delta_time=1.0,
        anchor_plate_id=0):
    """
    Calculates spreading rate and length of ridge segments of spreading features (mid-ocean ridges) of resolved topologies at specified time.
    
    The transform segments of spreading features are ignored.
    
    Resolves topologies at 'time', tessellates all resolved spreading features to within 'threshold_sampling_distance_radians' radians and
    returns a list of tuples where each tuple represents a tessellated point and contains the following parameters:
    
    - point longitude
    - point latitude
    - spreading velocity magnitude (in cm/yr)
    - length of arc segment (in degrees) that current point is on
    
    
    rotation_features_or_model: Rotation model or feature collection(s), or list of features, or filename(s).
    
    topology_features: Topology feature collection(s), or list of features, or filename(s) or any combination of those.
    
    time: Reconstruction time to resolved topologies.
    
    threshold_sampling_distance_radians: Threshold sampling distance along spreading features (in radians).
    
    spreading_feature_types: Only spreading features with a feature type contained in this list are considered.
                             If None then all spreading features are considered.
    
    transform_segment_deviation_in_radians: How much a segment can deviate from the stage pole before
                                            it's considered a transform segment (in radians).
    
    velocity_delta_time: Delta time interval used to calculate spreading velocity.
    
    Returns: List of the tuples described above.
    """
    time = float(time)

    # Turn rotation data into a RotationModel (if not already).
    rotation_model = pygplates.RotationModel(rotation_features_or_model)

    # Turn topology data into a list of features (if not already).
    topology_features = pygplates.FeaturesFunctionArgument(topology_features)

    # Resolve our topological plate polygons (and deforming networks) to the current 'time'.
    # We generate both the resolved topology boundaries and the boundary sections between them.
    resolved_topologies = []
    shared_boundary_sections = []
    pygplates.resolve_topologies(topology_features.get_features(),
                                 rotation_model, resolved_topologies, time,
                                 shared_boundary_sections, anchor_plate_id)

    # List of tesselated spreading points and associated spreading parameters for the current 'time'.
    output_data = []

    # Iterate over the shared boundary sections of all resolved topologies.
    for shared_boundary_section in shared_boundary_sections:

        spreading_feature = shared_boundary_section.get_feature()

        # Skip sections that are not spreading features (if requested).
        if (spreading_feature_types and spreading_feature.get_feature_type()
                not in spreading_feature_types):
            continue

        # Find the stage rotation of the spreading feature in the frame of reference of its reconstructed geometry at the current 'time'.
        # The stage pole can then be directly geometrically compared to the reconstructed spreading geometry.
        spreading_stage_rotation = separate_ridge_transform_segments.get_stage_rotation_for_reconstructed_geometry(
            spreading_feature, rotation_model, time)
        if not spreading_stage_rotation:
            # Skip current feature - it's not a spreading feature.
            continue

        # Iterate over the shared sub-segments of the current line.
        # These are the parts of the line that actually contribute to topological boundaries.
        for shared_sub_segment in shared_boundary_section.get_shared_sub_segments(
        ):

            # Split into ridge and transform segments.
            ridge_and_transform_segment_geometries = separate_ridge_transform_segments.separate_geometry_into_ridges_and_transforms(
                spreading_stage_rotation,
                shared_sub_segment.get_resolved_geometry(),
                transform_segment_deviation_in_radians)
            if not ridge_and_transform_segment_geometries:
                # Skip shared sub segment - it's not a polyline (or polygon).
                continue

            # Only interested in ridge segments.
            ridge_sub_segment_geometries, _ = ridge_and_transform_segment_geometries

            # Ensure the ridge sub-segments are tessellated to within the threshold sampling distance.
            tessellated_shared_sub_segment_polylines = [
                ridge_sub_segment_geometry.to_tessellated(
                    threshold_sampling_distance_radians)
                for ridge_sub_segment_geometry in ridge_sub_segment_geometries
            ]

            # Iterate over the great circle arcs of the tessellated polylines to get the arc midpoints and lengths.
            # There is an arc between each adjacent pair of points in the polyline.
            arc_midpoints = []
            arc_lengths = []
            for tessellated_shared_sub_segment_polyline in tessellated_shared_sub_segment_polylines:
                for arc in tessellated_shared_sub_segment_polyline.get_segments(
                ):
                    if not arc.is_zero_length():
                        arc_midpoints.append(arc.get_arc_point(0.5))
                        arc_lengths.append(arc.get_arc_length())

            # Shouldn't happen, but just in case ridge sub-segment polylines coincide with points.
            if not arc_midpoints:
                continue

            # Calculate the spreading velocities at the arc midpoints.
            #
            # Note that the stage rotation can be used directly on the reconstructed geometries because
            # it is already in the frame of reference of the reconstructed geometries.
            spreading_velocity_vectors = pygplates.calculate_velocities(
                arc_midpoints, spreading_stage_rotation, velocity_delta_time,
                pygplates.VelocityUnits.cms_per_yr)

            for arc_index in range(len(arc_midpoints)):

                arc_midpoint = arc_midpoints[arc_index]
                arc_length = arc_lengths[arc_index]
                lat, lon = arc_midpoint.to_lat_lon()

                spreading_velocity_magnitude = spreading_velocity_vectors[
                    arc_index].get_magnitude()

                # The data will be output in GMT format (ie, lon first, then lat, etc).
                output_data.append((lon, lat, spreading_velocity_magnitude,
                                    math.degrees(arc_length)))

    return output_data
def resolve_topologies(rotation_model, topological_features,
                       reconstruction_time, output_filename_prefix,
                       output_filename_extension, anchor_plate_id):

    # FIXME: Temporary fix to avoid getting OGR GMT/Shapefile error "Mismatch in field names..." and
    # missing geometries when saving resolved topologies/sections to GMT/Shapefile.
    # It's caused by the OGR writer inside pyglates trying to write out features with different
    # shapefiles attribute field (key) names to the same file. We get around this by removing
    # all shapefile attributes.
    topological_features = pygplates.FeaturesFunctionArgument(
        topological_features).get_features()
    for topological_feature in topological_features:
        topological_feature.remove(
            pygplates.PropertyName.gpml_shapefile_attributes)

    # Resolve our topological plate polygons (and deforming networks) to the current 'reconstruction_time'.
    # We generate both the resolved topology boundaries and the boundary sections between them.
    resolved_topologies = []
    shared_boundary_sections = []
    pygplates.resolve_topologies(topological_features, rotation_model,
                                 resolved_topologies, reconstruction_time,
                                 shared_boundary_sections, anchor_plate_id)

    # We'll create a feature for each boundary polygon feature and each type of
    # resolved topological section feature we find.
    resolved_topology_features = []
    ridge_transform_boundary_section_features = []
    subduction_boundary_section_features = []
    left_subduction_boundary_section_features = []
    right_subduction_boundary_section_features = []

    # Iterate over the resolved topologies.
    for resolved_topology in resolved_topologies:
        resolved_topology_features.append(
            resolved_topology.get_resolved_feature())

    # Iterate over the shared boundary sections.
    for shared_boundary_section in shared_boundary_sections:

        # Get all the geometries of the current boundary section.
        boundary_section_features = [
            shared_sub_segment.get_resolved_feature() for shared_sub_segment in
            shared_boundary_section.get_shared_sub_segments()
        ]

        # Add the feature to the correct list depending on feature type, etc.
        if shared_boundary_section.get_feature().get_feature_type(
        ) == pygplates.FeatureType.create_gpml('SubductionZone'):

            # Put all subduction zones in one collection/file.
            subduction_boundary_section_features.extend(
                boundary_section_features)

            # Also put subduction zones in left/right collection/file.
            polarity_property = shared_boundary_section.get_feature().get(
                pygplates.PropertyName.create_gpml('subductionPolarity'))
            if polarity_property:
                polarity = polarity_property.get_value().get_content()
                if polarity == 'Left':
                    left_subduction_boundary_section_features.extend(
                        boundary_section_features)
                elif polarity == 'Right':
                    right_subduction_boundary_section_features.extend(
                        boundary_section_features)
        else:
            # Put all ridges in one collection/file.
            ridge_transform_boundary_section_features.extend(
                boundary_section_features)

    if resolved_topology_features:
        # Put the features in a feature collection so we can write them to a file.
        resolved_topology_feature_collection = pygplates.FeatureCollection(
            resolved_topology_features)
        resolved_topology_features_filename = '{0}boundary_polygons_{1:0.2f}Ma.{2}'.format(
            output_filename_prefix, reconstruction_time,
            output_filename_extension)
        resolved_topology_feature_collection.write(
            resolved_topology_features_filename)

    if ridge_transform_boundary_section_features:
        # Put the features in a feature collection so we can write them to a file.
        ridge_transform_boundary_section_feature_collection = pygplates.FeatureCollection(
            ridge_transform_boundary_section_features)
        ridge_transform_boundary_section_features_filename = '{0}ridge_transform_boundaries_{1:0.2f}Ma.{2}'.format(
            output_filename_prefix, reconstruction_time,
            output_filename_extension)
        ridge_transform_boundary_section_feature_collection.write(
            ridge_transform_boundary_section_features_filename)

    if subduction_boundary_section_features:
        # Put the features in a feature collection so we can write them to a file.
        subduction_boundary_section_feature_collection = pygplates.FeatureCollection(
            subduction_boundary_section_features)
        subduction_boundary_section_features_filename = '{0}subduction_boundaries_{1:0.2f}Ma.{2}'.format(
            output_filename_prefix, reconstruction_time,
            output_filename_extension)
        subduction_boundary_section_feature_collection.write(
            subduction_boundary_section_features_filename)

    if left_subduction_boundary_section_features:
        # Put the features in a feature collection so we can write them to a file.
        left_subduction_boundary_section_feature_collection = pygplates.FeatureCollection(
            left_subduction_boundary_section_features)
        left_subduction_boundary_section_features_filename = '{0}subduction_boundaries_sL_{1:0.2f}Ma.{2}'.format(
            output_filename_prefix, reconstruction_time,
            output_filename_extension)
        left_subduction_boundary_section_feature_collection.write(
            left_subduction_boundary_section_features_filename)

    if right_subduction_boundary_section_features:
        # Put the features in a feature collection so we can write them to a file.
        right_subduction_boundary_section_feature_collection = pygplates.FeatureCollection(
            right_subduction_boundary_section_features)
        right_subduction_boundary_section_features_filename = '{0}subduction_boundaries_sR_{1:0.2f}Ma.{2}'.format(
            output_filename_prefix, reconstruction_time,
            output_filename_extension)
        right_subduction_boundary_section_feature_collection.write(
            right_subduction_boundary_section_features_filename)
Exemple #4
0
def calc_subducting_sediment_volume(time):

    rotation_model = pygplates.RotationModel(rotation_filename)
    topology_features = pygplates.FeaturesFunctionArgument(
            (topology_dir + '/Global_EarthByte_230-0Ma_GK07_AREPS_PlateBoundaries.gpml',
            topology_dir + '/Global_EarthByte_230-0Ma_GK07_AREPS_Topology_BuildingBlocks.gpml')).get_features()

    sediment_thickness_grid_filename = sediment_thickness_grid_dir + '/sed_thick_0.2d_{0}.grd'.format(time)
    
    subduction_convergence_data = subduction_convergence.subduction_convergence(
                rotation_model,
                topology_features,
                tessellation_threshold_radians,
                time)
    
    subduction_points = [pygplates.PointOnSphere(data[1], data[0]) for data in subduction_convergence_data]
    
    # Sample the sediment thickness raster at the subduction points.
    sediment_thicknesses = raster_query.query_raster_at_points(
            sediment_thickness_grid_filename,
            subduction_points,
            search_radius_radians,
            smoothing_radius_radians)
    
    subducting_lon_lat_thickness_velocity_volume_list = []
    
    # Iterate over subduction points/thicknesses and calculate statistics (including subducting volume).
    weighted_mean_subducting_sed_thickness = 0.0
    weighted_second_moment_subducting_sed_thickness = 0.0
    total_subducting_length_metres = 0.0
    total_subducting_sediment_volume_metres_3_per_year = 0.0
    for subduction_point_index, sediment_thickness in enumerate(sediment_thicknesses):
        if math.isnan(sediment_thickness):
            continue
        
        subduction_convergence_item = subduction_convergence_data[subduction_point_index]
        
        subducting_lon = subduction_convergence_item[0]
        subducting_lat = subduction_convergence_item[1]
        convergence_velocity_magnitude_cm_per_yr = subduction_convergence_item[2]
        convergence_obliquity_degrees = subduction_convergence_item[3]
        #absolute_velocity_magnitude = subduction_convergence_item[4]
        #absolute_obliquity_degrees = subduction_convergence_item[5]
        subducting_length_degrees = subduction_convergence_item[6]
        #subducting_arc_normal_azimuth = subduction_convergence_item[7]
        #subducting_plate_id = subduction_convergence_item[8]
        #overriding_plate_id = subduction_convergence_item[9]
        
        subducting_length_metres = (
            math.radians(subducting_length_degrees) * 1e3 * pygplates.Earth.mean_radius_in_kms)
        
        weighted_mean_subducting_sed_thickness += subducting_length_metres * sediment_thickness
        weighted_second_moment_subducting_sed_thickness += subducting_length_metres * sediment_thickness * sediment_thickness
        
        total_subducting_length_metres += subducting_length_metres
        convergence_normal_velocity_metres_per_year = (
            # 1e-2 converts cm/y to m/y...
            1e-2 * math.fabs(convergence_velocity_magnitude_cm_per_yr) *
            # Negative convergence handled by cos(obliquity_angle)...
            math.cos(math.radians(convergence_obliquity_degrees)))
        subducting_sediment_volume_metres_3_per_year = (
                sediment_thickness * subducting_length_metres * convergence_normal_velocity_metres_per_year)
        total_subducting_sediment_volume_metres_3_per_year += subducting_sediment_volume_metres_3_per_year
        
        subducting_lon_lat_thickness_velocity_volume_list.append((
                subducting_lon,
                subducting_lat,
                sediment_thickness,
                subducting_sediment_volume_metres_3_per_year / subducting_length_metres,
                # cms/year ...
                1e2 * convergence_normal_velocity_metres_per_year))
    
    # mean = M = sum(Ci * Xi) / sum(Ci)
    # std_dev  = sqrt[sum(Ci * (Xi - M)^2) / sum(Ci)]
    #          = sqrt[(sum(Ci * Xi^2) - 2 * M * sum(Ci * Xi) + M^2 * sum(Ci)) / sum(Ci)]
    #          = sqrt[(sum(Ci * Xi^2) - 2 * M * M * sum(Ci) + M^2 * sum(Ci)) / sum(Ci)]
    #          = sqrt[(sum(Ci * Xi^2) - M^2 * sum(Ci)) / sum(Ci)]
    #          = sqrt[(sum(Ci * Xi^2) / sum(Ci) - M^2]
    mean_sed_thickness = weighted_mean_subducting_sed_thickness / total_subducting_length_metres
    variance_sed_thickness = (
            (weighted_second_moment_subducting_sed_thickness / total_subducting_length_metres) -
            mean_sed_thickness * mean_sed_thickness)
    std_dev_sed_thickness = math.sqrt(variance_sed_thickness) if variance_sed_thickness > 0.0 else 0.0
    
    return (time,
            subducting_lon_lat_thickness_velocity_volume_list,
            mean_sed_thickness,
            std_dev_sed_thickness,
            total_subducting_length_metres,
            total_subducting_sediment_volume_metres_3_per_year)
def resolve_topologies_into_features(
    rotation_features_or_model,
    topology_features,
    time,
    transform_segment_deviation_in_radians=separate_ridge_transform_segments.
    DEFAULT_TRANSFORM_SEGMENT_DEVIATION_RADIANS):
    """
    Resolves topologies at specified time and returns resolved topologies and their boundary sections as subduction zones,
    mid-ocean ridges (ridge/transform) and others (not subduction zones or mid-ocean ridges).
    
    rotation_features_or_model: Rotation model or feature collection(s), or list of features, or filename(s).
    
    topology_features: Topology feature collection(s), or list of features, or filename(s) or any combination of those.
    
    time: Reconstruction time to resolved topologies.
    
    transform_segment_deviation_in_radians: How much a mid-ocean ridge segment can deviate from the stage pole before
                                            it's considered a transform segment (in radians).
    
    Returns: A tuple containing the following lists...
            - resolved topology features (topological plates and networks)
            - ridge and transform boundary sections (resolved features)
            - ridge boundary sections (resolved features)
            - transform boundary sections (resolved features)
            - subduction boundary sections (resolved features)
            - left subduction boundary sections (resolved features)
            - right subduction boundary sections (resolved features)
            - other boundary sections (resolved features) that are not subduction zones or mid-ocean ridges (ridge/transform)
    """
    time = float(time)

    # Turn rotation data into a RotationModel (if not already).
    rotation_model = pygplates.RotationModel(rotation_features_or_model)

    # Turn topology data into a list of features (if not already).
    topology_features = pygplates.FeaturesFunctionArgument(topology_features)

    # Resolve our topological plate polygons (and deforming networks) to the current 'time'.
    # We generate both the resolved topology boundaries and the boundary sections between them.
    resolved_topologies = []
    shared_boundary_sections = []
    pygplates.resolve_topologies(topology_features.get_features(),
                                 rotation_model, resolved_topologies, time,
                                 shared_boundary_sections)

    # We'll create a feature for each boundary polygon feature and each type of
    # resolved topological section feature we find.
    resolved_topology_features = []
    ridge_transform_boundary_section_features = []
    ridge_boundary_section_features = []
    transform_boundary_section_features = []
    subduction_boundary_section_features = []
    left_subduction_boundary_section_features = []
    right_subduction_boundary_section_features = []
    other_boundary_section_features = []

    # Iterate over the resolved topologies.
    for resolved_topology in resolved_topologies:
        resolved_topology_features.append(
            resolved_topology.get_resolved_feature())

    # Iterate over the shared boundary sections.
    for shared_boundary_section in shared_boundary_sections:

        # Get all the geometries of the current boundary section.
        boundary_section_features = [
            shared_sub_segment.get_resolved_feature() for shared_sub_segment in
            shared_boundary_section.get_shared_sub_segments()
        ]

        # Add the feature to the correct list depending on feature type, etc.
        if shared_boundary_section.get_feature().get_feature_type(
        ) == pygplates.FeatureType.gpml_subduction_zone:

            # Put all subduction zones in one collection/file.
            subduction_boundary_section_features.extend(
                boundary_section_features)

            # Also put subduction zones in left/right collection/file.
            polarity_property = shared_boundary_section.get_feature().get(
                pygplates.PropertyName.create_gpml('subductionPolarity'))
            if polarity_property:
                polarity = polarity_property.get_value().get_content()
                if polarity == 'Left':
                    left_subduction_boundary_section_features.extend(
                        boundary_section_features)
                elif polarity == 'Right':
                    right_subduction_boundary_section_features.extend(
                        boundary_section_features)

        elif shared_boundary_section.get_feature().get_feature_type(
        ) == pygplates.FeatureType.gpml_mid_ocean_ridge:
            ridge_transform_boundary_section_features.extend(
                boundary_section_features)

            # Find the stage rotation of the MOR in the frame of reference of its reconstructed geometry at the current 'time'.
            # The stage pole can then be directly geometrically compared to the reconstructed spreading geometry.
            spreading_stage_rotation = separate_ridge_transform_segments.get_stage_rotation_for_reconstructed_geometry(
                shared_boundary_section.get_feature(), rotation_model, time)
            if spreading_stage_rotation:
                boundary_section_geometries = [
                    shared_sub_segment.get_resolved_geometry()
                    for shared_sub_segment in
                    shared_boundary_section.get_shared_sub_segments()
                ]
                for boundary_section_index, boundary_section_feature in enumerate(
                        boundary_section_features):
                    # Split into ridge and transform segments.
                    ridge_and_transform_segment_geometries = separate_ridge_transform_segments.separate_geometry_into_ridges_and_transforms(
                        spreading_stage_rotation,
                        boundary_section_geometries[boundary_section_index],
                        transform_segment_deviation_in_radians)
                    if ridge_and_transform_segment_geometries:
                        ridge_sub_segment_geometries, transform_sub_segment_geometries = ridge_and_transform_segment_geometries

                        if ridge_sub_segment_geometries:
                            ridge_boundary_section_feature = boundary_section_feature.clone(
                            )
                            ridge_boundary_section_feature.set_geometry(
                                ridge_sub_segment_geometries)
                            ridge_boundary_section_features.append(
                                ridge_boundary_section_feature)

                        if transform_sub_segment_geometries:
                            transform_boundary_section_feature = boundary_section_feature.clone(
                            )
                            transform_boundary_section_feature.set_geometry(
                                transform_sub_segment_geometries)
                            transform_boundary_section_features.append(
                                transform_boundary_section_feature)

                    # else skip shared sub segment - it's not a polyline (or polygon).

            # else most likely MOR doesn't have left/right plate IDs or reconstruction/conjugate plate IDs,
            # so don't split into ridge and transform segments.

        else:
            other_boundary_section_features.extend(boundary_section_features)

    return (resolved_topology_features,
            ridge_transform_boundary_section_features,
            ridge_boundary_section_features,
            transform_boundary_section_features,
            subduction_boundary_section_features,
            left_subduction_boundary_section_features,
            right_subduction_boundary_section_features,
            other_boundary_section_features)
Exemple #6
0
def separate_features_into_ridges_and_transforms(
        rotation_features_or_model,
        spreading_features,
        spreading_feature_types = None,
        transform_segment_deviation_in_radians = DEFAULT_TRANSFORM_SEGMENT_DEVIATION_RADIANS):
    """
    Split the geometries of isochrons and mid-ocean ridges into ridge and transform segments based
    on each segment’s alignment with the geometry’s stage pole at its time of appearance.
    
    rotation_features_or_model: Rotation model or feature collection(s), or list of features, or filename(s).
    
    spreading_features: Spreading feature collection(s), or list of features, or filename(s) or any combination of those.
    
    spreading_feature_types: Only spreading features with a feature type contained in this list are considered.
                             If None then all spreading features are considered.
    
    transform_segment_deviation_in_radians: How much a segment can deviate from the stage pole before
                                            it's considered a transform segment (in radians).
    
    Returns: The separated ridge and transform features respectively, of type
             2-tuple (list of pygplates.Feature, list of pygplates.Feature).
    """
    
    # Turn rotation data into a RotationModel (if not already).
    #
    # OPTIMISATION:
    # We will be reconstructing (and reverse reconstructing) mid-ocean ridges in groups, where ridges in each
    # group have the same time-of-appearance. They need to have the same time of appearance because, in pyGPlates,
    # version 3 half-stage rotations start spreading from the time-of-appearance in 10 My intervals
    # (ie, the 10My intervals are 'begin_time', 'begin_time-10', begin_time-20', ..., reconstruction_time).
    # And because the mid-ocean ridges with the same time-of-appearance also have the same time intervals
    # they'll reuse the cached reconstruction trees (one cached tree per time interval).
    # This will avoid a lot of wasted time recreating these trees if the cache is continually flushed
    # (eg, by mixing mid-ocean ridges with different appearance times).
    #
    # A cache size of 100 is enough to go back to 1,000Ma (100 entries * 10My interval).
    rotation_model = pygplates.RotationModel(rotation_features_or_model, 100)
    
    # Turn spreading feature data into a list of features (if not already).
    spreading_features = pygplates.FeaturesFunctionArgument(spreading_features)
    
    # Gather all spreading features with the same begin time (time-of-appearance) into groups.
    #
    # This is an optimisation that enables reconstructing multiple mid-ocean ridges with the same begin time together.
    # This can make a *big* difference to the running time (see note above regarding rotation model cache size).
    spreading_features_grouped_by_begin_time = {}
    
    # Iterate over all geometries in spreading features.
    for spreading_feature in spreading_features.get_features():
        
        # Filter spreading feature types if requested.
        if (spreading_feature_types and
            spreading_feature.get_feature_type() not in spreading_feature_types):
            continue
        
        begin_time, _ = spreading_feature.get_valid_time()
        
        # Add to list of spreading features with same begin time.
        if begin_time not in spreading_features_grouped_by_begin_time:
            spreading_features_grouped_by_begin_time[begin_time] = []
        spreading_features_grouped_by_begin_time[begin_time].append(spreading_feature)
    
    # The separated ridge/transform segment features.
    # Both types of segment feature will have the same feature type as the feature they are extracted from.
    ridge_segment_features = []
    transform_segment_features = []
    
    # Iterate over groups of spreading features with the same begin time (time-of-appearance).
    for begin_time, spreading_features_with_begin_time in spreading_features_grouped_by_begin_time.items():
        
        # Reconstruct the spreading features to their common birth time.
        reconstructed_spreading_features = []
        pygplates.reconstruct(
                spreading_features_with_begin_time,
                rotation_model,
                reconstructed_spreading_features,
                begin_time,
                group_with_feature=True)
        
        ridge_segment_features_with_begin_time = []
        transform_segment_features_with_begin_time = []
        
        # Iterate over reconstructed spreading features.
        for spreading_feature, reconstructed_spreading_geometries in reconstructed_spreading_features:
            
            # Find the stage rotation of the spreading feature in the frame of reference of its
            # geometry at its birth time. The stage pole can then be directly geometrically compared
            # to the reconstructed spreading geometry.
            stage_rotation = get_stage_rotation_for_reconstructed_geometry(spreading_feature, rotation_model, begin_time)
            if not stage_rotation:
                # Skip current feature - it's not a spreading feature.
                continue
            
            ridge_segment_geometries = []
            transform_segment_geometries = []
            
            # A feature usually has a single geometry but it could have more - iterate over them all.
            for reconstructed_spreading_geometry in reconstructed_spreading_geometries:
                ridge_and_transform_segment_geometries = separate_geometry_into_ridges_and_transforms(
                        stage_rotation,
                        reconstructed_spreading_geometry.get_reconstructed_geometry(),
                        transform_segment_deviation_in_radians)
                if ridge_and_transform_segment_geometries:
                    ridge_segment_geometries.extend(ridge_and_transform_segment_geometries[0])
                    transform_segment_geometries.extend(ridge_and_transform_segment_geometries[1])
            
            # Put all ridge segment geometries into one feature and transform segment geometries into another.
            if ridge_segment_geometries:
                ridge_segment_feature = spreading_feature.clone()
                ridge_segment_feature.set_geometry(ridge_segment_geometries)
                ridge_segment_features_with_begin_time.append(ridge_segment_feature)
            if transform_segment_geometries:
                transform_segment_feature = spreading_feature.clone()
                transform_segment_feature.set_geometry(transform_segment_geometries)
                transform_segment_features_with_begin_time.append(transform_segment_feature)
        
        # Reverse reconstruct the segmented spreading features from their common birth time.
        #
        # Each new feature needs to be reverse reconstructed from birth time to present day because the
        # geometries are reconstructed (but need to be stored in present day positions within the feature).
        pygplates.reverse_reconstruct(
                (ridge_segment_features_with_begin_time, transform_segment_features_with_begin_time),
                rotation_model,
                begin_time)
        
        ridge_segment_features.extend(ridge_segment_features_with_begin_time)
        transform_segment_features.extend(transform_segment_features_with_begin_time)
    
    return ridge_segment_features, transform_segment_features
Exemple #7
0
def subduction_convergence(
        # Rotation model or feature collection(s), or list of features, or filename(s)...
        rotation_features_or_model,
        # Topology feature collection(s), or list of features, or filename(s) or any combination of those...
        topology_features,
        # Threshold sampling distance along subduction zones (in radians)...
        threshold_sampling_distance_radians,
        time,
        velocity_delta_time=1.0,
        anchor_plate_id=0):

    # Turn rotation data into a RotationModel (if not already).
    rotation_model = pygplates.RotationModel(rotation_features_or_model)

    # Turn topology data into a list of features (if not already).
    topology_features = pygplates.FeaturesFunctionArgument(topology_features)

    # Resolve our topological plate polygons (and deforming networks) to the current 'time'.
    # We generate both the resolved topology boundaries and the boundary sections between them.
    resolved_topologies = []
    shared_boundary_sections = []
    pygplates.resolve_topologies(topology_features.get_features(),
                                 rotation_model, resolved_topologies, time,
                                 shared_boundary_sections, anchor_plate_id)

    # List of tesselated subduction zone shared subsegment points and associated convergence parameters
    # for the current 'time'.
    output_data = []

    # Iterate over the shared boundary sections of all resolved topologies.
    for shared_boundary_section in shared_boundary_sections:

        # Skip sections that are not subduction zones.
        if shared_boundary_section.get_feature().get_feature_type(
        ) != pygplates.FeatureType.gpml_subduction_zone:
            continue

        # Iterate over the shared sub-segments of the current subducting line.
        # These are the parts of the subducting line that actually contribute to topological boundaries.
        for shared_sub_segment in shared_boundary_section.get_shared_sub_segments(
        ):

            # Find the overriding and subducting plates on either side of the shared sub-segment.
            overriding_and_subducting_plates = find_overriding_and_subducting_plates(
                shared_sub_segment, time)
            if not overriding_and_subducting_plates:
                continue
            overriding_plate, subducting_plate, subduction_polarity = overriding_and_subducting_plates
            overriding_plate_id = overriding_plate.get_feature(
            ).get_reconstruction_plate_id()
            subducting_plate_id = subducting_plate.get_feature(
            ).get_reconstruction_plate_id()

            # The plate ID of the subduction zone line (as opposed to the subducting plate).
            #
            # Update: The plate IDs of the subduction zone line and overriding plate can differ
            # even in a non-deforming model due to smaller plates, not modelled by topologies, moving
            # differently than the larger topological plate being modelled - and the subduction zone line
            # having plate IDs of the smaller plates near them. For that reason we use the plate ID
            # of the subduction zone line whenever we can. Since some subduction zone lines can be
            # topological lines, they might actually be deforming (or intended to be deforming) and
            # hence their plate ID is not meaningful or at least we can't be sure whether it will
            # be zero or the overriding plate (or something else). So if the subduction zone line
            # is a topological line then we'll use the overriding plate ID instead.
            #
            if isinstance(shared_boundary_section.get_topological_section(),
                          pygplates.ResolvedTopologicalLine):
                subduction_zone_plate_id = overriding_plate_id
            else:
                subduction_zone_plate_id = shared_sub_segment.get_feature(
                ).get_reconstruction_plate_id()

            # Get the rotation of the subducting plate relative to the overriding plate
            # from 'time + velocity_delta_time' to 'time'.
            convergence_relative_stage_rotation = rotation_model.get_rotation(
                time,
                subducting_plate_id,
                time + velocity_delta_time,
                overriding_plate_id,
                anchor_plate_id=anchor_plate_id)
            #
            # The subduction zones have been reconstructed using the rotation "R(0->t2,A->M)":
            #
            #   reconstructed_geometry = R(0->t2,A->M) * present_day_geometry
            #
            # We can write "R(0->t2,A->M)" in terms of the stage rotation "R(t1->t2,F->M)" as:
            #
            #   R(0->t2,A->M) = R(0->t2,A->F) * R(0->t2,F->M)
            #                 = R(0->t2,A->F) * R(t1->t2,F->M) * R(0->t1,F->M)
            #                 = R(0->t2,A->F) * stage_rotation * R(0->t1,F->M)
            #
            # ...where 't1' is 't+1' and 't2' is 't' (ie, from 't1' to 't2').
            #
            # So to get the *reconstructed* geometry into the stage rotation reference frame
            # we need to rotate it by "inverse[R(0->t2,A->F)]":
            #
            #   reconstructed_geometry = R(0->t2,A->F) * stage_rotation * R(0->t1,F->M) * present_day_geometry
            #   inverse[R(0->t2,A->F)] * reconstructed_geometry = stage_rotation * R(0->t1,F->M) * present_day_geometry
            #
            # Once we've done that we can calculate the velocities of those geometry points
            # using the stage rotation. Then the velocities need to be rotated back from the
            # stage rotation reference frame using the rotation "R(0->t2,A->F)".
            #
            from_stage_frame_relative_to_overriding = rotation_model.get_rotation(
                time, overriding_plate_id, anchor_plate_id=anchor_plate_id)
            to_stage_frame_relative_to_overriding = from_stage_frame_relative_to_overriding.get_inverse(
            )

            # Get the rotation of the subduction zone relative to the anchor plate
            # from 'time + velocity_delta_time' to 'time'.
            #
            # Note: We don't need to convert to and from the stage rotation reference frame
            # like the above convergence because this stage rotation is relative to the anchor plate
            # and so the above to/from stage rotation frame conversion "R(0->t2,A->F)" is the
            # identity rotation since the fixed plate (F) is the anchor plate (A).
            subduction_zone_equivalent_stage_rotation = rotation_model.get_rotation(
                time,
                subduction_zone_plate_id,
                time + velocity_delta_time,
                anchor_plate_id=anchor_plate_id)

            # We need to reverse the subducting_normal vector direction if overriding plate is to
            # the right of the subducting line since great circle arc normal is always to the left.
            if subduction_polarity == 'Left':
                subducting_normal_reversal = 1
            else:
                subducting_normal_reversal = -1

            # Ensure the shared sub-segment is tessellated to within the threshold sampling distance.
            tessellated_shared_sub_segment_polyline = (
                shared_sub_segment.get_resolved_geometry().to_tessellated(
                    threshold_sampling_distance_radians))

            # Iterate over the great circle arcs of the tessellated polyline to get the
            # arc midpoints, lengths and subducting normals.
            # There is an arc between each adjacent pair of points in the polyline.
            arc_midpoints = []
            arc_lengths = []
            subducting_arc_normals = []
            for arc in tessellated_shared_sub_segment_polyline.get_segments():
                if not arc.is_zero_length():
                    arc_midpoints.append(arc.get_arc_point(0.5))
                    arc_lengths.append(arc.get_arc_length())
                    # The normal to the subduction zone in the direction of subduction (towards overriding plate).
                    subducting_arc_normals.append(
                        subducting_normal_reversal *
                        arc.get_great_circle_normal())

            # Shouldn't happen, but just in case the shared sub-segment polyline coincides with a point.
            if not arc_midpoints:
                continue

            # The subducting arc normals relative to North (azimuth).
            # Convert global 3D normal vectors to local (magnitude, azimuth, inclination) tuples (one tuple per point).
            subducting_arc_local_normals = pygplates.LocalCartesian.convert_from_geocentric_to_magnitude_azimuth_inclination(
                arc_midpoints, subducting_arc_normals)

            # Calculate the convergence velocities, and subduction zone velocities relative to
            # overriding plate, at the arc midpoints.
            #
            # Note; We need to convert the reconstructed geometry points into the stage rotation
            # reference frame to calculate velocities and then convert the velocities using the
            # reverse transform as mentioned above.
            arc_midpoints_in_stage_frame_relative_to_overriding = [
                to_stage_frame_relative_to_overriding * arc_midpoint
                for arc_midpoint in arc_midpoints
            ]
            convergence_velocity_vectors_in_stage_frame_relative_to_overriding = pygplates.calculate_velocities(
                arc_midpoints_in_stage_frame_relative_to_overriding,
                convergence_relative_stage_rotation, velocity_delta_time,
                pygplates.VelocityUnits.cms_per_yr)
            convergence_velocity_vectors = [
                from_stage_frame_relative_to_overriding * velocity
                for velocity in
                convergence_velocity_vectors_in_stage_frame_relative_to_overriding
            ]

            # Calculate the absolute velocities at the arc midpoints.
            absolute_velocity_vectors = pygplates.calculate_velocities(
                arc_midpoints, subduction_zone_equivalent_stage_rotation,
                velocity_delta_time, pygplates.VelocityUnits.cms_per_yr)

            for arc_index in range(len(arc_midpoints)):
                arc_midpoint = arc_midpoints[arc_index]
                arc_length = arc_lengths[arc_index]
                subducting_arc_normal = subducting_arc_normals[arc_index]
                subducting_arc_normal_azimuth = subducting_arc_local_normals[
                    arc_index][1]
                lat, lon = arc_midpoint.to_lat_lon()

                # The direction towards which we rotate from the subducting normal in a clockwise fashion.
                clockwise_direction = pygplates.Vector3D.cross(
                    subducting_arc_normal, arc_midpoint.to_xyz())

                # Calculate the convergence rate parameters.
                convergence_velocity_vector = convergence_velocity_vectors[
                    arc_index]
                if convergence_velocity_vector.is_zero_magnitude():
                    convergence_velocity_magnitude = 0
                    convergence_obliquity_degrees = 0
                else:
                    convergence_velocity_magnitude = convergence_velocity_vector.get_magnitude(
                    )
                    convergence_obliquity_degrees = math.degrees(
                        pygplates.Vector3D.angle_between(
                            convergence_velocity_vector,
                            subducting_arc_normal))
                    # Anti-clockwise direction has range (0, -180) instead of (0, 180).
                    if pygplates.Vector3D.dot(convergence_velocity_vector,
                                              clockwise_direction) < 0:
                        convergence_obliquity_degrees = -convergence_obliquity_degrees

                    # See if plates are diverging (moving away from each other).
                    # If plates are diverging (moving away from each other) then make the
                    # velocity magnitude negative to indicate this. This could be inferred from
                    # the obliquity but it seems this is the standard way to output convergence rate.
                    if math.fabs(convergence_obliquity_degrees) > 90:
                        convergence_velocity_magnitude = -convergence_velocity_magnitude

                # Calculate the absolute rate parameters.
                absolute_velocity_vector = absolute_velocity_vectors[arc_index]
                if absolute_velocity_vector.is_zero_magnitude():
                    absolute_velocity_magnitude = 0
                    absolute_obliquity_degrees = 0
                else:
                    absolute_velocity_magnitude = absolute_velocity_vector.get_magnitude(
                    )
                    absolute_obliquity_degrees = math.degrees(
                        pygplates.Vector3D.angle_between(
                            absolute_velocity_vector, subducting_arc_normal))
                    # Anti-clockwise direction has range (0, -180) instead of (0, 180).
                    if pygplates.Vector3D.dot(absolute_velocity_vector,
                                              clockwise_direction) < 0:
                        absolute_obliquity_degrees = -absolute_obliquity_degrees

                    # See if the subduction zone absolute motion is heading in the direction of the
                    # overriding plate. If it is then make the velocity magnitude negative to
                    # indicate this. This could be inferred from the obliquity but it seems this
                    # is the standard way to output convergence rate.
                    #
                    # Note that we are not calculating the motion of the subduction zone
                    # relative to the overriding plate - they are usually attached to each other
                    # and hence wouldn't move relative to each other.
                    if math.fabs(absolute_obliquity_degrees) < 90:
                        absolute_velocity_magnitude = -absolute_velocity_magnitude

                # The data will be output in GMT format (ie, lon first, then lat, etc).
                output_data.append(
                    (lon, lat, convergence_velocity_magnitude,
                     convergence_obliquity_degrees,
                     absolute_velocity_magnitude, absolute_obliquity_degrees,
                     math.degrees(arc_length),
                     math.degrees(subducting_arc_normal_azimuth),
                     subducting_plate_id, overriding_plate_id))

    # Return data sorted since it's easier to compare results (when at least lon/lat is sorted).
    return sorted(output_data)
def calculate_velocities(
        # Rotation model or feature collection(s), or list of features, or filename(s)...
        rotation_features_or_model,
        # Geometry feature collection(s), or list of features, or filename(s) or any combination of those...
        reconstructable_features,
        # Threshold sampling distance along subduction zones (in radians)...
        threshold_sampling_distance_radians,
        reconstruction_time,
        velocity_delta_time=1.0,
        anchor_plate_id=0):
    """
    Reconstructs geometries at 'reconstruction_time', tessellates all *line* geometries to within
    'threshold_sampling_distance_radians' radians and returns the following velocity-related parameters
    at each tessellates point:
    
    - point longitude
    - point latitude
    - absolute (relative to anchor plate) velocity magnitude (in cm/yr)
    - absolute velocity obliquity angle (angle between line normal vector and absolute velocity vector)
      * note that this is zero if the input geometries are points (not lines)
    - length of arc segment (in degrees) that current point is on
      * note that this is zero if the input geometries are points (not lines)
    - line arc normal azimuth angle (clockwise starting at North, ie, 0 to 360 degrees) at current point
      * note that this is zero if the input geometries are points (not lines)
    - plate ID
    
    The obliquity angles are in the range (-180 180). The range (0, 180) goes clockwise
    (when viewed from above the Earth) from the line normal direction to the velocity vector.
    The range (0, -180) goes counter-clockwise.
    You can change the range (-180, 180) to the range (0, 360) by adding 360 to negative angles.
    
    """

    # Turn rotation data into a RotationModel (if not already).
    rotation_model = pygplates.RotationModel(rotation_features_or_model)

    # Turn geometry data into a list of features (if not already).
    reconstructable_features = pygplates.FeaturesFunctionArgument(
        reconstructable_features)

    # Reconstruct our reconstructable features to the current 'reconstruction_time'.
    reconstructed_feature_geometries = []
    pygplates.reconstruct(reconstructable_features.get_features(),
                          rotation_model, reconstructed_feature_geometries,
                          reconstruction_time, anchor_plate_id)

    # List of tesselated line points and associated velocity parameters for the current 'reconstruction_time'.
    output_data = []

    # Iterate over all reconstructed feature geometries.
    for reconstructed_feature_geometry in reconstructed_feature_geometries:

        reconstruction_plate_id = reconstructed_feature_geometry.get_feature(
        ).get_reconstruction_plate_id()

        # Get the rotation of the reconstructed geometry relative to the anchor plate
        # from 'reconstruction_time + velocity_delta_time' to 'reconstruction_time'.
        #
        # The geometries have been reconstructed using the rotation "R(0->t2,A->M)":
        #
        #   reconstructed_geometry = R(0->t2,A->M) * present_day_geometry
        #
        # We can write "R(0->t2,A->M)" in terms of the stage rotation "R(t1->t2,F->M)" as:
        #
        #   R(0->t2,A->M) = R(0->t2,A->F) * R(0->t2,F->M)
        #                 = R(0->t2,A->F) * R(t1->t2,F->M) * R(0->t1,F->M)
        #                 = R(0->t2,A->F) * stage_rotation * R(0->t1,F->M)
        #
        # ...where 't1' is 't+1' and 't2' is 't' (ie, from 't1' to 't2').
        #
        # So to get the *reconstructed* geometry into the stage rotation reference frame
        # we need to rotate it by "inverse[R(0->t2,A->F)]":
        #
        #   reconstructed_geometry = R(0->t2,A->F) * stage_rotation * R(0->t1,F->M) * present_day_geometry
        #   inverse[R(0->t2,A->F)] * reconstructed_geometry = stage_rotation * R(0->t1,F->M) * present_day_geometry
        #
        # Once we've done that we can calculate the velocities of those geometry points
        # using the stage rotation. Then the velocities need to be rotated back from the
        # stage rotation reference frame using the rotation "R(0->t2,A->F)".
        #
        # However, since we're calculating the *absolute* velocity (ie, relative to anchor plate)
        # the above to/from stage rotation frame conversion "R(0->t2,A->F)" is the
        # identity rotation since the fixed plate (F) is the anchor plate (A).
        #
        # This long explanation was obviously unnecessary in our case but it's good to note in case this
        # code ever gets modified to support *convergence* velocities (ie, relative to non-anchor plate).
        equivalent_stage_rotation = rotation_model.get_rotation(
            reconstruction_time,
            reconstruction_plate_id,
            reconstruction_time + velocity_delta_time,
            anchor_plate_id=anchor_plate_id)

        calculate_velocities_along_reconstructed_geometry(
            output_data,
            reconstructed_feature_geometry.get_reconstructed_geometry(),
            equivalent_stage_rotation, reconstruction_plate_id,
            threshold_sampling_distance_radians, velocity_delta_time)

    # Return data sorted since it's easier to compare results (when at least lon/lat is sorted).
    return output_data
Exemple #9
0
    def __init__(
            self,
            rotation_features_or_model,
            topology_features,
            reconstruction_begin_time,
            reconstruction_end_time,
            reconstruction_time_interval,
            points,
            point_begin_times=None,
            point_end_times=None,
            point_plate_ids=None,
            detect_collisions=True,
            global_collision_parameters=DEFAULT_GLOBAL_COLLISION_PARAMETERS,
            feature_specific_collision_parameters=None):
        """
        rotation_features_or_model: Rotation model or feature collection(s), or list of features, or filename(s).

        topology_features: Topology feature collection(s), or list of features, or filename(s) or any combination of those.

        detect_collisions: Whether to test for collisions or not. Defaults to True.

        global_collision_parameters: The collision parameters to use for any feature type not specified in 'feature_specific_collision_parameters'.
                                     Should be a 2-tuple of (threshold velocity delta in kms/my, threshold distance to boundary per My in kms/my).
                                     The first threshold parameter means:
                                        A point that transitions from one plate to another can disappear if the change in velocity exceeds this threshold.
                                     The second threshold parameter means:
                                        Only those transitioning points exceeding the threshold velocity delta and that are close enough to a plate boundary can disappear.
                                        The distance is proportional to the relative velocity (change in velocity), plus a constant offset based on the threshold distance to boundary
                                        to account for plate boundaries that change shape significantly from one time step to the next
                                        (note that some boundaries are meant to do this and others are a result of digitisation).
                                        The actual distance threshold used is (threshold_distance_to_boundary + relative_velocity) * time_interval
                                     Defaults to parameters used in GPlates 2.0, if not specified.

        feature_specific_collision_parameters: Optional sequence of collision parameters specific to feature types.
                                               If specified then should be a sequence of 2-tuples, with each 2-tuple specifying (feature_type, collision_parameters).
                                               And where each 'collision_parameters' is a 2-tuple of (threshold velocity delta in kms/my, threshold distance to boundary per My in kms/my).
                                                   See 'global_collision_parameters' for details on these thresholds.
                                               Any feature type not specified here defaults to using 'global_collision_parameters'.
        """

        # Turn rotation data into a RotationModel (if not already).
        self.rotation_model = pygplates.RotationModel(
            rotation_features_or_model)

        # Turn topology data into a list of features (if not already).
        self.topology_features = pygplates.FeaturesFunctionArgument(
            topology_features).get_features()

        # Set up an array of reconstruction times covering the reconstruction time span.
        self.reconstruction_begin_time = reconstruction_begin_time
        self.reconstruction_end_time = reconstruction_end_time
        if reconstruction_time_interval <= 0.0:
            raise ValueError(
                "'reconstruction_time_interval' must be positive.")
        # Reconstruction can go forward or backward in time.
        if self.reconstruction_begin_time > self.reconstruction_end_time:
            self.reconstruction_time_step = -reconstruction_time_interval
        else:
            self.reconstruction_time_step = reconstruction_time_interval
        # Get number of times including end time if time span is a multiple of time step.
        # The '1' is because, for example, 2 time intervals is 3 times.
        # The '1e-6' deals with limited floating-point precision, eg, we want (3.0 - 0.0) / 1.0 to be 3.0 and not 2.999999 (which gets truncated to 2).
        self.num_times = 1 + int(
            math.floor(1e-6 + float(self.reconstruction_end_time -
                                    self.reconstruction_begin_time) /
                       self.reconstruction_time_step))
        # It's possible the time step is larger than the time span, in which case we change it to equal the time span.
        # This guarantees there'll be at least one time step (which has two times; one at either end of interval).
        if self.num_times == 1:
            self.num_times = 2
            self.reconstruction_time_step = self.reconstruction_end_time - self.reconstruction_begin_time
        self.reconstruction_time_interval = math.fabs(
            self.reconstruction_time_step)

        self.last_time_index = self.num_times - 1

        self.points = points
        self.num_points = len(points)

        # Use the specified point begin times if provided (otherwise use 'inf').
        self.point_begin_times = point_begin_times
        if self.point_begin_times is None:
            self.point_begin_times = [float('inf')] * self.num_points
        elif len(self.point_begin_times) != self.num_points:
            raise ValueError(
                "Length of 'point_begin_times' must match length of 'points'.")

        # Use the specified point end times if provided (otherwise use '-inf').
        self.point_end_times = point_end_times
        if self.point_end_times is None:
            self.point_end_times = [float('-inf')] * self.num_points
        elif len(self.point_end_times) != self.num_points:
            raise ValueError(
                "Length of 'point_end_times' must match length of 'points'.")

        # Use the specified point plate IDs if provided (otherwise use '0').
        # These plate IDs are only used when a point falls outside all resolved topologies during a time step.
        self.point_plate_ids = point_plate_ids
        if self.point_plate_ids is None:
            self.point_plate_ids = [0] * self.num_points
        elif len(self.point_plate_ids) != self.num_points:
            raise ValueError(
                "Length of 'point_plate_ids' must match length of 'points'.")

        self.detect_collisions = detect_collisions
        # Convert list of (feature_type, collision_parameters) tuples to a dictionary.
        if feature_specific_collision_parameters:
            self.feature_specific_collision_parameters = dict(
                feature_specific_collision_parameters)
        else:
            self.feature_specific_collision_parameters = dict()
        # Fallback for any feature type not specified in the optional feature-specific list.
        self.global_collision_parameters = global_collision_parameters
def query_raster_with_resolved_topologies(raster_filename,
                                          rotation_features_or_model,
                                          time,
                                          query_features,
                                          tessellation_threshold_radians,
                                          search_radius_radians,
                                          smoothing_radius_radians=None,
                                          query_feature_types=None,
                                          anchor_plate_id=0):
    """
    Resolves and tessellates topological features, and queries raster at the resolved tessellated positions.
    
    raster_filename: The filename of the raster/grid file.
    
    rotation_features_or_model: Rotation model or feature collection(s), or list of features, or filename(s).
    
    time: The reconstruction time.
    
    query_features: Topological feature collection(s), or list of features, or filename(s) or any combination of those.
    
    tessellation_threshold_radians: Threshold sampling distance along resolved topological section polylines (in radians).
    
    search_radius_radians: The distance (in radians) from query point to search for non-NaN raster grid values.
                           If none are found for a query point then it will have a query scalar value of NaN.
    
    smoothing_radius_radians: Determines which non-NaN raster grid values (if any) to use in a weighted average for a query point.
                              All points within a radius of 'min_dist + smoothing_radius_radians' of the query point are included
                              (where 'min_dist' is the distance to the closest non-NaN raster grid location).
                              Note that 'smoothing_radius_radians' should be less than 'search_radius_radians'.
                              Default value is None which results in only the closest non-NaN raster grid value being chosen.
    
    query_feature_types: Optional sequence of feature types to filter/accept (defaults to all feature types).
                         Note: The feature types apply to the topological *sections* (eg, subduction zone).
    
    Returns a list with a tuple for each query point containing the following parameters:
    - query point longitude
    - query point latitude
    - query scalar value, is either:
      + sampled from raster (at query point location), or
      + the nearest raster value(s) if query point is in masked (NaN) region of raster (and within search radius), or
      + NaN if there are no non-NaN grid values within search radius.
    - length of tessellated polyline arc segment (in radians) that query point is on.
    """

    # Turn rotation data into a RotationModel (if not already).
    rotation_model = pygplates.RotationModel(rotation_features_or_model)

    # Turn query data into a list of features (if not already).
    query_features = pygplates.FeaturesFunctionArgument(
        query_features).get_features()

    # Note that, for *topological* features, we cannot remove those not matching the allowed feature types
    # because we need to resolve *all* topologies and then filter out the shared topology sections by feature type.

    # Resolve our topological plate polygons (and deforming networks) to the current 'time'.
    # We generate both the resolved topology boundaries and the boundary sections between them.
    query_resolved_topologies = []
    query_shared_boundary_sections = []
    pygplates.resolve_topologies(query_features, rotation_model,
                                 query_resolved_topologies, time,
                                 query_shared_boundary_sections,
                                 anchor_plate_id)

    # Iterate over the shared boundary sections of all resolved topologies.
    query_reconstructed_geometries = []
    for query_shared_boundary_section in query_shared_boundary_sections:
        # Skip sections that are not included in the list of boundary feature types (if any).
        query_feature = query_shared_boundary_section.get_feature()
        if (query_feature_types and query_feature.get_feature_type()
                not in query_feature_types):
            continue

        # Iterate over the shared sub-segments of the current boundary line.
        # These are the parts of the boundary line that actually contribute to topological boundaries.
        for query_shared_sub_segment in query_shared_boundary_section.get_shared_sub_segments(
        ):
            query_reconstructed_geometries.append(
                query_shared_sub_segment.get_resolved_geometry())

    # Tessellate query geometries to get query points and weights.
    query_points, query_point_weights = tessellate_geometries(
        query_reconstructed_geometries, tessellation_threshold_radians)

    # Query the raster at the query points.
    query_point_scalars = query_raster_at_points(raster_filename, query_points,
                                                 search_radius_radians,
                                                 smoothing_radius_radians)

    query_data = []
    for query_point_index in range(len(query_points)):
        query_point_lat, query_point_lon = query_points[
            query_point_index].to_lat_lon()
        query_point_scalar = query_point_scalars[query_point_index]
        query_point_weight = query_point_weights[query_point_index]

        # The data will be output in GMT format (ie, lon first, then lat, etc).
        query_data.append((query_point_lon, query_point_lat,
                           query_point_scalar, query_point_weight))

    return query_data
def query_raster_with_reconstructed_geometries(raster_filename,
                                               rotation_features_or_model,
                                               time,
                                               query_features,
                                               tessellation_threshold_radians,
                                               search_radius_radians,
                                               smoothing_radius_radians=None,
                                               query_feature_types=None,
                                               anchor_plate_id=0):
    """
    Reconstructs and tessellates regular features, and queries raster at the reconstructed tessellated positions.
    
    raster_filename: The filename of the raster/grid file.
    
    rotation_features_or_model: Rotation model or feature collection(s), or list of features, or filename(s).
    
    time: The reconstruction time.
    
    query_features: Regular feature collection(s), or list of features, or filename(s) or any combination of those.
    
    tessellation_threshold_radians: Threshold sampling distance along polylines/polygons (in radians).
    
    search_radius_radians: The distance (in radians) from query point to search for non-NaN raster grid values.
                           If none are found for a query point then it will have a query scalar value of NaN.
    
    smoothing_radius_radians: Determines which non-NaN raster grid values (if any) to use in a weighted average for a query point.
                              All points within a radius of 'min_dist + smoothing_radius_radians' of the query point are included
                              (where 'min_dist' is the distance to the closest non-NaN raster grid location).
                              Note that 'smoothing_radius_radians' should be less than 'search_radius_radians'.
                              Default value is None which results in only the closest non-NaN raster grid value being chosen.
    
    query_feature_types: Optional sequence of feature types to filter/accept (defaults to all feature types).
    
    Returns a list with a tuple for each query point containing the following parameters:
    - query point longitude
    - query point latitude
    - query scalar value, is either:
      + sampled from raster (at query point location), or
      + the nearest raster value(s) if query point is in masked (NaN) region of raster (and within search radius), or
      + NaN if there are no non-NaN grid values within search radius.
    - length of tessellated arc segment (in radians) that query point is on (if a tessellated point on polyline/polygon), or
      NaN if query point is from a point or multipoint geometry.
    
    Only polylines and polygons are tessellated. Points and multipoints just return their points.
    Each tessellated point is the midpoint of a segment of the tessellated polyline/polygon.
    The tessellated weight is the arc length (in radians) of the associated segments.
    """

    # Turn rotation data into a RotationModel (if not already).
    rotation_model = pygplates.RotationModel(rotation_features_or_model)

    # Turn query data into a list of features (if not already).
    query_features = pygplates.FeaturesFunctionArgument(
        query_features).get_features()

    if query_feature_types:
        # Remove those features not matching the allowed feature types.
        # Create a new list containing only features matching the allowed feature types.
        query_features = [
            feature for feature in query_features
            if feature.get_feature_type() in query_feature_types
        ]

    # Reconstruct features that exist at the current 'time'.
    query_reconstructed_feature_geometries = []
    pygplates.reconstruct(query_features, rotation_model,
                          query_reconstructed_feature_geometries, time,
                          anchor_plate_id)

    query_reconstructed_geometries = []
    for query_reconstructed_feature_geometry in query_reconstructed_feature_geometries:
        query_reconstructed_geometries.append(
            query_reconstructed_feature_geometry.get_reconstructed_geometry())

    # Tessellate query geometries to get query points and weights.
    query_points, query_point_weights = tessellate_geometries(
        query_reconstructed_geometries, tessellation_threshold_radians)

    # Query the raster at the query points.
    query_point_scalars = query_raster_at_points(raster_filename, query_points,
                                                 search_radius_radians,
                                                 smoothing_radius_radians)

    query_data = []
    for query_point_index in range(len(query_points)):
        query_point_lat, query_point_lon = query_points[
            query_point_index].to_lat_lon()
        query_point_scalar = query_point_scalars[query_point_index]
        query_point_weight = query_point_weights[query_point_index]

        # The data will be output in GMT format (ie, lon first, then lat, etc).
        query_data.append((query_point_lon, query_point_lat,
                           query_point_scalar, query_point_weight))

    return query_data
    def __init__(self,
                 rotation_features_or_model,
                 topology_features,
                 reconstruction_begin_time,
                 reconstruction_end_time,
                 reconstruction_time_interval,
                 points,
                 point_begin_times=None,
                 point_end_times=None,
                 point_plate_ids=None,
                 detect_collisions=DEFAULT_COLLISION):
        """
        rotation_features_or_model: Rotation model or feature collection(s), or list of features, or filename(s).
        
        topology_features: Topology feature collection(s), or list of features, or filename(s) or any combination of those.
        
        detect_collisions: Collision detection function, or None. Defaults to DEFAULT_COLLISION.
        """

        # Turn rotation data into a RotationModel (if not already).
        self.rotation_model = pygplates.RotationModel(
            rotation_features_or_model)

        # Turn topology data into a list of features (if not already).
        self.topology_features = pygplates.FeaturesFunctionArgument(
            topology_features).get_features()

        # Set up an array of reconstruction times covering the reconstruction time span.
        self.reconstruction_begin_time = reconstruction_begin_time
        self.reconstruction_end_time = reconstruction_end_time
        if reconstruction_time_interval <= 0.0:
            raise ValueError(
                "'reconstruction_time_interval' must be positive.")
        # Reconstruction can go forward or backward in time.
        if self.reconstruction_begin_time > self.reconstruction_end_time:
            self.reconstruction_time_step = -reconstruction_time_interval
        else:
            self.reconstruction_time_step = reconstruction_time_interval
        # Get number of times including end time if time span is a multiple of time step.
        # The '1' is because, for example, 2 time intervals is 3 times.
        # The '1e-6' deals with limited floating-point precision, eg, we want (3.0 - 0.0) / 1.0 to be 3.0 and not 2.999999 (which gets truncated to 2).
        self.num_times = 1 + int(
            math.floor(1e-6 + float(self.reconstruction_end_time -
                                    self.reconstruction_begin_time) /
                       self.reconstruction_time_step))
        # It's possible the time step is larger than the time span, in which case we change it to equal the time span.
        # This guarantees there'll be at least one time step (which has two times; one at either end of interval).
        if self.num_times == 1:
            self.num_times = 2
            self.reconstruction_time_step = self.reconstruction_end_time - self.reconstruction_begin_time
        self.reconstruction_time_interval = math.fabs(
            self.reconstruction_time_step)

        self.last_time_index = self.num_times - 1

        self.points = points
        self.num_points = len(points)

        # Use the specified point begin times if provided (otherwise use 'inf').
        self.point_begin_times = point_begin_times
        if self.point_begin_times is None:
            self.point_begin_times = [float('inf')] * self.num_points
        elif len(self.point_begin_times) != self.num_points:
            raise ValueError(
                "Length of 'point_begin_times' must match length of 'points'.")

        # Use the specified point end times if provided (otherwise use '-inf').
        self.point_end_times = point_end_times
        if self.point_end_times is None:
            self.point_end_times = [float('-inf')] * self.num_points
        elif len(self.point_end_times) != self.num_points:
            raise ValueError(
                "Length of 'point_end_times' must match length of 'points'.")

        # Use the specified point plate IDs if provided (otherwise use '0').
        # These plate IDs are only used when a point falls outside all resolved topologies during a time step.
        self.point_plate_ids = point_plate_ids
        if self.point_plate_ids is None:
            self.point_plate_ids = [0] * self.num_points
        elif len(self.point_plate_ids) != self.num_points:
            raise ValueError(
                "Length of 'point_plate_ids' must match length of 'points'.")

        self.detect_collisions = detect_collisions
def subduction_convergence(rotation_features_or_model,
                           topology_features,
                           threshold_sampling_distance_radians,
                           time,
                           velocity_delta_time=1.0,
                           anchor_plate_id=0):
    # Docstring in numpydoc format...
    """Find the convergence and absolute velocities sampled along subduction zones at a particular geological time.
    
    Each sampled point along subduction zones returns the following information:
    
    * subducting convergence (relative to subduction zone) velocity magnitude (in cm/yr)
    * subducting convergence velocity obliquity angle (angle between subduction zone normal vector and convergence velocity vector)
    * subduction zone absolute (relative to anchor plate) velocity magnitude (in cm/yr)
    * subduction zone absolute velocity obliquity angle (angle between subduction zone normal vector and absolute velocity vector)
    * length of arc segment (in degrees) that current point is on
    * subducting arc normal azimuth angle (clockwise starting at North, ie, 0 to 360 degrees) at current point
    * subducting plate ID
    * overriding plate ID
    
    The obliquity angles are in the range (-180 180). The range (0, 180) goes clockwise (when viewed from above the Earth) from the
    subducting normal direction to the velocity vector. The range (0, -180) goes counter-clockwise.
    You can change the range (-180, 180) to the range (0, 360) by adding 360 to negative angles.
    
    Note that the convergence velocity magnitude is negative if the plates are diverging (if convergence obliquity angle
    is greater than 90 or less than -90). And note that the absolute velocity magnitude is negative if the subduction zone (trench)
    is moving towards the overriding plate (if absolute obliquity angle is less than 90 or greater than -90) - note that this
    ignores the kinematics of the subducting plate.
    
    Parameters
    ----------
    rotation_features_or_model : pygplates.RotationModel, or any combination of str, pygplates.FeatureCollection, pygplates.Feature
        The rotation model can be specified as a RotationModel. Or it can be specified as a rotation feature collection,
        or rotation filename, or rotation feature, or sequence of rotation features, or a sequence (eg, list or tuple) of any combination
        of those four types.
    topology_features: any combination of str, pygplates.FeatureCollection, pygplates.Feature
        The topological boundary and network features and the topological section features they reference (regular and topological lines).
        Can be specified as a feature collection, or filename, or feature, or sequence of features, or a sequence (eg, list or tuple)
        of any combination of those four types.
    threshold_sampling_distance_radians: float
        Threshold sampling distance along subduction zones (in radians).
    time: float
        The reconstruction time at which to query subduction convergence.
    velocity_delta_time: float, optional
        The delta time interval used for velocity calculations. Defaults to 1My.
    anchor_plate_id: int, optional
        The anchor plate of the rotation model. Defaults to zero.
    
    Returns
    -------
    list of tuples
        The results for all points sampled along subduction zones.
        The size of the returned list is equal to the number of sampled points.
        Each tuple in the list corresponds to a point and has the following tuple items:
    
    Notes
    -----
    Each point in the output is the midpoint of a great circle arc between two adjacent points in the subduction zone polyline.
    The subduction zone normal vector used in the obliquity calculations is perpendicular to the great circle arc of each point (arc midpoint)
    and pointing towards the overriding plate (rather than away from it).
    
    Each subduction zone is sampled at approximately uniform intervals along its length (specified via a threshold sampling distance).
    The sampling along the entire length of a subduction zone is not exactly uniform. Each segment along a subduction zone is sampled
    such that the samples have a uniform spacing that is less than or equal to the threshold sampling distance. However each segment
    in a subduction zone might have a slightly different spacing distance (since segment lengths are not integer multiples of
    the threshold sampling distance).
    
    The subducting arc normal (at each arc segment mid-point) always points *towards* the overriding plate.
    """

    # Turn rotation data into a RotationModel (if not already).
    rotation_model = pygplates.RotationModel(rotation_features_or_model)

    # Turn topology data into a list of features (if not already).
    topology_features = pygplates.FeaturesFunctionArgument(topology_features)

    # Resolve our topological plate polygons (and deforming networks) to the current 'time'.
    # We generate both the resolved topology boundaries and the boundary sections between them.
    resolved_topologies = []
    shared_boundary_sections = []
    pygplates.resolve_topologies(topology_features.get_features(),
                                 rotation_model, resolved_topologies, time,
                                 shared_boundary_sections, anchor_plate_id)

    # List of tesselated subduction zone shared subsegment points and associated convergence parameters
    # for the current 'time'.
    output_data = []

    # Iterate over the shared boundary sections of all resolved topologies.
    for shared_boundary_section in shared_boundary_sections:

        # Skip sections that are not subduction zones.
        if shared_boundary_section.get_feature().get_feature_type(
        ) != pygplates.FeatureType.gpml_subduction_zone:
            continue

        # Iterate over the shared sub-segments of the current subducting line.
        # These are the parts of the subducting line that actually contribute to topological boundaries.
        for shared_sub_segment in shared_boundary_section.get_shared_sub_segments(
        ):

            # Find the overriding and subducting plates on either side of the shared sub-segment.
            overriding_and_subducting_plates = find_overriding_and_subducting_plates(
                shared_sub_segment, time)
            if not overriding_and_subducting_plates:
                continue
            overriding_plate, subducting_plate, subduction_polarity = overriding_and_subducting_plates
            overriding_plate_id = overriding_plate.get_feature(
            ).get_reconstruction_plate_id()
            subducting_plate_id = subducting_plate.get_feature(
            ).get_reconstruction_plate_id()

            # We need to reverse the subducting_normal vector direction if overriding plate is to
            # the right of the subducting line since great circle arc normal is always to the left.
            if subduction_polarity == 'Left':
                subducting_normal_reversal = 1
            else:
                subducting_normal_reversal = -1

            # The plate ID of the subduction zone line (as opposed to the subducting plate).
            #
            # Update: The plate IDs of the subduction zone line and overriding plate can differ
            # even in a non-deforming model due to smaller plates, not modelled by topologies, moving
            # differently than the larger topological plate being modelled - and the subduction zone line
            # having plate IDs of the smaller plates near them. For that reason we use the plate ID
            # of the subduction zone line whenever we can.
            #
            # If the current shared sub-segment is part of a topological line then we obtain its sub-sub-segments
            # (if we have pyGPlates revision 22 or above). This is because subduction zone lines that are
            # topological lines might actually be deforming (or intended to be deforming) and hence their
            # plate ID is not meaningful or at least we can't be sure whether it will be zero or the
            # overriding plate (or something else). In this case we look at the plate IDs of the
            # sub-sub-segments. However if we have pyGPlates revision 21 or below then we cannot do this,
            # in which case (for a topological line) we'll use the overriding plate ID instead.
            #
            if CAN_HANDLE_TOPOLOGICAL_LINES:
                sub_segments_of_topological_line_sub_segment = shared_sub_segment.get_sub_segments(
                )
                if sub_segments_of_topological_line_sub_segment:
                    # Iterate over the sub-sub-segments associated with the topological line.
                    for sub_sub_segment in sub_segments_of_topological_line_sub_segment:
                        subduction_zone_plate_id = sub_sub_segment.get_feature(
                        ).get_reconstruction_plate_id()
                        sub_segment_geometry = sub_sub_segment.get_resolved_geometry(
                        )
                        _sub_segment_subduction_convergence(
                            output_data, time, sub_segment_geometry,
                            subduction_zone_plate_id, overriding_plate_id,
                            subducting_plate_id, subducting_normal_reversal,
                            threshold_sampling_distance_radians,
                            velocity_delta_time, rotation_model,
                            anchor_plate_id)
                else:  # It's not a topological line...
                    subduction_zone_plate_id = shared_sub_segment.get_feature(
                    ).get_reconstruction_plate_id()
                    sub_segment_geometry = shared_sub_segment.get_resolved_geometry(
                    )
                    _sub_segment_subduction_convergence(
                        output_data, time, sub_segment_geometry,
                        subduction_zone_plate_id, overriding_plate_id,
                        subducting_plate_id, subducting_normal_reversal,
                        threshold_sampling_distance_radians,
                        velocity_delta_time, rotation_model, anchor_plate_id)
            else:  # Cannot handle topological lines (so use overriding plate ID when one is detected)...
                if isinstance(
                        shared_boundary_section.get_topological_section(),
                        pygplates.ResolvedTopologicalLine):
                    subduction_zone_plate_id = overriding_plate_id
                else:
                    subduction_zone_plate_id = shared_sub_segment.get_feature(
                    ).get_reconstruction_plate_id()
                sub_segment_geometry = shared_sub_segment.get_resolved_geometry(
                )
                _sub_segment_subduction_convergence(
                    output_data, time, sub_segment_geometry,
                    subduction_zone_plate_id, overriding_plate_id,
                    subducting_plate_id, subducting_normal_reversal,
                    threshold_sampling_distance_radians, velocity_delta_time,
                    rotation_model, anchor_plate_id)

    # Return data sorted since it's easier to compare results (when at least lon/lat is sorted).
    return sorted(output_data)