spacecoords 0.1.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.
- spacecoords-0.1.2/LICENSE +21 -0
- spacecoords-0.1.2/PKG-INFO +71 -0
- spacecoords-0.1.2/README.md +4 -0
- spacecoords-0.1.2/pyproject.toml +82 -0
- spacecoords-0.1.2/setup.cfg +4 -0
- spacecoords-0.1.2/src/spacecoords/__init__.py +45 -0
- spacecoords-0.1.2/src/spacecoords/celestial.py +218 -0
- spacecoords-0.1.2/src/spacecoords/cli.py +22 -0
- spacecoords-0.1.2/src/spacecoords/constants.py +51 -0
- spacecoords-0.1.2/src/spacecoords/download.py +59 -0
- spacecoords-0.1.2/src/spacecoords/frames.py +174 -0
- spacecoords-0.1.2/src/spacecoords/linalg.py +280 -0
- spacecoords-0.1.2/src/spacecoords/projection.py +43 -0
- spacecoords-0.1.2/src/spacecoords/py.typed +0 -0
- spacecoords-0.1.2/src/spacecoords/spherical.py +184 -0
- spacecoords-0.1.2/src/spacecoords/spice.py +8 -0
- spacecoords-0.1.2/src/spacecoords/spk_basic.py +71 -0
- spacecoords-0.1.2/src/spacecoords/types.py +35 -0
- spacecoords-0.1.2/src/spacecoords/version.py +3 -0
- spacecoords-0.1.2/src/spacecoords.egg-info/PKG-INFO +71 -0
- spacecoords-0.1.2/src/spacecoords.egg-info/SOURCES.txt +27 -0
- spacecoords-0.1.2/src/spacecoords.egg-info/dependency_links.txt +1 -0
- spacecoords-0.1.2/src/spacecoords.egg-info/entry_points.txt +2 -0
- spacecoords-0.1.2/src/spacecoords.egg-info/requires.txt +30 -0
- spacecoords-0.1.2/src/spacecoords.egg-info/top_level.txt +1 -0
- spacecoords-0.1.2/tests/test_celestial.py +75 -0
- spacecoords-0.1.2/tests/test_linalg.py +223 -0
- spacecoords-0.1.2/tests/test_simple_frames.py +121 -0
- spacecoords-0.1.2/tests/test_spherical.py +108 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2025] [Daniel Kastinen]
|
|
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,71 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spacecoords
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: A collection of coordinate transforms for convenient use
|
|
5
|
+
Author-email: Daniel Kastinen <daniel.kastinen@irf.se>
|
|
6
|
+
Maintainer-email: Daniel Kastinen <daniel.kastinen@irf.se>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) [2025] [Daniel Kastinen]
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Project-URL: Documentation, https://danielk.developer.irf.se/spacecoords/
|
|
30
|
+
Project-URL: Repository, https://github.com/danielk333/spacecoords
|
|
31
|
+
Classifier: Intended Audience :: Science/Research
|
|
32
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
34
|
+
Classifier: Operating System :: OS Independent
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Requires-Python: >=3.10
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
License-File: LICENSE
|
|
39
|
+
Requires-Dist: numpy>=2
|
|
40
|
+
Provides-Extra: all
|
|
41
|
+
Requires-Dist: astropy>=6; extra == "all"
|
|
42
|
+
Requires-Dist: spiceypy>=8.0.0; extra == "all"
|
|
43
|
+
Requires-Dist: jplephem>=2.23; extra == "all"
|
|
44
|
+
Requires-Dist: requests>=2.20; extra == "all"
|
|
45
|
+
Provides-Extra: develop
|
|
46
|
+
Requires-Dist: pytest; extra == "develop"
|
|
47
|
+
Requires-Dist: pytest-cov; extra == "develop"
|
|
48
|
+
Requires-Dist: flake8; extra == "develop"
|
|
49
|
+
Requires-Dist: wheel; extra == "develop"
|
|
50
|
+
Requires-Dist: build; extra == "develop"
|
|
51
|
+
Requires-Dist: twine; extra == "develop"
|
|
52
|
+
Requires-Dist: auditwheel; extra == "develop"
|
|
53
|
+
Requires-Dist: numpydoc; extra == "develop"
|
|
54
|
+
Requires-Dist: black; extra == "develop"
|
|
55
|
+
Requires-Dist: matplotlib; extra == "develop"
|
|
56
|
+
Requires-Dist: ipykernel; extra == "develop"
|
|
57
|
+
Requires-Dist: mkdocs-material; extra == "develop"
|
|
58
|
+
Requires-Dist: mkdocstrings[python]; extra == "develop"
|
|
59
|
+
Requires-Dist: mkdocs-jupyter; extra == "develop"
|
|
60
|
+
Requires-Dist: mkdocs-gen-files; extra == "develop"
|
|
61
|
+
Requires-Dist: mkdocs-literate-nav; extra == "develop"
|
|
62
|
+
Requires-Dist: mkdocs-section-index; extra == "develop"
|
|
63
|
+
Provides-Extra: tests
|
|
64
|
+
Requires-Dist: pytest; extra == "tests"
|
|
65
|
+
Requires-Dist: pytest-cov; extra == "tests"
|
|
66
|
+
Dynamic: license-file
|
|
67
|
+
|
|
68
|
+
# spacecoords
|
|
69
|
+
|
|
70
|
+
A collection of coordinate transforms for convenient use across our applications to avoid
|
|
71
|
+
duplication
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "spacecoords"
|
|
7
|
+
version = "0.1.2"
|
|
8
|
+
description = "A collection of coordinate transforms for convenient use"
|
|
9
|
+
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
|
+
authors = [{ name = "Daniel Kastinen", email = "daniel.kastinen@irf.se" }]
|
|
11
|
+
maintainers = [{ name = "Daniel Kastinen", email = "daniel.kastinen@irf.se" }]
|
|
12
|
+
license = { file = "LICENSE" }
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Intended Audience :: Science/Research",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
]
|
|
20
|
+
requires-python = ">=3.10"
|
|
21
|
+
dependencies = ["numpy >= 2"]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Documentation = "https://danielk.developer.irf.se/spacecoords/"
|
|
25
|
+
Repository = "https://github.com/danielk333/spacecoords"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
all = ["astropy>=6", "spiceypy>=8.0.0", "jplephem>=2.23", "requests>=2.20"]
|
|
30
|
+
develop = [
|
|
31
|
+
"pytest",
|
|
32
|
+
"pytest-cov",
|
|
33
|
+
"flake8",
|
|
34
|
+
"wheel",
|
|
35
|
+
"build",
|
|
36
|
+
"twine",
|
|
37
|
+
"auditwheel",
|
|
38
|
+
"numpydoc",
|
|
39
|
+
"black",
|
|
40
|
+
"matplotlib",
|
|
41
|
+
"ipykernel",
|
|
42
|
+
"mkdocs-material",
|
|
43
|
+
"mkdocstrings[python]",
|
|
44
|
+
"mkdocs-jupyter",
|
|
45
|
+
"mkdocs-gen-files",
|
|
46
|
+
"mkdocs-literate-nav",
|
|
47
|
+
"mkdocs-section-index",
|
|
48
|
+
]
|
|
49
|
+
tests = ["pytest", "pytest-cov"]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
[tool.setuptools]
|
|
53
|
+
include-package-data = true
|
|
54
|
+
license-files = ["LICENSE"]
|
|
55
|
+
|
|
56
|
+
[project.scripts]
|
|
57
|
+
spacecoords = "spacecoords.cli:main"
|
|
58
|
+
|
|
59
|
+
[tool.setuptools.packages.find]
|
|
60
|
+
where = ["src"]
|
|
61
|
+
|
|
62
|
+
[tool.black]
|
|
63
|
+
line-length = 110
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
[tool.pytest.ini_options]
|
|
67
|
+
addopts = "-v --cov=spacecoords"
|
|
68
|
+
testpaths = ["tests"]
|
|
69
|
+
|
|
70
|
+
[tool.coverage.report]
|
|
71
|
+
exclude_also = [
|
|
72
|
+
# Don't complain if tests don't hit defensive assertion code:
|
|
73
|
+
"raise AssertionError",
|
|
74
|
+
"raise NotImplementedError",
|
|
75
|
+
|
|
76
|
+
# Don't complain if non-runnable code isn't run:
|
|
77
|
+
"if 0:",
|
|
78
|
+
"if __name__ == .__main__.:",
|
|
79
|
+
|
|
80
|
+
# Don't complain about abstract methods, they aren't run:
|
|
81
|
+
"@(abc\\.)?abstractmethod",
|
|
82
|
+
]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""General purpose convenience functions for different coordinate systems and linear algebra
|
|
2
|
+
functions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from types import ModuleType
|
|
6
|
+
import importlib.util
|
|
7
|
+
from .version import __version__
|
|
8
|
+
|
|
9
|
+
from . import linalg
|
|
10
|
+
from . import spherical
|
|
11
|
+
from . import constants
|
|
12
|
+
from . import projection
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _make_missing_module(name: str, dep: str):
|
|
16
|
+
class _MissingModule(ModuleType):
|
|
17
|
+
def __getattr__(self, key):
|
|
18
|
+
raise ImportError(
|
|
19
|
+
f"The optional dependency `{dep}` for is missing.\n"
|
|
20
|
+
f"Install it with `pip install spacecoords[all]` or `pip install {dep}`."
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
return _MissingModule(name)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Optional modules
|
|
27
|
+
if importlib.util.find_spec("astropy") is not None:
|
|
28
|
+
from . import celestial
|
|
29
|
+
else:
|
|
30
|
+
astropy = _make_missing_module("celestial", "astropy")
|
|
31
|
+
|
|
32
|
+
if importlib.util.find_spec("jplephem") is not None:
|
|
33
|
+
from . import spk_basic
|
|
34
|
+
else:
|
|
35
|
+
naif_ephemeris = _make_missing_module("spk_basic", "jplephem")
|
|
36
|
+
|
|
37
|
+
if importlib.util.find_spec("spice") is not None:
|
|
38
|
+
from . import spice
|
|
39
|
+
else:
|
|
40
|
+
naif_spice = _make_missing_module("spice", "spiceypy")
|
|
41
|
+
|
|
42
|
+
if importlib.util.find_spec("requests") is not None:
|
|
43
|
+
from . import download
|
|
44
|
+
else:
|
|
45
|
+
download = _make_missing_module("download", "requests")
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
"""Coordinate frame transformations and related functions.
|
|
4
|
+
Main usage is the `convert` function that wraps Astropy frame transformations.
|
|
5
|
+
"""
|
|
6
|
+
from typing import Type, Any
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import numpy as np
|
|
9
|
+
from astropy.time import Time
|
|
10
|
+
import astropy.coordinates as coord
|
|
11
|
+
import astropy.units as units
|
|
12
|
+
import astropy.config as config
|
|
13
|
+
|
|
14
|
+
from .types import (
|
|
15
|
+
NDArray_N,
|
|
16
|
+
NDArray_3,
|
|
17
|
+
NDArray_6,
|
|
18
|
+
NDArray_3xN,
|
|
19
|
+
NDArray_6xN,
|
|
20
|
+
T,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
"""List of astropy frames
|
|
24
|
+
"""
|
|
25
|
+
ASTROPY_FRAMES = {
|
|
26
|
+
"TEME": "TEME",
|
|
27
|
+
"ITRS": "ITRS",
|
|
28
|
+
"ITRF": "ITRS",
|
|
29
|
+
"ICRS": "ICRS",
|
|
30
|
+
"ICRF": "ICRS",
|
|
31
|
+
"GCRS": "GCRS",
|
|
32
|
+
"GCRF": "GCRS",
|
|
33
|
+
"HCRS": "HCRS",
|
|
34
|
+
"HCRF": "HCRS",
|
|
35
|
+
"HeliocentricMeanEcliptic".upper(): "HeliocentricMeanEcliptic",
|
|
36
|
+
"GeocentricMeanEcliptic".upper(): "GeocentricMeanEcliptic",
|
|
37
|
+
"HeliocentricTrueEcliptic".upper(): "HeliocentricTrueEcliptic",
|
|
38
|
+
"GeocentricTrueEcliptic".upper(): "GeocentricTrueEcliptic",
|
|
39
|
+
"BarycentricMeanEcliptic".upper(): "BarycentricMeanEcliptic",
|
|
40
|
+
"BarycentricTrueEcliptic".upper(): "BarycentricTrueEcliptic",
|
|
41
|
+
"SPICEJ2000": "ICRS",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
"""List of frames that are not time-dependant
|
|
45
|
+
"""
|
|
46
|
+
ASTROPY_NOT_OBSTIME = [
|
|
47
|
+
"ICRS",
|
|
48
|
+
"BarycentricMeanEcliptic",
|
|
49
|
+
"BarycentricTrueEcliptic",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_solarsystem_body_state(
|
|
54
|
+
body: str,
|
|
55
|
+
time: Time,
|
|
56
|
+
kernel_dir: Path,
|
|
57
|
+
ephemeris: str = "jpl",
|
|
58
|
+
) -> NDArray_6xN | NDArray_6:
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
This is to not have to remember how to do this astropy config stuff
|
|
62
|
+
# https://docs.astropy.org/en/stable/api/astropy.coordinates.solar_system_ephemeris.html
|
|
63
|
+
"""
|
|
64
|
+
with config.set_temp_cache(path=str(kernel_dir), delete=False):
|
|
65
|
+
pos, vel = coord.get_body_barycentric_posvel(body, time, ephemeris=ephemeris)
|
|
66
|
+
|
|
67
|
+
size = len(time)
|
|
68
|
+
shape: tuple[int, ...] = (6, size) if size > 0 else (6,)
|
|
69
|
+
state = np.empty(shape, dtype=np.float64)
|
|
70
|
+
state[:3, ...] = pos.xyz.to(units.m).value
|
|
71
|
+
state[3:, ...] = vel.d_xyz.to(units.m / units.s).value
|
|
72
|
+
return state
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def not_geocentric(frame: str) -> bool:
|
|
76
|
+
"""Check if the given frame name is one of the non-geocentric frames."""
|
|
77
|
+
frame = frame.upper()
|
|
78
|
+
return frame in ["ICRS", "ICRF", "HCRS", "HCRF"] or frame.startswith("Heliocentric".upper())
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def is_geocentric(frame: str) -> bool:
|
|
82
|
+
"""Check if the frame name is a supported geocentric frame"""
|
|
83
|
+
return not not_geocentric(frame)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def convert(
|
|
87
|
+
t: NDArray_N,
|
|
88
|
+
states: NDArray_6xN,
|
|
89
|
+
in_frame: str,
|
|
90
|
+
out_frame: str,
|
|
91
|
+
frame_kwargs: dict[str, Any],
|
|
92
|
+
) -> NDArray_6xN:
|
|
93
|
+
"""Perform predefined coordinate transformations using Astropy.
|
|
94
|
+
Always returns a copy of the array.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
t
|
|
99
|
+
Absolute time corresponding to the input states.
|
|
100
|
+
states
|
|
101
|
+
Size `(6,n)` matrix of states in SI units where rows 1-3
|
|
102
|
+
are position and 4-6 are velocity.
|
|
103
|
+
in_frame
|
|
104
|
+
Name of the frame the input states are currently in.
|
|
105
|
+
out_frame
|
|
106
|
+
Name of the state to transform to.
|
|
107
|
+
frame_kwargs
|
|
108
|
+
Any arguments needed for the specific transform detailed by `astropy`
|
|
109
|
+
in their documentation
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
Size `(6,n)` matrix of states in SI units where rows
|
|
114
|
+
1-3 are position and 4-6 are velocity.
|
|
115
|
+
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
in_frame = in_frame.upper()
|
|
119
|
+
out_frame = out_frame.upper()
|
|
120
|
+
|
|
121
|
+
if in_frame == out_frame:
|
|
122
|
+
return states.copy()
|
|
123
|
+
|
|
124
|
+
if in_frame in ASTROPY_FRAMES:
|
|
125
|
+
in_frame_ = ASTROPY_FRAMES[in_frame]
|
|
126
|
+
in_frame_cls = getattr(coord, in_frame_)
|
|
127
|
+
else:
|
|
128
|
+
err_str = [
|
|
129
|
+
f"In frame '{in_frame}' not recognized, ",
|
|
130
|
+
"please check spelling or perform manual transformation",
|
|
131
|
+
]
|
|
132
|
+
raise ValueError("".join(err_str))
|
|
133
|
+
|
|
134
|
+
kw = {}
|
|
135
|
+
kw.update(frame_kwargs)
|
|
136
|
+
if in_frame_ not in ASTROPY_NOT_OBSTIME:
|
|
137
|
+
kw["obstime"] = t
|
|
138
|
+
|
|
139
|
+
astropy_states = _convert_to_astropy(states, in_frame_cls, kw)
|
|
140
|
+
|
|
141
|
+
if out_frame in ASTROPY_FRAMES:
|
|
142
|
+
out_frame_ = ASTROPY_FRAMES[out_frame]
|
|
143
|
+
out_frame_cls = getattr(coord, out_frame_)
|
|
144
|
+
else:
|
|
145
|
+
err_str = [
|
|
146
|
+
f"Out frame '{out_frame}' not recognized, ",
|
|
147
|
+
"please check spelling or perform manual transformation",
|
|
148
|
+
]
|
|
149
|
+
raise ValueError("".join(err_str))
|
|
150
|
+
|
|
151
|
+
kw = {}
|
|
152
|
+
kw.update(frame_kwargs)
|
|
153
|
+
if out_frame_ not in ASTROPY_NOT_OBSTIME:
|
|
154
|
+
kw["obstime"] = t
|
|
155
|
+
|
|
156
|
+
out_states = astropy_states.transform_to(out_frame_cls(**kw))
|
|
157
|
+
|
|
158
|
+
rets = states.copy()
|
|
159
|
+
rets[:3, ...] = out_states.cartesian.xyz.to(units.m).value
|
|
160
|
+
rets[3:, ...] = out_states.velocity.d_xyz.to(units.m / units.s).value
|
|
161
|
+
|
|
162
|
+
return rets
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _convert_to_astropy(
|
|
166
|
+
states: NDArray_6xN | NDArray_6,
|
|
167
|
+
frame: Type[T],
|
|
168
|
+
frame_kwargs: dict[str, Any],
|
|
169
|
+
) -> T:
|
|
170
|
+
state_p = coord.CartesianRepresentation(states[:3, ...] * units.m)
|
|
171
|
+
state_v = coord.CartesianDifferential(states[3:, ...] * units.m / units.s)
|
|
172
|
+
astropy_states = frame(state_p.with_differentials(state_v), **frame_kwargs) # type: ignore
|
|
173
|
+
return astropy_states
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def geodetic_to_ITRS(
|
|
177
|
+
lat: NDArray_N | float,
|
|
178
|
+
lon: NDArray_N | float,
|
|
179
|
+
alt: NDArray_N | float,
|
|
180
|
+
degrees: bool = True,
|
|
181
|
+
) -> NDArray_3xN | NDArray_3:
|
|
182
|
+
"""Use `astropy.coordinates.WGS84GeodeticRepresentation` to transform from WGS84 to ITRS."""
|
|
183
|
+
ang_unit = units.deg if degrees else units.rad
|
|
184
|
+
|
|
185
|
+
wgs_cord = coord.WGS84GeodeticRepresentation(
|
|
186
|
+
lon=lon * ang_unit,
|
|
187
|
+
lat=lat * ang_unit,
|
|
188
|
+
height=alt * units.m,
|
|
189
|
+
)
|
|
190
|
+
itrs_cord = coord.ITRS(wgs_cord)
|
|
191
|
+
|
|
192
|
+
if isinstance(lat, np.ndarray):
|
|
193
|
+
size = lat.size
|
|
194
|
+
else:
|
|
195
|
+
size = 0
|
|
196
|
+
|
|
197
|
+
shape: tuple[int, ...] = (6, size) if size > 0 else (6,)
|
|
198
|
+
state = np.empty(shape, dtype=np.float64)
|
|
199
|
+
state[:3, ...] = itrs_cord.cartesian.xyz.to(units.m).value
|
|
200
|
+
state[3:, ...] = itrs_cord.velocity.d_xyz.to(units.m / units.s).value
|
|
201
|
+
|
|
202
|
+
return state
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def ITRS_to_geodetic(
|
|
206
|
+
state: NDArray_3xN | NDArray_N,
|
|
207
|
+
degrees: bool = True,
|
|
208
|
+
) -> tuple[NDArray_N | float, NDArray_N | float, NDArray_N | float]:
|
|
209
|
+
"""Use `astropy.coordinates.WGS84GeodeticRepresentation` to transform from ITRS to WGS84."""
|
|
210
|
+
ang_unit = units.deg if degrees else units.rad
|
|
211
|
+
|
|
212
|
+
itrs_cord = _convert_to_astropy(state, coord.ITRS, {})
|
|
213
|
+
wgs_cord = coord.WGS84GeodeticRepresentation(itrs_cord)
|
|
214
|
+
return (
|
|
215
|
+
wgs_cord.lat.to(ang_unit).value,
|
|
216
|
+
wgs_cord.lon.to(ang_unit).value,
|
|
217
|
+
wgs_cord.height.to(units.m).value,
|
|
218
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def main():
|
|
5
|
+
import spacecoords as sc
|
|
6
|
+
|
|
7
|
+
parser = argparse.ArgumentParser(description="Download files")
|
|
8
|
+
subparsers = parser.add_subparsers(help="Available download interfaces", dest="command")
|
|
9
|
+
|
|
10
|
+
naif_parser = subparsers.add_parser("naif_kernel", help="Download NAIF Kernels")
|
|
11
|
+
naif_parser.add_argument(
|
|
12
|
+
"kernel_type",
|
|
13
|
+
choices=list(sc.download.KERNEL_PATHS.keys()),
|
|
14
|
+
help="Type of kernel (determines location on server)",
|
|
15
|
+
)
|
|
16
|
+
naif_parser.add_argument("kernel_name", help="Kernel filename")
|
|
17
|
+
naif_parser.add_argument("output_file", help="Path to output file")
|
|
18
|
+
|
|
19
|
+
args = parser.parse_args()
|
|
20
|
+
|
|
21
|
+
if args.command == "naif_kernel":
|
|
22
|
+
sc.download.naif_kernel_main(args)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
"""Constants useful for different coordinate systems"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
G: float = 6.67430e-11
|
|
7
|
+
"""Newtonian constant of gravitation (m^3 kg^-1 s^-2), CODATA Recommended Values of the Fundamental
|
|
8
|
+
Physical Constants 2022.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WGS84:
|
|
13
|
+
"""World Geodetic System 1984 constants."""
|
|
14
|
+
|
|
15
|
+
a: float = 6378.137 * 1e3
|
|
16
|
+
"""float: semi-major axis parameter in meters of the World Geodetic System 1984 (WGS84)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
b: float = 6356.7523142 * 1e3
|
|
20
|
+
"""float: semi-minor axis parameter in meters of the World Geodetic System 1984 (WGS84)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
esq: float = 6.69437999014 * 0.001
|
|
24
|
+
"""float: `esq` parameter in meters of the World Geodetic System 1984 (WGS84)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
e1sq: float = 6.73949674228 * 0.001
|
|
28
|
+
"""float: `e1sq` parameter in meters of the World Geodetic System 1984 (WGS84)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
f: float = 1 / 298.257223563
|
|
32
|
+
"""float: `f` parameter of the World Geodetic System 1984 (WGS84)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
R_earth: float = 6371.0088e3
|
|
37
|
+
"""float: Radius of the Earth using the International
|
|
38
|
+
Union of Geodesy and Geophysics (IUGG) definition
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class WGS72:
|
|
43
|
+
"""World Geodetic System 1972 constants."""
|
|
44
|
+
|
|
45
|
+
MU_earth: float = 398600.8 * 1e9
|
|
46
|
+
"""float: Standard gravitational parameter of the Earth using the WGS72 convention.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
M_earth: float = MU_earth / G
|
|
50
|
+
"""float: Mass of the Earth using the WGS72 convention.
|
|
51
|
+
"""
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
NAIF_URL = "https://naif.jpl.nasa.gov/pub/naif/"
|
|
7
|
+
|
|
8
|
+
KERNEL_PATHS = {
|
|
9
|
+
"planetary": "generic_kernels/spk/planets/",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def naif_kernel(
|
|
14
|
+
kernel_path: str,
|
|
15
|
+
output_file: Path,
|
|
16
|
+
progress: bool = True,
|
|
17
|
+
chunk_size: int = 8192,
|
|
18
|
+
):
|
|
19
|
+
url = NAIF_URL + kernel_path
|
|
20
|
+
with requests.get(url, stream=True) as r:
|
|
21
|
+
r.raise_for_status()
|
|
22
|
+
total_length = r.headers.get("content-length")
|
|
23
|
+
downloaded = 0
|
|
24
|
+
if total_length is None:
|
|
25
|
+
with open(output_file, "wb") as fh:
|
|
26
|
+
for chunk in r.iter_content(chunk_size=chunk_size):
|
|
27
|
+
fh.write(chunk)
|
|
28
|
+
if progress:
|
|
29
|
+
downloaded += len(chunk)
|
|
30
|
+
sys.stdout.write(f"{downloaded / 1024:.1f} KB")
|
|
31
|
+
sys.stdout.flush()
|
|
32
|
+
if progress:
|
|
33
|
+
print()
|
|
34
|
+
else:
|
|
35
|
+
total_length = int(total_length)
|
|
36
|
+
prog_width = min(os.get_terminal_size()[0] - 10, 100)
|
|
37
|
+
with open(output_file, "wb") as fh:
|
|
38
|
+
for chunk in r.iter_content(chunk_size=chunk_size):
|
|
39
|
+
if not chunk:
|
|
40
|
+
continue
|
|
41
|
+
fh.write(chunk)
|
|
42
|
+
if progress:
|
|
43
|
+
downloaded += len(chunk)
|
|
44
|
+
done = int(prog_width * downloaded / total_length)
|
|
45
|
+
sys.stdout.write(
|
|
46
|
+
f"\r[{'=' * done}{' ' * (prog_width - done)}] "
|
|
47
|
+
f"{downloaded / 1024:.1f} KB / {total_length / 1024:.1f} KB"
|
|
48
|
+
)
|
|
49
|
+
sys.stdout.flush()
|
|
50
|
+
if progress:
|
|
51
|
+
print()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def naif_kernel_main(args):
|
|
55
|
+
naif_kernel(
|
|
56
|
+
kernel_path=KERNEL_PATHS[args.kernel_type] + args.kernel_name,
|
|
57
|
+
output_file=args.output_file,
|
|
58
|
+
progress=True,
|
|
59
|
+
)
|