starplot 0.13.0__py2.py3-none-any.whl → 0.15.0__py2.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.
Files changed (74) hide show
  1. starplot/__init__.py +5 -2
  2. starplot/base.py +311 -197
  3. starplot/cli.py +33 -0
  4. starplot/coordinates.py +6 -0
  5. starplot/data/__init__.py +6 -24
  6. starplot/data/bigsky.py +58 -40
  7. starplot/data/constellation_lines.py +827 -0
  8. starplot/data/constellation_stars.py +1501 -0
  9. starplot/data/constellations.py +600 -27
  10. starplot/data/db.py +17 -0
  11. starplot/data/dsos.py +24 -141
  12. starplot/data/stars.py +45 -24
  13. starplot/geod.py +0 -6
  14. starplot/geometry.py +181 -0
  15. starplot/horizon.py +302 -231
  16. starplot/map.py +100 -463
  17. starplot/mixins.py +75 -14
  18. starplot/models/__init__.py +1 -1
  19. starplot/models/base.py +18 -129
  20. starplot/models/constellation.py +55 -32
  21. starplot/models/dso.py +132 -67
  22. starplot/models/moon.py +57 -78
  23. starplot/models/planet.py +44 -69
  24. starplot/models/star.py +91 -60
  25. starplot/models/sun.py +32 -53
  26. starplot/optic.py +21 -18
  27. starplot/plotters/__init__.py +2 -0
  28. starplot/plotters/constellations.py +342 -0
  29. starplot/plotters/dsos.py +49 -68
  30. starplot/plotters/experimental.py +171 -0
  31. starplot/plotters/milkyway.py +39 -0
  32. starplot/plotters/stars.py +126 -122
  33. starplot/profile.py +16 -0
  34. starplot/settings.py +26 -0
  35. starplot/styles/__init__.py +2 -0
  36. starplot/styles/base.py +56 -34
  37. starplot/styles/ext/antique.yml +11 -9
  38. starplot/styles/ext/blue_dark.yml +8 -10
  39. starplot/styles/ext/blue_gold.yml +135 -0
  40. starplot/styles/ext/blue_light.yml +14 -12
  41. starplot/styles/ext/blue_medium.yml +23 -20
  42. starplot/styles/ext/cb_wong.yml +9 -7
  43. starplot/styles/ext/grayscale.yml +4 -3
  44. starplot/styles/ext/grayscale_dark.yml +7 -5
  45. starplot/styles/ext/map.yml +9 -6
  46. starplot/styles/ext/nord.yml +7 -7
  47. starplot/styles/ext/optic.yml +1 -1
  48. starplot/styles/extensions.py +1 -0
  49. starplot/utils.py +19 -0
  50. starplot/warnings.py +21 -0
  51. {starplot-0.13.0.dist-info → starplot-0.15.0.dist-info}/METADATA +19 -18
  52. starplot-0.15.0.dist-info/RECORD +97 -0
  53. starplot-0.15.0.dist-info/entry_points.txt +3 -0
  54. starplot/data/bayer.py +0 -3499
  55. starplot/data/flamsteed.py +0 -2682
  56. starplot/data/library/constellation_borders_inv.gpkg +0 -0
  57. starplot/data/library/constellation_lines_hips.json +0 -709
  58. starplot/data/library/constellation_lines_inv.gpkg +0 -0
  59. starplot/data/library/constellations.gpkg +0 -0
  60. starplot/data/library/constellations_hip.fab +0 -88
  61. starplot/data/library/milkyway.gpkg +0 -0
  62. starplot/data/library/milkyway_inv.gpkg +0 -0
  63. starplot/data/library/ongc.gpkg.zip +0 -0
  64. starplot/data/library/stars.bigsky.mag11.parquet +0 -0
  65. starplot/data/library/stars.hipparcos.parquet +0 -0
  66. starplot/data/messier.py +0 -111
  67. starplot/data/prep/__init__.py +0 -0
  68. starplot/data/prep/constellations.py +0 -108
  69. starplot/data/prep/dsos.py +0 -299
  70. starplot/data/prep/utils.py +0 -16
  71. starplot/models/geometry.py +0 -44
  72. starplot-0.13.0.dist-info/RECORD +0 -101
  73. {starplot-0.13.0.dist-info → starplot-0.15.0.dist-info}/LICENSE +0 -0
  74. {starplot-0.13.0.dist-info → starplot-0.15.0.dist-info}/WHEEL +0 -0
