gtrack 0.3.1__tar.gz → 0.3.2__tar.gz

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.
Files changed (26) hide show
  1. {gtrack-0.3.1 → gtrack-0.3.2}/PKG-INFO +1 -1
  2. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/point_rotation.py +49 -0
  3. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack.egg-info/PKG-INFO +1 -1
  4. {gtrack-0.3.1 → gtrack-0.3.2}/pyproject.toml +1 -1
  5. {gtrack-0.3.1 → gtrack-0.3.2}/tests/test_point_rotation.py +61 -0
  6. {gtrack-0.3.1 → gtrack-0.3.2}/README.md +0 -0
  7. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/__init__.py +0 -0
  8. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/boundaries.py +0 -0
  9. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/config.py +0 -0
  10. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/geometry.py +0 -0
  11. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/hpc_integration.py +0 -0
  12. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/initial_conditions.py +0 -0
  13. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/io_formats.py +0 -0
  14. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/logging.py +0 -0
  15. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/mesh.py +0 -0
  16. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/mor_seeds.py +0 -0
  17. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/polygon_filter.py +0 -0
  18. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/spatial.py +0 -0
  19. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack.egg-info/SOURCES.txt +0 -0
  20. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack.egg-info/dependency_links.txt +0 -0
  21. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack.egg-info/requires.txt +0 -0
  22. {gtrack-0.3.1 → gtrack-0.3.2}/gtrack.egg-info/top_level.txt +0 -0
  23. {gtrack-0.3.1 → gtrack-0.3.2}/setup.cfg +0 -0
  24. {gtrack-0.3.1 → gtrack-0.3.2}/tests/test_core.py +0 -0
  25. {gtrack-0.3.1 → gtrack-0.3.2}/tests/test_geometry.py +0 -0
  26. {gtrack-0.3.1 → gtrack-0.3.2}/tests/test_regression.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gtrack
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: GPlates-based Tracking of Lithosphere and Kinematics
5
5
  Author: S. Ghelichkhani
6
6
  License: MIT
@@ -148,6 +148,55 @@ class PointCloud:
148
148
  xyz = LatLon2XYZ(latlon)
149
149
  return cls(xyz=xyz, properties=properties or {})
150
150
 
151
+ @classmethod
152
+ def from_xyz(
153
+ cls,
154
+ xyz: np.ndarray,
155
+ properties: Optional[Dict[str, np.ndarray]] = None,
156
+ normalize_to_earth: bool = False
157
+ ) -> "PointCloud":
158
+ """
159
+ Create PointCloud from Cartesian XYZ coordinates.
160
+
161
+ This is a convenience classmethod that provides symmetry with from_latlon.
162
+ It can optionally normalize points to Earth's surface.
163
+
164
+ Parameters
165
+ ----------
166
+ xyz : np.ndarray
167
+ Cartesian coordinates, shape (N, 3).
168
+ properties : dict, optional
169
+ Properties to attach to the points.
170
+ normalize_to_earth : bool, default=False
171
+ If True, normalize points to Earth's radius (~6.3781e6 m).
172
+ Useful when input points are on a unit sphere.
173
+
174
+ Returns
175
+ -------
176
+ PointCloud
177
+ New PointCloud with the given XYZ coordinates.
178
+
179
+ Examples
180
+ --------
181
+ >>> # Points already at Earth's radius
182
+ >>> xyz = np.array([[6378100, 0, 0], [0, 6378100, 0]])
183
+ >>> cloud = PointCloud.from_xyz(xyz)
184
+ >>>
185
+ >>> # Points on unit sphere, scale to Earth
186
+ >>> xyz_unit = np.array([[1, 0, 0], [0, 1, 0]])
187
+ >>> cloud = PointCloud.from_xyz(xyz_unit, normalize_to_earth=True)
188
+ """
189
+ from .geometry import EARTH_RADIUS
190
+ xyz = np.asarray(xyz)
191
+
192
+ if normalize_to_earth:
193
+ # Normalize to unit sphere, then scale to Earth's radius
194
+ norms = np.linalg.norm(xyz, axis=1, keepdims=True)
195
+ norms = np.maximum(norms, 1e-10) # Avoid division by zero
196
+ xyz = xyz / norms * EARTH_RADIUS
197
+
198
+ return cls(xyz=xyz, properties=properties or {})
199
+
151
200
  def add_property(self, name: str, values: np.ndarray) -> None:
