gtrack 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,255 @@
1
+ """
2
+ Initial age calculation for icosahedral mesh points.
3
+
4
+ This module provides functions to compute initial seafloor ages
5
+ from distance to the nearest ridge, matching GPlately's approach.
6
+ """
7
+
8
+ import numpy as np
9
+ from typing import Callable, List, Optional, Tuple
10
+
11
+ import pygplates
12
+
13
+
14
+ def compute_initial_ages(
15
+ ocean_points: pygplates.MultiPointOnSphere,
16
+ resolved_topologies: List,
17
+ shared_boundary_sections: List,
18
+ initial_ocean_mean_spreading_rate: float = 75.0,
19
+ fill_value: float = 5000.0,
20
+ age_distance_law: Optional[Callable[[np.ndarray, float], np.ndarray]] = None,
21
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
22
+ """
23
+ Compute initial seafloor ages from distance to nearest ridge.
24
+
25
+ For each ocean point, finds the nearest mid-ocean ridge within the same
26
+ plate polygon and calculates age using the distance/spreading-rate formula.
27
+
28
+ Parameters
29
+ ----------
30
+ ocean_points : pygplates.MultiPointOnSphere
31
+ Ocean basin points (after filtering out continental points).
32
+ resolved_topologies : list
33
+ Resolved topologies from pygplates.resolve_topologies().
34
+ shared_boundary_sections : list
35
+ Shared boundary sections from pygplates.resolve_topologies().
36
+ initial_ocean_mean_spreading_rate : float, default=75.0
37
+ Mean spreading rate in mm/yr (numerically equal to km/Myr).
38
+ Default is GPlately's value of 75 mm/yr.
39
+ fill_value : float, default=5000.0
40
+ Distance value (km) for points in plates without ridges.
41
+ Corresponds to ~133 Myr at 75 mm/yr spreading rate.
42
+ age_distance_law : callable, optional
43
+ Custom function to convert distances to ages.
44
+ Signature: (distances_km, spreading_rate_mm_yr) -> ages_myr
45
+ If None, uses default: age = distance / (rate / 2)
46
+
47
+ Returns
48
+ -------
49
+ lons : np.ndarray
50
+ Longitudes of ocean points in degrees.
51
+ lats : np.ndarray
52
+ Latitudes of ocean points in degrees.
53
+ ages : np.ndarray
54
+ Computed ages in Myr.
55
+
56
+ Notes
57
+ -----
58
+ The default age formula is:
59
+ age_myr = distance_km / (spreading_rate_mm_yr / 2)
60
+
61
+ The division by 2 accounts for half-spreading rate (each plate moves
62
+ at half the full spreading rate). The units work because:
63
+ 75 mm/yr = 75 km/Myr (numerically equal)
64
+
65
+ Examples
66
+ --------
67
+ >>> # Resolve topologies at starting time
68
+ >>> resolved_topologies = []
69
+ >>> shared_boundary_sections = []
70
+ >>> pygplates.resolve_topologies(
71
+ ... topology_features, rotation_model, resolved_topologies,
72
+ ... starting_age, shared_boundary_sections
73
+ ... )
74
+ >>> lons, lats, ages = compute_initial_ages(
75
+ ... ocean_points, resolved_topologies, shared_boundary_sections
76
+ ... )
77
+ """
78
+ all_lons = []
79
+ all_lats = []
80
+ all_distances = []
81
+
82
+ # Create point features from MultiPointOnSphere for the query
83
+ point_feature = pygplates.Feature()
84
+ point_feature.set_geometry(ocean_points)
85
+ point_features = [point_feature]
86
+
87
+ # Process each plate topology
88
+ for topology in resolved_topologies:
89
+ plate_id = topology.get_resolved_feature().get_reconstruction_plate_id()
90
+
91
+ # Find mid-ocean ridges that bound this plate
92
+ mid_ocean_ridges_on_plate = []
93
+ for shared_boundary_section in shared_boundary_sections:
94
+ if (
95
+ shared_boundary_section.get_feature().get_feature_type()
96
+ == pygplates.FeatureType.create_gpml("MidOceanRidge")
97
+ ):
98
+ for shared_subsegment in shared_boundary_section.get_shared_sub_segments():
99
+ sharing_resolved_topologies = (
100
+ shared_subsegment.get_sharing_resolved_topologies()
101
+ )
102
+ for resolved_polygon in sharing_resolved_topologies:
103
+ if (
104
+ resolved_polygon.get_feature().get_reconstruction_plate_id()
105
+ == plate_id
106
+ ):
107
+ mid_ocean_ridges_on_plate.append(
108
+ shared_subsegment.get_resolved_geometry()
109
+ )
110
+
111
+ # Process points within this plate
112
+ for pf in point_features:
113
+ for points in pf.get_geometries():
114
+ for point in points:
115
+ if topology.get_resolved_geometry().is_point_in_polygon(point):
116
+ lat, lon = point.to_lat_lon()
117
+
118
+ if len(mid_ocean_ridges_on_plate) > 0:
119
+ # Find minimum distance to any ridge
120
+ min_distance = None
121
+ for ridge in mid_ocean_ridges_on_plate:
122
+ distance = pygplates.GeometryOnSphere.distance(
123
+ point, ridge, min_distance
124
+ )
125
+ if distance is not None:
126
+ min_distance = distance
127
+
128
+ # Convert to km
129
+ distance_km = min_distance * pygplates.Earth.mean_radius_in_kms
130
+ else:
131
+ # No ridges bounding this plate - use fill value
132
+ distance_km = fill_value
133
+
134
+ all_lons.append(lon)
135
+ all_lats.append(lat)
136
+ all_distances.append(distance_km)
137
+
138
+ # Convert to arrays
139
+ lons = np.array(all_lons)
140
+ lats = np.array(all_lats)
141
+ distances = np.array(all_distances)
142
+
143
+ # Compute ages using provided or default formula
144
+ if age_distance_law is not None:
145
+ ages = age_distance_law(distances, initial_ocean_mean_spreading_rate)
146
+ else:
147
+ ages = default_age_distance_law(distances, initial_ocean_mean_spreading_rate)
148
+
149
+ return lons, lats, ages
150
+
151
+
152
+ def default_age_distance_law(
153
+ distances_km: np.ndarray,
154
+ spreading_rate_mm_yr: float
155
+ ) -> np.ndarray:
156
+ """
157
+ Default age-distance law: age = distance / (spreading_rate / 2).
158
+
159
+ This is GPlately's formula, where the half-spreading rate is used
160
+ because each plate moves at half the full spreading rate.
161
+
162
+ Parameters
163
+ ----------
164
+ distances_km : np.ndarray
165
+ Distances to nearest ridge in km.
166
+ spreading_rate_mm_yr : float
167
+ Full spreading rate in mm/yr (numerically equal to km/Myr).
168
+
169
+ Returns
170
+ -------
171
+ np.ndarray
172
+ Ages in Myr.
173
+
174
+ Notes
175
+ -----
176
+ The formula works because 1 mm/yr = 1 km/Myr numerically:
177
+ 75 mm/yr × 1e6 yr/Myr × 1e-6 km/mm = 75 km/Myr
178
+ """
179
+ half_spreading_rate = spreading_rate_mm_yr / 2.0
180
+ return distances_km / half_spreading_rate
181
+
182
+
183
+ def compute_initial_ages_kdtree(
184
+ ocean_lats: np.ndarray,
185
+ ocean_lons: np.ndarray,
186
+ ridge_lats: np.ndarray,
187
+ ridge_lons: np.ndarray,
188
+ initial_ocean_mean_spreading_rate: float = 75.0,
189
+ earth_radius_km: float = 6371.0,
190
+ age_distance_law: Optional[Callable[[np.ndarray, float], np.ndarray]] = None,
191
+ ) -> np.ndarray:
192
+ """
193
+ Compute initial ages using a fast KDTree approach.
194
+
195
+ This is a simpler alternative that doesn't use plate polygon queries.
196
+ It finds the globally nearest ridge point for each ocean point.
197
+
198
+ Parameters
199
+ ----------
200
+ ocean_lats : np.ndarray
201
+ Ocean point latitudes in degrees.
202
+ ocean_lons : np.ndarray
203
+ Ocean point longitudes in degrees.
204
+ ridge_lats : np.ndarray
205
+ Ridge point latitudes in degrees.
206
+ ridge_lons : np.ndarray
207
+ Ridge point longitudes in degrees.
208
+ initial_ocean_mean_spreading_rate : float, default=75.0
209
+ Mean spreading rate in mm/yr.
210
+ earth_radius_km : float, default=6371.0
211
+ Earth radius in km.
212
+ age_distance_law : callable, optional
213
+ Custom function to convert distances to ages.
214
+
215
+ Returns
216
+ -------
217
+ ages : np.ndarray
218
+ Computed ages in Myr.
219
+
220
+ Notes
221
+ -----
222
+ This method is faster but less accurate than the plate-based approach
223
+ because it doesn't respect plate boundaries. Use for quick estimates
224
+ or when plate topology information is not available.
225
+ """
226
+ from scipy.spatial import cKDTree
227
+
228
+ # Convert to Cartesian XYZ on unit sphere
229
+ def latlon_to_xyz(lats, lons):
230
+ lats_rad = np.radians(lats)
231
+ lons_rad = np.radians(lons)
232
+ x = np.cos(lats_rad) * np.cos(lons_rad)
233
+ y = np.cos(lats_rad) * np.sin(lons_rad)
234
+ z = np.sin(lats_rad)
235
+ return np.column_stack([x, y, z])
236
+
237
+ ocean_xyz = latlon_to_xyz(ocean_lats, ocean_lons)
238
+ ridge_xyz = latlon_to_xyz(ridge_lats, ridge_lons)
239
+
240
+ # Build KDTree and query
241
+ tree = cKDTree(ridge_xyz)
242
+ chord_distances, _ = tree.query(ocean_xyz, k=1)
243
+
244
+ # Convert chord distance to great circle distance
245
+ # chord = 2 * sin(angle/2), so angle = 2 * arcsin(chord/2)
246
+ angles_rad = 2 * np.arcsin(np.clip(chord_distances / 2, 0, 1))
247
+ distances_km = angles_rad * earth_radius_km
248
+
249
+ # Compute ages
250
+ if age_distance_law is not None:
251
+ ages = age_distance_law(distances_km, initial_ocean_mean_spreading_rate)
252
+ else:
253
+ ages = default_age_distance_law(distances_km, initial_ocean_mean_spreading_rate)
254
+
255
+ return ages