spherical-tools 0.1.0__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.
- spherical_tools-0.1.0/LICENSE +21 -0
- spherical_tools-0.1.0/PKG-INFO +74 -0
- spherical_tools-0.1.0/README.md +38 -0
- spherical_tools-0.1.0/pyproject.toml +23 -0
- spherical_tools-0.1.0/setup.cfg +4 -0
- spherical_tools-0.1.0/src/spherical_tools/__init__.py +26 -0
- spherical_tools-0.1.0/src/spherical_tools/spherical.py +262 -0
- spherical_tools-0.1.0/src/spherical_tools.egg-info/PKG-INFO +74 -0
- spherical_tools-0.1.0/src/spherical_tools.egg-info/SOURCES.txt +10 -0
- spherical_tools-0.1.0/src/spherical_tools.egg-info/dependency_links.txt +1 -0
- spherical_tools-0.1.0/src/spherical_tools.egg-info/requires.txt +2 -0
- spherical_tools-0.1.0/src/spherical_tools.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Tom New
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spherical-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Utilities for working with spherical geometry
|
|
5
|
+
Author-email: Tom New <tomnewtonnew@gmail.com>
|
|
6
|
+
License: # MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Tom New
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/tom-new/spherical-tools
|
|
29
|
+
Keywords: geoscience,coordinates,spherical,cartesian,geometry
|
|
30
|
+
Requires-Python: >=3.9
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
License-File: LICENSE
|
|
33
|
+
Requires-Dist: numpy
|
|
34
|
+
Requires-Dist: scipy
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# spherical-tools
|
|
38
|
+
|
|
39
|
+
**Convert between geographic, spherical, and Cartesian coordinates.**
|
|
40
|
+
This is a lightweight Python library designed for geoscientists working with coordinates on the sphere.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 📦 Installation
|
|
45
|
+
|
|
46
|
+
Install the latest development version:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install git+https://github.com/tom-new/spherical-tools.git
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 🚀 Quick Start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import spherical_tools as st
|
|
58
|
+
|
|
59
|
+
# Convert from spherical (r, theta, phi) to Cartesian
|
|
60
|
+
# theta = azimuthal angle, phi = polar angle
|
|
61
|
+
x, y, z = st.sph2cart(1, 30, 60, degrees=True)
|
|
62
|
+
|
|
63
|
+
# convert from Cartesian to spherical
|
|
64
|
+
r, theta, phi = st.cart2sph(x, y, z, degrees=True)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
By deafault, angles are in **radians** but input and output angles can be in degrees by setting `degrees=True`.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 📚 Function Reference
|
|
72
|
+
|
|
73
|
+
- `sph2cart(r, theta, phi, degrees=False)`
|
|
74
|
+
- `cart2sph(x, y, z, degrees=False)`
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# spherical-tools
|
|
2
|
+
|
|
3
|
+
**Convert between geographic, spherical, and Cartesian coordinates.**
|
|
4
|
+
This is a lightweight Python library designed for geoscientists working with coordinates on the sphere.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 📦 Installation
|
|
9
|
+
|
|
10
|
+
Install the latest development version:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install git+https://github.com/tom-new/spherical-tools.git
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 🚀 Quick Start
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import spherical_tools as st
|
|
22
|
+
|
|
23
|
+
# Convert from spherical (r, theta, phi) to Cartesian
|
|
24
|
+
# theta = azimuthal angle, phi = polar angle
|
|
25
|
+
x, y, z = st.sph2cart(1, 30, 60, degrees=True)
|
|
26
|
+
|
|
27
|
+
# convert from Cartesian to spherical
|
|
28
|
+
r, theta, phi = st.cart2sph(x, y, z, degrees=True)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
By deafault, angles are in **radians** but input and output angles can be in degrees by setting `degrees=True`.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 📚 Function Reference
|
|
36
|
+
|
|
37
|
+
- `sph2cart(r, theta, phi, degrees=False)`
|
|
38
|
+
- `cart2sph(x, y, z, degrees=False)`
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "spherical-tools"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Utilities for working with spherical geometry"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
authors = [{name = "Tom New", email = "tomnewtonnew@gmail.com"}]
|
|
12
|
+
license = {file = "LICENSE"}
|
|
13
|
+
dependencies = ["numpy", "scipy"]
|
|
14
|
+
keywords = ["geoscience", "coordinates", "spherical", "cartesian", "geometry"]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://github.com/tom-new/spherical-tools"
|
|
18
|
+
|
|
19
|
+
[tool.setuptools]
|
|
20
|
+
package-dir = {"" = "src"}
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
where = ["src"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
__version__ = version("spherical_tools")
|
|
5
|
+
except PackageNotFoundError:
|
|
6
|
+
__version__ = "unknown"
|
|
7
|
+
|
|
8
|
+
from .spherical import (
|
|
9
|
+
cart2sph,
|
|
10
|
+
geo2sph,
|
|
11
|
+
sph2geo,
|
|
12
|
+
geo2sph,
|
|
13
|
+
cart2polar,
|
|
14
|
+
great_circle_distance,
|
|
15
|
+
fill_great_circle,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"cart2sph",
|
|
20
|
+
"geo2sph",
|
|
21
|
+
"sph2geo",
|
|
22
|
+
"geo2sph",
|
|
23
|
+
"cart2polar",
|
|
24
|
+
"great_circle_distance",
|
|
25
|
+
"fill_great_circle",
|
|
26
|
+
]
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import numpy
|
|
2
|
+
from numpy.typing import ArrayLike, NDArray
|
|
3
|
+
from scipy.spatial import geometric_slerp
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def cart2sph(cartesian_coord_array: ArrayLike, degrees: bool = False) -> NDArray:
|
|
7
|
+
"""Take shape (N,3) or (3,) cartesian coord_array and return an array of the same shape in spherical polar form (r,theta,phi). Based on StackOverflow response: http://stackoverflow.com/a/4116899.
|
|
8
|
+
|
|
9
|
+
Use radians for angles by default, degrees if ``degrees == True``."""
|
|
10
|
+
|
|
11
|
+
# cast input sequence to numpy.ndarray with dtype numpy.float64
|
|
12
|
+
cartesian_coord_array = numpy.asarray(cartesian_coord_array, dtype=numpy.float64)
|
|
13
|
+
|
|
14
|
+
# create new array to hold spherical coordinates
|
|
15
|
+
spherical_coord_array = numpy.empty(cartesian_coord_array.shape)
|
|
16
|
+
|
|
17
|
+
# convert to spherical coordinates
|
|
18
|
+
spherical_coord_array[..., 0] = numpy.linalg.norm(cartesian_coord_array, axis=-1)
|
|
19
|
+
spherical_coord_array[..., 1] = numpy.arctan2(
|
|
20
|
+
cartesian_coord_array[..., 1], cartesian_coord_array[..., 0]
|
|
21
|
+
)
|
|
22
|
+
spherical_coord_array[..., 2] = numpy.arccos(
|
|
23
|
+
cartesian_coord_array[..., 2] / spherical_coord_array[..., 0]
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# convert from radians to degrees if required, otherwise skip
|
|
27
|
+
if degrees:
|
|
28
|
+
spherical_coord_array[..., 1:] = numpy.rad2deg(spherical_coord_array[..., 1:])
|
|
29
|
+
|
|
30
|
+
return spherical_coord_array
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def sph2cart(spherical_coord_array: ArrayLike, degrees=False) -> NDArray:
|
|
34
|
+
"""Take shape (N,3) or (3,) spherical_coord_array (radius, azimuth, pole) and return an array of the same shape in cartesian coordinate form (x, y, z). Based on the equations provided at: http://en.wikipedia.org/wiki/List_of_common_coordinate_transformations#From_spherical_coordinates.
|
|
35
|
+
|
|
36
|
+
Use radians for angles by default, degrees if ``degrees == True``."""
|
|
37
|
+
|
|
38
|
+
# cast input sequence to numpy.ndarray with dtype numpy.float64
|
|
39
|
+
spherical_coord_array = numpy.asarray(spherical_coord_array, dtype=numpy.float64)
|
|
40
|
+
|
|
41
|
+
# create new array to hold Cartesian coordinates
|
|
42
|
+
cartesian_coord_array = numpy.empty(spherical_coord_array.shape)
|
|
43
|
+
|
|
44
|
+
# convert from degrees to radians if required, otherwise skip
|
|
45
|
+
if degrees:
|
|
46
|
+
spherical_coord_array[..., 1:] = numpy.deg2rad(spherical_coord_array[..., 1:])
|
|
47
|
+
|
|
48
|
+
# now the conversion to Cartesian coords
|
|
49
|
+
cartesian_coord_array[..., 0] = (
|
|
50
|
+
spherical_coord_array[..., 0]
|
|
51
|
+
* numpy.cos(spherical_coord_array[..., 1])
|
|
52
|
+
* numpy.sin(spherical_coord_array[..., 2])
|
|
53
|
+
)
|
|
54
|
+
cartesian_coord_array[..., 1] = (
|
|
55
|
+
spherical_coord_array[..., 0]
|
|
56
|
+
* numpy.sin(spherical_coord_array[..., 1])
|
|
57
|
+
* numpy.sin(spherical_coord_array[..., 2])
|
|
58
|
+
)
|
|
59
|
+
cartesian_coord_array[..., 2] = spherical_coord_array[..., 0] * numpy.cos(
|
|
60
|
+
spherical_coord_array[..., 2]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return cartesian_coord_array
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def geo2sph(geographical_coord_array: ArrayLike, degrees=False) -> NDArray:
|
|
67
|
+
"""Take shape (N,2), (N,3), (2,), or (3,) geographical_coord_array ([radius], lon, lat) and return an array of the same shape in spherical coordinate form ([radius], azimuth, pole)."""
|
|
68
|
+
|
|
69
|
+
# cast to numpy.ndarray with type ``numpy.float64`` since this function is likely to be passed integer degrees of lon or lat
|
|
70
|
+
geographical_coord_array = numpy.asarray(
|
|
71
|
+
geographical_coord_array, dtype=numpy.float64
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# create new array to hold spherical coordinates
|
|
75
|
+
spherical_coord_array = geographical_coord_array.copy()
|
|
76
|
+
|
|
77
|
+
# reverse orientation of polar angle
|
|
78
|
+
spherical_coord_array[..., -1] = 90 - spherical_coord_array[..., -1]
|
|
79
|
+
# assume that outgoing spherical coordinates should be in radians, so convert from degrees by default
|
|
80
|
+
if not degrees:
|
|
81
|
+
spherical_coord_array[..., -2:] = numpy.deg2rad(spherical_coord_array[..., -2:])
|
|
82
|
+
|
|
83
|
+
return spherical_coord_array
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def sph2geo(spherical_coord_array: ArrayLike, degrees=False) -> NDArray:
|
|
87
|
+
"""Take shape (N,2), (N,3), (2,), or (3,) spherical_coord_array ([radius], azimuth, pole) and return an array of the same shape in geographical coordinate form ([radius], lon, lat)."""
|
|
88
|
+
|
|
89
|
+
# cast to numpy.ndarray with type ``numpy.float64`` just in case some loon decides to pass an integer amount of radians
|
|
90
|
+
spherical_coord_array = numpy.asarray(spherical_coord_array, dtype=numpy.float64)
|
|
91
|
+
|
|
92
|
+
# create a new array to hold the geographical coordinates
|
|
93
|
+
geographical_coord_array = spherical_coord_array.copy()
|
|
94
|
+
|
|
95
|
+
# incoming spherical coordinates are assumed to be in radians, so convert to degrees by default
|
|
96
|
+
if not degrees:
|
|
97
|
+
geographical_coord_array[..., -2:] = numpy.rad2deg(
|
|
98
|
+
geographical_coord_array[..., -2:]
|
|
99
|
+
)
|
|
100
|
+
# reverse orientation of polar angle
|
|
101
|
+
geographical_coord_array[..., -1] = 90 - geographical_coord_array[..., -1]
|
|
102
|
+
|
|
103
|
+
return geographical_coord_array
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def cart2polar(cartesian_coord_array: ArrayLike, degrees=False) -> NDArray:
|
|
107
|
+
"""Take shape (N,2) cartesian_coord_array and return an array of the same shape in polar coordinates (radius, azimuth).
|
|
108
|
+
|
|
109
|
+
Use radians for angles by default, degrees if ``degrees == True``."""
|
|
110
|
+
|
|
111
|
+
# cast input sequence to numpy.ndarray with dtype numpy.float64
|
|
112
|
+
cartesian_coord_array = numpy.asarray(cartesian_coord_array, dtype=numpy.float64)
|
|
113
|
+
|
|
114
|
+
# create new array to hold spherical coordinates
|
|
115
|
+
polar_coord_array = numpy.empty(cartesian_coord_array.shape)
|
|
116
|
+
|
|
117
|
+
# convert to spherical coordinates
|
|
118
|
+
polar_coord_array[..., 0] = numpy.linalg.norm(cartesian_coord_array, axis=-1)
|
|
119
|
+
polar_coord_array[..., 1] = numpy.arctan2(
|
|
120
|
+
cartesian_coord_array[..., 1], cartesian_coord_array[..., 0]
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# convert from radians to degrees if required, otherwise skip
|
|
124
|
+
if degrees:
|
|
125
|
+
polar_coord_array[..., 1] = numpy.rad2deg(polar_coord_array[..., 1])
|
|
126
|
+
|
|
127
|
+
return polar_coord_array
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def polar2cart(polar_coord_array: ArrayLike, degrees=False) -> NDArray:
|
|
131
|
+
"""Take shape (N,2) polar_coord_array and return an array of the same shape in Cartesian coordinates.
|
|
132
|
+
|
|
133
|
+
Use radians for angles by default, degrees if ``degrees == True``."""
|
|
134
|
+
|
|
135
|
+
# cast input sequence to numpy.ndarray with dtype numpy.float64
|
|
136
|
+
polar_coord_array = numpy.asarray(polar_coord_array, dtype=numpy.float64)
|
|
137
|
+
|
|
138
|
+
# convert from degrees to radians if required, otherwise skip
|
|
139
|
+
if degrees:
|
|
140
|
+
polar_coord_array[..., 1] = numpy.deg2rad(polar_coord_array[..., 1])
|
|
141
|
+
|
|
142
|
+
# create new array to hold spherical coordinates
|
|
143
|
+
cartesian_coord_array = numpy.empty(polar_coord_array.shape)
|
|
144
|
+
|
|
145
|
+
# convert to spherical coordinates
|
|
146
|
+
cartesian_coord_array[..., 0] = (
|
|
147
|
+
numpy.cos(polar_coord_array[..., 1]) * polar_coord_array[..., 0]
|
|
148
|
+
)
|
|
149
|
+
cartesian_coord_array[..., 1] = (
|
|
150
|
+
numpy.sin(polar_coord_array[..., 1]) * polar_coord_array[..., 0]
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return cartesian_coord_array
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def great_circle_distance(
|
|
157
|
+
array_1: ArrayLike,
|
|
158
|
+
array_2: ArrayLike,
|
|
159
|
+
coordinate_system: str = "spherical",
|
|
160
|
+
sphere_radius: bool | float = False,
|
|
161
|
+
) -> float:
|
|
162
|
+
"""Calculate the haversine-based distance between two arrays of points on the surface of a sphere (array shape must be (N,3) or (3,)). Should be more accurate than the arc cosine strategy. See, for example: http://en.wikipedia.org/wiki/Haversine_formula.
|
|
163
|
+
|
|
164
|
+
This function assumes that all your coordinate pairs have the same radius. If they don't, why!? It doesn't make sense to calculate the great circle distance between coordinates that don't lie on the same spherical shell. If you're a complete psychopath though, you can override the radius (or radii) with custom values---either a single value to use for everything or an array-like of values of length N.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
assert coordinate_system in ["spherical", "cartesian"]
|
|
168
|
+
array_1 = numpy.asarray(array_1, dtype=numpy.float64)
|
|
169
|
+
array_2 = numpy.asarray(array_2, dtype=numpy.float64)
|
|
170
|
+
if coordinate_system == "cartesian":
|
|
171
|
+
array_1 = cart2sph(array_1)
|
|
172
|
+
array_2 = cart2sph(array_2)
|
|
173
|
+
if not sphere_radius:
|
|
174
|
+
sphere_radius = array_1[
|
|
175
|
+
..., 0
|
|
176
|
+
] # get the radius (or radii) if the user has not overridden the radius
|
|
177
|
+
phi_1 = array_1[..., 1]
|
|
178
|
+
phi_2 = array_2[..., 1]
|
|
179
|
+
theta_1 = array_1[..., 2]
|
|
180
|
+
theta_2 = array_2[..., 2]
|
|
181
|
+
|
|
182
|
+
spherical_distance = (
|
|
183
|
+
2.0
|
|
184
|
+
* sphere_radius
|
|
185
|
+
* numpy.arcsin(
|
|
186
|
+
numpy.sqrt(
|
|
187
|
+
((1 - numpy.cos(theta_2 - theta_1)) / 2.0)
|
|
188
|
+
+ numpy.sin(theta_1)
|
|
189
|
+
* numpy.sin(theta_2)
|
|
190
|
+
* ((1 - numpy.cos(phi_2 - phi_1)) / 2.0)
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return spherical_distance
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def fill_great_circle(
|
|
199
|
+
g0: ArrayLike,
|
|
200
|
+
g1: ArrayLike,
|
|
201
|
+
res: float = 1.0,
|
|
202
|
+
n_points: int | None = None,
|
|
203
|
+
return_angle: bool = False,
|
|
204
|
+
):
|
|
205
|
+
"""
|
|
206
|
+
Sample points along the great-circle path between two geographic coordinates.
|
|
207
|
+
|
|
208
|
+
parameters
|
|
209
|
+
----------
|
|
210
|
+
g0, g1 : array-like
|
|
211
|
+
(lon, lat) in degrees.
|
|
212
|
+
res : float, default 1.0
|
|
213
|
+
Target angular spacing in degrees between consecutive points. Ignored if
|
|
214
|
+
`n_points` is given.
|
|
215
|
+
n_points : int or None, default None
|
|
216
|
+
Number of samples (including endpoints). If None, it is computed from `res`.
|
|
217
|
+
return_angle : bool, default False
|
|
218
|
+
If True, also return the great-circle angle (degrees) between g0 and g1.
|
|
219
|
+
|
|
220
|
+
returns
|
|
221
|
+
-------
|
|
222
|
+
g_profile : (n_points, 2) ndarray
|
|
223
|
+
Longitudes (deg) and latitudes (deg) along the great circle. Longitudes are
|
|
224
|
+
unwrapped to avoid jumps at 180.
|
|
225
|
+
angle_deg : float, optional
|
|
226
|
+
Great-circle angle in degrees (only if `return_angle` is True).
|
|
227
|
+
"""
|
|
228
|
+
# combine input coords into (2, 2) array
|
|
229
|
+
geo = numpy.array([g0, g1], dtype=float)
|
|
230
|
+
|
|
231
|
+
# prepend radius = 1 for each point (required by geo2sph/scipy slerp workflow)
|
|
232
|
+
radii = numpy.ones((geo.shape[0], 1))
|
|
233
|
+
geo_with_r = numpy.concatenate((radii, geo), axis=1)
|
|
234
|
+
|
|
235
|
+
# conversions
|
|
236
|
+
sph = geo2sph(geo_with_r) # -> (r, theta, phi)
|
|
237
|
+
c0, c1 = sph2cart(sph) # two 3D cartesian vectors
|
|
238
|
+
|
|
239
|
+
# angular separation in degrees
|
|
240
|
+
angle_deg = numpy.degrees(numpy.arccos(numpy.dot(c0, c1)))
|
|
241
|
+
|
|
242
|
+
# choose number of samples
|
|
243
|
+
if n_points is None:
|
|
244
|
+
n_points = int(numpy.ceil(angle_deg / res)) + 1 # +1 to include both endpoints
|
|
245
|
+
|
|
246
|
+
# interpolate on the unit sphere
|
|
247
|
+
t = numpy.linspace(0.0, 1.0, n_points)
|
|
248
|
+
cart_profile = geometric_slerp(c0, c1, t=t)
|
|
249
|
+
|
|
250
|
+
# back to geographic lon/lat
|
|
251
|
+
sph_profile = cart2sph(cart_profile)
|
|
252
|
+
geo_profile = sph2geo(sph_profile[:, 1:]) # drop radius
|
|
253
|
+
|
|
254
|
+
# unwrap longitudes to avoid jumps across the dateline
|
|
255
|
+
geo_profile[:, 0] = numpy.unwrap(
|
|
256
|
+
numpy.radians(geo_profile[:, 0]), period=2 * numpy.pi
|
|
257
|
+
)
|
|
258
|
+
geo_profile[:, 0] = numpy.degrees(geo_profile[:, 0])
|
|
259
|
+
|
|
260
|
+
if return_angle:
|
|
261
|
+
return geo_profile, angle_deg
|
|
262
|
+
return geo_profile
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spherical-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Utilities for working with spherical geometry
|
|
5
|
+
Author-email: Tom New <tomnewtonnew@gmail.com>
|
|
6
|
+
License: # MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Tom New
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/tom-new/spherical-tools
|
|
29
|
+
Keywords: geoscience,coordinates,spherical,cartesian,geometry
|
|
30
|
+
Requires-Python: >=3.9
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
License-File: LICENSE
|
|
33
|
+
Requires-Dist: numpy
|
|
34
|
+
Requires-Dist: scipy
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# spherical-tools
|
|
38
|
+
|
|
39
|
+
**Convert between geographic, spherical, and Cartesian coordinates.**
|
|
40
|
+
This is a lightweight Python library designed for geoscientists working with coordinates on the sphere.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 📦 Installation
|
|
45
|
+
|
|
46
|
+
Install the latest development version:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install git+https://github.com/tom-new/spherical-tools.git
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 🚀 Quick Start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import spherical_tools as st
|
|
58
|
+
|
|
59
|
+
# Convert from spherical (r, theta, phi) to Cartesian
|
|
60
|
+
# theta = azimuthal angle, phi = polar angle
|
|
61
|
+
x, y, z = st.sph2cart(1, 30, 60, degrees=True)
|
|
62
|
+
|
|
63
|
+
# convert from Cartesian to spherical
|
|
64
|
+
r, theta, phi = st.cart2sph(x, y, z, degrees=True)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
By deafault, angles are in **radians** but input and output angles can be in degrees by setting `degrees=True`.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 📚 Function Reference
|
|
72
|
+
|
|
73
|
+
- `sph2cart(r, theta, phi, degrees=False)`
|
|
74
|
+
- `cart2sph(x, y, z, degrees=False)`
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/spherical_tools/__init__.py
|
|
5
|
+
src/spherical_tools/spherical.py
|
|
6
|
+
src/spherical_tools.egg-info/PKG-INFO
|
|
7
|
+
src/spherical_tools.egg-info/SOURCES.txt
|
|
8
|
+
src/spherical_tools.egg-info/dependency_links.txt
|
|
9
|
+
src/spherical_tools.egg-info/requires.txt
|
|
10
|
+
src/spherical_tools.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
spherical_tools
|