starplot/models/dso.py CHANGED
@@ -1,23 +1,39 @@
1
- from typing import Optional, Union
1
+ from typing import Optional, Union, Iterator
2
2
 
3
+ from ibis import _
3
4
  from shapely.geometry import Polygon, MultiPolygon
4
5
 
5
- from starplot.data.dsos import DsoType, load_ongc, ONGC_TYPE_MAP
6
+ from starplot.data.dsos import load
6
7
  from starplot.mixins import CreateMapMixin, CreateOpticMixin
7
- from starplot.models.base import SkyObject, SkyObjectManager
8
- from starplot.models.geometry import to_24h
9
- from starplot import geod
8
+ from starplot.models.base import SkyObject
10
9
 
11
10
 
12
- class DsoManager(SkyObjectManager):
13
- @classmethod
14
- def all(cls):
15
- all_dsos = load_ongc()
11
+ class DsoType:
12
+ """
13
+ Type of deep sky object (DSOs), as designated in OpenNGC
14
+ """
16
15
 
17
- for d in all_dsos.itertuples():
18
- magnitude = d.mag_v or d.mag_b or None
19
- magnitude = float(magnitude) if magnitude else None
20
- yield from_tuple(d)
16
+ STAR = "*"
17
+ DOUBLE_STAR = "**"
18
+ ASSOCIATION_OF_STARS = "*Ass"
19
+ OPEN_CLUSTER = "OCl"
20
+ GLOBULAR_CLUSTER = "GCl"
21
+ GALAXY = "G"
22
+ GALAXY_PAIR = "GPair"
23
+ GALAXY_TRIPLET = "GTrpl"
24
+ GROUP_OF_GALAXIES = "GGroup"
25
+ NEBULA = "Neb"
26
+ PLANETARY_NEBULA = "PN"
27
+ EMISSION_NEBULA = "EmN"
28
+ STAR_CLUSTER_NEBULA = "Cl+N"
29
+ REFLECTION_NEBULA = "RfN"
30
+ DARK_NEBULA = "DrkN"
31
+ HII_IONIZED_REGION = "HII"
32
+ SUPERNOVA_REMNANT = "SNR"
33
+ NOVA_STAR = "Nova"
34
+ NONEXISTENT = "NonEx"
35
+ UNKNOWN = "Other"
36
+ DUPLICATE_RECORD = "Dup"
21
37
 
22
38
 
23
39
  class DSO(SkyObject, CreateMapMixin, CreateOpticMixin):
@@ -26,12 +42,11 @@ class DSO(SkyObject, CreateMapMixin, CreateOpticMixin):
26
42
  So, you can use any attributes of this model in your callables. Note that some may be null.
27
43
  """
28
44
 
29
- _manager = DsoManager
30
-
31
45
  name: str
32
46
  """Name of the DSO (as specified in OpenNGC)"""
33
47
 
34
48
  type: DsoType
49
+ """Type of DSO"""
35
50
 
36
51
  magnitude: Optional[float] = None
37
52
  """Magnitude (if available)"""
@@ -64,7 +79,7 @@ class DSO(SkyObject, CreateMapMixin, CreateOpticMixin):
64
79
  """
65
80
 
66
81
  geometry: Union[Polygon, MultiPolygon] = None
67
- """Shapely Polygon of the DSO's extent. Right ascension coordinates are in 24H format."""
82
+ """Shapely Polygon of the DSO's extent. Right ascension coordinates are in degrees (0...360)."""
68
83
 
69
84
  def __init__(
70
85
  self,
@@ -81,8 +96,9 @@ class DSO(SkyObject, CreateMapMixin, CreateOpticMixin):
81
96
  ngc: str = None,
82
97
  ic: str = None,
83
98
  geometry: Union[Polygon, MultiPolygon] = None,
99
+ constellation_id: str = None,
84
100
  ) -> None:
85
- super().__init__(ra, dec)
101
+ super().__init__(ra, dec, constellation_id)
86
102
  self.name = name
87
103
  self.type = type
88
104
  self.magnitude = magnitude
@@ -99,21 +115,47 @@ class DSO(SkyObject, CreateMapMixin, CreateOpticMixin):
99
115
  return f"DSO(name={self.name}, magnitude={self.magnitude})"
100
116
 
101
117
  @classmethod
102
- def get(**kwargs) -> "DSO":
118
+ def all(cls) -> Iterator["DSO"]:
119
+ df = load().to_pandas()
120
+
121
+ for d in df.itertuples():
122
+ yield from_tuple(d)
123
+
124
+ @classmethod
125
+ def get(cls, **kwargs) -> "DSO":
103
126
  """
104
127
  Get a DSO, by matching its attributes.
105
128
 
106
- Example: `d = DSO.get(m=13)`
129
+ Example:
130
+
131
+ d = DSO.get(m=13)
107
132
 
108
133
  Args:
109
134
  **kwargs: Attributes on the DSO you want to match
110
135
 
111
136
  Raises: `ValueError` if more than one DSO is matched
112
137
  """
113
- pass
138
+ filters = []
139
+
140
+ for k, v in kwargs.items():
141
+ filters.append(getattr(_, k) == v)
142
+
143
+ df = load(filters=filters).to_pandas()
144
+
145
+ results = [from_tuple(d) for d in df.itertuples()]
146
+
147
+ if len(results) == 1:
148
+ return results[0]
149
+
150
+ if len(results) > 1:
151
+ raise ValueError(
152
+ "More than one match. Use find() instead or narrow your search."
153
+ )
154
+
155
+ return None
114
156
 
115
157
  @classmethod
116
- def find(where: list) -> list["DSO"]:
158
+ def find(cls, where: list) -> list["DSO"]:
117
159
  """
118
160
  Find DSOs
119
161
 
@@ -124,60 +166,83 @@ class DSO(SkyObject, CreateMapMixin, CreateOpticMixin):
124
166
  List of DSOs that match all `where` expressions
125
167
 
126
168
  """
127
- pass
128
-
129
-
130
- def create_ellipse(d):
131
- maj_ax, min_ax, angle = d.maj_ax, d.min_ax, d.angle
132
-
133
- if maj_ax is None:
134
- return d.geometry
135
-
136
- if angle is None:
137
- angle = 0
138
-
139
- maj_ax_degrees = (maj_ax / 60) / 2
140
-
141
- if not min_ax:
142
- min_ax_degrees = maj_ax_degrees
143
- else:
144
- min_ax_degrees = (min_ax / 60) / 2
145
-
146
- points = geod.ellipse(
147
- (d.ra_degrees / 15, d.dec_degrees),
148
- min_ax_degrees * 2,
149
- maj_ax_degrees * 2,
150
- angle,
151
- num_pts=100,
152
- )
153
-
154
- # points = [geod.to_radec(p) for p in points]
155
- points = [(round(ra, 4), round(dec, 4)) for ra, dec in points]
156
- return Polygon(points)
169
+ df = load(filters=where).to_pandas()
170
+ return [from_tuple(d) for d in df.itertuples()]
157
171
 
158
172
 
159
173
  def from_tuple(d: tuple) -> DSO:
160
- magnitude = d.mag_v or d.mag_b or None
161
- magnitude = float(magnitude) if magnitude else None
162
- geometry = d.geometry
163
-
164
- if str(geometry.geom_type) not in ["Polygon", "MultiPolygon"]:
165
- geometry = create_ellipse(d)
166
-
167
- geometry = to_24h(geometry)
168
-
169
- return DSO(
174
+ dso = DSO(
170
175
  name=d.name,
171
- ra=d.ra_degrees / 15,
172
- dec=d.dec_degrees,
173
- type=ONGC_TYPE_MAP[d.type],
176
+ ra=d.ra,
177
+ dec=d.dec,
178
+ type=d.type,
174
179
  maj_ax=d.maj_ax,
175
180
  min_ax=d.min_ax,
176
181
  angle=d.angle,
177
- magnitude=magnitude,
178
- size=d.size_deg2,
182
+ magnitude=d.magnitude,
183
+ size=d.size,
179
184
  m=d.m,
180
185
  ngc=d.ngc,
181
186
  ic=d.ic,
182
- geometry=geometry,
187
+ geometry=d.geometry,
188
+ constellation_id=d.constellation_id,
183
189
  )
190
+ dso._row_id = getattr(d, "rowid", None)
191
+ return dso
192
+
193
+
194
+ ONGC_TYPE = {
195
+ # Star Clusters ----------
196
+ DsoType.OPEN_CLUSTER: "OCl",
197
+ DsoType.GLOBULAR_CLUSTER: "GCl",
198
+ # Galaxies ----------
199
+ DsoType.GALAXY: "G",
200
+ DsoType.GALAXY_PAIR: "GPair",
201
+ DsoType.GALAXY_TRIPLET: "GTrpl",
202
+ DsoType.GROUP_OF_GALAXIES: "GGroup",
203
+ # Nebulas ----------
204
+ DsoType.NEBULA: "Neb",
205
+ DsoType.PLANETARY_NEBULA: "PN",
206
+ DsoType.EMISSION_NEBULA: "EmN",
207
+ DsoType.STAR_CLUSTER_NEBULA: "Cl+N",
208
+ DsoType.REFLECTION_NEBULA: "RfN",
209
+ # Stars ----------
210
+ DsoType.STAR: "*",
211
+ DsoType.DOUBLE_STAR: "**",
212
+ DsoType.ASSOCIATION_OF_STARS: "*Ass",
213
+ # Others
214
+ DsoType.HII_IONIZED_REGION: "HII",
215
+ DsoType.DARK_NEBULA: "DrkN",
216
+ DsoType.SUPERNOVA_REMNANT: "SNR",
217
+ DsoType.NOVA_STAR: "Nova",
218
+ DsoType.NONEXISTENT: "NonEx",
219
+ DsoType.UNKNOWN: "Other",
220
+ DsoType.DUPLICATE_RECORD: "Dup",
221
+ }
222
+
223
+ ONGC_TYPE_MAP = {v: k for k, v in ONGC_TYPE.items()}
224
+
225
+ DSO_LEGEND_LABELS = {
226
+ # Galaxies ----------
227
+ DsoType.GALAXY: "Galaxy",
228
+ DsoType.GALAXY_PAIR: "Galaxy",
229
+ DsoType.GALAXY_TRIPLET: "Galaxy",
230
+ DsoType.GROUP_OF_GALAXIES: "Galaxy",
231
+ # Nebulas ----------
232
+ DsoType.NEBULA: "Nebula",
233
+ DsoType.PLANETARY_NEBULA: "Nebula",
234
+ DsoType.EMISSION_NEBULA: "Nebula",
235
+ DsoType.STAR_CLUSTER_NEBULA: "Nebula",
236
+ DsoType.REFLECTION_NEBULA: "Nebula",
237
+ # Star Clusters ----------
238
+ DsoType.OPEN_CLUSTER: "Open Cluster",
239
+ DsoType.GLOBULAR_CLUSTER: "Globular Cluster",
240
+ # Stars ----------
241
+ DsoType.DOUBLE_STAR: "Double Star",
242
+ DsoType.ASSOCIATION_OF_STARS: "Association of stars",
243
+ DsoType.NOVA_STAR: "Nova Star",
244
+ # Others
245
+ DsoType.HII_IONIZED_REGION: "HII Ionized Region",
246
+ DsoType.DARK_NEBULA: "Dark Nebula",
247
+ DsoType.SUPERNOVA_REMNANT: "Supernova Remnant",
248
+ }
starplot/models/moon.py CHANGED
@@ -7,8 +7,8 @@ from skyfield import almanac
7
7
  from shapely import Polygon
8
8
 
9
9
  from starplot.data import load
10
- from starplot.models.base import SkyObject, SkyObjectManager
11
- from starplot.models.geometry import circle
10
+ from starplot.models.base import SkyObject
11
+ from starplot.geometry import circle
12
12
  from starplot.utils import dt_or_now
13
13
 
14
14
 
@@ -25,14 +25,50 @@ class MoonPhase(str, Enum):
25
25
  WANING_CRESCENT = "Waning Crescent"
26
26
 
27
27
 
28
- class MoonManager(SkyObjectManager):
29
- @classmethod
30
- def all(cls):
31
- raise NotImplementedError
28
+ class Moon(SkyObject):
29
+ """Moon model. Only used for Earth's moon right now, but will potentially represent other planets' moons in future versions."""
32
30
 
33
- @classmethod
34
- def find(cls):
35
- raise NotImplementedError
31
+ name: str = "Moon"
32
+ """Name of the moon"""
33
+
34
+ dt: datetime
35
+ """Date/time of moon's position"""
36
+
37
+ apparent_size: float
38
+ """Apparent size (degrees)"""
39
+
40
+ phase_angle: float
41
+ """Angle of the moon from the Sun (degrees)"""
42
+
43
+ phase_description: str
44
+ """Description of the moon's phase. The Moon will be considered New/Full/Quarter if it's within 12 hours of that precise phase."""
45
+
46
+ illumination: float
47
+ """Percent of illumination (0...1)"""
48
+
49
+ geometry: Polygon = None
50
+ """Shapely Polygon of the moon's extent. Right ascension coordinates are in degrees (0...360)."""
51
+
52
+ def __init__(
53
+ self,
54
+ ra: float,
55
+ dec: float,
56
+ name: str,
57
+ dt: datetime,
58
+ apparent_size: float,
59
+ phase_angle: float,
60
+ phase_description: str,
61
+ illumination: str,
62
+ geometry: Polygon = None,
63
+ ) -> None:
64
+ super().__init__(ra, dec)
65
+ self.name = name
66
+ self.dt = dt
67
+ self.apparent_size = apparent_size
68
+ self.phase_angle = phase_angle
69
+ self.phase_description = phase_description
70
+ self.illumination = illumination
71
+ self.geometry = geometry
36
72
 
37
73
  @classmethod
38
74
  def get(
@@ -41,7 +77,16 @@ class MoonManager(SkyObjectManager):
41
77
  lat: float = None,
42
78
  lon: float = None,
43
79
  ephemeris: str = "de421_2001.bsp",
44
- ):
80
+ ) -> "Moon":
81
+ """
82
+ Get the Moon for a specific date/time and observing location.
83
+
84
+ Args:
85
+ dt: Datetime you want the moon for (must be timezone aware!). _Defaults to current UTC time_.
86
+ lat: Latitude of observing location. If you set this (and longitude), then the Moon's _apparent_ RA/DEC will be calculated.
87
+ lon: Longitude of observing location
88
+ ephemeris: Ephemeris to use for calculating moon positions (see [Skyfield's documentation](https://rhodesmill.org/skyfield/planets.html) for details)
89
+ """
45
90
  RADIUS_KM = 1_740
46
91
 
47
92
  dt = dt_or_now(dt)
@@ -106,7 +151,7 @@ class MoonManager(SkyObjectManager):
106
151
  phase = MoonPhase.WANING_CRESCENT
107
152
 
108
153
  return Moon(
109
- ra=ra.hours,
154
+ ra=ra.hours * 15,
110
155
  dec=dec.degrees,
111
156
  name="Moon",
112
157
  dt=dt,
@@ -114,71 +159,5 @@ class MoonManager(SkyObjectManager):
114
159
  phase_angle=phase_angle,
115
160
  phase_description=phase.value,
116
161
  illumination=illumination,
117
- geometry=circle((ra.hours, dec.degrees), apparent_diameter_degrees),
162
+ geometry=circle((ra.hours * 15, dec.degrees), apparent_diameter_degrees),
118
163
  )
119
-
120
-
121
- class Moon(SkyObject):
122
- """Moon model. Only used for Earth's moon right now, but will potentially represent other planets' moons in future versions."""
123
-
124
- _manager = MoonManager
125
-
126
- name: str = "Moon"
127
- """Name of the moon"""
128
-
129
- dt: datetime
130
- """Date/time of moon's position"""
131
-
132
- apparent_size: float
133
- """Apparent size (degrees)"""
134
-
135
- phase_angle: float
136
- """Angle of the moon from the Sun (degrees)"""
137
-
138
- phase_description: str
139
- """Description of the moon's phase. The Moon will be considered New/Full/Quarter if it's within 12 hours of that precise phase."""
140
-
141
- illumination: float
142
- """Percent of illumination (0...1)"""
143
-
144
- geometry: Polygon = None
145
- """Shapely Polygon of the moon's extent. Right ascension coordinates are in 24H format."""
146
-
147
- def __init__(
148
- self,
149
- ra: float,
150
- dec: float,
151
- name: str,
152
- dt: datetime,
153
- apparent_size: float,
154
- phase_angle: float,
155
- phase_description: str,
156
- illumination: str,
157
- geometry: Polygon = None,
158
- ) -> None:
159
- super().__init__(ra, dec)
160
- self.name = name
161
- self.dt = dt
162
- self.apparent_size = apparent_size
163
- self.phase_angle = phase_angle
164
- self.phase_description = phase_description
165
- self.illumination = illumination
166
- self.geometry = geometry
167
-
168
- @classmethod
169
- def get(
170
- dt: datetime = None,
171
- lat: float = None,
172
- lon: float = None,
173
- ephemeris: str = "de421_2001.bsp",
174
- ) -> "Moon":
175
- """
176
- Get the Moon for a specific date/time and observing location.
177
-
178
- Args:
179
- dt: Datetime you want the moon for (must be timezone aware!). _Defaults to current UTC time_.
180
- lat: Latitude of observing location. If you set this (and longitude), then the Moon's _apparent_ RA/DEC will be calculated.
181
- lon: Longitude of observing location
182
- ephemeris: Ephemeris to use for calculating moon positions (see [Skyfield's documentation](https://rhodesmill.org/skyfield/planets.html) for details)
183
- """
184
- pass
starplot/models/planet.py CHANGED
@@ -8,8 +8,8 @@ from skyfield.api import Angle, wgs84
8
8
  from shapely import Polygon
