starplot 0.11.3__py2.py3-none-any.whl → 0.12.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 (42) hide show
  1. starplot/__init__.py +1 -1
  2. starplot/base.py +21 -1
  3. starplot/data/constellations.py +16 -1
  4. starplot/data/dsos.py +1 -1
  5. starplot/data/library/constellations.gpkg +0 -0
  6. starplot/data/library/ongc.gpkg.zip +0 -0
  7. starplot/data/prep/__init__.py +0 -0
  8. starplot/data/prep/constellations.py +108 -0
  9. starplot/data/prep/dsos.py +299 -0
  10. starplot/data/prep/utils.py +16 -0
  11. starplot/map.py +77 -81
  12. starplot/models/__init__.py +1 -1
  13. starplot/models/base.py +9 -3
  14. starplot/models/constellation.py +27 -5
  15. starplot/models/dso.py +68 -18
  16. starplot/models/geometry.py +44 -0
  17. starplot/models/moon.py +8 -0
  18. starplot/models/objects.py +5 -1
  19. starplot/models/planet.py +14 -1
  20. starplot/models/star.py +47 -7
  21. starplot/models/sun.py +14 -1
  22. starplot/optic.py +11 -4
  23. starplot/plotters/dsos.py +59 -45
  24. starplot/plotters/stars.py +15 -29
  25. starplot/styles/base.py +98 -52
  26. starplot/styles/ext/antique.yml +29 -1
  27. starplot/styles/ext/blue_dark.yml +20 -2
  28. starplot/styles/ext/blue_light.yml +29 -1
  29. starplot/styles/ext/blue_medium.yml +30 -1
  30. starplot/styles/ext/cb_wong.yml +28 -1
  31. starplot/styles/ext/color_print.yml +3 -0
  32. starplot/styles/ext/grayscale.yml +18 -1
  33. starplot/styles/ext/grayscale_dark.yml +20 -1
  34. starplot/styles/ext/nord.yml +33 -7
  35. starplot/styles/markers.py +107 -0
  36. starplot/utils.py +1 -1
  37. {starplot-0.11.3.dist-info → starplot-0.12.0.dist-info}/METADATA +4 -13
  38. starplot-0.12.0.dist-info/RECORD +67 -0
  39. starplot/data/library/de440s.bsp +0 -0
  40. starplot-0.11.3.dist-info/RECORD +0 -62
  41. {starplot-0.11.3.dist-info → starplot-0.12.0.dist-info}/LICENSE +0 -0
  42. {starplot-0.11.3.dist-info → starplot-0.12.0.dist-info}/WHEEL +0 -0
starplot/models/star.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from typing import Optional
2
2
 
3
3
  import numpy as np
4
+ from shapely import Point
4
5
 
5
6
  from starplot.models.base import SkyObject, SkyObjectManager
6
7
  from starplot.data.stars import StarCatalog, STAR_NAMES, load as _load_stars
@@ -14,14 +15,17 @@ class StarManager(SkyObjectManager):
14
15
  # TODO : add datetime kwarg
15
16
 
16
17
  for s in all_stars.itertuples():
17
- hip_id = s.Index
18
- obj = Star(ra=s.ra_hours, dec=s.dec_degrees, magnitude=s.magnitude, bv=s.bv)
18
+ yield from_tuple(s, catalog)
19
19
 
20
- if np.isfinite(hip_id):
21
- obj.hip = hip_id
22
- obj.name = STAR_NAMES.get(hip_id)
20
+ @classmethod
21
+ def find(cls, where, catalog: StarCatalog = StarCatalog.HIPPARCOS):
22
+ all_objects = cls.all(catalog)
23
+ return super().find(where=where, all_objects=all_objects)
23
24
 
24
- yield obj
25
+ @classmethod
26
+ def get(cls, catalog: StarCatalog = StarCatalog.HIPPARCOS, **kwargs):
27
+ all_objects = cls.all(catalog)
28
+ return super().get(all_objects=all_objects, **kwargs)
25
29
 
26
30
 
27
31
  class Star(SkyObject):
@@ -43,9 +47,15 @@ class Star(SkyObject):
43
47
  tyc: Optional[str] = None