152
201
  """
153
202
  Add or update a property.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gtrack
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: GPlates-based Tracking of Lithosphere and Kinematics
5
5
  Author: S. Ghelichkhani
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gtrack"
7
- version = "0.3.1"
7
+ version = "0.3.2"
8
8
  description = "GPlates-based Tracking of Lithosphere and Kinematics"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -48,6 +48,67 @@ class TestPointCloud:
48
48
  distances = np.linalg.norm(cloud.xyz, axis=1)
49
49
  np.testing.assert_allclose(distances, EARTH_RADIUS, rtol=1e-10)
50
50
 
51
+ def test_from_xyz_basic(self):
52
+ """Test creating PointCloud via from_xyz classmethod."""
53
+ xyz = normalize_to_sphere(np.random.randn(50, 3))
54
+ cloud = PointCloud.from_xyz(xyz)
55
+
56
+ assert cloud.n_points == 50
57
+ np.testing.assert_allclose(cloud.xyz, xyz)
58
+ assert cloud.plate_ids is None
59
+ assert len(cloud.properties) == 0
60
+
61
+ def test_from_xyz_with_properties(self):
62
+ """Test from_xyz passes properties correctly."""
63
+ xyz = normalize_to_sphere(np.random.randn(30, 3))
64
+ props = {'depth': np.random.rand(30), 'temp': np.random.rand(30)}
65
+ cloud = PointCloud.from_xyz(xyz, properties=props)
66
+
67
+ assert cloud.n_points == 30
68
+ assert 'depth' in cloud.properties
69
+ assert 'temp' in cloud.properties
70
+
71
+ def test_from_xyz_normalize_to_earth(self):
72
+ """Test from_xyz with normalize_to_earth scales to Earth's radius."""
73
+ # Points on unit sphere
74
+ xyz_unit = np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
75
+ cloud = PointCloud.from_xyz(xyz_unit, normalize_to_earth=True)
76
+
77
+ distances = np.linalg.norm(cloud.xyz, axis=1)
78
+ np.testing.assert_allclose(distances, EARTH_RADIUS, rtol=1e-10)
79
+
80
+ def test_from_xyz_normalize_arbitrary_radius(self):
81
+ """Test from_xyz normalizes points at arbitrary radii to Earth's radius."""
82
+ # Points at radius 100
83
+ xyz = np.array([[100.0, 0.0, 0.0], [0.0, 0.0, 50.0]])
84
+ cloud = PointCloud.from_xyz(xyz, normalize_to_earth=True)
85
+
86
+ distances = np.linalg.norm(cloud.xyz, axis=1)
87
+ np.testing.assert_allclose(distances, EARTH_RADIUS, rtol=1e-10)
88
+
89
+ # Direction should be preserved
90
+ direction_original = xyz / np.linalg.norm(xyz, axis=1, keepdims=True)
91
+ direction_result = cloud.xyz / np.linalg.norm(cloud.xyz, axis=1, keepdims=True)
92
+ np.testing.assert_allclose(direction_result, direction_original, atol=1e-10)
93
+
94
+ def test_from_xyz_no_normalize(self):
95
+ """Test from_xyz without normalization preserves coordinates exactly."""
96
+ xyz = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
97
+ cloud = PointCloud.from_xyz(xyz, normalize_to_earth=False)
98
+
99
+ np.testing.assert_array_equal(cloud.xyz, xyz)
100
+
101
+ def test_from_xyz_near_zero_vector(self):
102
+ """Test from_xyz handles near-zero vectors without crashing."""
103
+ xyz = np.array([[1e-15, 1e-15, 1e-15], [1.0, 0.0, 0.0]])
104
+ cloud = PointCloud.from_xyz(xyz, normalize_to_earth=True)
105
+
106
+ # Should not raise; second point should be at Earth's radius
107
+ distances = np.linalg.norm(cloud.xyz, axis=1)
108
+ np.testing.assert_allclose(distances[1], EARTH_RADIUS, rtol=1e-10)
109
+ # First point (near-zero) should still produce a finite result
110
+ assert np.all(np.isfinite(cloud.xyz[0]))
111
+
51
112
  def test_latlon_property(self):
52
113
  """Test that latlon property correctly converts from XYZ."""
53
114
  original_latlon = np.array([
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes