pylunar 0.7.1__py3-none-any.whl
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.
- pylunar/__init__.py +42 -0
- pylunar/data/lunar.db +0 -0
- pylunar/helpers.py +55 -0
- pylunar/lunar_feature.py +179 -0
- pylunar/lunar_feature_container.py +97 -0
- pylunar/moon_info.py +656 -0
- pylunar/types.py +31 -0
- pylunar-0.7.1.dist-info/AUTHORS.rst +16 -0
- pylunar-0.7.1.dist-info/LICENSE +26 -0
- pylunar-0.7.1.dist-info/METADATA +78 -0
- pylunar-0.7.1.dist-info/RECORD +13 -0
- pylunar-0.7.1.dist-info/WHEEL +5 -0
- pylunar-0.7.1.dist-info/top_level.txt +1 -0
pylunar/__init__.py
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# This file is part of pylunar.
|
2
|
+
#
|
3
|
+
# Developed by Michael Reuter.
|
4
|
+
#
|
5
|
+
# See the LICENSE file at the top-level directory of this distribution
|
6
|
+
# for details of code ownership.
|
7
|
+
#
|
8
|
+
# Use of this source code is governed by a 3-clause BSD-style
|
9
|
+
# license that can be found in the LICENSE file.
|
10
|
+
|
11
|
+
__all__ = [
|
12
|
+
"__author__",
|
13
|
+
"__email__",
|
14
|
+
"__version__",
|
15
|
+
"LunarFeature",
|
16
|
+
"LunarFeatureContainer",
|
17
|
+
"mjd_to_date_tuple",
|
18
|
+
"MoonInfo",
|
19
|
+
"tuple_to_string",
|
20
|
+
"version_info",
|
21
|
+
]
|
22
|
+
|
23
|
+
from importlib.metadata import PackageNotFoundError, version
|
24
|
+
|
25
|
+
__author__ = "Michael Reuter"
|
26
|
+
__email__ = "mareuternh@gmail.com"
|
27
|
+
try:
|
28
|
+
__version__ = version("pylunar")
|
29
|
+
except PackageNotFoundError:
|
30
|
+
# package is not installed
|
31
|
+
__version__ = "0.0.0"
|
32
|
+
|
33
|
+
version_info = __version__.split(".")
|
34
|
+
"""The decomposed version, split across "``.``."
|
35
|
+
|
36
|
+
Use this for version comparison.
|
37
|
+
"""
|
38
|
+
|
39
|
+
from .helpers import mjd_to_date_tuple, tuple_to_string
|
40
|
+
from .lunar_feature import LunarFeature
|
41
|
+
from .lunar_feature_container import LunarFeatureContainer
|
42
|
+
from .moon_info import MoonInfo
|
pylunar/data/lunar.db
ADDED
Binary file
|
pylunar/helpers.py
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# This file is part of pylunar.
|
2
|
+
#
|
3
|
+
# Developed by Michael Reuter.
|
4
|
+
#
|
5
|
+
# See the LICENSE file at the top-level directory of this distribution
|
6
|
+
# for details of code ownership.
|
7
|
+
#
|
8
|
+
# Use of this source code is governed by a 3-clause BSD-style
|
9
|
+
# license that can be found in the LICENSE file.
|
10
|
+
|
11
|
+
"""Module for helper functions."""
|
12
|
+
|
13
|
+
from __future__ import annotations
|
14
|
+
|
15
|
+
__all__ = ["mjd_to_date_tuple", "tuple_to_string"]
|
16
|
+
|
17
|
+
import ephem
|
18
|
+
|
19
|
+
from .types import DateTimeTuple, DmsCoordinate
|
20
|
+
|
21
|
+
|
22
|
+
def mjd_to_date_tuple(mjd: float, round_off: bool = False) -> DateTimeTuple:
|
23
|
+
"""Convert a Modified Julian date to a UTC time tuple.
|
24
|
+
|
25
|
+
Parameters
|
26
|
+
----------
|
27
|
+
mjd : float
|
28
|
+
The Modified Julian Date to convert.
|
29
|
+
round_off : bool, optional
|
30
|
+
Flag to round the seconds.
|
31
|
+
|
32
|
+
Returns
|
33
|
+
-------
|
34
|
+
tuple
|
35
|
+
The UTC time for the MJD.
|
36
|
+
"""
|
37
|
+
date_tuple: DateTimeTuple
|
38
|
+
date_tuple = tuple(int(x) for x in ephem.Date(mjd).tuple()) if round_off else ephem.Date(mjd).tuple()
|
39
|
+
return date_tuple
|
40
|
+
|
41
|
+
|
42
|
+
def tuple_to_string(coord: DmsCoordinate) -> str:
|
43
|
+
"""Return a colon-delimited string.
|
44
|
+
|
45
|
+
Parameters
|
46
|
+
----------
|
47
|
+
coord : tuple of 3 ints
|
48
|
+
The coordinate to transform.
|
49
|
+
|
50
|
+
Returns
|
51
|
+
-------
|
52
|
+
str
|
53
|
+
The colon-delimited coordinate string.
|
54
|
+
"""
|
55
|
+
return ":".join([str(x) for x in coord])
|
pylunar/lunar_feature.py
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
# This file is part of pylunar.
|
2
|
+
#
|
3
|
+
# Developed by Michael Reuter.
|
4
|
+
#
|
5
|
+
# See the LICENSE file at the top-level directory of this distribution
|
6
|
+
# for details of code ownership.
|
7
|
+
#
|
8
|
+
# Use of this source code is governed by a 3-clause BSD-style
|
9
|
+
# license that can be found in the LICENSE file.
|
10
|
+
|
11
|
+
"""Module for the LunarFeature class."""
|
12
|
+
|
13
|
+
from __future__ import annotations
|
14
|
+
|
15
|
+
__all__ = ["LunarFeature"]
|
16
|
+
|
17
|
+
import math
|
18
|
+
import os
|
19
|
+
|
20
|
+
from .types import FeatureRow, Range
|
21
|
+
|
22
|
+
|
23
|
+
class LunarFeature:
|
24
|
+
"""Class keeping all the information for a given Lunar feature.
|
25
|
+
|
26
|
+
Parameters
|
27
|
+
----------
|
28
|
+
name : str
|
29
|
+
The name of the Lunar feature (no unicode).
|
30
|
+
diameter : float
|
31
|
+
The diameter (km) of the Lunar feature.
|
32
|
+
latitude : float
|
33
|
+
The latitude (degrees) of the Lunar feature. Negative is South,
|
34
|
+
positive is North.
|
35
|
+
longitude : float
|
36
|
+
The longitude (degrees) of the Lunar feature. Negative is West,
|
37
|
+
positive is East.
|
38
|
+
delta_latitude : float
|
39
|
+
The size (degrees) in latitude of the Lunar feature.
|
40
|
+
delta_longitude : float
|
41
|
+
The size (degrees) in longitude of the Lunar feature.
|
42
|
+
feature_type : str
|
43
|
+
The classification of the Lunar feature: i.e. Crater, Mons.
|
44
|
+
quad_name : str
|
45
|
+
Name of lunar quadrant containing feature's center point as
|
46
|
+
determined by the International Astronomical Union (IAU) Working
|
47
|
+
Group for Planetary System Nomenclature (WGPSN).
|
48
|
+
quad_code : str
|
49
|
+
Specific lunar quadrant containing feature's center point as
|
50
|
+
determined by the IAU WGPSN.
|
51
|
+
code_name : str
|
52
|
+
The AstroLeague club name for the Lunar feature. Can be: Lunar,
|
53
|
+
LunarII or Both.
|
54
|
+
lunar_club_type : str or None
|
55
|
+
The Lunar Club classification of the feature: Naked Eye, Binocular,
|
56
|
+
Telescope. For a LunarII only feature this is None.
|
57
|
+
"""
|
58
|
+
|
59
|
+
def __init__(
|
60
|
+
self,
|
61
|
+
name: str,
|
62
|
+
diameter: float,
|
63
|
+
latitude: float,
|
64
|
+
longitude: float,
|
65
|
+
delta_latitude: float,
|
66
|
+
delta_longitude: float,
|
67
|
+
feature_type: str,
|
68
|
+
quad_name: str,
|
69
|
+
quad_code: str,
|
70
|
+
code_name: str,
|
71
|
+
lunar_club_type: str | None,
|
72
|
+
):
|
73
|
+
self.name = name
|
74
|
+
self.diameter = diameter
|
75
|
+
self.latitude = latitude
|
76
|
+
self.longitude = longitude
|
77
|
+
self.delta_latitude = delta_latitude
|
78
|
+
self.delta_longitude = delta_longitude
|
79
|
+
self.feature_type = feature_type
|
80
|
+
self.quad_name = quad_name
|
81
|
+
self.quad_code = quad_code
|
82
|
+
self.code_name = code_name
|
83
|
+
self.lunar_club_type = str(lunar_club_type)
|
84
|
+
|
85
|
+
def __str__(self) -> str:
|
86
|
+
"""Class string representation.
|
87
|
+
|
88
|
+
Returns
|
89
|
+
-------
|
90
|
+
str
|
91
|
+
The string representation.
|
92
|
+
"""
|
93
|
+
result = []
|
94
|
+
result.append(f"Name = {self.name}")
|
95
|
+
result.append(f"Lat/Long = ({self.latitude:.2f}, {self.longitude:.2f})")
|
96
|
+
result.append(f"Type = {self.feature_type}")
|
97
|
+
result.append(f"Delta Lat/Long = ({self.delta_latitude:.2f}, {self.delta_longitude:.2f})")
|
98
|
+
return os.linesep.join(result)
|
99
|
+
|
100
|
+
@classmethod
|
101
|
+
def from_row(cls: type[LunarFeature], row: FeatureRow) -> LunarFeature:
|
102
|
+
"""Initialize from a database row.
|
103
|
+
|
104
|
+
Parameters
|
105
|
+
----------
|
106
|
+
row : list
|
107
|
+
The database row containing the information.
|
108
|
+
|
109
|
+
Returns
|
110
|
+
-------
|
111
|
+
:class:`.LunarFeature`
|
112
|
+
Class initialized from database row.
|
113
|
+
"""
|
114
|
+
return cls(*row[1:])
|
115
|
+
|
116
|
+
def feature_angle(self) -> float:
|
117
|
+
"""Get the angle of the feature on the lunar face relative to North.
|
118
|
+
|
119
|
+
The feature angle is determined by calculating atan2(lon, lat) and
|
120
|
+
then adding 360 degrees if the result is less than zero. This makes
|
121
|
+
North zero degrees, East 90 degrees, South 180 degrees and West 270
|
122
|
+
degrees.
|
123
|
+
|
124
|
+
Returns
|
125
|
+
-------
|
126
|
+
float
|
127
|
+
The feature angle in degrees.
|
128
|
+
"""
|
129
|
+
lat_rad = math.radians(self.latitude)
|
130
|
+
lon_rad = math.radians(self.longitude)
|
131
|
+
fa = math.degrees(math.atan2(lon_rad, lat_rad))
|
132
|
+
fa += 360.0 if fa < 0 else 0.0
|
133
|
+
return fa
|
134
|
+
|
135
|
+
def latitude_range(self) -> Range:
|
136
|
+
"""Get the latitude range of the feature.
|
137
|
+
|
138
|
+
Returns
|
139
|
+
-------
|
140
|
+
tuple(float, float)
|
141
|
+
The (minimum, maximum) latitude values for the feature.
|
142
|
+
"""
|
143
|
+
min_lat = self.latitude - (self.delta_latitude / 2.0)
|
144
|
+
max_lat = self.latitude + (self.delta_latitude / 2.0)
|
145
|
+
return (min_lat, max_lat)
|
146
|
+
|
147
|
+
def list_from_feature(self) -> list[object]:
|
148
|
+
"""Convert the feature information into a list.
|
149
|
+
|
150
|
+
Returns
|
151
|
+
-------
|
152
|
+
list
|
153
|
+
The list of lunar features.
|
154
|
+
"""
|
155
|
+
return [
|
156
|
+
self.name,
|
157
|
+
self.diameter,
|
158
|
+
self.latitude,
|
159
|
+
self.longitude,
|
160
|
+
self.delta_latitude,
|
161
|
+
self.delta_longitude,
|
162
|
+
self.feature_type,
|
163
|
+
self.quad_name,
|
164
|
+
self.quad_code,
|
165
|
+
self.code_name,
|
166
|
+
self.lunar_club_type,
|
167
|
+
]
|
168
|
+
|
169
|
+
def longitude_range(self) -> Range:
|
170
|
+
"""Get the longitude range of the feature.
|
171
|
+
|
172
|
+
Returns
|
173
|
+
-------
|
174
|
+
tuple(float, float)
|
175
|
+
The (minimum, maximum) longitude values for the feature.
|
176
|
+
"""
|
177
|
+
min_lon = self.longitude - (self.delta_longitude / 2.0)
|
178
|
+
max_lon = self.longitude + (self.delta_longitude / 2.0)
|
179
|
+
return (min_lon, max_lon)
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# This file is part of pylunar.
|
2
|
+
#
|
3
|
+
# Developed by Michael Reuter.
|
4
|
+
#
|
5
|
+
# See the LICENSE file at the top-level directory of this distribution
|
6
|
+
# for details of code ownership.
|
7
|
+
#
|
8
|
+
# Use of this source code is governed by a 3-clause BSD-style
|
9
|
+
# license that can be found in the LICENSE file.
|
10
|
+
|
11
|
+
"""Module for the LunarFeatureContainer class."""
|
12
|
+
|
13
|
+
from __future__ import annotations
|
14
|
+
|
15
|
+
__all__ = ["LunarFeatureContainer"]
|
16
|
+
|
17
|
+
import collections
|
18
|
+
import sys
|
19
|
+
from typing import Generator
|
20
|
+
|
21
|
+
if sys.version_info >= (3, 10):
|
22
|
+
from importlib.resources import files
|
23
|
+
else:
|
24
|
+
from importlib_resources import files
|
25
|
+
|
26
|
+
import sqlite3
|
27
|
+
|
28
|
+
from .lunar_feature import LunarFeature
|
29
|
+
from .moon_info import MoonInfo
|
30
|
+
|
31
|
+
|
32
|
+
class LunarFeatureContainer:
|
33
|
+
"""Collection of Lunar features available from the database.
|
34
|
+
|
35
|
+
Parameters
|
36
|
+
----------
|
37
|
+
club_name : str
|
38
|
+
The name of the observing club to sort on. Values are Lunar and
|
39
|
+
LunarII.
|
40
|
+
"""
|
41
|
+
|
42
|
+
def __init__(self, club_name: str):
|
43
|
+
dbname = str(files("pylunar.data").joinpath("lunar.db"))
|
44
|
+
self.conn = sqlite3.connect(dbname)
|
45
|
+
self.club_name = club_name
|
46
|
+
self.features: dict[int, LunarFeature] = collections.OrderedDict()
|
47
|
+
self.club_type: set[str] = set()
|
48
|
+
self.feature_type: set[str] = set()
|
49
|
+
|
50
|
+
def __iter__(self) -> Generator[LunarFeature, None, None]:
|
51
|
+
"""Create iterator for container.
|
52
|
+
|
53
|
+
Yields
|
54
|
+
------
|
55
|
+
:class:`.LunarFeature`
|
56
|
+
The current lunar feature.
|
57
|
+
"""
|
58
|
+
yield from self.features.values()
|
59
|
+
|
60
|
+
def __len__(self) -> int:
|
61
|
+
"""Length of the container.
|
62
|
+
|
63
|
+
Returns
|
64
|
+
-------
|
65
|
+
int
|
66
|
+
The length of the container.
|
67
|
+
"""
|
68
|
+
return len(self.features)
|
69
|
+
|
70
|
+
def load(self, moon_info: MoonInfo | None = None, limit: int | None = None) -> None:
|
71
|
+
"""Read the Lunar features from the database.
|
72
|
+
|
73
|
+
Parameters
|
74
|
+
----------
|
75
|
+
moon_info : :class:`.MoonInfo`, optional
|
76
|
+
Instance of the Lunar information class.
|
77
|
+
limit : int, optional
|
78
|
+
Restrict the number of features read to the given value.
|
79
|
+
"""
|
80
|
+
if len(self.features) != 0:
|
81
|
+
self.features = collections.OrderedDict()
|
82
|
+
|
83
|
+
cur = self.conn.cursor()
|
84
|
+
sql = f'select * from Features where Lunar_Code = "{self.club_name}" or ' 'Lunar_Code = "Both"'
|
85
|
+
if limit is not None:
|
86
|
+
sql += f" limit {limit}"
|
87
|
+
cur.execute(sql)
|
88
|
+
|
89
|
+
for row in cur:
|
90
|
+
feature = LunarFeature.from_row(row)
|
91
|
+
is_visible = True if moon_info is None else moon_info.is_visible(feature)
|
92
|
+
if is_visible:
|
93
|
+
self.features[id(feature)] = feature
|
94
|
+
self.club_type.add(row[11])
|
95
|
+
self.feature_type.add(row[7])
|
96
|
+
|
97
|
+
cur.close()
|
pylunar/moon_info.py
ADDED
@@ -0,0 +1,656 @@
|
|
1
|
+
# This file is part of pylunar.
|
2
|
+
#
|
3
|
+
# Developed by Michael Reuter.
|
4
|
+
#
|
5
|
+
# See the LICENSE file at the top-level directory of this distribution
|
6
|
+
# for details of code ownership.
|
7
|
+
#
|
8
|
+
# Use of this source code is governed by a 3-clause BSD-style
|
9
|
+
# license that can be found in the LICENSE file.
|
10
|
+
|
11
|
+
"""Module for the MoonInfo class."""
|
12
|
+
|
13
|
+
from __future__ import annotations
|
14
|
+
|
15
|
+
__all__ = ["MoonInfo"]
|
16
|
+
|
17
|
+
from datetime import datetime
|
18
|
+
from enum import Enum
|
19
|
+
import math
|
20
|
+
from operator import itemgetter
|
21
|
+
|
22
|
+
import ephem
|
23
|
+
import pytz
|
24
|
+
|
25
|
+
from .helpers import mjd_to_date_tuple, tuple_to_string
|
26
|
+
from .lunar_feature import LunarFeature
|
27
|
+
from .types import DateTimeTuple, DmsCoordinate, MoonPhases
|
28
|
+
|
29
|
+
|
30
|
+
class PhaseName(Enum):
|
31
|
+
"""Phase names for the lunar cycle."""
|
32
|
+
|
33
|
+
NEW_MOON = 0
|
34
|
+
WAXING_CRESCENT = 1
|
35
|
+
FIRST_QUARTER = 2
|
36
|
+
WAXING_GIBBOUS = 3
|
37
|
+
FULL_MOON = 4
|
38
|
+
WANING_GIBBOUS = 5
|
39
|
+
LAST_QUARTER = 6
|
40
|
+
WANING_CRESCENT = 7
|
41
|
+
|
42
|
+
|
43
|
+
class TimeOfDay(Enum):
|
44
|
+
"""Time of day from the lunar terminator."""
|
45
|
+
|
46
|
+
MORNING = 0
|
47
|
+
EVENING = 1
|
48
|
+
|
49
|
+
|
50
|
+
class MoonInfo:
|
51
|
+
"""Handle all moon information.
|
52
|
+
|
53
|
+
Attributes
|
54
|
+
----------
|
55
|
+
observer : ephem.Observer instance.
|
56
|
+
The instance containing the observer's location information.
|
57
|
+
moon : ephem.Moon instance
|
58
|
+
The instance of the moon object.
|
59
|
+
|
60
|
+
Parameters
|
61
|
+
----------
|
62
|
+
latitude : tuple of 3 ints
|
63
|
+
The latitude of the observer in GPS DMS(Degrees, Minutes and
|
64
|
+
Seconds) format.
|
65
|
+
longitude : tuple of 3 ints
|
66
|
+
The longitude of the observer in GPS DMS(Degrees, Minutes and
|
67
|
+
Seconds) format.
|
68
|
+
name : str, optional
|
69
|
+
A name for the observer's location.
|
70
|
+
"""
|
71
|
+
|
72
|
+
DAYS_TO_HOURS = 24.0
|
73
|
+
MAIN_PHASE_CUTOFF = 2.0
|
74
|
+
# Time cutoff (hours) around the NM, FQ, FM, and LQ phases
|
75
|
+
FEATURE_CUTOFF = 15.0
|
76
|
+
# The offset (degrees) from the colongitude used for visibility check
|
77
|
+
NO_CUTOFF_TYPE = ("Landing Site", "Mare", "Oceanus")
|
78
|
+
# Feature types that are not subject to longitude cutoffs
|
79
|
+
LIBRATION_ZONE = 80.0
|
80
|
+
# Latitude and/or longitude where librations have a big effect
|
81
|
+
MAXIMUM_LIBRATION_PHASE_ANGLE_CUTOFF = 65.0
|
82
|
+
# The maximum value of the libration phase angle difference for a feature
|
83
|
+
|
84
|
+
reverse_phase_lookup = {
|
85
|
+
"new_moon": (ephem.previous_last_quarter_moon, "last_quarter"),
|
86
|
+
"first_quarter": (ephem.previous_new_moon, "new_moon"),
|
87
|
+
"full_moon": (ephem.previous_first_quarter_moon, "first_quarter"),
|
88
|
+
"last_quarter": (ephem.previous_full_moon, "full_moon"),
|
89
|
+
}
|
90
|
+
|
91
|
+
def __init__(self, latitude: DmsCoordinate, longitude: DmsCoordinate, name: str | None = None):
|
92
|
+
self.observer = ephem.Observer()
|
93
|
+
self.observer.lat = tuple_to_string(latitude)
|
94
|
+
self.observer.long = tuple_to_string(longitude)
|
95
|
+
self.moon = ephem.Moon()
|
96
|
+
|
97
|
+
def age(self) -> float:
|
98
|
+
"""Lunar age in days.
|
99
|
+
|
100
|
+
Returns
|
101
|
+
-------
|
102
|
+
float
|
103
|
+
The lunar age.
|
104
|
+
"""
|
105
|
+
prev_new = ephem.previous_new_moon(self.observer.date)
|
106
|
+
return float(self.observer.date - prev_new)
|
107
|
+
|
108
|
+
def fractional_age(self) -> float:
|
109
|
+
"""Lunar fractional age which is always less than 1.0.
|
110
|
+
|
111
|
+
Returns
|
112
|
+
-------
|
113
|
+
float
|
114
|
+
The fractional lunar age.
|
115
|
+
"""
|
116
|
+
prev_new = ephem.previous_new_moon(self.observer.date)
|
117
|
+
next_new = ephem.next_new_moon(self.observer.date)
|
118
|
+
return float((self.observer.date - prev_new) / (next_new - prev_new))
|
119
|
+
|
120
|
+
def altitude(self) -> float:
|
121
|
+
"""Lunar altitude in degrees.
|
122
|
+
|
123
|
+
Returns
|
124
|
+
-------
|
125
|
+
float
|
126
|
+
The lunar altitiude.
|
127
|
+
"""
|
128
|
+
return math.degrees(self.moon.alt)
|
129
|
+
|
130
|
+
def angular_size(self) -> float:
|
131
|
+
"""Lunar current angular size in degrees.
|
132
|
+
|
133
|
+
Returns
|
134
|
+
-------
|
135
|
+
float
|
136
|
+
The lunar angular size.
|
137
|
+
"""
|
138
|
+
moon_size: float = self.moon.size
|
139
|
+
return moon_size / 3600.0
|
140
|
+
|
141
|
+
def azimuth(self) -> float:
|
142
|
+
"""Lunar azimuth in degrees.
|
143
|
+
|
144
|
+
Returns
|
145
|
+
-------
|
146
|
+
float
|
147
|
+
The lunar azimuth.
|
148
|
+
"""
|
149
|
+
return math.degrees(self.moon.az)
|
150
|
+
|
151
|
+
def colong(self) -> float:
|
152
|
+
"""Lunar selenographic colongitude in degrees.
|
153
|
+
|
154
|
+
Returns
|
155
|
+
-------
|
156
|
+
float
|
157
|
+
The lunar seleographic colongitude.
|
158
|
+
"""
|
159
|
+
return math.degrees(self.moon.colong)
|
160
|
+
|
161
|
+
def dec(self) -> float:
|
162
|
+
"""Lunar current declination in degrees.
|
163
|
+
|
164
|
+
Returns
|
165
|
+
-------
|
166
|
+
float
|
167
|
+
The lunar declination.
|
168
|
+
"""
|
169
|
+
return math.degrees(self.moon.dec)
|
170
|
+
|
171
|
+
def earth_distance(self) -> float:
|
172
|
+
"""Lunar current distance from the earth in km.
|
173
|
+
|
174
|
+
Returns
|
175
|
+
-------
|
176
|
+
float
|
177
|
+
THe earth-moon distance.
|
178
|
+
"""
|
179
|
+
return float(self.moon.earth_distance * ephem.meters_per_au / 1000.0)
|
180
|
+
|
181
|
+
def elongation(self) -> float:
|
182
|
+
"""Lunar elongation from the sun in degrees.
|
183
|
+
|
184
|
+
Returns
|
185
|
+
-------
|
186
|
+
float
|
187
|
+
The lunar solar elongation.
|
188
|
+
"""
|
189
|
+
elongation = math.degrees(self.moon.elong)
|
190
|
+
if elongation < 0:
|
191
|
+
elongation += 360.0
|
192
|
+
return elongation
|
193
|
+
|
194
|
+
def fractional_phase(self) -> float:
|
195
|
+
"""Lunar fractional illumination which is always less than 1.0.
|
196
|
+
|
197
|
+
Returns
|
198
|
+
-------
|
199
|
+
float
|
200
|
+
The lunar fractional phase.
|
201
|
+
"""
|
202
|
+
return float(self.moon.moon_phase)
|
203
|
+
|
204
|
+
def libration_lat(self) -> float:
|
205
|
+
"""Lunar current latitudinal libration in degrees.
|
206
|
+
|
207
|
+
Returns
|
208
|
+
-------
|
209
|
+
float
|
210
|
+
The lunar libration latitude.
|
211
|
+
"""
|
212
|
+
return math.degrees(self.moon.libration_lat)
|
213
|
+
|
214
|
+
def libration_lon(self) -> float:
|
215
|
+
"""Lunar current longitudinal libration in degrees.
|
216
|
+
|
217
|
+
Returns
|
218
|
+
-------
|
219
|
+
float
|
220
|
+
The lunar libration longitude.
|
221
|
+
"""
|
222
|
+
return math.degrees(self.moon.libration_long)
|
223
|
+
|
224
|
+
def libration_phase_angle(self) -> float:
|
225
|
+
"""Phase angle of lunar current libration in degrees.
|
226
|
+
|
227
|
+
Returns
|
228
|
+
-------
|
229
|
+
float
|
230
|
+
The lunar libration phase angle.
|
231
|
+
"""
|
232
|
+
phase_angle = math.atan2(self.moon.libration_long, self.moon.libration_lat)
|
233
|
+
phase_angle += 2.0 * math.pi if phase_angle < 0 else 0.0
|
234
|
+
return math.degrees(phase_angle)
|
235
|
+
|
236
|
+
def magnitude(self) -> float:
|
237
|
+
"""Lunar current magnitude.
|
238
|
+
|
239
|
+
Returns
|
240
|
+
-------
|
241
|
+
float
|
242
|
+
The lunar magnitude.
|
243
|
+
"""
|
244
|
+
return float(self.moon.mag)
|
245
|
+
|
246
|
+
def colong_to_long(self) -> float:
|
247
|
+
"""Selenographic longitude in degrees based on the terminator.
|
248
|
+
|
249
|
+
Returns
|
250
|
+
-------
|
251
|
+
float
|
252
|
+
The lunar seleographic longitude.
|
253
|
+
"""
|
254
|
+
colong: float = self.colong()
|
255
|
+
if 90.0 <= colong < 270.0:
|
256
|
+
longitude = 180.0 - colong
|
257
|
+
elif 270.0 <= colong < 360.0:
|
258
|
+
longitude = 360.0 - colong
|
259
|
+
else:
|
260
|
+
longitude = -colong
|
261
|
+
|
262
|
+
return longitude
|
263
|
+
|
264
|
+
def is_libration_ok(self, feature: LunarFeature) -> bool:
|
265
|
+
"""Determine if lunar feature is visible due to libration effect.
|
266
|
+
|
267
|
+
Parameters
|
268
|
+
----------
|
269
|
+
feature : :class:`.LunarFeature`
|
270
|
+
The lunar feature instance to check.
|
271
|
+
|
272
|
+
Returns
|
273
|
+
-------
|
274
|
+
bool
|
275
|
+
True if visible, False if not.
|
276
|
+
"""
|
277
|
+
is_lon_in_zone = math.fabs(feature.longitude) > self.LIBRATION_ZONE
|
278
|
+
is_lat_in_zone = math.fabs(feature.latitude) > self.LIBRATION_ZONE
|
279
|
+
if is_lat_in_zone or is_lon_in_zone:
|
280
|
+
feature_angle = feature.feature_angle()
|
281
|
+
libration_phase_angle = self.libration_phase_angle()
|
282
|
+
delta_phase_angle = libration_phase_angle - feature_angle
|
283
|
+
delta_phase_angle -= 360.0 if delta_phase_angle > 180.0 else 0.0
|
284
|
+
|
285
|
+
return math.fabs(delta_phase_angle) <= self.MAXIMUM_LIBRATION_PHASE_ANGLE_CUTOFF
|
286
|
+
|
287
|
+
return True
|
288
|
+
|
289
|
+
def is_visible(self, feature: LunarFeature) -> bool:
|
290
|
+
"""Determine if lunar feature is visible.
|
291
|
+
|
292
|
+
Parameters
|
293
|
+
----------
|
294
|
+
feature : :class:`.LunarFeature`
|
295
|
+
The lunar feature instance to check.
|
296
|
+
|
297
|
+
Returns
|
298
|
+
-------
|
299
|
+
bool
|
300
|
+
True if visible, False if not.
|
301
|
+
"""
|
302
|
+
selco_lon = self.colong_to_long()
|
303
|
+
current_tod = self.time_of_day()
|
304
|
+
|
305
|
+
min_lon = feature.longitude - feature.delta_longitude / 2
|
306
|
+
max_lon = feature.longitude + feature.delta_longitude / 2
|
307
|
+
|
308
|
+
if min_lon > max_lon:
|
309
|
+
min_lon, max_lon = max_lon, min_lon
|
310
|
+
|
311
|
+
is_visible = False
|
312
|
+
latitude_scaling = math.cos(math.radians(feature.latitude))
|
313
|
+
if feature.feature_type not in MoonInfo.NO_CUTOFF_TYPE:
|
314
|
+
cutoff = MoonInfo.FEATURE_CUTOFF / latitude_scaling
|
315
|
+
else:
|
316
|
+
cutoff = MoonInfo.FEATURE_CUTOFF
|
317
|
+
|
318
|
+
if current_tod == TimeOfDay.MORNING.name:
|
319
|
+
# Minimum longitude for morning visibility
|
320
|
+
lon_cutoff = min_lon - cutoff
|
321
|
+
if feature.feature_type in MoonInfo.NO_CUTOFF_TYPE:
|
322
|
+
is_visible = selco_lon <= min_lon
|
323
|
+
else:
|
324
|
+
is_visible = lon_cutoff <= selco_lon <= min_lon
|
325
|
+
else:
|
326
|
+
# Maximum longitude for evening visibility
|
327
|
+
lon_cutoff = max_lon + cutoff
|
328
|
+
if feature.feature_type in MoonInfo.NO_CUTOFF_TYPE:
|
329
|
+
is_visible = max_lon <= selco_lon
|
330
|
+
else:
|
331
|
+
is_visible = max_lon <= selco_lon <= lon_cutoff
|
332
|
+
|
333
|
+
return is_visible and self.is_libration_ok(feature)
|
334
|
+
|
335
|
+
def next_four_phases(self) -> MoonPhases:
|
336
|
+
"""Next four phases in date sorted order (closest phase first).
|
337
|
+
|
338
|
+
Returns
|
339
|
+
-------
|
340
|
+
list[(str, float)]
|
341
|
+
Set of lunar phases specified by an abbreviated phase name and
|
342
|
+
Modified Julian Date.
|
343
|
+
"""
|
344
|
+
phases = {}
|
345
|
+
phases["new_moon"] = ephem.next_new_moon(self.observer.date)
|
346
|
+
phases["first_quarter"] = ephem.next_first_quarter_moon(self.observer.date)
|
347
|
+
phases["full_moon"] = ephem.next_full_moon(self.observer.date)
|
348
|
+
phases["last_quarter"] = ephem.next_last_quarter_moon(self.observer.date)
|
349
|
+
|
350
|
+
sorted_phases = sorted(phases.items(), key=itemgetter(1))
|
351
|
+
sorted_phases = [(phase[0], mjd_to_date_tuple(phase[1])) for phase in sorted_phases]
|
352
|
+
|
353
|
+
return sorted_phases
|
354
|
+
|
355
|
+
def phase_name(self) -> str:
|
356
|
+
"""Return standard name of lunar phase, i.e. Waxing Cresent.
|
357
|
+
|
358
|
+
This function returns a standard name for lunar phase based on the
|
359
|
+
current selenographic colongitude.
|
360
|
+
|
361
|
+
Returns
|
362
|
+
-------
|
363
|
+
str
|
364
|
+
The lunar phase name.
|
365
|
+
"""
|
366
|
+
next_phase_name = self.next_four_phases()[0][0]
|
367
|
+
try:
|
368
|
+
next_phase_time = getattr(ephem, f"next_{next_phase_name}")(self.observer.date)
|
369
|
+
except AttributeError:
|
370
|
+
next_phase_time = getattr(ephem, f"next_{next_phase_name}_moon")(self.observer.date)
|
371
|
+
previous_phase = self.reverse_phase_lookup[next_phase_name]
|
372
|
+
time_to_next_phase = math.fabs(next_phase_time - self.observer.date) * self.DAYS_TO_HOURS
|
373
|
+
time_to_previous_phase = (
|
374
|
+
math.fabs(self.observer.date - previous_phase[0](self.observer.date)) * self.DAYS_TO_HOURS
|
375
|
+
)
|
376
|
+
previous_phase_name = previous_phase[1]
|
377
|
+
|
378
|
+
phase_name = ""
|
379
|
+
if time_to_previous_phase < self.MAIN_PHASE_CUTOFF:
|
380
|
+
phase_name = getattr(PhaseName, previous_phase_name.upper()).name
|
381
|
+
elif time_to_next_phase < self.MAIN_PHASE_CUTOFF:
|
382
|
+
phase_name = getattr(PhaseName, next_phase_name.upper()).name
|
383
|
+
else:
|
384
|
+
if previous_phase_name == "new_moon" and next_phase_name == "first_quarter":
|
385
|
+
phase_name = PhaseName.WAXING_CRESCENT.name
|
386
|
+
elif previous_phase_name == "first_quarter" and next_phase_name == "full_moon":
|
387
|
+
phase_name = PhaseName.WAXING_GIBBOUS.name
|
388
|
+
elif previous_phase_name == "full_moon" and next_phase_name == "last_quarter":
|
389
|
+
phase_name = PhaseName.WANING_GIBBOUS.name
|
390
|
+
elif previous_phase_name == "last_quarter" and next_phase_name == "new_moon":
|
391
|
+
phase_name = PhaseName.WANING_CRESCENT.name
|
392
|
+
return phase_name
|
393
|
+
|
394
|
+
def phase_shape_in_ascii(self) -> str:
|
395
|
+
"""Display lunar phase shape in ASCII art.
|
396
|
+
|
397
|
+
This function returns a multi-line string demonstrate current lunar
|
398
|
+
shape in ASCII format.
|
399
|
+
|
400
|
+
Returns
|
401
|
+
-------
|
402
|
+
str
|
403
|
+
The lunar phase shape.
|
404
|
+
"""
|
405
|
+
phase = self.phase_name()
|
406
|
+
|
407
|
+
if phase == PhaseName.NEW_MOON.name:
|
408
|
+
return """ _..._
|
409
|
+
.:::::::.
|
410
|
+
:::::::::::
|
411
|
+
:::::::::::
|
412
|
+
`:::::::::'
|
413
|
+
`':::'' """
|
414
|
+
elif phase == PhaseName.WAXING_CRESCENT.name:
|
415
|
+
return """ _..._
|
416
|
+
.::::. `.
|
417
|
+
:::::::. :
|
418
|
+
:::::::: :
|
419
|
+
`::::::' .'
|
420
|
+
`'::'-' """
|
421
|
+
elif phase == PhaseName.FIRST_QUARTER.name:
|
422
|
+
return """ _..._
|
423
|
+
.:::: `.
|
424
|
+
:::::: :
|
425
|
+
:::::: :
|
426
|
+
`::::: .'
|
427
|
+
`'::.-' """
|
428
|
+
elif phase == PhaseName.WAXING_GIBBOUS.name:
|
429
|
+
return """ _..._
|
430
|
+
.::' `.
|
431
|
+
::: :
|
432
|
+
::: :
|
433
|
+
`::. .'
|
434
|
+
`':..-' """
|
435
|
+
elif phase == PhaseName.FULL_MOON.name:
|
436
|
+
return """ _..._
|
437
|
+
.' `.
|
438
|
+
: :
|
439
|
+
: :
|
440
|
+
`. .'
|
441
|
+
`-...-' """
|
442
|
+
elif phase == PhaseName.WANING_GIBBOUS.name:
|
443
|
+
return """ _..._
|
444
|
+
.' `::.
|
445
|
+
: :::
|
446
|
+
: :::
|
447
|
+
`. .::'
|
448
|
+
`-..:'' """
|
449
|
+
elif phase == PhaseName.LAST_QUARTER.name:
|
450
|
+
return """ _..._
|
451
|
+
.' ::::.
|
452
|
+
: ::::::
|
453
|
+
: ::::::
|
454
|
+
`. :::::'
|
455
|
+
`-.::'' """
|
456
|
+
elif phase == PhaseName.WAXING_CRESCENT.name:
|
457
|
+
return """ _..._
|
458
|
+
.' .::::.
|
459
|
+
: ::::::::
|
460
|
+
: ::::::::
|
461
|
+
`. '::::::'
|
462
|
+
`-.::'' """
|
463
|
+
else:
|
464
|
+
return phase
|
465
|
+
|
466
|
+
def phase_emoji(self) -> str:
|
467
|
+
"""Return standard emoji of lunar phase, i.e. '🌒'.
|
468
|
+
|
469
|
+
This function returns a standard emoji for lunar phase based on the
|
470
|
+
current selenographic colongitude.
|
471
|
+
|
472
|
+
Returns
|
473
|
+
-------
|
474
|
+
str
|
475
|
+
The lunar phase emoji.
|
476
|
+
"""
|
477
|
+
return {
|
478
|
+
"NEW_MOON": "🌑",
|
479
|
+
"WAXING_CRESCENT": "🌒",
|
480
|
+
"FIRST_QUARTER": "🌓",
|
481
|
+
"WAXING_GIBBOUS": "🌔",
|
482
|
+
"FULL_MOON": "🌕",
|
483
|
+
"WANING_GIBBOUS": "🌖",
|
484
|
+
"LAST_QUARTER": "🌗",
|
485
|
+
"WANING_CRESCENT": "🌘",
|
486
|
+
}[self.phase_name()]
|
487
|
+
|
488
|
+
def ra(self) -> float:
|
489
|
+
"""Lunar current right ascension in degrees.
|
490
|
+
|
491
|
+
Returns
|
492
|
+
-------
|
493
|
+
float
|
494
|
+
The lunar right ascension.
|
495
|
+
"""
|
496
|
+
return math.degrees(self.moon.ra)
|
497
|
+
|
498
|
+
def rise_set_times(self, timezone: str) -> MoonPhases:
|
499
|
+
"""Calculate the rise, set and transit times in the local time system.
|
500
|
+
|
501
|
+
Parameters
|
502
|
+
----------
|
503
|
+
timezone : str
|
504
|
+
The timezone identifier for the calculations.
|
505
|
+
|
506
|
+
Returns
|
507
|
+
-------
|
508
|
+
list[(str, tuple)]
|
509
|
+
Set of rise, set, and transit times in the local time system. If
|
510
|
+
event does not happen, 'Does not xxx' is tuple value.
|
511
|
+
"""
|
512
|
+
utc = pytz.utc
|
513
|
+
try:
|
514
|
+
tz = pytz.timezone(timezone)
|
515
|
+
except pytz.UnknownTimeZoneError:
|
516
|
+
tz = utc
|
517
|
+
|
518
|
+
func_map = {"rise": "rising", "transit": "transit", "set": "setting"}
|
519
|
+
|
520
|
+
# Need to set observer's horizon and pressure to get times
|
521
|
+
old_pressure = self.observer.pressure
|
522
|
+
old_horizon = self.observer.horizon
|
523
|
+
|
524
|
+
self.observer.pressure = 0
|
525
|
+
self.observer.horizon = "-0:34"
|
526
|
+
|
527
|
+
current_date_utc = datetime(*mjd_to_date_tuple(self.observer.date, round_off=True), tzinfo=utc) # type: ignore
|
528
|
+
current_date = current_date_utc.astimezone(tz)
|
529
|
+
current_day = current_date.day
|
530
|
+
times = {}
|
531
|
+
does_not = None
|
532
|
+
for time_type in ("rise", "transit", "set"):
|
533
|
+
mjd_time = getattr(self.observer, "{}_{}".format("next", func_map[time_type]))(self.moon)
|
534
|
+
utc_time = datetime(*mjd_to_date_tuple(mjd_time, round_off=True), tzinfo=utc) # type: ignore
|
535
|
+
local_date = utc_time.astimezone(tz)
|
536
|
+
if local_date.day == current_day:
|
537
|
+
times[time_type] = local_date
|
538
|
+
else:
|
539
|
+
mjd_time = getattr(self.observer, "{}_{}".format("previous", func_map[time_type]))(self.moon)
|
540
|
+
utc_time = datetime(*mjd_to_date_tuple(mjd_time, round_off=True), tzinfo=utc) # type: ignore
|
541
|
+
local_date = utc_time.astimezone(tz)
|
542
|
+
if local_date.day == current_day:
|
543
|
+
times[time_type] = local_date
|
544
|
+
else:
|
545
|
+
does_not = (time_type, f"Does not {time_type}")
|
546
|
+
|
547
|
+
# Return observer and moon to previous state
|
548
|
+
self.observer.pressure = old_pressure
|
549
|
+
self.observer.horizon = old_horizon
|
550
|
+
self.moon.compute(self.observer)
|
551
|
+
|
552
|
+
original_sorted_times = sorted(times.items(), key=itemgetter(1))
|
553
|
+
sorted_times: MoonPhases = [(xtime[0], xtime[1].timetuple()[:6]) for xtime in original_sorted_times]
|
554
|
+
if does_not is not None:
|
555
|
+
sorted_times.insert(0, does_not)
|
556
|
+
|
557
|
+
return sorted_times
|
558
|
+
|
559
|
+
def subsolar_lat(self) -> float:
|
560
|
+
"""Latitude in degress on the moon where the sun is overhead.
|
561
|
+
|
562
|
+
Returns
|
563
|
+
-------
|
564
|
+
float
|
565
|
+
The lunar subsolar latitude.
|
566
|
+
"""
|
567
|
+
return math.degrees(self.moon.subsolar_lat)
|
568
|
+
|
569
|
+
def time_of_day(self) -> str:
|
570
|
+
"""Terminator time of day.
|
571
|
+
|
572
|
+
This function determines if the terminator is sunrise (morning) or
|
573
|
+
sunset (evening).
|
574
|
+
|
575
|
+
Returns
|
576
|
+
-------
|
577
|
+
str
|
578
|
+
The lunar time of day.
|
579
|
+
"""
|
580
|
+
colong = self.colong()
|
581
|
+
if 90.0 <= colong < 270.0:
|
582
|
+
return TimeOfDay.EVENING.name
|
583
|
+
else:
|
584
|
+
return TimeOfDay.MORNING.name
|
585
|
+
|
586
|
+
def time_from_new_moon(self) -> float:
|
587
|
+
"""Time (hours) from the previous new moon.
|
588
|
+
|
589
|
+
This function calculates the time from the previous new moon.
|
590
|
+
|
591
|
+
Returns
|
592
|
+
-------
|
593
|
+
float
|
594
|
+
The time from new moon.
|
595
|
+
"""
|
596
|
+
previous_new_moon = ephem.previous_new_moon(self.observer.date)
|
597
|
+
return float(MoonInfo.DAYS_TO_HOURS * (self.observer.date - previous_new_moon))
|
598
|
+
|
599
|
+
def time_to_full_moon(self) -> float:
|
600
|
+
"""Time (days) to the next full moon.
|
601
|
+
|
602
|
+
This function calculates the time to the next full moon.
|
603
|
+
|
604
|
+
Returns
|
605
|
+
-------
|
606
|
+
float
|
607
|
+
The time to full moon.
|
608
|
+
"""
|
609
|
+
next_full_moon = ephem.next_full_moon(self.observer.date)
|
610
|
+
return float(next_full_moon - self.observer.date)
|
611
|
+
|
612
|
+
def time_to_new_moon(self) -> float:
|
613
|
+
"""Time (hours) to the next new moon.
|
614
|
+
|
615
|
+
This function calculates the time to the next new moon.
|
616
|
+
|
617
|
+
Returns
|
618
|
+
-------
|
619
|
+
float
|
620
|
+
The time to new moon.
|
621
|
+
"""
|
622
|
+
next_new_moon = ephem.next_new_moon(self.observer.date)
|
623
|
+
return float(MoonInfo.DAYS_TO_HOURS * (next_new_moon - self.observer.date))
|
624
|
+
|
625
|
+
def update(self, datetime: DateTimeTuple) -> None:
|
626
|
+
"""Update the moon information based on time.
|
627
|
+
|
628
|
+
This fuction updates the Observer instance's datetime setting. The
|
629
|
+
incoming datetime tuple should be in UTC with the following placement
|
630
|
+
of values: (YYYY, m, d, H, M, S) as defined below::
|
631
|
+
|
632
|
+
YYYY
|
633
|
+
Four digit year
|
634
|
+
|
635
|
+
m
|
636
|
+
month (1-12)
|
637
|
+
|
638
|
+
d
|
639
|
+
day (1-31)
|
640
|
+
|
641
|
+
H
|
642
|
+
hours (0-23)
|
643
|
+
|
644
|
+
M
|
645
|
+
minutes (0-59)
|
646
|
+
|
647
|
+
S
|
648
|
+
seconds (0-59)
|
649
|
+
|
650
|
+
Parameters
|
651
|
+
----------
|
652
|
+
datetime : tuple
|
653
|
+
The current UTC time in a tuple of numbers.
|
654
|
+
"""
|
655
|
+
self.observer.date = datetime
|
656
|
+
self.moon.compute(self.observer)
|
pylunar/types.py
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# This file is part of pylunar.
|
2
|
+
#
|
3
|
+
# Developed by Michael Reuter.
|
4
|
+
#
|
5
|
+
# See the LICENSE file at the top-level directory of this distribution
|
6
|
+
# for details of code ownership.
|
7
|
+
#
|
8
|
+
# Use of this source code is governed by a 3-clause BSD-style
|
9
|
+
# license that can be found in the LICENSE file.
|
10
|
+
|
11
|
+
"""Module for holding types."""
|
12
|
+
|
13
|
+
from __future__ import annotations
|
14
|
+
|
15
|
+
import sys
|
16
|
+
|
17
|
+
if sys.version_info >= (3, 10):
|
18
|
+
from typing import TypeAlias
|
19
|
+
else:
|
20
|
+
from typing_extensions import TypeAlias
|
21
|
+
|
22
|
+
from typing import List, Tuple, Union
|
23
|
+
|
24
|
+
DateTimeTuple: TypeAlias = Tuple[int, int, int, int, int, Union[int, float]]
|
25
|
+
MoonPhases: TypeAlias = List[Tuple[str, Union[DateTimeTuple, str]]]
|
26
|
+
DmsCoordinate: TypeAlias = Tuple[int, int, int]
|
27
|
+
Range: TypeAlias = Tuple[float, float]
|
28
|
+
LunarFeatureList: TypeAlias = Tuple[
|
29
|
+
str, float, float, float, float, float, str, str, str, str, Union[str, None]
|
30
|
+
]
|
31
|
+
FeatureRow: TypeAlias = Tuple[int, str, float, float, float, float, float, str, str, str, str, str]
|
@@ -0,0 +1,16 @@
|
|
1
|
+
=======
|
2
|
+
Credits
|
3
|
+
=======
|
4
|
+
|
5
|
+
Development Lead
|
6
|
+
----------------
|
7
|
+
|
8
|
+
* Michael Reuter <mareuternh@gmail.com>
|
9
|
+
|
10
|
+
Contributors
|
11
|
+
------------
|
12
|
+
|
13
|
+
* `1kastner <https://github.com/1kastner>`_
|
14
|
+
* `Louis Knapp <https://github.com/lknapp>`_
|
15
|
+
* `Bryan Neal Garrison <https://github.com/noblecloud>`_
|
16
|
+
* `Peyman Majidi Moein <https://github.com/peymanmajidi>`_
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Copyright 2016-2024 Michael Reuter
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
4
|
+
modification, are permitted provided that the following conditions are met:
|
5
|
+
|
6
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
7
|
+
list of conditions and the following disclaimer.
|
8
|
+
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
14
|
+
may be used to endorse or promote products derived from this software
|
15
|
+
without specific prior written permission.
|
16
|
+
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
20
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
21
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
23
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
24
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
25
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
26
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@@ -0,0 +1,78 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: pylunar
|
3
|
+
Version: 0.7.1
|
4
|
+
Summary: Information for completing the Astronomical League's Lunar and Lunar II observing programs.
|
5
|
+
Author-email: Michael Reuter <mareuternh@gmail.com>
|
6
|
+
Project-URL: Documentation, http://pylunar.readthedocs.io
|
7
|
+
Project-URL: Repository, https://github.com/mareuter/pylunar
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
9
|
+
Classifier: Intended Audience :: Developers
|
10
|
+
Classifier: License :: OSI Approved :: BSD License
|
11
|
+
Classifier: Natural Language :: English
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
18
|
+
Description-Content-Type: text/x-rst
|
19
|
+
License-File: LICENSE
|
20
|
+
License-File: AUTHORS.rst
|
21
|
+
Requires-Dist: ephem ==4.1.5
|
22
|
+
Requires-Dist: pytz ==2024.1
|
23
|
+
Requires-Dist: importlib-resources ==6.1.1 ; python_version < "3.10"
|
24
|
+
Requires-Dist: typing-extensions ==4.9.0 ; python_version < "3.10"
|
25
|
+
Provides-Extra: build
|
26
|
+
Requires-Dist: build ==1.0.3 ; extra == 'build'
|
27
|
+
Requires-Dist: twine ==4.0.2 ; extra == 'build'
|
28
|
+
Provides-Extra: dev
|
29
|
+
Requires-Dist: pylunar[build,docs,lint,test] ; extra == 'dev'
|
30
|
+
Requires-Dist: tox ==4.11.4 ; extra == 'dev'
|
31
|
+
Provides-Extra: docs
|
32
|
+
Requires-Dist: sphinx ~=7.1 ; extra == 'docs'
|
33
|
+
Requires-Dist: sphinx-rtd-theme ==2.0.0 ; extra == 'docs'
|
34
|
+
Provides-Extra: lint
|
35
|
+
Requires-Dist: pre-commit ~=3.5.0 ; extra == 'lint'
|
36
|
+
Provides-Extra: test
|
37
|
+
Requires-Dist: coverage[toml] ==7.4.0 ; extra == 'test'
|
38
|
+
Requires-Dist: pytest ==7.4.4 ; extra == 'test'
|
39
|
+
|
40
|
+
=============================
|
41
|
+
Python Lunar
|
42
|
+
=============================
|
43
|
+
|
44
|
+
.. |license| image:: https://img.shields.io/pypi/l/pylunar.svg
|
45
|
+
:target: http://opensource.org/licenses/BSD
|
46
|
+
:alt: BSD License
|
47
|
+
|
48
|
+
.. |version| image:: http://img.shields.io/pypi/v/pylunar.svg
|
49
|
+
:target: https://pypi.python.org/pypi/pylunar
|
50
|
+
:alt: Software Version
|
51
|
+
|
52
|
+
.. |github| image:: https://github.com/mareuter/pylunar/actions/workflows/ci.yaml/badge.svg
|
53
|
+
:target: https://github.com/mareuter/pylunar
|
54
|
+
:alt: Github build status
|
55
|
+
|
56
|
+
.. |python| image:: https://img.shields.io/pypi/pyversions/pylunar.svg
|
57
|
+
:target: https://pypi.python.org/pypi/pylunar
|
58
|
+
:alt: Supported Python
|
59
|
+
|
60
|
+
.. |docs| image:: https://readthedocs.org/projects/pylunar/badge/?version=latest
|
61
|
+
:target: https://pylunar.readthedocs.io
|
62
|
+
:alt: Readthedocs status
|
63
|
+
|
64
|
+
.. |pre-commit| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit
|
65
|
+
:target: https://github.com/pre-commit/pre-commit
|
66
|
+
:alt: Uses pre-commit
|
67
|
+
|
68
|
+
|license| |python| |version| |github| |docs| |pre-commit|
|
69
|
+
|
70
|
+
Information for completing the Astronomical League's Lunar and Lunar II observing programs. Uses the `pyephem <https://rhodesmill.org/pyephem>`_ package to calculate lunar information.
|
71
|
+
|
72
|
+
|
73
|
+
Features
|
74
|
+
--------
|
75
|
+
|
76
|
+
* Offer moon information based on location and date/time.
|
77
|
+
* Offer lunar targets for AL observing clubs based on terminator location.
|
78
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
pylunar/__init__.py,sha256=-I-kK_YCtnOkwYPs2Qe9mCaanU5XJLnXxhHzNbKJpAk,1056
|
2
|
+
pylunar/helpers.py,sha256=Ip1koGrhbay318QLOhQCMwBLDA_Q1bKXHDkU_Un3EZE,1323
|
3
|
+
pylunar/lunar_feature.py,sha256=oK-ALplg29DtGzKXqPUbssri3K0_1Ftz97SPzNhP8v8,5521
|
4
|
+
pylunar/lunar_feature_container.py,sha256=ud8-B_CqQ_0x9FSbkj2eHMPRT5PAwSai6D6tU9dGkQY,2796
|
5
|
+
pylunar/moon_info.py,sha256=AdYdfgKgwnL2nRs9QF9x28_pflCaxsX10VKFvqaXLGY,19958
|
6
|
+
pylunar/types.py,sha256=DbPCc9_kzRuSOxOfeKaLJdWfykdbzRPnOTY-EN2xrsc,973
|
7
|
+
pylunar/data/lunar.db,sha256=y2u5wR_DpHjPYH7fuR-T_q0nbPzN1GuP8_OXorjVr14,28672
|
8
|
+
pylunar-0.7.1.dist-info/AUTHORS.rst,sha256=mqGQzrJPFGm44DZxZFs0d080QDWXdtfFlv1i_fdJerg,332
|
9
|
+
pylunar-0.7.1.dist-info/LICENSE,sha256=EF_CKfNhkKjEynpn9Msb6HaGNIBN9VfeGyrJFJQ5FYk,1479
|
10
|
+
pylunar-0.7.1.dist-info/METADATA,sha256=Jkl2_uVETgCknLpOeWwUhE4bBH0P1kReYzSbldNXxOA,3035
|
11
|
+
pylunar-0.7.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
12
|
+
pylunar-0.7.1.dist-info/top_level.txt,sha256=vEQZCgYlUuoq6D4q99Kj5fSpNs2Mo-jW8vKV3dnpI-U,8
|
13
|
+
pylunar-0.7.1.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
pylunar
|