44
48
  """Tycho ID, if available"""
45
49
 
50
+ ccdm: Optional[str] = None
51
+ """CCDM Component Identifier (if applicable)"""
52
+
46
53
  name: Optional[str] = None
47
54
  """Name, if available"""
48
55
 
56
+ geometry: Point = None
57
+ """Shapely Point of the star's position. Right ascension coordinates are in 24H format."""
58
+
49
59
  def __init__(
50
60
  self,
51
61
  ra: float,
@@ -55,13 +65,17 @@ class Star(SkyObject):
55
65
  hip: int = None,
56
66
  name: str = None,
57
67
  tyc: str = None,
68
+ ccdm: str = None,
69
+ geometry: Point = None,
58
70
  ) -> None:
59
71
  super().__init__(ra, dec)
60
72
  self.magnitude = magnitude
61
73
  self.bv = bv
62
- self.hip = hip
74
+ self.hip = hip if hip is not None and np.isfinite(hip) else None
63
75
  self.name = name
64
76
  self.tyc = tyc
77
+ self.ccdm = ccdm
78
+ self.geometry = Point([ra, dec])
65
79
 
66
80
  def __repr__(self) -> str:
67
81
  return f"Star(hip={self.hip}, tyc={self.tyc}, magnitude={self.magnitude}, ra={self.ra}, dec={self.dec})"
@@ -93,3 +107,29 @@ class Star(SkyObject):
93
107
 
94
108
  """
95
109
  pass
110
+
111
+
112
+ def from_tuple(star: tuple, catalog: StarCatalog) -> Star:
113
+ m = star.magnitude
114
+ ra, dec = star.ra_hours, star.dec_degrees
115
+
116
+ if catalog == StarCatalog.HIPPARCOS:
117
+ hip_id = star.Index
118
+ tyc_id = None
119
+ ccdm = None
120
+ else:
121
+ hip_id = star.hip
122
+ tyc_id = star.Index
123
+ ccdm = star.ccdm
124
+
125
+ return Star(
126
+ ra=ra,
127
+ dec=dec,
128
+ magnitude=m,
129
+ bv=star.bv,
130
+ hip=hip_id,
131
+ tyc=tyc_id,
132
+ ccdm=ccdm,
133
+ name=STAR_NAMES.get(hip_id) if np.isfinite(hip_id) else None,
134
+ geometry=Point([ra, dec]),
135
+ )
starplot/models/sun.py CHANGED
@@ -2,9 +2,11 @@ from datetime import datetime
2
2
 
3
3
  import numpy as np
4
4
  from skyfield.api import Angle, wgs84
5
+ from shapely import Polygon
5
6
 
6
7
  from starplot.data import load
7
8
  from starplot.models.base import SkyObject, SkyObjectManager
9
+ from starplot.models.geometry import circle
8
10
  from starplot.utils import dt_or_now
9
11
 
10
12
 
@@ -51,6 +53,7 @@ class SunManager(SkyObjectManager):
51
53
  name="Sun",
52
54
  dt=dt,
53
55
  apparent_size=apparent_diameter_degrees,
56
+ geometry=circle((ra.hours, dec.degrees), apparent_diameter_degrees),
54
57
  )
55
58
 
56
59
 
@@ -68,13 +71,23 @@ class Sun(SkyObject):
68
71
  apparent_size: float
69
72
  """Apparent size (degrees)"""
70
73
 
74
+ geometry: Polygon = None
75
+ """Shapely Polygon of the Sun's extent. Right ascension coordinates are in 24H format."""
76
+
71
77
  def __init__(
72
- self, ra: float, dec: float, name: str, dt: datetime, apparent_size: float
78
+ self,
79
+ ra: float,
80
+ dec: float,
81
+ name: str,
82
+ dt: datetime,
83
+ apparent_size: float,
84
+ geometry: Polygon = None,
73
85
  ) -> None:
74
86
  super().__init__(ra, dec)
75
87
  self.name = name
76
88
  self.dt = dt
77
89
  self.apparent_size = apparent_size
90
+ self.geometry = geometry
78
91
 
79
92
  @classmethod
80
93
  def get(
starplot/optic.py CHANGED
@@ -14,7 +14,14 @@ from starplot.mixins import ExtentMaskMixin
14
14
  from starplot.models import Star
15
15
  from starplot.optics import Optic
16
16
  from starplot.plotters import StarPlotterMixin, DsoPlotterMixin
17
- from starplot.styles import PlotStyle, ObjectStyle, LabelStyle, extensions, use_style
17
+ from starplot.styles import (
18
+ PlotStyle,
19
+ ObjectStyle,
20
+ LabelStyle,
21
+ extensions,
22
+ use_style,
23
+ ZOrderEnum,
24
+ )
18
25
  from starplot.utils import azimuth_to_string
19
26
 
20
27
  pd.options.mode.chained_assignment = None # default='warn'
@@ -315,7 +322,7 @@ class OpticPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
315
322
  facecolor=self.style.background_color.as_hex(),
316
323
  linewidth=0,
317
324
  fill=True,
318
- zorder=-1000,
325
+ zorder=ZOrderEnum.LAYER_1,
319
326
  )
320
327
  self.ax.add_patch(self._background_clip_path)
321
328
 
@@ -326,7 +333,7 @@ class OpticPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
326
333
  linewidth=2 * self._size_multiplier,
327
334
  edgecolor=self.style.border_line_color.as_hex(),
328
335
  fill=False,
329
- zorder=128,
336
+ zorder=ZOrderEnum.LAYER_5 + 100,
330
337
  )
331
338
  self.ax.add_patch(inner_border)
332
339
 
@@ -338,7 +345,7 @@ class OpticPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
338
345
  linewidth=20 * self._size_multiplier,
339
346
  edgecolor=self.style.border_bg_color.as_hex(),
340
347
  fill=False,
341
- zorder=64,
348
+ zorder=ZOrderEnum.LAYER_5,
342
349
  )
343
350
  self.ax.add_patch(outer_border)
344
351
 
starplot/plotters/dsos.py CHANGED
@@ -1,19 +1,26 @@
1
+ from functools import cache
1
2
  from typing import Callable, Mapping
2
3
 
3
4
  from starplot.data.dsos import (
4
5
  DsoType,
5
- DEFAULT_DSO_TYPES,
6
- ONGC_TYPE,
7
6
  ONGC_TYPE_MAP,
8
7
  DSO_LEGEND_LABELS,
9
8
  DSO_LABELS_DEFAULT,
10
9
  DsoLabelMaker,
11
10
  load_ongc,
12
11
  )
13
- from starplot.models import DSO
12
+ from starplot.models.dso import DSO, from_tuple
14
13
  from starplot.styles import MarkerSymbolEnum
15
14
 
16
15
 
16
+ def _where(*args, **kwargs):
17
+ where = kwargs.pop("where", [])
18
+
19
+ if mag := kwargs.pop("mag", None):
20
+ where.append(DSO.magnitude.is_null() | (DSO.magnitude <= mag))
21
+ return where
22
+
23
+
17
24
  class DsoPlotterMixin:
18
25
  def _plot_dso_polygon(self, polygon, style):
19
26
  coords = list(zip(*polygon.exterior.coords.xy))
@@ -22,21 +29,16 @@ class DsoPlotterMixin:
22
29
  coords.append(coords[0])
23
30
  self._polygon(coords, style.marker.to_polygon_style(), closed=False)
24
31
 
25
- def _dso_from_df_tuple(self, d):
26
- magnitude = d.mag_v or d.mag_b or None
27
- magnitude = float(magnitude) if magnitude else None
28
- return DSO(
29
- name=d.name,
30
- ra=d.ra_degrees / 15,
31
- dec=d.dec_degrees,
32
- type=ONGC_TYPE_MAP[d.type],
33
- maj_ax=d.maj_ax,
34
- min_ax=d.min_ax,
35
- angle=d.angle,
36
- magnitude=magnitude,
37
- size=d.size_deg2,
38
- m=d.m,
39
- )
32
+ def messier(self, *args, **kwargs):
33
+ """
34
+ Plots Messier objects
35
+
36
+ This is just a small wrapper around the `dsos()` function, so any `kwargs` will be passed through.
37
+ """
38
+ where = _where(**kwargs)
39
+ where.append(DSO.m.is_not_null())
40
+ kwargs.pop("where", None)
41
+ self.dsos(where=where, **kwargs)
40
42
 
41
43
  def open_clusters(self, *args, **kwargs):
42
44
  """
@@ -44,7 +46,10 @@ class DsoPlotterMixin:
44
46
 
45
47
  This is just a small wrapper around the `dsos()` function, so any `kwargs` will be passed through.
46
48
  """
47
- self.dsos(types=[DsoType.OPEN_CLUSTER], **kwargs)
49
+ where = _where(**kwargs)
50
+ where.append(DSO.type == DsoType.OPEN_CLUSTER)
51
+ kwargs.pop("where", None)
52
+ self.dsos(where=where, **kwargs)
48
53
 
49
54
  def globular_clusters(self, *args, **kwargs):
50
55
  """
@@ -52,7 +57,10 @@ class DsoPlotterMixin:
52
57
 
53
58
  This is just a small wrapper around the `dsos()` function, so any `kwargs` will be passed through.
54
59
  """
55
- self.dsos(types=[DsoType.GLOBULAR_CLUSTER], **kwargs)
60
+ where = _where(**kwargs)
61
+ where.append(DSO.type == DsoType.GLOBULAR_CLUSTER)
62
+ kwargs.pop("where", None)
63
+ self.dsos(where=where, **kwargs)
56
64
 
57
65
  def galaxies(self, *args, **kwargs):
58
66
  """
@@ -64,14 +72,15 @@ class DsoPlotterMixin:
64
72
 
65
73
  This is just a small wrapper around the `dsos()` function, so any `kwargs` will be passed through.
66
74
  """
67
- self.dsos(
68
- types=[
69
- DsoType.GALAXY,
70
- DsoType.GALAXY_PAIR,
71
- DsoType.GALAXY_TRIPLET,
72
- ],
73
- **kwargs,
74
- )
75
+ galaxy_types = [
76
+ DsoType.GALAXY,
77
+ DsoType.GALAXY_PAIR,
78
+ DsoType.GALAXY_TRIPLET,
79
+ ]
80
+ where = _where(**kwargs)
81
+ where.append(DSO.type.is_in(galaxy_types))
82
+ kwargs.pop("where", None)
83
+ self.dsos(where=where, **kwargs)
75
84
 
76
85
  def nebula(self, *args, **kwargs):
77
86
  """
@@ -85,25 +94,30 @@ class DsoPlotterMixin:
85
94
 
86
95
  This is just a small wrapper around the `dsos()` function, so any `kwargs` will be passed through.
87
96
  """
88
- self.dsos(
89
- types=[
90
- DsoType.NEBULA,
91
- DsoType.PLANETARY_NEBULA,
92
- DsoType.EMISSION_NEBULA,
93
- DsoType.STAR_CLUSTER_NEBULA,
94
- DsoType.REFLECTION_NEBULA,
95
- ],
96
- **kwargs,
97
- )
97
+ nebula_types = [
98
+ DsoType.NEBULA,
99
+ DsoType.PLANETARY_NEBULA,
100
+ DsoType.EMISSION_NEBULA,
101
+ DsoType.STAR_CLUSTER_NEBULA,
102
+ DsoType.REFLECTION_NEBULA,
103
+ ]
104
+ where = _where(**kwargs)
105
+ where.append(DSO.type.is_in(nebula_types))
106
+ kwargs.pop("where", None)
107
+ self.dsos(where=where, **kwargs)
108
+
109
+ @cache
110
+ def _load_dsos(self):
111
+ return load_ongc(bbox=self._extent_mask())
98
112
 
99
113
  def dsos(
100
114
  self,
101
115
  mag: float = 8.0,
102
- types: list[DsoType] = DEFAULT_DSO_TYPES,
103
116
  true_size: bool = True,
104
117
  labels: Mapping[str, str] = DSO_LABELS_DEFAULT,
105
118
  legend_labels: Mapping[DsoType, str] = DSO_LEGEND_LABELS,
106
119
  alpha_fn: Callable[[DSO], float] = None,
120
+ label_fn: Callable[[DSO], str] = None,
107
121
  where: list = None,
108
122
  where_labels: list = None,
109
123
  ):
