Пример #1
0
def detect_collision(timestep, curr_point, prev_point, curr_stage_rotation,
                     prev_stage_rotation, curr_topology_plate_id,
                     prev_resolved_boundary, prev_topology_plate_id):
    '''
    this is taken from Simon Williams github and bastardised (slightly)
    '''

    #missing stage rotations?

    #we want to return true and false to filter points
    #so if point (a) at time 0 is at x, then at time 1, is at x + distance, that distance should
    #a reflection of normal evolution of ocean crust. if it's super big, then it's probably been subducted
    #threshold =
    #time = 100 (as in, rotating points from 101 to 100)
    prev_location_velocity = pygplates.calculate_velocities(
        curr_point, prev_stage_rotation, timestep,
        pygplates.VelocityUnits.kms_per_my)[0]
    curr_location_velocity = pygplates.calculate_velocities(
        curr_point, curr_stage_rotation, timestep,
        pygplates.VelocityUnits.kms_per_my)[0]

    delta_velocity = curr_location_velocity - prev_location_velocity
    delta_velocity_magnitude = delta_velocity.get_magnitude()
    # Since all feature types use the same collision parameters we can use the boundary polygon instead of iterating over its sub-segments.
    if detect_collision_using_collision_parameters(
            timestep, delta_velocity_magnitude, prev_point,
            prev_resolved_boundary, threshold_velocity_delta,
            threshold_distance_to_boundary_per_my):
        return True

    return False
def get_plate_velocities(velocity_domain_features,
                         topology_features,
                         rotation_model,
                         time,
                         delta_time,
                         rep):

    # Define a function 

    # All domain points and associated (magnitude, azimuth, inclination) velocities for the current time.
    all_domain_points = []
    all_velocities = []

    # Partition our velocity domain features into our topological plate polygons at the current 'time'.
    plate_partitioner = pygplates.PlatePartitioner(topology_features, rotation_model, float(time))

    for velocity_domain_feature in velocity_domain_features:

        # A velocity domain feature usually has a single geometry but we'll assume it can be any number.
        # Iterate over them all.
        for velocity_domain_geometry in velocity_domain_feature.get_geometries():

            for velocity_domain_point in velocity_domain_geometry.get_points():

                all_domain_points.append(velocity_domain_point)

                partitioning_plate = plate_partitioner.partition_point(velocity_domain_point)
                if partitioning_plate:

                    # We need the newly assigned plate ID to get the equivalent stage rotation of that tectonic plate.
                    partitioning_plate_id = partitioning_plate.get_feature().get_reconstruction_plate_id()

                    # Get the stage rotation of partitioning plate from 'time + delta_time' to 'time'.
                    equivalent_stage_rotation = rotation_model.get_rotation(float(time), partitioning_plate_id, time + float(delta_time))

                    # Calculate velocity at the velocity domain point.
                    # This is from 'time + delta_time' to 'time' on the partitioning plate.
                    velocity_vectors = pygplates.calculate_velocities(
                        [velocity_domain_point],
                        equivalent_stage_rotation,
                        delta_time)
                    
                    if rep=='mag_azim':
                        # Convert global 3D velocity vectors to local (magnitude, azimuth, inclination) tuples (one tuple per point).
                        velocities = pygplates.LocalCartesian.convert_from_geocentric_to_magnitude_azimuth_inclination(
                            [velocity_domain_point],
                            velocity_vectors)
                        all_velocities.append(velocities[0])

                    elif rep=='vector_comp':
                        # Convert global 3D velocity vectors to local (magnitude, azimuth, inclination) tuples (one tuple per point).
                        velocities = pygplates.LocalCartesian.convert_from_geocentric_to_north_east_down(
                                [velocity_domain_point],
                                velocity_vectors)
                        all_velocities.append(velocities[0])

                else:
                    all_velocities.append((0,0,0))

    return all_velocities