9
9
 
10
10
  from starplot.data import load
11
- from starplot.models.base import SkyObject, SkyObjectManager
12
- from starplot.models.geometry import circle
11
+ from starplot.models.base import SkyObject
12
+ from starplot.geometry import circle
13
13
  from starplot.utils import dt_or_now
14
14
 
15
15
 
@@ -48,73 +48,9 @@ Retrieved on 18-APR-2024
48
48
  """
49
49
 
50
50
 
51
- class PlanetManager(SkyObjectManager):
52
- @classmethod
53
- def all(
54
- cls,
55
- dt: datetime = None,
56
- lat: float = None,
57
- lon: float = None,
58
- ephemeris: str = "de421_2001.bsp",
59
- ):
60
- dt = dt_or_now(dt)
61
- ephemeris = load(ephemeris)
62
- timescale = load.timescale().from_datetime(dt)
63
- earth = ephemeris["earth"]
64
-
65
- for p in PlanetName:
66
- planet = ephemeris[f"{p.value} barycenter"]
67
-
68
- if lat is not None and lon is not None:
69
- position = earth + wgs84.latlon(lat, lon)
70
- astrometric = position.at(timescale).observe(planet)
71
- apparent = astrometric.apparent()
72
- ra, dec, distance = apparent.radec()
73
- else:
74
- astrometric = earth.at(timescale).observe(planet)
75
- ra, dec, distance = astrometric.radec()
76
-
77
- # angular diameter:
78
- # https://rhodesmill.org/skyfield/examples.html#what-is-the-angular-diameter-of-a-planet-given-its-radius
79
- apparent_diameter_degrees = Angle(
80
- radians=np.arcsin(PLANET_RADIUS_KM[p] / distance.km) * 2.0
81
- ).degrees
82
-
83
- yield Planet(
84
- ra=ra.hours,
85
- dec=dec.degrees,
86
- name=p,
87
- dt=dt,
88
- apparent_size=apparent_diameter_degrees,
89
- geometry=circle((ra.hours, dec.degrees), apparent_diameter_degrees),
90
- )
91
-
92
- @classmethod
93
- def find(cls):
94
- raise NotImplementedError
95
-
96
- @classmethod
97
- def get(
98
- cls,
99
- name: str,
100
- dt: datetime = None,
101
- lat: float = None,
102
- lon: float = None,
103
- ephemeris: str = "de421_2001.bsp",
104
- ):
105
- dt = dt_or_now(dt)
106
- for p in cls.all(dt, lat, lon, ephemeris):
107
- if p.name.lower() == name.lower():
108
- return p
109
-
110
- return None
111
-
112
-
113
51
  class Planet(SkyObject):
114
52
  """Planet model."""
115
53
 
116
- _manager = PlanetManager
117
-
118
54
  name: str
119
55
  """