@@ -112,11 +126,11 @@ class DsoPlotterMixin:
112
126
 
113
127
  Args:
114
128
  mag: Limiting magnitude of DSOs to plot. For more control of what DSOs to plot, use the `where` kwarg. **Note:** if you pass `mag` and `where` then `mag` will be ignored
115
- types: List of DSO types to plot
116
129
  true_size: If True, then each DSO will be plotted as its true apparent size in the sky (note: this increases plotting time). If False, then the style's marker size will be used. Also, keep in mind not all DSOs have a defined size (according to OpenNGC) -- so these will use the style's marker size.
117
130
  labels: A dictionary that maps DSO names (as specified in OpenNGC) to the label that'll be plotted for that object. By default, the DSO's name in OpenNGC will be used as the label. If you want to hide all labels, then set this arg to `None`.
118
131
  legend_labels: A dictionary that maps a `DsoType` to the legend label that'll be plotted for that type of DSO. If you want to hide all DSO legend labels, then set this arg to `None`.
119
132
  alpha_fn: Callable for calculating the alpha value (aka "opacity") of each DSO. If `None`, then the marker style's alpha will be used.
133
+ label_fn: Callable for determining the label of each DSO. If `None`, then the names in the `labels` kwarg will be used.
120
134
  where: A list of expressions that determine which DSOs to plot. See [Selecting Objects](/reference-selecting-objects/) for details.
121
135
  where_labels: A list of expressions that determine which DSOs are labeled on the plot. See [Selecting Objects](/reference-selecting-objects/) for details.