Пример #3
0
    def __calc_velocities(self, velocity_domain_features, topology_features,
                          rotation_model, time, delta_time):
        # All domain points and associated (magnitude, azimuth, inclination) velocities for the current time.
        all_domain_points = []
        all_velocities = []

        # Partition our velocity domain features into our topological plate polygons at the current 'time'.
        plate_partitioner = pygplates.PlatePartitioner(topology_features,
                                                       rotation_model, time)

        for velocity_domain_feature in velocity_domain_features:

            # A velocity domain feature usually has a single geometry but we'll assume it can be any number.
            # Iterate over them all.
            for velocity_domain_geometry in velocity_domain_feature.get_geometries(
            ):

                for velocity_domain_point in velocity_domain_geometry.get_points(
                ):

                    all_domain_points.append(velocity_domain_point)

                    partitioning_plate = plate_partitioner.partition_point(
                        velocity_domain_point)
                    if partitioning_plate:

                        # We need the newly assigned plate ID to get the equivalent stage rotation of that tectonic plate.
                        partitioning_plate_id = partitioning_plate.get_feature(
                        ).get_reconstruction_plate_id()

                        # Get the stage rotation of partitioning plate from 'time + delta_time' to 'time'.
                        equivalent_stage_rotation = rotation_model.get_rotation(
                            time, partitioning_plate_id, time + delta_time)

                        # Calculate velocity at the velocity domain point.
                        # This is from 'time + delta_time' to 'time' on the partitioning plate.
                        # NB: velocity unit is fixed to cm/yr, but we convert it to m/yr and further on non-dimensionalise it later.
                        velocity_vectors = pygplates.calculate_velocities(
                            [velocity_domain_point],
                            equivalent_stage_rotation,
                            delta_time,
                            velocity_units=pygplates.VelocityUnits.cms_per_yr)

                        # add it to the list
                        all_velocities.extend(velocity_vectors)
                    else:
                        all_velocities.extend([
                            pygplates.Vector3D(numpy.NaN, numpy.NaN, numpy.NaN)
                        ])

        return all_velocities
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
Пример #5
0
def warp_subduction_segment(tessellated_line,
                            rotation_model,
                            subducting_plate_id,
                            overriding_plate_id,
                            subduction_polarity,
                            time,
                            end_time,
                            time_step,
                            dip_angle_radians,
                            subducting_plate_disappearance_time=-1,
                            use_small_circle_path=False):

    # 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

    # tesselate the line, and create an array with the same length as the tesselated points
    # with zero as the starting depth for each point at t=0
    points = [point for point in tessellated_line]
    point_depths = [0. for point in points]

    # Need at least two points for a polyline. Otherwise, return None for all results
    if len(points) < 2:
        points = None; point_depths = None; polyline = None
        return points, point_depths, polyline

    polyline = pygplates.PolylineOnSphere(points)

    warped_polylines = []

    # Add original unwarped polyline first.
    warped_polylines.append(polyline)

    #warped_end_time = time - warped_time_interval
    warped_end_time = end_time
    if warped_end_time < 0:
        warped_end_time = 0

    # iterate over each time in the range defined by the input parameters
    for warped_time in np.arange(time, warped_end_time-time_step,-time_step):

        #if warped_time<=23. and subducting_plate_id==902:
        #    if overriding_plate_id in [224,0,101]:
        #        print('forcing Farallon to become Cocos where overriding plate id is %d' % overriding_plate_id)
        #        subducting_plate_id = 909
        #    else:
        #        print('forcing Farallon to become Nazca where overriding plate id is %d' % overriding_plate_id)
        #        subducting_plate_id = 911
        if warped_time<=subducting_plate_disappearance_time:
            print('Using %0.2f to %0.2f Ma stage pole for plate %d' % (subducting_plate_disappearance_time+time_step, subducting_plate_disappearance_time, subducting_plate_id))
            stage_rotation = rotation_model.get_rotation(subducting_plate_disappearance_time+time_step, 
                                                         subducting_plate_id, 
                                                         subducting_plate_disappearance_time)

        else:
            # the stage rotation that describes the motion of the subducting plate,
            # with respect to the fixed plate for the rotation model
            stage_rotation = rotation_model.get_rotation(warped_time-time_step, subducting_plate_id, warped_time)

        if use_small_circle_path:
            stage_pole, stage_pole_angle_radians = stage_rotation.get_euler_pole_and_angle()

        # get velocity vectors at each point along polyline
        relative_velocity_vectors = pygplates.calculate_velocities(
                points,
                stage_rotation,
                time_step,
                pygplates.VelocityUnits.kms_per_my)

        # Get subducting normals for each segment of tessellated polyline.
        # Also add an imaginary normal prior to first and post last points
        # (makes its easier to later calculate average normal at tessellated points).
        # The number of normals will be one greater than the number of points.
        subducting_normals = []
        subducting_normals.append(None) # Imaginary segment prior to first point.
        for segment in polyline.get_segments():
            if segment.is_zero_length():
                subducting_normals.append(None)
            else:
                # The normal to the subduction zone in the direction of subduction (towards overriding plate).
                subducting_normals.append(
                    subducting_normal_reversal * segment.get_great_circle_normal())
        subducting_normals.append(None) # Imaginary segment after to last point.

        # get vectors of normals and parallels for each segment, use these 
        # to get a normal and parallel at each point location
        normals = []
        parallels = []            
        for point_index in range(len(points)):
            prev_normal = subducting_normals[point_index]
            next_normal = subducting_normals[point_index + 1]

            if prev_normal is None and next_normal is None:
                # Skip point altogether (both adjoining segments are zero length).
                continue

            if prev_normal is None:
                normal = next_normal
            elif next_normal is None:
                normal = prev_normal
            else:
                normal = (prev_normal + next_normal).to_normalised()

            parallel = pygplates.Vector3D.cross(point.to_xyz(), normal).to_normalised()

            normals.append(normal)
            parallels.append(parallel)

        # iterate over each point to determine the incremented position 
        # based on plate motion and subduction dip
        warped_points = []
        warped_point_depths = []
        for point_index, point in enumerate(points):
            normal = normals[point_index]
            parallel = parallels[point_index]

            velocity = relative_velocity_vectors[point_index]
            if velocity.is_zero_magnitude():
                # Point hasn't moved.
                warped_points.append(point)
                warped_point_depths.append(point_depths[point_index])
                continue

            # reconstruct the tracked point from position at current time to
            # position at the next time step
            normal_angle = pygplates.Vector3D.angle_between(velocity, normal)
            parallel_angle = pygplates.Vector3D.angle_between(velocity, parallel)

            # Trench parallel and normal components of velocity.
            velocity_normal = np.cos(normal_angle) * velocity.get_magnitude()
            velocity_parallel = np.cos(parallel_angle) * velocity.get_magnitude()

            normal_vector = normal.to_normalised() * velocity_normal
            parallel_vector = parallel.to_normalised() * velocity_parallel

            # Adjust velocity based on subduction vertical dip angle.
            velocity_dip = parallel_vector + np.cos(dip_angle_radians) * normal_vector

            #deltaZ is the amount that this point increases in depth within the time step
            deltaZ = np.sin(dip_angle_radians) * velocity.get_magnitude()

            # Should be 90 degrees always.
            #print np.degrees(np.arccos(pygplates.Vector3D.dot(normal_vector, parallel_vector)))

            if use_small_circle_path:
                # Rotate original stage pole by the same angle that effectively
                # rotates the velocity vector to the dip velocity vector.
                dip_stage_pole_rotate = pygplates.FiniteRotation(
                        point,
                        pygplates.Vector3D.angle_between(velocity_dip, velocity))
                dip_stage_pole = dip_stage_pole_rotate * stage_pole
            else:
                # Get the unnormalised vector perpendicular to both the point and velocity vector.
                dip_stage_pole_x, dip_stage_pole_y, dip_stage_pole_z = pygplates.Vector3D.cross(
                        point.to_xyz(), velocity_dip).to_xyz()

                # PointOnSphere requires a normalised (ie, unit length) vector (x, y, z).
                dip_stage_pole = pygplates.PointOnSphere(
                        dip_stage_pole_x, dip_stage_pole_y, dip_stage_pole_z, normalise=True)


            # Get angle that velocity will rotate seed point along great circle arc 
            # over 'time_step' My (if velocity in Kms / My).
            dip_stage_angle_radians = velocity_dip.get_magnitude() * (
                    time_step / pygplates.Earth.mean_radius_in_kms)

            if use_small_circle_path:
                # Increase rotation angle to adjust for fact that we're moving a
                # shorter distance with small circle (compared to great circle).
                dip_stage_angle_radians /= np.abs(np.sin(
                        pygplates.Vector3D.angle_between(
                                dip_stage_pole.to_xyz(), point.to_xyz())))
                # Use same sign as original stage rotation.
                if stage_pole_angle_radians < 0:
                    dip_stage_angle_radians = -dip_stage_angle_radians

            # get the stage rotation that describes the lateral motion of the 
            # point taking the dip into account
            dip_stage_rotation = pygplates.FiniteRotation(dip_stage_pole, dip_stage_angle_radians)

            # increment the point long,lat and depth
            warped_point = dip_stage_rotation * point
            warped_points.append(warped_point)
            warped_point_depths.append(point_depths[point_index] + deltaZ)

        # finished warping all points in polyline
        # --> increment the polyline for this time step
        warped_polyline = pygplates.PolylineOnSphere(warped_points)
        warped_polylines.append(warped_polyline)

        # For next warping iteration.
        points = warped_points
        polyline = warped_polyline
        point_depths = warped_point_depths
        
    return points, point_depths, polyline
