Modify a reconstruction pole
This example:
reads a rotation file,
modifies a total reconstruction pole such that a reconstructed point coincides with its desired location at a particular reconstruction time, and
writes the modifications back to the same rotation file.
The functionality in this example is similar to the Modify Reconstruction Pole
tool
in GPlates.
Sample code
import pygplates
import sys
# One or more rotation filenames.
rotation_filenames = ['rotations.rot']
# The reconstruction time (Ma) at which the rotation adjustment needs to be applied.
reconstruction_time = pygplates.GeoTimeInstant(60)
# The plate ID of the plate whose rotation we are adjusting.
reconstruction_plate_id = 801
# The present day point position specified using latitude and longitude (in degrees).
present_day_latitude = -20
present_day_longitude = 135
present_day_position = pygplates.PointOnSphere(
present_day_latitude, present_day_longitude)
# The desired reconstructed point position specified using latitude and longitude (in degrees).
desired_reconstructed_latitude = -45
desired_reconstructed_longitude = 130
desired_reconstructed_position = pygplates.PointOnSphere(
desired_reconstructed_latitude, desired_reconstructed_longitude)
# Create a point feature so we can reconstruct it using its plate ID.
point_feature = pygplates.Feature()
point_feature.set_reconstruction_plate_id(reconstruction_plate_id)
point_feature.set_geometry(present_day_position)
# Load the rotation features from rotation files.
# Using a 'pygplates.FeaturesFunctionArgument' to make it easier to write changes back out to the files.
rotation_features = pygplates.FeaturesFunctionArgument(rotation_filenames)
# A rotation model using the rotation features before they are modified.
rotation_model_before_adjustment = pygplates.RotationModel(rotation_features.get_features())
# Reconstruct our point feature to obtain the reconstructed point location.
reconstructed_feature_geometries = []
pygplates.reconstruct(point_feature, rotation_model_before_adjustment, reconstructed_feature_geometries, reconstruction_time)
reconstructed_position = reconstructed_feature_geometries[0].get_reconstructed_geometry()
# Print the actual and desired reconstructed point positions to show they are different.
print 'Reconstructed lat/lon position before adjustment (%f, %f)' % reconstructed_position.to_lat_lon()
print 'Desired reconstructed lat/lon position (%f, %f)' % desired_reconstructed_position.to_lat_lon()
# If the actual and desired reconstructed positions are different then adjust rotation feature(s) to make them the same.
if reconstructed_position != desired_reconstructed_position:
# The rotation that moves the actual reconstructed position to the desired position.
rotation_adjustment = pygplates.FiniteRotation(reconstructed_position, desired_reconstructed_position)
# Modify the rotation feature (or features) corresponding to the reconstruction plate ID.
for rotation_feature in rotation_features.get_features():
# Get the rotation feature information.
total_reconstruction_pole = rotation_feature.get_total_reconstruction_pole()
if not total_reconstruction_pole:
# Not a rotation feature.
continue
fixed_plate_id, moving_plate_id, rotation_sequence = total_reconstruction_pole
# We're only interested in rotation features whose moving plate ID matches are reconstruction plate ID.
if moving_plate_id != reconstruction_plate_id:
continue
# Get the enabled rotation samples - ignore the disabled samples.
enabled_rotation_samples = rotation_sequence.get_enabled_time_samples()
if not enabled_rotation_samples:
# All time samples are disabled.
continue
# Match sure the time span of the rotation feature's enabled samples spans the reconstruction time.
if not (enabled_rotation_samples[0].get_time() <= reconstruction_time and
enabled_rotation_samples[-1].get_time() >= reconstruction_time):
continue
# Get the finite rotation at the reconstruction time.
# If the reconstruction time is between rotation samples then it will get interpolated.
rotation_property_value = rotation_sequence.get_value(reconstruction_time)
if not rotation_property_value:
continue
rotation = rotation_property_value.get_finite_rotation()
# The rotation adjustment needs to be applied to the rotation feature (total reconstruction pole).
# Since this is a rotation relative to the fixed plate of the rotation feature, and not the anchored plate,
# we need to transform the adjustment appropriately before applying it.
fixed_plate_frame = rotation_model_before_adjustment.get_rotation(reconstruction_time, fixed_plate_id)
fixed_plate_frame_rotation_adjustment = fixed_plate_frame.get_inverse() * rotation_adjustment * fixed_plate_frame
adjusted_rotation = fixed_plate_frame_rotation_adjustment * rotation
# If one of the enabled rotation samples matches the reconstruction time then
# get its description so we don't clobber it when we write the adjusted rotation.
rotation_description = None
for rotation_sample in enabled_rotation_samples:
if rotation_sample.get_time() == reconstruction_time:
rotation_description = rotation_sample.get_description()
break
# Set the adjusted rotation back into the rotation sequence.
rotation_sequence.set_value(
pygplates.GpmlFiniteRotation(adjusted_rotation),
reconstruction_time,
rotation_description)
# Our rotation adjustment may require crossovers to be re-synchronised.
if not pygplates.synchronise_crossovers(
rotation_features.get_features(),
crossover_threshold_degrees = 0.01,
# Default to 'pygplates.CrossoverType.synch_old_crossover_and_stages' when/if crossover tags
# are missing in the rotation file...
crossover_type_function = pygplates.CrossoverTypeFunction.type_from_xo_tags_in_comment_default_xo_ys):
print >> sys.stderr, 'Unable to synchronise all crossovers.'
# Get a new rotation model that uses the adjusted rotation features.
rotation_model_after_adjustment = pygplates.RotationModel(rotation_features.get_features())
reconstructed_feature_geometries = []
pygplates.reconstruct(point_feature, rotation_model_after_adjustment, reconstructed_feature_geometries, reconstruction_time)
reconstructed_position = reconstructed_feature_geometries[0].get_reconstructed_geometry()
# Print the adjusted reconstructed point position - should now be same as desired position.
print 'Reconstructed lat/lon position after adjustment (%f, %f)' % reconstructed_position.to_lat_lon()
# Write the (modified) rotation feature collections back to the files they came from.
rotation_files = rotation_features.get_files()
if rotation_files:
for feature_collection, filename in rotation_files:
feature_collection.write(filename)
Details
The filenames of one or more rotation files. We’ll be writing modifications back out to these files.
rotation_filenames = ['rotations.rot']
pygplates.GeoTimeInstant
purely because its comparison
operators (==, !=, <, <=, >, >=) handle numerical tolerance in floating-point comparisons. This is
a good idea in general when comparing floating-point numbers even though in our case the sample code
would probably still work if we directly compared floating-point numbers (without a comparison threshold) -
in other words if we wrote this as reconstruction_time = 60
instead.reconstruction_time = pygplates.GeoTimeInstant(60)
pygplates.PointOnSphere
.present_day_latitude = -20
present_day_longitude = 135
present_day_position = pygplates.PointOnSphere(
present_day_latitude, present_day_longitude)
desired_reconstructed_latitude = -45
desired_reconstructed_longitude = 130
desired_reconstructed_position = pygplates.PointOnSphere(
desired_reconstructed_latitude, desired_reconstructed_longitude)
pygplates.Feature
.point_feature = pygplates.Feature()
point_feature.set_reconstruction_plate_id(reconstruction_plate_id)
point_feature.set_geometry(present_day_position)
pygplates.FeaturesFunctionArgument
to load our rotation file(s).pygplates.FeatureCollection
and then
later saved
them back to their rotation file(s).rotation_features = pygplates.FeaturesFunctionArgument(rotation_filenames)
rotation model
.rotation_model_before_adjustment = pygplates.RotationModel(rotation_features.get_features())
reconstruct
our point feature.set its valid time
)
we should get one pygplates.ReconstructedFeatureGeometry
from which we obtain the
reconstructed point position
.reconstructed_feature_geometries = []
pygplates.reconstruct(point_feature, rotation_model_before_adjustment, reconstructed_feature_geometries, reconstruction_time)
reconstructed_position = reconstructed_feature_geometries[0].get_reconstructed_geometry()
reconstructed_position
to desired_reconstructed_position
.
The rotation is created using the constructor
of pygplates.FiniteRotation
.if reconstructed_position != desired_reconstructed_position:
rotation_adjustment = pygplates.FiniteRotation(reconstructed_position, desired_reconstructed_position)
pygplates.Feature.get_total_reconstruction_pole()
.for rotation_feature in rotation_features.get_features():
total_reconstruction_pole = rotation_feature.get_total_reconstruction_pole()
if not total_reconstruction_pole:
continue
fixed_plate_id, moving_plate_id, rotation_sequence = total_reconstruction_pole
if moving_plate_id != reconstruction_plate_id:
continue
time sequence
of total rotations of
a moving plate relative to a fixed plate.pygplates.GpmlIrregularSampling.get_enabled_time_samples()
.reconstruction_time
is a pygplates.GeoTimeInstant
, comparisons with it
will handle numerical tolerance (as mentioned above). This ensures that the test will pass if the
reconstruction time coincides with the time of the first or last rotation sample.enabled_rotation_samples = rotation_sequence.get_enabled_time_samples()
if not enabled_rotation_samples:
continue
if not (enabled_rotation_samples[0].get_time() <= reconstruction_time and
enabled_rotation_samples[-1].get_time() >= reconstruction_time):
continue
rotation_description = None
for rotation_sample in enabled_rotation_samples:
if rotation_sample.get_time() == reconstruction_time:
rotation_description = rotation_sample.get_description()
break
pygplates.GpmlIrregularSampling.get_value()
.interpolate
between the two nearest rotation time samples in the rotation sequence
if the reconstruction time does not coincide with a rotation sample.rotation_property_value = rotation_sequence.get_value(reconstruction_time)
if not rotation_property_value:
continue
rotation = rotation_property_value.get_finite_rotation()
Now that we have the original rotation from the rotation feature we need to calculate a rotation adjustment such that the new rotation will result in the present day position reconstructing to the desired reconstructed position.
The reconstruction of the present day point position is given by the equation for the Equivalent total rotation which shows the equivalent total rotation of moving plate \(P_{M}\) (relative to anchored plate \(P_{A}\)) at time \(t\) (relative to present day) is:
Using the approach in Composing finite rotations we write the desired reconstructed position in terms of the actual reconstructed position:
…where the rotation adjustment \(R(\text{reconstructed_position} \rightarrow \text{desired_reconstructed_position})\) represents the
rotation
from \(\text{reconstructed_position}\) to \(\text{desired_reconstructed_position}\) which (in pyGPlates) is
pygplates.FiniteRotation(reconstructed_position, desired_reconstructed_position)
.
The composed rotation from present day position to desired reconstructed position represents the adjusted equivalent rotation:
Pre-multiplying both sides by \(R(0 \rightarrow t,P_{A} \rightarrow P_{F})^{-1}\) gives:
…which represents the adjusted relative rotation \(R(0 \rightarrow t,P_{F} \rightarrow P_{M})_{adjusted}\) in terms of the original relative rotation \(R(0 \rightarrow t,P_{F} \rightarrow P_{M})\).
This is written in pyGPlates as:
fixed_plate_frame = rotation_model_before_adjustment.get_rotation(reconstruction_time, fixed_plate_id)
adjusted_rotation = fixed_plate_frame.get_inverse() * rotation_adjustment * fixed_plate_frame * rotation
…where fixed_plate_frame
represents \(R(0 \rightarrow t,P_{A} \rightarrow P_{F})\).
rotation = rotation_sequence.get_value(reconstruction_time).get_finite_rotation()
adjusted_rotation = fixed_plate_frame.get_inverse() * rotation_adjustment * fixed_plate_frame * rotation
rotation_sequence.set_value(
pygplates.GpmlFiniteRotation(adjusted_rotation),
reconstruction_time,
rotation_description)
pygplates.synchronise_crossovers()
to synchronise all crossover rotations.pygplates.CrossoverTypeFunction.type_from_xo_tags_in_comment_default_xo_ys
to
determine this for us. It will use @xo_
tags in the rotation file (pole comments/descriptions)
to determine this and default to the @xo_ys
tag if not present for a particular crossover.
See pygplates.synchronise_crossovers()
for more details.if not pygplates.synchronise_crossovers(
rotation_features.get_features(),
crossover_threshold_degrees = 0.01,
crossover_type_function = pygplates.CrossoverTypeFunction.type_from_xo_tags_in_comment_default_xo_ys):
print >> sys.stderr, 'Unable to synchronise all crossovers.'
rotation_model_after_adjustment = pygplates.RotationModel(rotation_features.get_features())
reconstructed_feature_geometries = []
pygplates.reconstruct(point_feature, rotation_model_after_adjustment, reconstructed_feature_geometries, reconstruction_time)
reconstructed_position = reconstructed_feature_geometries[0].get_reconstructed_geometry()
print 'Reconstructed lat/lon position after adjustment (%f, %f)' % reconstructed_position.to_lat_lon()
pygplates.FeaturesFunctionArgument
to list those feature collections that came from files as well as their associated filenames.rotation_files = rotation_features.get_files()
if rotation_files:
for feature_collection, filename in rotation_files:
feature_collection.write(filename)
Output
Reconstructed lat/lon position before adjustment (-45.962028, 131.398490)
Desired reconstructed lat/lon position (-45.000000, 130.000000)
Reconstructed lat/lon position after adjustment (-45.000000, 130.000000)