122
136
  """
@@ -141,21 +155,21 @@ class DsoPlotterMixin:
141
155
  else:
142
156
  legend_labels = {**DSO_LEGEND_LABELS, **legend_labels}
143
157
 
144
- nearby_dsos = load_ongc(bbox=self._extent_mask())
145
- dso_types = [ONGC_TYPE[dtype] for dtype in types]
146
- nearby_dsos = nearby_dsos[nearby_dsos["type"].isin(dso_types)]
158
+ nearby_dsos = self._load_dsos() # load_ongc(bbox=self._extent_mask())
159
+ # dso_types = [ONGC_TYPE[dtype] for dtype in types]
160
+ # nearby_dsos = nearby_dsos[nearby_dsos["type"].isin(dso_types)]
147
161
 
148
162
  for d in nearby_dsos.itertuples():
149
163
  ra = d.ra_degrees
150
164
  dec = d.dec_degrees
151
- label = labels.get(d.name)
152
165
  dso_type = ONGC_TYPE_MAP[d.type]
153
166
  style = self.style.get_dso_style(dso_type)
154
167
  maj_ax, min_ax, angle = d.maj_ax, d.min_ax, d.angle
155
168
  legend_label = legend_labels.get(dso_type)
156
169
  magnitude = d.mag_v or d.mag_b or None
157
170
  magnitude = float(magnitude) if magnitude else None
158
- _dso = self._dso_from_df_tuple(d)
171
+ _dso = from_tuple(d)
172
+ label = labels.get(d.name) if label_fn is None else label_fn(_dso)
159
173
 
160
174
  if any(
161
175
  [
@@ -1,14 +1,12 @@
1
1
  from typing import Callable, Mapping
2
2
  from functools import cache
3
3
 
4
- import numpy as np
5
-
6
4
  from skyfield.api import Star as SkyfieldStar
7
5
 
8
6
  from starplot import callables
9
7
  from starplot.data import bayer, stars
10
8
  from starplot.data.stars import StarCatalog, STAR_NAMES
11
- from starplot.models import Star
9
+ from starplot.models.star import Star, from_tuple
12
10
  from starplot.styles import ObjectStyle, LabelStyle, use_style
13
11
 
14
12
 
@@ -83,16 +81,17 @@ class StarPlotterMixin:
83
81
  style: LabelStyle,
84
82
  labels: Mapping[str, str],
85
83
  bayer_labels: bool,
84
+ label_fn: Callable[[Star], str],
86
85
  ):
87
86
  for s in star_objects:
88
87
  if where_labels and not all([e.evaluate(s) for e in where_labels]):
89
88
  continue
90
89
 
91
- name = labels.get(s.hip)
90
+ label = labels.get(s.hip) if label_fn is None else label_fn(s)
92
91
  bayer_desig = bayer.hip.get(s.hip)
93
92
 
94
- if name:
95
- self.text(name, s.ra, s.dec, style)
93
+ if label:
94
+ self.text(label, s.ra, s.dec, style)
96
95
 
97
96
  if bayer_labels and bayer_desig:
98
97
  self.text(bayer_desig, s.ra, s.dec, self.style.bayer_labels)
@@ -114,6 +113,7 @@ class StarPlotterMixin:
114
113
  size_fn: Callable[[Star], float] = callables.size_by_magnitude,
115
114
  alpha_fn: Callable[[Star], float] = callables.alpha_by_magnitude,
116
115
  color_fn: Callable[[Star], str] = None,
116
+ label_fn: Callable[[Star], str] = None,
117
117
  where: list = None,
118
118
  where_labels: list = None,
119
119
  labels: Mapping[int, str] = STAR_NAMES,
@@ -133,6 +133,7 @@ class StarPlotterMixin:
133
133
  size_fn: Callable for calculating the marker size of each star. If `None`, then the marker style's size will be used.
134
134
  alpha_fn: Callable for calculating the alpha value (aka "opacity") of each star. If `None`, then the marker style's alpha will be used.
135
135
  color_fn: Callable for calculating the color of each star. If `None`, then the marker style's color will be used.
136
+ label_fn: Callable for determining the label of each star. If `None`, then the names in the `labels` kwarg will be used.
136
137
  where: A list of expressions that determine which stars to plot. See [Selecting Objects](/reference-selecting-objects/) for details.
137
138
  where_labels: A list of expressions that determine which stars are labeled on the plot. See [Selecting Objects](/reference-selecting-objects/) for details.
138
139
  labels: A dictionary that maps a star's HIP id to the label that'll be plotted for that star. If you want to hide name labels, then set this arg to `None`.
@@ -174,28 +175,7 @@ class StarPlotterMixin:
174
175
  starz = []
175
176
 
176
177
  for star in nearby_stars_df.itertuples():
177
- m = star.magnitude
178
- ra, dec = star.ra, star.dec
179
-
180
- if catalog == StarCatalog.HIPPARCOS:
181
- hip_id = star.Index
182
- tyc_id = None
183
- else:
184
- hip_id = star.hip
185
- tyc_id = star.Index
186
-
187
- obj = Star(
188
- ra=ra / 15,
189
- dec=dec,
190
- magnitude=m,
191
- bv=star.bv,
192
- hip=hip_id,
193
- tyc=tyc_id,
194
- )
195
-
196
- if np.isfinite(hip_id):
197
- obj.hip = hip_id
198
- obj.name = STAR_NAMES.get(hip_id)
178
+ obj = from_tuple(star, catalog)
199
179
 
200
180
  if not all([e.evaluate(obj) for e in where]):
201
181
  continue
@@ -208,6 +188,10 @@ class StarPlotterMixin:
208
188
 
209
189
  starz.sort(key=lambda s: s[2], reverse=True) # sort by descending size
210
190
 
191
+ if not starz:
192
+ self.logger.debug(f"Star count = {len(starz)}")
193
+ return
194
+
211
195
  x, y, sizes, alphas, colors, star_objects = zip(*starz)
212
196
 
213
197
  self._objects.stars.extend(star_objects)
@@ -231,4 +215,6 @@ class StarPlotterMixin:
231
215
 
232
216
  self._add_legend_handle_marker(legend_label, style.marker)
233
217
 
234
- self._star_labels(star_objects, where_labels, style.label, labels, bayer_labels)
218
+ self._star_labels(
219
+ star_objects, where_labels, style.label, labels, bayer_labels, label_fn
220
+ )