Пример #6
0
def get_velocities(rotation_model,
                   topology_features,
                   time,
                   velocity_domain_features=None,
                   delta_time=1,
                   velocity_type='MagAzim'):

    if velocity_domain_features is None:
        velocity_domain_features = create_gpml_healpix_mesh(
            32, feature_type='MeshNode')

    # All domain points and associated (magnitude, azimuth, inclination) velocities for the current time.
    all_domain_points = []
    all_velocities = []
    plate_ids = []

    # Partition our velocity domain features into our topological plate polygons at the current 'time'.
    plate_partitioner = pygplates.PlatePartitioner(topology_features,
                                                   rotation_model, time)

    for velocity_domain_feature in velocity_domain_features:

        # A velocity domain feature usually has a single geometry but we'll assume it can be any number.
        # Iterate over them all.
        for velocity_domain_geometry in velocity_domain_feature.get_geometries(
        ):

            for velocity_domain_point in velocity_domain_geometry.get_points():

                all_domain_points.append(velocity_domain_point)

                partitioning_plate = plate_partitioner.partition_point(
                    velocity_domain_point)
                if partitioning_plate:

                    # We need the newly assigned plate ID to get the equivalent stage rotation of that tectonic plate.
                    partitioning_plate_id = partitioning_plate.get_feature(
                    ).get_reconstruction_plate_id()

                    # Get the stage rotation of partitioning plate from 'time + delta_time' to 'time'.
                    equivalent_stage_rotation = rotation_model.get_rotation(
                        time, partitioning_plate_id, time + delta_time)

                    # Calculate velocity at the velocity domain point.
                    # This is from 'time + delta_time' to 'time' on the partitioning plate.
                    velocity_vectors = pygplates.calculate_velocities(
                        [velocity_domain_point], equivalent_stage_rotation,
                        delta_time)

                    if velocity_type == 'east_north':
                        # Convert global 3D velocity vectors to local (magnitude, azimuth, inclination) tuples (one tuple per point).
                        velocities = pygplates.LocalCartesian.convert_from_geocentric_to_north_east_down(
                            [velocity_domain_point], velocity_vectors)
                        all_velocities.append(velocities[0])

                    else:
                        # Convert global 3D velocity vectors to local (magnitude, azimuth, inclination) tuples (one tuple per point).
                        velocities = pygplates.LocalCartesian.convert_from_geocentric_to_magnitude_azimuth_inclination(
                            [velocity_domain_point], velocity_vectors)
                        all_velocities.append(velocities[0])

                    plate_ids.append(partitioning_plate_id)

                else:
                    # If point is not within a polygon, set velocity and plate_id to zero
                    all_velocities.append((0, 0, 0))
                    plate_ids.append(0)

    pt_vel1 = []
    pt_vel2 = []
    if velocity_type == 'east_north':
        for velocity_vector in all_velocities:
            if getattr(velocity_vector, 'get_x', None) is not None:
                pt_vel1.append(velocity_vector.get_y())
                pt_vel2.append(velocity_vector.get_x())
            else:
                pt_vel1.append(0.)
                pt_vel2.append(0.)
    else:
        for velocity_vector in all_velocities:
            pt_vel1.append(velocity_vector[0])
            pt_vel2.append(velocity_vector[1])

    pt_lon = []
    pt_lat = []
    for pt in all_domain_points:
        pt_lon.append(pt.to_lat_lon()[1])
        pt_lat.append(pt.to_lat_lon()[0])

    return pt_lat, pt_lon, pt_vel1, pt_vel2, plate_ids
Пример #7
0
def spread_rate(boundary_section, rotation_model, time):
    '''
    Each mid-ocean ridge constructed in GPlates contains segments (defined as
    a straight line between two digital points in GPLates) of spreading
    (i.e. ridges) and segments of oceanic transform faults (OTFs) that connect these
    segments. This script takes a boundary section of a plate model and loops through
    the segments to separate the spreading from fault segments based on the angle
    to the stage pole. It requires:

    boundary_section: a boundary section of a topological plate model
    rotation_model: the associated rotation file with the plate model
    time: the time of interest
    '''

    velocity_seg = []  # velocity x segment length
    velocity_ret = []  # velocity returned
    velocity_pl = []  # velocity per length
    seg_len_ret = []  # all segment lengths
    seg_plate_id = []  #segment plate IDs
    vel_mm = np.zeros([2])  #velocity in mm
    vel_mm[0] = np.inf

    shd_len = 0

    #start loop through segments in plate boundary network
    for shared_sub_segment in boundary_section.get_shared_sub_segments():

        #set variables
        left_plate_id = shared_sub_segment.get_feature().get_left_plate()
        right_plate_id = shared_sub_segment.get_feature().get_right_plate()
        plate_id = shared_sub_segment.get_feature(
        ).get_reconstruction_plate_id()
        stage_rotation = rotation_model.get_rotation(time + 1, left_plate_id,
                                                     time, right_plate_id, 0)
        finite_rotation = rotation_model.get_rotation(time, right_plate_id, 0,
                                                      0)
        stage_pole, stage_angle_radians = stage_rotation.get_euler_pole_and_angle(
        )
        stage_pole_recon = finite_rotation * stage_pole
        shd_len = shared_sub_segment.get_geometry().get_arc_length()

        for segment in shared_sub_segment.get_geometry().get_segments():
            split_geometry = (segment.get_start_point(),
                              segment.get_end_point())
            if segment.is_zero_length(
            ):  # check to see if segment is zero length
                continue

            # Get the point in the middle of the segment and its tangential direction.
            segment_midpoint = segment.get_arc_point(0.5)
            segment_direction_at_midpoint = segment.get_arc_direction(0.5)

            # Get the direction from the segment midpoint to the stage pole.
            # This is the tangential direction at the start of an arc from the segment
            # midpoint to the stage pole (the zero parameter indicates the arc start
            # point which is the segment midpoint).
            segment_to_stage_pole_direction = pygplates.GreatCircleArc(
                segment_midpoint, stage_pole_recon).get_arc_direction(0)

            # The angle that the segment deviates from the stage pole direction.
            deviation_of_segment_direction_from_stage_pole = \
            pygplates.Vector3D.angle_between(segment_direction_at_midpoint,\
                                             segment_to_stage_pole_direction)

            # Change the angle to be between 0 and 90
            deviation_of_segment_direction_from_stage_pole_mod = \
            90-np.abs(90-np.remainder(np.degrees(deviation_of_segment_direction_from_stage_pole),\
                                      180))

            if deviation_of_segment_direction_from_stage_pole_mod <= 70:
                # make file for plotting on a map
                seg_plate_id.append([left_plate_id, right_plate_id])
                seg_len = segment.get_arc_length()
                reconstructed_points = []

                for geometry in split_geometry:
                    for point in geometry.get_points():
                        #print point
                        point_X = point.to_lat_lon()[1]
                        point_Y = point.to_lat_lon()[0]
                        velocity_point = ((point_Y, point_X))

                    reconstructed_points.append(velocity_point)

                #get_velocity of left and right divergent plates at mid ocean ridges
                #if no left or right plate id, get velocity of plate id (i.e. velocity relative to spin axis)
                if left_plate_id == 0 or right_plate_id == 0:

                    equivalent_stage_rotation = rotation_model.get_rotation(
                        time, plate_id, time + 1)

                    velocity = pygplates.calculate_velocities(reconstructed_points,equivalent_stage_rotation,1,\
                                                                pygplates.VelocityUnits.cms_per_yr)

                    velocity_magnitude_azimuth = pygplates.LocalCartesian.convert_from_geocentric_to_magnitude_azimuth_inclination(\
                    reconstructed_points,velocity)

                else:
                    #if we do have relative plate motions we get the relative velocity
                    #(to_time, moving_plate, from_time, fixed_plate)
                    velocity = pygplates.calculate_velocities(reconstructed_points,stage_rotation,1,\
                                                                   pygplates.VelocityUnits.cms_per_yr)

                    velocity_magnitude_azimuth = pygplates.LocalCartesian.convert_from_geocentric_to_magnitude_azimuth_inclination(\
                    reconstructed_points,velocity)

                seg_len_ret.append(
                    (seg_len * pygplates.Earth.mean_radius_in_kms))
                #magnitude only, times segment length so that mean can be found
                velocity_seg.append(
                    (velocity_magnitude_azimuth[0][0] * seg_len *
                     pygplates.Earth.mean_radius_in_kms))
                velocity_ret.append((velocity_magnitude_azimuth[0][0]))
                velocity_pl.append(
                    (velocity_magnitude_azimuth[0][0]) /
                    (seg_len * pygplates.Earth.mean_radius_in_kms))
                if velocity_magnitude_azimuth[0][0] > vel_mm[1]:
                    vel_mm[1] = velocity_magnitude_azimuth[0][0]
                elif velocity_magnitude_azimuth[0][0] < vel_mm[0]:
                    vel_mm[0] = velocity_magnitude_azimuth[0][0]

    return velocity_ret, velocity_pl, velocity_seg, vel_mm, shd_len * pygplates.Earth.mean_radius_in_kms, seg_len_ret, seg_plate_id
def calculate_velocities_along_reconstructed_geometry(
        output_data, reconstructed_geometry, equivalent_stage_rotation,
        reconstruction_plate_id, threshold_sampling_distance_radians,
        velocity_delta_time):

    # Ensure the reconstructed geometry is tessellated to within the threshold sampling distance.
    # Ignores tessellation if geometry is a point or multipoint.
    try:
        tessellated_reconstructed_geometry = reconstructed_geometry.to_tessellated(
            threshold_sampling_distance_radians)

        # Iterate over the great circle arcs of the tessellated polyline/polygon to get the
        # arc midpoints, lengths and normals.
        # There is an arc between each adjacent pair of points in the polyline/polygon.
        arc_midpoints = []
        arc_lengths = []
        arc_normals = []
        for arc in tessellated_reconstructed_geometry.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 polyline/polygon.
                arc_normals.append(arc.get_great_circle_normal())

    except AttributeError:
        arc_midpoints = None

    # If either:
    #
    #   - the reconstructed geometry is a point or multipoint, or
    #   - the tessellated polyline/polygon coincided with a point.
    #
    # ...then just use the original points, and set arc lengths and normals to zero.
    if not arc_midpoints:

        reconstructed_points = reconstructed_geometry.get_points()

        # Calculate the absolute velocities at the reconstructed points.
        absolute_velocity_vectors = pygplates.calculate_velocities(
            reconstructed_points, equivalent_stage_rotation,
            velocity_delta_time, pygplates.VelocityUnits.cms_per_yr)

        for point_index, point in enumerate(reconstructed_points):

            lat, lon = point.to_lat_lon()

            # The data will be output in GMT format (ie, lon first, then lat, etc).
            output_data.append((
                lon,
                lat,
                absolute_velocity_vectors[point_index].get_magnitude(),
                0.0,  # absolute_obliquity_degrees
                0.0,  # math.degrees(arc_length)
                0.0,  # math.degrees(arc_normal_azimuth)
                reconstruction_plate_id))

        return

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

    # Calculate the absolute velocities at the arc midpoints.
    absolute_velocity_vectors = pygplates.calculate_velocities(
        arc_midpoints, 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]
        arc_normal = arc_normals[arc_index]
        arc_normal_azimuth = arc_local_normals[arc_index][1]
        lat, lon = arc_midpoint.to_lat_lon()

        # 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,
                                                 arc_normal))

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

            # 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

        # The data will be output in GMT format (ie, lon first, then lat, etc).
        output_data.append(
            (lon, lat, absolute_velocity_magnitude, absolute_obliquity_degrees,
             math.degrees(arc_length), math.degrees(arc_normal_azimuth),
             reconstruction_plate_id))
