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.
- {gtrack-0.3.1 → gtrack-0.3.2}/PKG-INFO +1 -1
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/point_rotation.py +49 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack.egg-info/PKG-INFO +1 -1
- {gtrack-0.3.1 → gtrack-0.3.2}/pyproject.toml +1 -1
- {gtrack-0.3.1 → gtrack-0.3.2}/tests/test_point_rotation.py +61 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/README.md +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/__init__.py +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/boundaries.py +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/config.py +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/geometry.py +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/hpc_integration.py +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/initial_conditions.py +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/io_formats.py +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/logging.py +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/mesh.py +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/mor_seeds.py +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/polygon_filter.py +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack/spatial.py +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack.egg-info/SOURCES.txt +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack.egg-info/dependency_links.txt +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack.egg-info/requires.txt +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/gtrack.egg-info/top_level.txt +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/setup.cfg +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/tests/test_core.py +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/tests/test_geometry.py +0 -0
- {gtrack-0.3.1 → gtrack-0.3.2}/tests/test_regression.py +0 -0
|
@@ -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.
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|