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