Пример #9
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)
Пример #10
0
    def _detect_collision(self, time, prev_point, curr_point,
                          prev_topology_plate_id, prev_resolved_plate_boundary,
                          curr_topology_plate_id, stage_rotation_dict):
        #
        # If transitioning from a rigid plate to another rigid plate with a different plate ID then
        # calculate the difference in velocities and continue testing as follows
        # (otherwise, if there's no transition, then the point is still active)...
        #
        # If the velocity difference is below a threshold then we assume the previous plate was split,
        # or two plates joined. In this case the point has not subducted (forward in time) or
        # been consumed by a mid-ocean (backward in time) and hence is still active.
        #
        # If the velocity difference is large enough then we see if the distance of the *previous* position
        # to the polygon boundary (of rigid plate containing it) exceeds a threshold.
        # If the distance exceeds the threshold then the point is far enough away from the boundary that it
        # cannot be subducted or consumed by it and hence the point is still active.
        # However if the point is close enough then we assume the point was subducted/consumed
        # (remember that the point switched plate IDs).
        # Also note that the threshold distance increases according to the velocity difference to account for fast
        # moving points (that would otherwise tunnel through the boundary and accrete onto the other plate).
        # The reason for testing the distance from the *previous* point, and not from the *current* point, is:
        #
        #   (i)  A topological boundary may *appear* near the current point (such as a plate split at the current time)
        #        and we don't want that split to consume the current point regardless of the velocity difference.
        #        It won't get consumed because the *previous* point was not near a boundary (because before split happened).
        #        If the velocity difference is large enough then it might cause the current point to transition to the
        #        adjacent split plate in the *next* time step (and that's when it should get consumed, not in the current time step).
        #        An example of this is a mid-ocean ridge suddenly appearing (going forward in time).
        #
        #   (ii) A topological boundary may *disappear* near the current point (such as a plate merge at the current time)
        #        and we want that merge to consume the current point if the velocity difference is large enough.
        #        In this case the *previous* point is near a boundary (because before plate merged) and hence can be
        #        consumed (provided velocity difference is large enough). And since the boundary existed in the previous
        #        time step, it will affect position of the current point (and whether it gets consumed or not).
        #        An example of this is a mid-ocean ridge suddenly disappearing (going backward in time).
        #
        # ...note that items (i) and (ii) above apply both going forward and backward in time.
        #

        # See if a collision occurred.
        if (curr_topology_plate_id != prev_topology_plate_id
                and prev_topology_plate_id is not None
                and curr_topology_plate_id is not None):

            # Speed up by caching stage rotations in a dict.
            prev_location_velocity_stage_rotation = stage_rotation_dict.get(
                prev_topology_plate_id)
            if not prev_location_velocity_stage_rotation:
                prev_location_velocity_stage_rotation = self.rotation_model.get_rotation(
                    time + 1, prev_topology_plate_id, time)
                stage_rotation_dict[
                    prev_topology_plate_id] = prev_location_velocity_stage_rotation
            curr_location_velocity_stage_rotation = stage_rotation_dict.get(
                curr_topology_plate_id)
            if not curr_location_velocity_stage_rotation:
                curr_location_velocity_stage_rotation = self.rotation_model.get_rotation(
                    time + 1, curr_topology_plate_id, time)
                stage_rotation_dict[
                    curr_topology_plate_id] = curr_location_velocity_stage_rotation

            # Note that even though the current point is not inside the previous boundary (because different plate ID), we can still
            # calculate a velocity using its plate ID (because we really should use the same point in our velocity comparison).
            prev_location_velocity = pygplates.calculate_velocities(
                (curr_point, ), prev_location_velocity_stage_rotation, 1,
                pygplates.VelocityUnits.kms_per_my)[0]
            curr_location_velocity = pygplates.calculate_velocities(
                (curr_point, ), curr_location_velocity_stage_rotation, 1,
                pygplates.VelocityUnits.kms_per_my)[0]

            delta_velocity = curr_location_velocity - prev_location_velocity
            delta_velocity_magnitude = delta_velocity.get_magnitude()

            # If we have feature-specific collision parameters then iterate over the boundary sub-segments of the *previous* topological boundary
            # and test proximity to each sub-segment individually (with sub-segment feature type specific collision parameters).
            # Otherwise just test proximity to the entire boundary polygon using the global collision parameters.
            if self.feature_specific_collision_parameters:
                for prev_boundary_sub_segment in prev_resolved_plate_boundary.get_boundary_sub_segments(
                ):
                    # Use feature-specific collision parameters if found (falling back to global collision parameters).
                    threshold_velocity_delta, threshold_distance_to_boundary_per_my = self.feature_specific_collision_parameters.get(
                        prev_boundary_sub_segment.get_feature().
                        get_feature_type(),
                        # Default to global collision parameters if no collision parameters specified for sub-segment's feature type...
                        self.global_collision_parameters)

                    # Since each feature type could use different collision parameters we must use the current boundary sub-segment instead of the boundary polygon.
                    if self._detect_collision_using_collision_parameters(
                            delta_velocity_magnitude, prev_point,
                            prev_boundary_sub_segment.get_resolved_geometry(),
                            threshold_velocity_delta,
                            threshold_distance_to_boundary_per_my):
                        # Detected a collision.
                        return True
            else:
                # No feature-specific collision parameters so use global fallback.
                threshold_velocity_delta, threshold_distance_to_boundary_per_my = self.global_collision_parameters

                # Since all feature types use the same collision parameters we can use the boundary polygon instead of iterating over its sub-segments.
                if self._detect_collision_using_collision_parameters(
                        delta_velocity_magnitude, prev_point,
                        prev_resolved_plate_boundary.get_resolved_boundary(),
                        threshold_velocity_delta,
                        threshold_distance_to_boundary_per_my):
                    # Detected a collision.
                    return True

        return False
