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.
Files changed (35) hide show
  1. lasp_curryer-0.0.4/LICENSE +21 -0
  2. lasp_curryer-0.0.4/NOTICE +2 -0
  3. lasp_curryer-0.0.4/PKG-INFO +43 -0
  4. lasp_curryer-0.0.4/README.md +4 -0
  5. lasp_curryer-0.0.4/curryer/__init__.py +12 -0
  6. lasp_curryer-0.0.4/curryer/compute/__init__.py +9 -0
  7. lasp_curryer-0.0.4/curryer/compute/abstract.py +147 -0
  8. lasp_curryer-0.0.4/curryer/compute/constants.py +75 -0
  9. lasp_curryer-0.0.4/curryer/compute/elevation.py +740 -0
  10. lasp_curryer-0.0.4/curryer/compute/ephemeris.py +175 -0
  11. lasp_curryer-0.0.4/curryer/compute/pointing.py +494 -0
  12. lasp_curryer-0.0.4/curryer/compute/spatial.py +1473 -0
  13. lasp_curryer-0.0.4/curryer/kernels/__init__.py +9 -0
  14. lasp_curryer-0.0.4/curryer/kernels/attitude.py +334 -0
  15. lasp_curryer-0.0.4/curryer/kernels/classes.py +427 -0
  16. lasp_curryer-0.0.4/curryer/kernels/create.py +258 -0
  17. lasp_curryer-0.0.4/curryer/kernels/ephemeris.py +419 -0
  18. lasp_curryer-0.0.4/curryer/kernels/templates/kernel_macros.jinja2 +76 -0
  19. lasp_curryer-0.0.4/curryer/kernels/templates/mkspk_setup_template.txt +42 -0
  20. lasp_curryer-0.0.4/curryer/kernels/templates/msopck_setup_template.txt +42 -0
  21. lasp_curryer-0.0.4/curryer/kernels/writer.py +271 -0
  22. lasp_curryer-0.0.4/curryer/meta.py +104 -0
  23. lasp_curryer-0.0.4/curryer/spicetime/__init__.py +56 -0
  24. lasp_curryer-0.0.4/curryer/spicetime/constants.py +34 -0
  25. lasp_curryer-0.0.4/curryer/spicetime/convert.py +664 -0
  26. lasp_curryer-0.0.4/curryer/spicetime/leapsecond.py +288 -0
  27. lasp_curryer-0.0.4/curryer/spicetime/native.py +145 -0
  28. lasp_curryer-0.0.4/curryer/spicetime/utils.py +170 -0
  29. lasp_curryer-0.0.4/curryer/spicierpy/__init__.py +23 -0
  30. lasp_curryer-0.0.4/curryer/spicierpy/ext.py +526 -0
  31. lasp_curryer-0.0.4/curryer/spicierpy/obj.py +653 -0
  32. lasp_curryer-0.0.4/curryer/spicierpy/vectorized.py +181 -0
  33. lasp_curryer-0.0.4/curryer/tle.py +231 -0
  34. lasp_curryer-0.0.4/curryer/utils.py +254 -0
  35. 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,2 @@
1
+ Curryer
2
+ Copyright (c) 2023 The Regents of the University of Colorado
@@ -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,4 @@
1
+ # Curryer
2
+
3
+ A library for SPICE extensions and geospatial data product generation.
4
+
@@ -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,9 @@
1
+ """Methods for computing spatial and pointing datasets.
2
+
3
+ @author: Brandon Stone
4
+ """
5
+ from . import constants
6
+ from . import elevation
7
+ from . import ephemeris
8
+ from . import pointing
9
+ from . import spatial
@@ -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