starplot 0.20.5__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.
- starplot/__init__.py +59 -0
- starplot/callables.py +176 -0
- starplot/cli.py +29 -0
- starplot/config.py +80 -0
- starplot/coordinates.py +7 -0
- starplot/data/__init__.py +22 -0
- starplot/data/catalogs.py +356 -0
- starplot/data/constellations.py +71 -0
- starplot/data/db.py +33 -0
- starplot/data/dsos.py +85 -0
- starplot/data/ecliptic.py +201 -0
- starplot/data/library/constellation_names.parquet +0 -0
- starplot/data/library/dso_names.parquet +0 -0
- starplot/data/library/readme.md +1 -0
- starplot/data/library/star_designations.parquet +0 -0
- starplot/data/stars.py +79 -0
- starplot/data/translations.py +408 -0
- starplot/data/utils.py +35 -0
- starplot/geometry.py +319 -0
- starplot/mixins.py +360 -0
- starplot/models/__init__.py +21 -0
- starplot/models/base.py +77 -0
- starplot/models/comet.py +302 -0
- starplot/models/constellation.py +151 -0
- starplot/models/dso.py +317 -0
- starplot/models/milky_way.py +30 -0
- starplot/models/moon.py +130 -0
- starplot/models/objects.py +29 -0
- starplot/models/observer.py +125 -0
- starplot/models/optics.py +342 -0
- starplot/models/planet.py +137 -0
- starplot/models/satellite.py +138 -0
- starplot/models/star.py +253 -0
- starplot/models/sun.py +62 -0
- starplot/plots/__init__.py +7 -0
- starplot/plots/base.py +1023 -0
- starplot/plots/galaxy.py +372 -0
- starplot/plots/horizon.py +543 -0
- starplot/plots/map.py +515 -0
- starplot/plots/optic.py +469 -0
- starplot/plots/zenith.py +217 -0
- starplot/plotters/__init__.py +9 -0
- starplot/plotters/arrow.py +174 -0
- starplot/plotters/constellations.py +298 -0
- starplot/plotters/debug.py +21 -0
- starplot/plotters/dsos.py +294 -0
- starplot/plotters/experimental.py +722 -0
- starplot/plotters/gradients.py +153 -0
- starplot/plotters/legend.py +253 -0
- starplot/plotters/milkyway.py +51 -0
- starplot/plotters/stars.py +319 -0
- starplot/plotters/text.py +802 -0
- starplot/profile.py +16 -0
- starplot/projections.py +184 -0
- starplot/styles/__init__.py +6 -0
- starplot/styles/base.py +1344 -0
- starplot/styles/ext/antique.yml +175 -0
- starplot/styles/ext/blue_dark.yml +163 -0
- starplot/styles/ext/blue_gold.yml +147 -0
- starplot/styles/ext/blue_light.yml +123 -0
- starplot/styles/ext/blue_medium.yml +142 -0
- starplot/styles/ext/blue_night.yml +185 -0
- starplot/styles/ext/cb_wong.yml +124 -0
- starplot/styles/ext/color_print.yml +111 -0
- starplot/styles/ext/gradient_presets.yml +158 -0
- starplot/styles/ext/grayscale.yml +94 -0
- starplot/styles/ext/grayscale_dark.yml +136 -0
- starplot/styles/ext/map.yml +12 -0
- starplot/styles/ext/nord.yml +158 -0
- starplot/styles/ext/optic.yml +20 -0
- starplot/styles/ext/publication.yml +8 -0
- starplot/styles/extensions.py +129 -0
- starplot/styles/fonts-library/gfs-didot/DESCRIPTION.en_us.html +9 -0
- starplot/styles/fonts-library/gfs-didot/GFSDidot-Regular.ttf +0 -0
- starplot/styles/fonts-library/gfs-didot/METADATA.pb +16 -0
- starplot/styles/fonts-library/gfs-didot/OFL.txt +94 -0
- starplot/styles/fonts-library/inter/Inter-Bold.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-BoldItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ExtraBold.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ExtraLight.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ExtraLightItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Italic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Light.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-LightItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Regular.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-SemiBold.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-SemiBoldItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/LICENSE.txt +92 -0
- starplot/styles/fonts.py +15 -0
- starplot/styles/helpers.py +93 -0
- starplot/styles/markers.py +308 -0
- starplot/utils.py +169 -0
- starplot/warnings.py +21 -0
- starplot-0.20.5.dist-info/METADATA +146 -0
- starplot-0.20.5.dist-info/RECORD +98 -0
- starplot-0.20.5.dist-info/WHEEL +4 -0
- starplot-0.20.5.dist-info/entry_points.txt +3 -0
- starplot-0.20.5.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# ruff: noqa: F401,F403
|
|
2
|
+
|
|
3
|
+
from .constellation import Constellation, ConstellationBorder
|
|
4
|
+
from .comet import Comet
|
|
5
|
+
from .dso import DSO, DsoType
|
|
6
|
+
from .star import Star
|
|
7
|
+
from .planet import Planet
|
|
8
|
+
from .moon import Moon
|
|
9
|
+
from .sun import Sun
|
|
10
|
+
from .optics import (
|
|
11
|
+
Optic,
|
|
12
|
+
Scope,
|
|
13
|
+
Reflector,
|
|
14
|
+
Refractor,
|
|
15
|
+
Binoculars,
|
|
16
|
+
Camera,
|
|
17
|
+
)
|
|
18
|
+
from .objects import ObjectList
|
|
19
|
+
from .satellite import Satellite
|
|
20
|
+
from .observer import Observer
|
|
21
|
+
from .milky_way import MilkyWay
|
starplot/models/base.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from dataclasses import dataclass, fields
|
|
2
|
+
from functools import cache
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import pyarrow as pa
|
|
6
|
+
from skyfield.api import position_of_radec, load_constellation_map
|
|
7
|
+
|
|
8
|
+
from starplot.mixins import CreateMapMixin, CreateOpticMixin
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@cache
|
|
12
|
+
def constellation_at():
|
|
13
|
+
return load_constellation_map()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(slots=True, kw_only=True)
|
|
17
|
+
class SkyObject(
|
|
18
|
+
CreateMapMixin,
|
|
19
|
+
CreateOpticMixin,
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Base class for sky objects.
|
|
23
|
+
|
|
24
|
+
All sky object classes inherit from this base class.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
ra: float
|
|
28
|
+
"""Right Ascension, in degrees (0 to 360)"""
|
|
29
|
+
|
|
30
|
+
dec: float
|
|
31
|
+
"""Declination, in degrees (-90 to 90)"""
|
|
32
|
+
|
|
33
|
+
constellation_id: Optional[str] = None
|
|
34
|
+
"""Three-letter IAU id of the constellation that contains this object"""
|
|
35
|
+
|
|
36
|
+
healpix_index: int = None
|
|
37
|
+
"""[HEALPix](https://en.wikipedia.org/wiki/HEALPix) pixel index of this object's RA/DEC"""
|
|
38
|
+
|
|
39
|
+
def constellation(self):
|
|
40
|
+
"""Returns an instance of the [`Constellation`][starplot.models.Constellation] that contains this object, or `None` if no constellation is found."""
|
|
41
|
+
from starplot.models import Constellation
|
|
42
|
+
|
|
43
|
+
return Constellation.get(iau_id=self.constellation_id)
|
|
44
|
+
|
|
45
|
+
def populate_constellation_id(self):
|
|
46
|
+
"""Populates the constellation_id field based on the location of this object"""
|
|
47
|
+
pos = position_of_radec(self.ra / 15, self.dec)
|
|
48
|
+
self.constellation_id = constellation_at()(pos).lower()
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
@cache
|
|
52
|
+
def _dir(cls):
|
|
53
|
+
return dir(cls)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
@cache
|
|
57
|
+
def _fields(cls):
|
|
58
|
+
return [f.name for f in fields(cls)]
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
@cache
|
|
62
|
+
def _pyarrow_schema(cls):
|
|
63
|
+
"""Returns explicit schema"""
|
|
64
|
+
return pa.schema(
|
|
65
|
+
[
|
|
66
|
+
pa.field("ra", pa.float64(), nullable=False),
|
|
67
|
+
pa.field("dec", pa.float64(), nullable=False),
|
|
68
|
+
pa.field("constellation_id", pa.string()),
|
|
69
|
+
pa.field("healpix_index", pa.int64()),
|
|
70
|
+
]
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(kw_only=True)
|
|
75
|
+
class CatalogObject:
|
|
76
|
+
pk: int
|
|
77
|
+
"""Primary key of object in catalog. Needs to be unique across all objects in the catalog."""
|
starplot/models/comet.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
from typing import Iterator
|
|
3
|
+
from functools import cache
|
|
4
|
+
from dataclasses import dataclass, fields
|
|
5
|
+
|
|
6
|
+
from skyfield.data import mpc
|
|
7
|
+
from skyfield.constants import GM_SUN_Pitjeva_2005_km3_s2 as GM_SUN
|
|
8
|
+
from shapely import Point
|
|
9
|
+
|
|
10
|
+
from starplot.data import load
|
|
11
|
+
from starplot.models.base import SkyObject
|
|
12
|
+
from starplot.models.observer import Observer
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class SkyfieldComet:
|
|
17
|
+
designation: str
|
|
18
|
+
reference: str
|
|
19
|
+
|
|
20
|
+
perihelion_year: int
|
|
21
|
+
perihelion_month: int
|
|
22
|
+
perihelion_day: float
|
|
23
|
+
perihelion_distance_au: float
|
|
24
|
+
eccentricity: float
|
|
25
|
+
argument_of_perihelion_degrees: float
|
|
26
|
+
longitude_of_ascending_node_degrees: float
|
|
27
|
+
inclination_degrees: float
|
|
28
|
+
|
|
29
|
+
perturbed_epoch_year: int | None = None
|
|
30
|
+
perturbed_epoch_month: int | None = None
|
|
31
|
+
perturbed_epoch_day: int | None = None
|
|
32
|
+
|
|
33
|
+
number: int | None = None
|
|
34
|
+
designation_packed: str | None = None
|
|
35
|
+
orbit_type: str | None = None
|
|
36
|
+
magnitude_g: float | None = None
|
|
37
|
+
magnitude_k: float | None = None
|
|
38
|
+
|
|
39
|
+
# Skyfield columns
|
|
40
|
+
# ('number', (0, 4)),
|
|
41
|
+
# ('orbit_type', (4, 5)),
|
|
42
|
+
# ('designation_packed', (5, 12)),
|
|
43
|
+
# ('perihelion_year', (14, 18)),
|
|
44
|
+
# ('perihelion_month', (19, 21)),
|
|
45
|
+
# ('perihelion_day', (22, 29)),
|
|
46
|
+
# ('perihelion_distance_au', (30, 39)),
|
|
47
|
+
# ('eccentricity', (41, 49)),
|
|
48
|
+
# ('argument_of_perihelion_degrees', (51, 59)),
|
|
49
|
+
# ('longitude_of_ascending_node_degrees', (61, 69)),
|
|
50
|
+
# ('inclination_degrees', (71, 79)),
|
|
51
|
+
# ('perturbed_epoch_year', (81, 85)),
|
|
52
|
+
# ('perturbed_epoch_month', (85, 87)),
|
|
53
|
+
# ('perturbed_epoch_day', (87, 89)),
|
|
54
|
+
# ('magnitude_g', (91, 95)),
|
|
55
|
+
# ('magnitude_k', (96, 100)),
|
|
56
|
+
# ('designation', (102, 158)),
|
|
57
|
+
# ('reference', (159, 168)),
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
MPC JSON
|
|
61
|
+
{
|
|
62
|
+
"Comet_num": 483,
|
|
63
|
+
"Orbit_type": "P",
|
|
64
|
+
"Year_of_perihelion": 2027,
|
|
65
|
+
"Month_of_perihelion": 11,
|
|
66
|
+
"Day_of_perihelion": 11.7324,
|
|
67
|
+
"Perihelion_dist": 2.486784,
|
|
68
|
+
"e": 0.221733,
|
|
69
|
+
"Peri": 49.461,
|
|
70
|
+
"Node": 199.0602,
|
|
71
|
+
"i": 14.1756,
|
|
72
|
+
"Epoch_year": 2025,
|
|
73
|
+
"Epoch_month": 10,
|
|
74
|
+
"Epoch_day": 22,
|
|
75
|
+
"H": 17.0,
|
|
76
|
+
"G": 4.0,
|
|
77
|
+
"Designation_and_name": "483P-B/PANSTARRS",
|
|
78
|
+
"Ref": "MPC185666"
|
|
79
|
+
}
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def from_mpc_json(cls, data: dict) -> "SkyfieldComet":
|
|
84
|
+
"""Converts an MPC orbit JSON for a comet to a dataclass that Skyfield can work with"""
|
|
85
|
+
|
|
86
|
+
d = {k.lower(): v for k, v in data.items()}
|
|
87
|
+
|
|
88
|
+
return SkyfieldComet(
|
|
89
|
+
number=d.get("comet_num"),
|
|
90
|
+
orbit_type=d.get("orbit_type"),
|
|
91
|
+
designation_packed=d.get("provisional_packed_desig"),
|
|
92
|
+
perihelion_year=d.get("year_of_perihelion"),
|
|
93
|
+
perihelion_month=d.get("month_of_perihelion"),
|
|
94
|
+
perihelion_day=d.get("day_of_perihelion"),
|
|
95
|
+
perihelion_distance_au=d.get("perihelion_dist"),
|
|
96
|
+
eccentricity=d.get("e"),
|
|
97
|
+
argument_of_perihelion_degrees=d.get("peri"),
|
|
98
|
+
longitude_of_ascending_node_degrees=d.get("node"),
|
|
99
|
+
inclination_degrees=d.get("i"),
|
|
100
|
+
perturbed_epoch_year=d.get("epoch_year"),
|
|
101
|
+
perturbed_epoch_month=d.get("epoch_month"),
|
|
102
|
+
perturbed_epoch_day=d.get("epoch_day"),
|
|
103
|
+
magnitude_g=d.get("g"),
|
|
104
|
+
# magnitude_k=None, # no mapping?
|
|
105
|
+
designation=d.get("designation_and_name"),
|
|
106
|
+
reference=d.get("ref"),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def from_mpc_remote(cls, name: str, reload: bool) -> "SkyfieldComet":
|
|
111
|
+
"""
|
|
112
|
+
Create a Skyfield comet by downloading the latest MPC comet data and finding the comet by name/designation
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
name: Name/designation of the comet
|
|
116
|
+
reload: If True, then the MPC data will be re-downloaded before finding the comet
|
|
117
|
+
"""
|
|
118
|
+
comets = get_comets(reload)
|
|
119
|
+
row = comets.loc[name]
|
|
120
|
+
return SkyfieldComet(**row.to_dict())
|
|
121
|
+
|
|
122
|
+
def __getitem__(self, key):
|
|
123
|
+
"""Allows accessing dataclass fields using dictionary-like syntax."""
|
|
124
|
+
if key in [f.name for f in fields(self)]:
|
|
125
|
+
return getattr(self, key)
|
|
126
|
+
else:
|
|
127
|
+
raise KeyError
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass(slots=True, kw_only=True)
|
|
131
|
+
class Comet(SkyObject):
|
|
132
|
+
"""
|
|
133
|
+
Comets can be created in three ways:
|
|
134
|
+
|
|
135
|
+
1. [`get`][starplot.Comet.get] (designation/name)
|
|
136
|
+
2. [`all`][starplot.Comet.all] (iterate through all comets available from MPC)
|
|
137
|
+
3. [`from_json`][starplot.Comet.from_json] (IAU MPC JSON)
|
|
138
|
+
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
name: str = None
|
|
142
|
+
"""
|
|
143
|
+
Name of the comet (as designated by IAU Minor Planet Center)
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
observer: Observer = None
|
|
147
|
+
"""Observer of this comet instance"""
|
|
148
|
+
|
|
149
|
+
distance: float | None = None
|
|
150
|
+
"""Distance to comet, in Astronomical units (the Earth-Sun distance of 149,597,870,700 m)"""
|
|
151
|
+
|
|
152
|
+
ephemeris: str = None
|
|
153
|
+
"""Ephemeris used when retrieving this instance"""
|
|
154
|
+
|
|
155
|
+
geometry: Point = None
|
|
156
|
+
"""Shapely Point of the comet's position. Right ascension coordinates are in degrees (0...360)."""
|
|
157
|
+
|
|
158
|
+
data: SkyfieldComet = None
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def from_json(
|
|
162
|
+
cls,
|
|
163
|
+
data: dict,
|
|
164
|
+
observer: Observer = None,
|
|
165
|
+
ephemeris: str = "de421.bsp",
|
|
166
|
+
) -> "Comet":
|
|
167
|
+
"""
|
|
168
|
+
Get a comet for a specific date/time/location from an IAU MPC JSON.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
data: Dictionary of the IAU MPC JSON
|
|
172
|
+
observer: Observer instance that specifies a time and location
|
|
173
|
+
ephemeris: Ephemeris to use for calculating comet positions (see [Skyfield's documentation](https://rhodesmill.org/skyfield/planets.html) for details)
|
|
174
|
+
"""
|
|
175
|
+
comet = SkyfieldComet.from_mpc_json(data)
|
|
176
|
+
observer = observer or Observer()
|
|
177
|
+
|
|
178
|
+
return get_comet_at_date_location(comet, observer, ephemeris)
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
def all(
|
|
182
|
+
cls,
|
|
183
|
+
observer: Observer = None,
|
|
184
|
+
ephemeris: str = "de421.bsp",
|
|
185
|
+
reload: bool = False,
|
|
186
|
+
) -> Iterator["Comet"]:
|
|
187
|
+
"""
|
|
188
|
+
Iterator for getting all comets at a specific date/time and observing location.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
observer: Observer instance that specifies a time and location
|
|
192
|
+
ephemeris: Ephemeris to use for calculating comet positions (see [Skyfield's documentation](https://rhodesmill.org/skyfield/planets.html) for details)
|
|
193
|
+
reload: If True, then the comet data file will be re-downloaded. Otherwise, it'll use the existing file if available.
|
|
194
|
+
"""
|
|
195
|
+
comets = get_comets(reload=reload)
|
|
196
|
+
observer = observer or Observer()
|
|
197
|
+
for name in comets.index.values:
|
|
198
|
+
row = comets.loc[name]
|
|
199
|
+
comet = SkyfieldComet(**row.to_dict())
|
|
200
|
+
yield get_comet_at_date_location(
|
|
201
|
+
comet=comet,
|
|
202
|
+
observer=observer,
|
|
203
|
+
ephemeris=ephemeris,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
@classmethod
|
|
207
|
+
def get(
|
|
208
|
+
cls,
|
|
209
|
+
name: str,
|
|
210
|
+
observer: Observer = None,
|
|
211
|
+
ephemeris: str = "de421.bsp",
|
|
212
|
+
reload: bool = False,
|
|
213
|
+
) -> "Comet":
|
|
214
|
+
"""
|
|
215
|
+
Get a comet for a specific date/time.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
name: Name of the comet you want to get (as designated by IAU Minor Planet Center)
|
|
219
|
+
observer: Observer instance that specifies a time and location
|
|
220
|
+
ephemeris: Ephemeris to use for calculating comet positions (see [Skyfield's documentation](https://rhodesmill.org/skyfield/planets.html) for details)
|
|
221
|
+
reload: If True, then the comet data file will be re-downloaded. Otherwise, it'll use the existing file if available.
|
|
222
|
+
"""
|
|
223
|
+
comet = SkyfieldComet.from_mpc_remote(name, reload)
|
|
224
|
+
observer = observer or Observer()
|
|
225
|
+
return get_comet_at_date_location(comet, observer, ephemeris)
|
|
226
|
+
|
|
227
|
+
def trajectory(
|
|
228
|
+
self, date_start: datetime, date_end: datetime, step: timedelta = None
|
|
229
|
+
) -> Iterator["Comet"]:
|
|
230
|
+
"""
|
|
231
|
+
Iterator for getting a trajectory of the comet.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
date_start: Starting date/time for the trajectory (inclusive)
|
|
235
|
+
date_end: End date/time for the trajectory (exclusive)
|
|
236
|
+
step: Time-step for the trajectory. Defaults to 1-day
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Iterator that yields a Comet instance at each step in the date range
|
|
240
|
+
"""
|
|
241
|
+
dt = date_start
|
|
242
|
+
step = step or timedelta(days=1)
|
|
243
|
+
|
|
244
|
+
while dt < date_end:
|
|
245
|
+
observer_kwargs = self.observer.model_dump()
|
|
246
|
+
observer_kwargs["dt"] = dt
|
|
247
|
+
new_observer = Observer(**observer_kwargs)
|
|
248
|
+
yield get_comet_at_date_location(
|
|
249
|
+
comet=self.data,
|
|
250
|
+
observer=new_observer,
|
|
251
|
+
ephemeris=self.ephemeris,
|
|
252
|
+
)
|
|
253
|
+
dt += step
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@cache
|
|
257
|
+
def get_comets(reload=False):
|
|
258
|
+
"""
|
|
259
|
+
Gets ALL comets currently tracked by IAU Minor Planet Center.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
reload: If True, then redownload the comet data if it already exists
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
DataFrame of all comets, indexed by name
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
with load.open(mpc.COMET_URL, reload=reload) as f:
|
|
269
|
+
comets = mpc.load_comets_dataframe(f)
|
|
270
|
+
|
|
271
|
+
# Keep only the most recent orbit for each comet, and index by designation for fast lookup.
|
|
272
|
+
comets = (
|
|
273
|
+
comets.sort_values("reference")
|
|
274
|
+
.groupby("designation", as_index=False)
|
|
275
|
+
.last()
|
|
276
|
+
.set_index("designation", drop=False)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
return comets
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def get_comet_at_date_location(
|
|
283
|
+
comet: SkyfieldComet, observer: Observer, ephemeris: str
|
|
284
|
+
) -> Comet:
|
|
285
|
+
"""
|
|
286
|
+
Creates a Comet instance for date and (optional) observing location.
|
|
287
|
+
"""
|
|
288
|
+
ts = load.timescale()
|
|
289
|
+
eph = load(ephemeris)
|
|
290
|
+
c = eph["sun"] + mpc.comet_orbit(comet, ts, GM_SUN)
|
|
291
|
+
ra, dec, distance = observer._astrometric(c, ephemeris=ephemeris)
|
|
292
|
+
|
|
293
|
+
return Comet(
|
|
294
|
+
name=comet.designation,
|
|
295
|
+
ra=ra.hours * 15,
|
|
296
|
+
dec=dec.degrees,
|
|
297
|
+
observer=observer,
|
|
298
|
+
distance=distance.au,
|
|
299
|
+
ephemeris=ephemeris,
|
|
300
|
+
geometry=Point(ra.hours * 15, dec.degrees),
|
|
301
|
+
data=comet,
|
|
302
|
+
)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from typing import Iterator
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
from ibis import _
|
|
5
|
+
from shapely import Polygon, MultiPolygon, LineString
|
|
6
|
+
|
|
7
|
+
from starplot.models.base import SkyObject, CatalogObject
|
|
8
|
+
from starplot.data.catalogs import Catalog, CONSTELLATIONS_IAU
|
|
9
|
+
from starplot.data.constellations import load
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(slots=True, kw_only=True)
|
|
13
|
+
class Constellation(CatalogObject, SkyObject):
|
|
14
|
+
"""
|
|
15
|
+
Constellation model.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
boundary: Polygon | MultiPolygon
|
|
19
|
+
"""
|
|
20
|
+
Shapely Polygon of the constellation's boundary. Right ascension coordinates are in degrees (0...360).
|
|
21
|
+
|
|
22
|
+
If the constellation's boundary crosses the 0-meridian, then this will be a MultiPolygon split at the meridian.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
star_hip_ids: list[int]
|
|
26
|
+
"""List of HIP ids for stars that are part of the _lines_ for this constellation."""
|
|
27
|
+
|
|
28
|
+
star_hip_lines: list[list[int, int]]
|
|
29
|
+
"""Nested list of star HIP ids that represent the lines of this constellation. Each pair of HIP ids represents a line between those stars."""
|
|
30
|
+
|
|
31
|
+
name: str = None
|
|
32
|
+
"""Name of constellation"""
|
|
33
|
+
|
|
34
|
+
iau_id: str = None
|
|
35
|
+
"""
|
|
36
|
+
International Astronomical Union (IAU) three-letter designation, all lowercase.
|
|
37
|
+
|
|
38
|
+
**Important**: Starplot treats Serpens as two separate constellations to make them easier to work with programatically.
|
|
39
|
+
Serpens Caput has the `iau_id` of `ser1` and Serpens Cauda is `ser2`
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
border: LineString = None
|
|
43
|
+
"""
|
|
44
|
+
Shapely LineString of the constellation's border. Right ascension coordinates are in degrees (0...360).
|
|
45
|
+
|
|
46
|
+
Coordinates in this geometry may extend past RA=360, if the border crosses the 0-meridian. This makes it ready to plot with the `line` function.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __repr__(self) -> str:
|
|
50
|
+
return f"Constellation(iau_id={self.iau_id}, name={self.name}, ra={self.ra}, dec={self.dec})"
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def all(cls, catalog: Catalog = CONSTELLATIONS_IAU) -> Iterator["Constellation"]:
|
|
54
|
+
"""
|
|
55
|
+
Get all constellations from a catalog
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
catalog: Catalog you want to get constellation objects from
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Iterator of Constellation instances
|
|
62
|
+
"""
|
|
63
|
+
df = load(catalog=catalog).to_pandas()
|
|
64
|
+
|
|
65
|
+
for c in df.itertuples():
|
|
66
|
+
yield from_tuple(c)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def get(
|
|
70
|
+
cls, catalog: Catalog = CONSTELLATIONS_IAU, sql: str = None, **kwargs
|
|
71
|
+
) -> "Constellation":
|
|
72
|
+
"""
|
|
73
|
+
Get a Constellation, by matching its attributes.
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
|
|
77
|
+
hercules = Constellation.get(name="Hercules")
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
catalog: The catalog of constellations to use
|
|
81
|
+
sql: SQL query for selecting constellation (table name is "_")
|
|
82
|
+
**kwargs: Attributes on the constellation you want to match
|
|
83
|
+
|
|
84
|
+
Raises: `ValueError` if more than one constellation is matched
|
|
85
|
+
"""
|
|
86
|
+
filters = []
|
|
87
|
+
|
|
88
|
+
for k, v in kwargs.items():
|
|
89
|
+
filters.append(getattr(_, k) == v)
|
|
90
|
+
|
|
91
|
+
df = load(catalog=catalog, filters=filters, sql=sql).to_pandas()
|
|
92
|
+
results = [from_tuple(c) for c in df.itertuples()]
|
|
93
|
+
|
|
94
|
+
if len(results) == 1:
|
|
95
|
+
return results[0]
|
|
96
|
+
|
|
97
|
+
if len(results) > 1:
|
|
98
|
+
raise ValueError(
|
|
99
|
+
"More than one match. Use find() instead or narrow your search."
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def find(
|
|
106
|
+
cls,
|
|
107
|
+
catalog: Catalog = CONSTELLATIONS_IAU,
|
|
108
|
+
where: list = None,
|
|
109
|
+
sql: str = None,
|
|
110
|
+
) -> list["Constellation"]:
|
|
111
|
+
"""
|
|
112
|
+
Find Constellations
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
catalog: The catalog of constellations to use
|
|
116
|
+
where: A list of expressions that determine which constellations to find. See [Selecting Objects](/reference-selecting-objects/) for details.
|
|
117
|
+
sql: SQL query for selecting constellations (table name is "_")
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
List of Constellations that match all `where` expressions
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
df = load(catalog=catalog, filters=where, sql=sql).to_pandas()
|
|
124
|
+
|
|
125
|
+
return [from_tuple(c) for c in df.itertuples()]
|
|
126
|
+
|
|
127
|
+
def constellation(self):
|
|
128
|
+
"""Not applicable to Constellation model, raises `NotImplementedError`"""
|
|
129
|
+
raise NotImplementedError()
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def get_label(cls, constellation):
|
|
133
|
+
"""
|
|
134
|
+
Default function for determining the plotted label for a constellation
|
|
135
|
+
|
|
136
|
+
Returns the uppercase name of the constellation.
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
return constellation.name.upper().replace(" ", "\n")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def from_tuple(c: tuple) -> Constellation:
|
|
143
|
+
kwargs = {f: getattr(c, f) for f in Constellation._fields() if hasattr(c, f)}
|
|
144
|
+
c = Constellation(**kwargs)
|
|
145
|
+
return c
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass(slots=True, kw_only=True)
|
|
149
|
+
class ConstellationBorder(CatalogObject, SkyObject):
|
|
150
|
+
geometry: LineString
|
|
151
|
+
"""Shapely LineString of the border. Right ascension coordinates are in degrees (0...360)."""
|