def _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):

    # Get the rotation of the subducting plate relative to the subduction zone line
    # from 'time + velocity_delta_time' to 'time'.
    convergence_relative_stage_rotation = rotation_model.get_rotation(
        time,
        subducting_plate_id,
        time + velocity_delta_time,
        subduction_zone_plate_id,
        anchor_plate_id=anchor_plate_id)
    #
    # In the following:
    #   * T is for Trench (subduction zone line)
    #   * S is subducting plate
    #   * A is anchor plate
    #
    # The subduction zones have been reconstructed using the rotation "R(0->t,A->T)":
    #
    #   reconstructed_geometry = R(0->t,A->T) * present_day_geometry
    #
    # We can write "R(0->t,A->T)" in terms of the convergence stage rotation "R(t+dt->t,T->S)" as:
    #
    #   R(0->t,A->T)  = R(0->t,A->S) * R(0->t,S->T)
    #                 = R(0->t,A->S) * inverse[R(0->t,T->S)]
    #                 = R(0->t,A->S) * inverse[R(t+dt->t,T->S) * R(0->t+dt,T->S)]
    #                 = R(0->t,A->S) * inverse[stage_rotation * R(0->t+dt,T->S)]
    #                 = R(0->t,A->S) * inverse[R(0->t+dt,T->S)] * inverse[stage_rotation]
    #                 = R(0->t,A->S) * R(0->t+dt,S->T) * inverse[stage_rotation]
    #
    # So to get the *reconstructed* subduction line geometry into the stage rotation reference frame
    # we need to rotate it by "inverse[R(0->t,A->S) * R(0->t+dt,S->T)]":
    #
    #   reconstructed_geometry = R(0->t,A->T) * present_day_geometry
    #                          = R(0->t,A->S) * R(0->t+dt,S->T) * inverse[stage_rotation] * present_day_geometry
    #   inverse[R(0->t,A->S) * R(0->t+dt,S->T)] * reconstructed_geometry = inverse[stage_rotation] * 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->t,A->S) * R(0->t+dt,S->T)".
    #
    from_convergence_stage_frame = (
        rotation_model.get_rotation(
            time, subducting_plate_id, anchor_plate_id=anchor_plate_id) *
        rotation_model.get_rotation(time + velocity_delta_time,
                                    subduction_zone_plate_id,
                                    fixed_plate_id=subducting_plate_id,
                                    anchor_plate_id=anchor_plate_id))
    to_convergence_stage_frame = from_convergence_stage_frame.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)

    # Ensure the shared sub-segment is tessellated to within the threshold sampling distance.
    tessellated_shared_sub_segment_polyline = (
        sub_segment_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:
        return

    # 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 at the arc midpoints.
    #
    # Note; We need to convert the reconstructed geometry points into the convergence stage rotation
    # reference frame to calculate velocities and then convert the velocities using the
    # reverse transform as mentioned above.
    arc_midpoints_in_convergence_stage_frame = [
        to_convergence_stage_frame * arc_midpoint
        for arc_midpoint in arc_midpoints
    ]
    convergence_velocity_vectors_in_convergence_stage_frame = pygplates.calculate_velocities(
        arc_midpoints_in_convergence_stage_frame,
        convergence_relative_stage_rotation, velocity_delta_time,
        pygplates.VelocityUnits.cms_per_yr)
    convergence_velocity_vectors = [
        from_convergence_stage_frame * velocity
        for velocity in convergence_velocity_vectors_in_convergence_stage_frame
    ]

    # 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))