lasp-curryer 0.0.4__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.
- lasp_curryer-0.0.4/LICENSE +21 -0
- lasp_curryer-0.0.4/NOTICE +2 -0
- lasp_curryer-0.0.4/PKG-INFO +43 -0
- lasp_curryer-0.0.4/README.md +4 -0
- lasp_curryer-0.0.4/curryer/__init__.py +12 -0
- lasp_curryer-0.0.4/curryer/compute/__init__.py +9 -0
- lasp_curryer-0.0.4/curryer/compute/abstract.py +147 -0
- lasp_curryer-0.0.4/curryer/compute/constants.py +75 -0
- lasp_curryer-0.0.4/curryer/compute/elevation.py +740 -0
- lasp_curryer-0.0.4/curryer/compute/ephemeris.py +175 -0
- lasp_curryer-0.0.4/curryer/compute/pointing.py +494 -0
- lasp_curryer-0.0.4/curryer/compute/spatial.py +1473 -0
- lasp_curryer-0.0.4/curryer/kernels/__init__.py +9 -0
- lasp_curryer-0.0.4/curryer/kernels/attitude.py +334 -0
- lasp_curryer-0.0.4/curryer/kernels/classes.py +427 -0
- lasp_curryer-0.0.4/curryer/kernels/create.py +258 -0
- lasp_curryer-0.0.4/curryer/kernels/ephemeris.py +419 -0
- lasp_curryer-0.0.4/curryer/kernels/templates/kernel_macros.jinja2 +76 -0
- lasp_curryer-0.0.4/curryer/kernels/templates/mkspk_setup_template.txt +42 -0
- lasp_curryer-0.0.4/curryer/kernels/templates/msopck_setup_template.txt +42 -0
- lasp_curryer-0.0.4/curryer/kernels/writer.py +271 -0
- lasp_curryer-0.0.4/curryer/meta.py +104 -0
- lasp_curryer-0.0.4/curryer/spicetime/__init__.py +56 -0
- lasp_curryer-0.0.4/curryer/spicetime/constants.py +34 -0
- lasp_curryer-0.0.4/curryer/spicetime/convert.py +664 -0
- lasp_curryer-0.0.4/curryer/spicetime/leapsecond.py +288 -0
- lasp_curryer-0.0.4/curryer/spicetime/native.py +145 -0
- lasp_curryer-0.0.4/curryer/spicetime/utils.py +170 -0
- lasp_curryer-0.0.4/curryer/spicierpy/__init__.py +23 -0
- lasp_curryer-0.0.4/curryer/spicierpy/ext.py +526 -0
- lasp_curryer-0.0.4/curryer/spicierpy/obj.py +653 -0
- lasp_curryer-0.0.4/curryer/spicierpy/vectorized.py +181 -0
- lasp_curryer-0.0.4/curryer/tle.py +231 -0
- lasp_curryer-0.0.4/curryer/utils.py +254 -0
- lasp_curryer-0.0.4/pyproject.toml +60 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 The Regents of the University of Colorado.
|
|
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,43 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: lasp-curryer
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: LASP SPICE extentions and geospatial data product generation tools.
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: LASP,SDP
|
|
7
|
+
Author: Brandon Stone
|
|
8
|
+
Author-email: brandon.h.stone@colorado.edu
|
|
9
|
+
Requires-Python: >=3.9,<4
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Operating System :: POSIX
|
|
16
|
+
Classifier: Operating System :: Unix
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Classifier: Topic :: Software Development
|
|
24
|
+
Requires-Dist: jinja2 (>=3)
|
|
25
|
+
Requires-Dist: netcdf4 (>=1.6)
|
|
26
|
+
Requires-Dist: numpy (>=1.23,<2.0)
|
|
27
|
+
Requires-Dist: openpyxl (>=3)
|
|
28
|
+
Requires-Dist: pandas (>=1.5)
|
|
29
|
+
Requires-Dist: pyhdf (>=0.11)
|
|
30
|
+
Requires-Dist: pyproj (>=3.6)
|
|
31
|
+
Requires-Dist: pyshp (>=2.3)
|
|
32
|
+
Requires-Dist: requests (>=2.28)
|
|
33
|
+
Requires-Dist: rioxarray (>=0.15)
|
|
34
|
+
Requires-Dist: scipy (>=1.9)
|
|
35
|
+
Requires-Dist: spiceypy (>=6)
|
|
36
|
+
Requires-Dist: xarray (>=2022.11)
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# Curryer
|
|
40
|
+
|
|
41
|
+
A library for SPICE extensions and geospatial data product generation.
|
|
42
|
+
|
|
43
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Helper library for using high-level SPICE functions, creating SPICE kernels,
|
|
2
|
+
and calculating spatial datasets.
|
|
3
|
+
|
|
4
|
+
@author: Brandon Stone
|
|
5
|
+
"""
|
|
6
|
+
from . import compute
|
|
7
|
+
from . import kernels
|
|
8
|
+
from . import spicetime
|
|
9
|
+
from . import spicierpy
|
|
10
|
+
from . import meta
|
|
11
|
+
from . import tle
|
|
12
|
+
from . import utils
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Abstract mission data class.
|
|
2
|
+
|
|
3
|
+
@author: Brandon Stone
|
|
4
|
+
"""
|
|
5
|
+
import functools
|
|
6
|
+
import logging
|
|
7
|
+
from abc import ABCMeta, abstractmethod
|
|
8
|
+
from io import StringIO
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def log_return(max_rows=5):
|
|
18
|
+
"""Log any output tables/arrays from a function.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def log_return_wrapper(func):
|
|
22
|
+
"""Store the function to log output from.
|
|
23
|
+
"""
|
|
24
|
+
_logger = logging.getLogger(getattr(func, '__module__', __name__))
|
|
25
|
+
|
|
26
|
+
@functools.wraps(func)
|
|
27
|
+
def log_return_call(*args, **kwargs):
|
|
28
|
+
"""Call the wrapper function and log the output.
|
|
29
|
+
"""
|
|
30
|
+
output = func(*args, **kwargs)
|
|
31
|
+
|
|
32
|
+
if _logger.isEnabledFor(logging.DEBUG):
|
|
33
|
+
output_name = output.__class__.__name__
|
|
34
|
+
|
|
35
|
+
# Table.
|
|
36
|
+
if isinstance(output, pd.DataFrame):
|
|
37
|
+
if pd.get_option('display.width') == 80:
|
|
38
|
+
pd.set_option('display.width', 140)
|
|
39
|
+
info_strm = StringIO()
|
|
40
|
+
_logger.debug('%s%s:\n %s', output_name, output.shape, output.to_string(max_rows=max_rows))
|
|
41
|
+
output.info(buf=info_strm)
|
|
42
|
+
_logger.debug('%s details:\n%s', output_name, info_strm.getvalue())
|
|
43
|
+
|
|
44
|
+
# Numpy array.
|
|
45
|
+
elif isinstance(output, np.ndarray):
|
|
46
|
+
if np.get_printoptions()['threshold'] == 1000:
|
|
47
|
+
np.set_printoptions(threshold=100)
|
|
48
|
+
_logger.debug('%s%s:\n %s', output_name, output.shape, output) # Auto trims.
|
|
49
|
+
|
|
50
|
+
return output
|
|
51
|
+
|
|
52
|
+
return log_return_call
|
|
53
|
+
|
|
54
|
+
return log_return_wrapper
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class AbstractMissionData(metaclass=ABCMeta):
|
|
58
|
+
"""Abstract class to get/write mission data.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def __init__(self, microsecond_cadence, *args, **kwargs):
|
|
63
|
+
self.microsecond_cadence = microsecond_cadence
|
|
64
|
+
self._loaded_kernels = None
|
|
65
|
+
self.allow_nans = True
|
|
66
|
+
|
|
67
|
+
def __del__(self):
|
|
68
|
+
if not self._loaded_kernels:
|
|
69
|
+
return
|
|
70
|
+
try:
|
|
71
|
+
self._loaded_kernels.unload(clear=True)
|
|
72
|
+
except:
|
|
73
|
+
logging.exception('Error while unloading kernels:\n')
|
|
74
|
+
|
|
75
|
+
@log_return()
|
|
76
|
+
def get_times(self, ugps_range, cadence=None):
|
|
77
|
+
"""Array of evenly spaced values.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
ugps_range : (int, int)
|
|
82
|
+
Time range [start, end).
|
|
83
|
+
cadence : int, optional
|
|
84
|
+
Number of microseconds to step by. Default=`self.DEFAULT_CADENCE`
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
numpy.ndarray
|
|
89
|
+
Array of time values (int64).
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
if cadence is None:
|
|
93
|
+
cadence = self.microsecond_cadence
|
|
94
|
+
if len(ugps_range) != 2:
|
|
95
|
+
raise ValueError('Must specify two uGPS times: inclusive start, exclusive end.')
|
|
96
|
+
|
|
97
|
+
logger.debug('Determining times in range [%s] with cadence [%s]', ugps_range, cadence)
|
|
98
|
+
return np.arange(ugps_range[0], ugps_range[1], cadence)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def write_to_database(table, session, dbtable, auto_commit):
|
|
102
|
+
"""Insert table values using the SQLAlchemy bulk insert method.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
table : DataFrame
|
|
107
|
+
session : Session
|
|
108
|
+
dbtable : Table
|
|
109
|
+
auto_commit : bool
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
int
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
logger.info('Inserting [%s] rows into [%s] @ [%s]', table.shape[0], dbtable.__table__.fullname, session)
|
|
117
|
+
|
|
118
|
+
if table.shape[0] == 0:
|
|
119
|
+
logger.warning('Skipping insert for empty dataset [%s]!', dbtable.__table__.fullname)
|
|
120
|
+
return 0
|
|
121
|
+
|
|
122
|
+
query = session.query(
|
|
123
|
+
dbtable,
|
|
124
|
+
).filter(
|
|
125
|
+
dbtable.ugps >= int(table.index.min()),
|
|
126
|
+
dbtable.ugps <= int(table.index.max()),
|
|
127
|
+
*[getattr(dbtable, col) == table[col].iloc[0] for col in table.columns if col != 'ugps']
|
|
128
|
+
# dbtable.instrumentmodeid == int(table['instrumentmodeid'].iloc[0]),
|
|
129
|
+
# dbtable.version == int(table['version'].iloc[0])
|
|
130
|
+
)
|
|
131
|
+
assert query.count() == 0, 'Data already exists!'
|
|
132
|
+
|
|
133
|
+
# Bulk insert a list of dicts (rows).
|
|
134
|
+
# Convert DataFrame to a Numpy records array to maintain
|
|
135
|
+
# the individual column data types. Convert to a list to change
|
|
136
|
+
# from Numpy scalar types to Python scalar types (e.g., int).
|
|
137
|
+
# TODO: Add an iter to flush every (e.g., 1440 rows / day's worth)?
|
|
138
|
+
col_names = (table.index.name, *table.columns)
|
|
139
|
+
session.bulk_insert_mappings(
|
|
140
|
+
dbtable,
|
|
141
|
+
(dict(zip(col_names, row)) for row in table.to_records().tolist())
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
assert query.count() == table.shape[0], 'Database is missing some data.'
|
|
145
|
+
if auto_commit:
|
|
146
|
+
session.commit()
|
|
147
|
+
return table.shape[0]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""SPICE related constants for TIM.
|
|
2
|
+
|
|
3
|
+
@author: Brandon Stone
|
|
4
|
+
"""
|
|
5
|
+
from enum import IntFlag, unique
|
|
6
|
+
from spiceypy.utils.exceptions import SpiceyError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Time-interval between output samples (TIM/SIM missions only).
|
|
10
|
+
# Units: Microseconds
|
|
11
|
+
EPHEMERIS_TIMESTEP_USEC = 60000000
|
|
12
|
+
ATTITUDE_TIMESTEP_USEC = 10000000
|
|
13
|
+
|
|
14
|
+
# Astronomical unit (2012 definition)
|
|
15
|
+
# Units: KM / 1 AU
|
|
16
|
+
# Source: http://www.iau.org/static/resolutions/IAU2012_English.pdf
|
|
17
|
+
# Note: Java uses ###.66 instead of ###.700
|
|
18
|
+
KM_PER_ASTRONOMICAL_UNIT = 149597870.700
|
|
19
|
+
|
|
20
|
+
# Speed of light in a vacuum
|
|
21
|
+
# Units: KM / SEC
|
|
22
|
+
SPEED_OF_LIGHT_KM_PER_S = 299792.458
|
|
23
|
+
|
|
24
|
+
# WGS84 Ellipsoid reference
|
|
25
|
+
# Units: KM
|
|
26
|
+
# Source: https://epsg.org/ellipsoid_7030/WGS-84.html
|
|
27
|
+
WGS84_SEMI_MAJOR_AXIS_KM = 6378.137
|
|
28
|
+
WGS84_SEMI_MINOR_AXIS_KM = 6356.752314245
|
|
29
|
+
WGS84_INVERSE_FLATTENING = 1 / 298.257223563
|
|
30
|
+
WGS84_ECCENTRICITY2 = 2 * WGS84_INVERSE_FLATTENING - WGS84_INVERSE_FLATTENING ** 2
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@unique
|
|
34
|
+
class SpatialQualityFlags(IntFlag):
|
|
35
|
+
GOOD = 0x0
|
|
36
|
+
|
|
37
|
+
SPICE_ERR_NO_RESULTS_FOUND = 0x1 # E.g. no surface intersect.
|
|
38
|
+
SPICE_ERR_MISSING_EPHEMERIS = 0x2
|
|
39
|
+
SPICE_ERR_MISSING_ATTITUDE = 0x4
|
|
40
|
+
SPICE_ERR_INVALID_LOCATION = 0x8 # E.g. inside planet.
|
|
41
|
+
SPICE_ERR_UNKNOWN_NONSPECIFIC = 0x10
|
|
42
|
+
|
|
43
|
+
CALC_UNEXPECTED_NOT_FINITE = 0x20
|
|
44
|
+
|
|
45
|
+
CALC_KERNELS_UNDESIRED_TLM = 0x40
|
|
46
|
+
CALC_KERNELS_FAILED = 0x80
|
|
47
|
+
|
|
48
|
+
CALC_ELLIPS_INSUFF_DATA = 0x100
|
|
49
|
+
CALC_ELLIPS_NO_INTERSECT = 0x200
|
|
50
|
+
|
|
51
|
+
# TODO: CALC_TERRAIN_INSUFF_DATA ?
|
|
52
|
+
CALC_TERRAIN_EXTREME_ZENITH = 0x400
|
|
53
|
+
CALC_TERRAIN_MAX_ITER = 0x800
|
|
54
|
+
CALC_TERRAIN_NOT_FINITE = 0x1000
|
|
55
|
+
|
|
56
|
+
CALC_ANCIL_INSUFF_DATA = 0x2000
|
|
57
|
+
CALC_ANCIL_NOT_FINITE = 0x4000
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def from_spice_error(cls, error: SpiceyError):
|
|
61
|
+
if error is None:
|
|
62
|
+
return cls.GOOD
|
|
63
|
+
if not isinstance(error, SpiceyError):
|
|
64
|
+
raise TypeError(f'Method requires a SpiceyError, not: {type(error)}')
|
|
65
|
+
# if isinstance(error, NotFoundError):
|
|
66
|
+
# return cls.SPICE_ERR_NO_LOOKUP_FOUND
|
|
67
|
+
if 'returns not found' in error.message:
|
|
68
|
+
return cls.SPICE_ERR_NO_RESULTS_FOUND
|
|
69
|
+
if 'SPICE(SPKINSUFFDATA)' in error.short:
|
|
70
|
+
return cls.SPICE_ERR_MISSING_EPHEMERIS
|
|
71
|
+
if 'SPICE(NOFRAMECONNECT)' in error.short:
|
|
72
|
+
return cls.SPICE_ERR_MISSING_ATTITUDE
|
|
73
|
+
if 'SPICE(NOTDISJOINT)' in error.short:
|
|
74
|
+
return cls.SPICE_ERR_INVALID_LOCATION
|
|
75
|
+
return cls.SPICE_ERR_UNKNOWN_NONSPECIFIC
|