120
56
  Name of the planet:
@@ -137,7 +73,7 @@ class Planet(SkyObject):
137
73
  """Apparent size (degrees)"""
138
74
 
139
75
  geometry: Polygon = None
140
- """Shapely Polygon of the planet's extent. Right ascension coordinates are in 24H format."""
76
+ """Shapely Polygon of the planet's extent. Right ascension coordinates are in degrees (0...360)."""
141
77
 
142
78
  def __init__(
143
79
  self,
@@ -156,6 +92,7 @@ class Planet(SkyObject):
156
92
 
157
93
  @classmethod
158
94
  def all(
95
+ cls,
159
96
  dt: datetime = None,
160
97
  lat: float = None,
161
98
  lon: float = None,
@@ -170,10 +107,43 @@ class Planet(SkyObject):
170
107
  lon: Longitude of observing location
171
108
  ephemeris: Ephemeris to use for calculating planet positions (see [Skyfield's documentation](https://rhodesmill.org/skyfield/planets.html) for details)
172
109
  """
173
- pass
110
+ dt = dt_or_now(dt)
111
+ ephemeris = load(ephemeris)
112
+ timescale = load.timescale().from_datetime(dt)
113
+ earth = ephemeris["earth"]
114
+
115
+ for p in PlanetName:
116
+ planet = ephemeris[f"{p.value} barycenter"]
117
+
118
+ if lat is not None and lon is not None:
119
+ position = earth + wgs84.latlon(lat, lon)
120
+ astrometric = position.at(timescale).observe(planet)
121
+ apparent = astrometric.apparent()
122
+ ra, dec, distance = apparent.radec()
123
+ else:
124
+ astrometric = earth.at(timescale).observe(planet)
125
+ ra, dec, distance = astrometric.radec()
126
+
127
+ # angular diameter:
128
+ # https://rhodesmill.org/skyfield/examples.html#what-is-the-angular-diameter-of-a-planet-given-its-radius
129
+ apparent_diameter_degrees = Angle(
130
+ radians=np.arcsin(PLANET_RADIUS_KM[p] / distance.km) * 2.0
131
+ ).degrees
132
+
133
+ yield Planet(
134
+ ra=ra.hours * 15,
135
+ dec=dec.degrees,
136
+ name=p,
137
+ dt=dt,
138
+ apparent_size=apparent_diameter_degrees,
139
+ geometry=circle(
140
+ (ra.hours * 15, dec.degrees), apparent_diameter_degrees
141
+ ),
142
+ )
174
143
 
175
144
  @classmethod
176
145
  def get(
146
+ cls,
177
147
  name: str,
178
148
  dt: datetime = None,
179
149
  lat: float = None,
@@ -190,4 +160,9 @@ class Planet(SkyObject):
190
160
  lon: Longitude of observing location
191
161
  ephemeris: Ephemeris to use for calculating planet positions (see [Skyfield's documentation](https://rhodesmill.org/skyfield/planets.html) for details)
192
162
  """
193
- pass
163
+ dt = dt_or_now(dt)
164
+ for p in cls.all(dt, lat, lon, ephemeris):
165
+ if p.name.lower() == name.lower():
166
+ return p
167
+
168
+ return None