starplot 0.15.5__py2.py3-none-any.whl → 0.15.7__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.
starplot/models/star.py CHANGED
@@ -1,11 +1,11 @@
1
1
  import math
2
- from typing import Optional, Union, Iterator
2
+ from typing import Optional, Union, Iterator, Any
3
3
 
4
4
  import numpy as np
5
- from shapely import Point
6
5
  from ibis import _
6
+ from pydantic import field_validator
7
7
 
8
- from starplot.models.base import SkyObject
8
+ from starplot.models.base import SkyObject, ShapelyPoint
9
9
  from starplot.data.stars import StarCatalog, load as _load_stars
10
10
 
11
11
 
@@ -38,38 +38,20 @@ class Star(SkyObject):
38
38
  flamsteed: Optional[int] = None
39
39
  """Flamsteed number, if available"""
40
40
 
41
- geometry: Point = None
41
+ geometry: ShapelyPoint = None
42
42
  """Shapely Point of the star's position. Right ascension coordinates are in degrees (0...360)."""
43
43
 
44
- def __init__(
45
- self,
46
- ra: float,
47
- dec: float,
48
- magnitude: float,
49
- bv: float = None,
50
- hip: int = None,
51
- name: str = None,
52
- tyc: str = None,
53
- ccdm: str = None,
54
- geometry: Point = None,
55
- constellation_id: str = None,
56
- bayer: str = None,
57
- flamsteed: int = None,
58
- ) -> None:
59
- super().__init__(ra, dec, constellation_id)
60
- self.magnitude = magnitude
61
- self.bv = bv
62
- self.hip = hip if hip is not None and np.isfinite(hip) else None
63
- self.name = name
64
- self.tyc = tyc
65
- self.ccdm = ccdm
66
- self.geometry = geometry
67
-
68
- if bayer:
69
- self.bayer = bayer
70
-
71
- if flamsteed and not math.isnan(flamsteed):
72
- self.flamsteed = int(flamsteed)
44
+ @field_validator("flamsteed", "hip", mode="before")
45
+ @classmethod
46
+ def nan(cls, value: int) -> int:
47
+ if not value or math.isnan(value):
48
+ return None
49
+
50
+ return int(value)
51
+
52
+ def model_post_init(self, context: Any) -> None:
53
+ self.bayer = self.bayer or None
54
+ self.hip = self.hip if self.hip is not None and np.isfinite(self.hip) else None
73
55
 
74
56
  def __repr__(self) -> str:
75
57
  return f"Star(hip={self.hip}, tyc={self.tyc}, magnitude={self.magnitude}, ra={self.ra}, dec={self.dec})"
@@ -83,7 +65,7 @@ class Star(SkyObject):
83
65
 
84
66
  @classmethod
85
67
  def get(
86
- cls, catalog: StarCatalog = StarCatalog.BIG_SKY_MAG11, **kwargs
68
+ cls, catalog: StarCatalog = StarCatalog.BIG_SKY_MAG11, sql: str = None, **kwargs
87
69
  ) -> Union["Star", None]:
88
70
  """
89
71
  Get a Star, by matching its attributes as specified in `**kwargs`
@@ -94,6 +76,7 @@ class Star(SkyObject):
94
76
 
95
77
  Args:
96
78
  catalog: The catalog of stars to use: "big-sky-mag11", or "big-sky" -- see [`StarCatalog`](/reference-data/#starplot.data.stars.StarCatalog) for details
79
+ sql: SQL query for selecting star (table name is "_")
97
80
  **kwargs: Attributes on the star you want to match
98
81
 
99
82
  Raises: `ValueError` if more than one star is matched
@@ -109,6 +92,7 @@ class Star(SkyObject):
109
92
  df = _load_stars(
110
93
  catalog=catalog,
111
94
  filters=filters,
95
+ sql=sql,
112
96
  ).to_pandas()
113
97
 
114
98
  results = [from_tuple(s) for s in df.itertuples()]
@@ -125,13 +109,17 @@ class Star(SkyObject):
125
109
 
126
110
  @classmethod
127
111
  def find(
128
- cls, where: list, catalog: StarCatalog = StarCatalog.BIG_SKY_MAG11
112
+ cls,
113
+ where: list = None,
114
+ sql: str = None,
115
+ catalog: StarCatalog = StarCatalog.BIG_SKY_MAG11,
129
116
  ) -> list["Star"]:
130
117
  """
131
118
  Find Stars
132
119
 
133
120
  Args:
134
121
  where: A list of expressions that determine which stars to find. See [Selecting Objects](/reference-selecting-objects/) for details.
122
+ sql: SQL query for selecting stars (table name is "_")
135
123
  catalog: The catalog of stars to use: "big-sky-mag11", or "big-sky" -- see [`StarCatalog`](/reference-data/#starplot.data.stars.StarCatalog) for details
136
124
 
137
125
  Returns:
@@ -141,6 +129,7 @@ class Star(SkyObject):
141
129
  df = _load_stars(
142
130
  catalog=catalog,
143
131
  filters=where,
132
+ sql=sql,
144
133
  ).to_pandas()
145
134
 
146
135
  return [from_tuple(s) for s in df.itertuples()]
@@ -157,10 +146,10 @@ def from_tuple(star: tuple) -> Star:
157
146
  ccdm=getattr(star, "ccdm", None),
158
147
  name=getattr(star, "name", None),
159
148
  geometry=star.geometry,
160
- constellation_id=getattr(star, "constellation", None),
161
149
  bayer=getattr(star, "bayer", None),
162
150
  flamsteed=getattr(star, "flamsteed", None),
163
151
  )
152
+ s._constellation_id = getattr(star, "constellation", None)
164
153
  s._row_id = getattr(star, "rowid", None)
165
154
 
166
155
  return s
starplot/models/sun.py CHANGED
@@ -2,10 +2,9 @@ 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
6
5
 
7
6
  from starplot.data import load
8
- from starplot.models.base import SkyObject
7
+ from starplot.models.base import SkyObject, ShapelyPolygon
9
8
  from starplot.geometry import circle
10
9
  from starplot.utils import dt_or_now
11
10
 
@@ -22,24 +21,9 @@ class Sun(SkyObject):
22
21
  apparent_size: float
23
22
  """Apparent size (degrees)"""
24
23
 
25
- geometry: Polygon = None
24
+ geometry: ShapelyPolygon = None
26
25
  """Shapely Polygon of the Sun's extent. Right ascension coordinates are in degrees (0...360)."""
27
26
 
28
- def __init__(
29
- self,
30
- ra: float,
31
- dec: float,
32
- name: str,
33
- dt: datetime,
34
- apparent_size: float,
35
- geometry: Polygon = None,
36
- ) -> None:
37
- super().__init__(ra, dec)
38
- self.name = name
39
- self.dt = dt
40
- self.apparent_size = apparent_size
41
- self.geometry = geometry
42
-
43
27
  @classmethod
44
28
  def get(
45
29
  cls,
starplot/optic.py CHANGED
@@ -220,6 +220,7 @@ class OpticPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
220
220
  legend_label: str = "Star",
221
221
  bayer_labels: bool = False,
222
222
  flamsteed_labels: bool = False,
223
+ sql: str = None,
223
224
  *args,
224
225
  **kwargs,
225
226
  ):
@@ -229,7 +230,7 @@ class OpticPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
229
230
  Args:
230
231
  where: A list of expressions that determine which stars to plot. See [Selecting Objects](/reference-selecting-objects/) for details.
231
232
  where_labels: A list of expressions that determine which stars are labeled on the plot. See [Selecting Objects](/reference-selecting-objects/) for details.
232
- catalog: The catalog of stars to use
233
+ catalog: The catalog of stars to use: "big-sky-mag11", or "big-sky" -- see [star catalogs](/data/star-catalogs/) for details
233
234
  style: If `None`, then the plot's style for stars will be used
234
235
  rasterize: If True, then the stars will be rasterized when plotted, which can speed up exporting to SVG and reduce the file size but with a loss of image quality
235
236
  size_fn: Callable for calculating the marker size of each star. If `None`, then the marker style's size will be used.
@@ -239,6 +240,7 @@ class OpticPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
239
240
  legend_label: Label for stars in the legend. If `None`, then they will not be in the legend.
240
241
  bayer_labels: If True, then Bayer labels for stars will be plotted.
241
242
  flamsteed_labels: If True, then Flamsteed number labels for stars will be plotted.
243
+ sql: SQL query for selecting stars (table name is `_`). This query will be applied _after_ any filters in the `where` kwarg.
242
244
  """
243
245
  optic_star_multiplier = self.FIELD_OF_VIEW_MAX / self.optic.true_fov
244
246
  size_fn_mx = None
starplot/optics.py CHANGED
@@ -3,18 +3,15 @@ import math
3
3
  from abc import ABC, abstractmethod
4
4
 
5
5
  import pyproj
6
-
7
6
  from matplotlib import patches
7
+ from pydantic import BaseModel, computed_field
8
8
 
9
9
  from starplot.utils import in_circle
10
10
 
11
11
 
12
- class Optic(ABC):
12
+ class Optic(BaseModel, ABC):
13
13
  """Abstract class for defining Optics."""
14
14
 
15
- def __init__(self) -> "Optic":
16
- pass
17
-
18
15
  def __str__(self):
19
16
  return "Optic"
20
17
 
@@ -70,18 +67,34 @@ class Scope(Optic):
70
67
  Scope: A new instance of a Scope optic
71
68
  """
72
69
 
73
- def __init__(
74
- self, focal_length: float, eyepiece_focal_length: float, eyepiece_fov: float
75
- ) -> "Scope":
76
- self.focal_length = focal_length
77
- self.eyepiece_focal_length = eyepiece_focal_length
78
- self.eyepiece_fov = eyepiece_fov
79
- self.magnification = self.focal_length / self.eyepiece_focal_length
80
- self.true_fov = self.eyepiece_fov / self.magnification
81
- self.radius = self._compute_radius(self.true_fov / 2)
70
+ focal_length: float
71
+ """Focal length (mm) of the telescope"""
72
+
73
+ eyepiece_focal_length: float
74
+ """Focal length (mm) of the eyepiece"""
75
+
76
+ eyepiece_fov: float
77
+ """Field of view (degrees) of the eyepiece"""
78
+
79
+ @computed_field
80
+ @property
81
+ def magnification(self) -> float:
82
+ """Magnification calculated from the telescope's focal length and eyepiece focal length"""
83
+ return self.focal_length / self.eyepiece_focal_length
84
+
85
+ @computed_field
86
+ @property
87
+ def true_fov(self) -> float:
88
+ """True field of view of telescope"""
89
+ return self.eyepiece_fov / self.magnification
90
+
91
+ @computed_field
92
+ @property
93
+ def radius(self) -> float:
94
+ return self._compute_radius(self.true_fov / 2)
82
95
 
83
96
  def __str__(self):
84
- return f"{self.focal_length}mm w/ {self.eyepiece_focal_length}mm ({self.magnification:.0f}x) @ {self.eyepiece_fov:.0f}\N{DEGREE SIGN} = {self.true_fov:.2f}\N{DEGREE SIGN} TFOV"
97
+ return f"{self.focal_length:.0f}mm w/ {self.eyepiece_focal_length:.0f}mm ({self.magnification:.0f}x) @ {self.eyepiece_fov:.0f}\N{DEGREE SIGN} = {self.true_fov:.2f}\N{DEGREE SIGN} TFOV"
85
98
 
86
99
  @property
87
100
  def xlim(self):
@@ -172,14 +185,25 @@ class Binoculars(Optic):
172
185
 
173
186
  """
174
187
 
175
- def __init__(self, magnification: float, fov: float) -> "Binoculars":
176
- self.magnification = magnification
177
- self.apparent_fov = fov
178
- self.true_fov = self.apparent_fov / self.magnification
179
- self.radius = self._compute_radius(self.true_fov / 2)
188
+ magnification: float
189
+ """Magnification of the binoculars"""
190
+
191
+ fov: float
192
+ """Apparent field of view of the binoculars"""
193
+
194
+ @computed_field
195
+ @property
196
+ def true_fov(self) -> float:
197
+ """True field of view of binoculars"""
198
+ return self.fov / self.magnification
199
+
200
+ @computed_field
201
+ @property
202
+ def radius(self) -> float:
203
+ return self._compute_radius(self.true_fov / 2)
180
204
 
181
205
  def __str__(self):
182
- return f"{self.magnification}x @ {self.apparent_fov}\N{DEGREE SIGN} = {self.true_fov}\N{DEGREE SIGN}"
206
+ return f"{self.magnification:.0f}x @ {self.fov:.0f}\N{DEGREE SIGN} = {self.true_fov}\N{DEGREE SIGN}"
183
207
 
184
208
  @property
185
209
  def xlim(self):
@@ -225,39 +249,56 @@ class Camera(Optic):
225
249
  sensor_height: Height of camera sensor (mm)
226
250
  sensor_width: Width of camera sensor (mm)
227
251
  lens_focal_length: Focal length of camera lens (mm)
228
- rotation: Angle (degrees) to rotate camera
252
+ rotation: Angle (degrees) to rotate camera, defaults to 0
229
253
 
230
254
  Returns:
231
255
  Camera: A new instance of a Camera optic
232
256
 
233
257
  """
234
258
 
235
- def __init__(
236
- self,
237
- sensor_height: float,
238
- sensor_width: float,
239
- lens_focal_length: float,
240
- rotation: float = 0,
241
- ) -> "Camera":
242
- self.sensor_height = sensor_height
243
- self.sensor_width = sensor_width
244
- self.lens_focal_length = lens_focal_length
245
-
246
- self.true_fov_x = 2 * math.degrees(
259
+ sensor_height: float
260
+ """Height (mm) of camera's sensor"""
261
+
262
+ sensor_width: float
263
+ """Width (mm) of camera's sensor"""
264
+
265
+ lens_focal_length: float
266
+ """Focal length (mm) of the camera's lens"""
267
+
268
+ rotation: float = 0
269
+ """Angle (degrees) to rotate the camera"""
270
+
271
+ @computed_field
272
+ @property
273
+ def true_fov_x(self) -> float:
274
+ return 2 * math.degrees(
247
275
  math.atan(self.sensor_width / (2 * self.lens_focal_length))
248
276
  )
249
- self.true_fov_y = 2 * math.degrees(
277
+
278
+ @computed_field
279
+ @property
280
+ def true_fov_y(self) -> float:
281
+ return 2 * math.degrees(
250
282
  math.atan(self.sensor_height / (2 * self.lens_focal_length))
251
283
  )
252
- self.true_fov = max(self.true_fov_x, self.true_fov_y)
253
284
 
254
- self.radius_x = self._compute_radius(self.true_fov_x / 2)
255
- self.radius_y = self._compute_radius(self.true_fov_y / 2)
285
+ @computed_field
286
+ @property
287
+ def true_fov(self) -> float:
288
+ return max(self.true_fov_x, self.true_fov_y)
289
+
290
+ @computed_field
291
+ @property
292
+ def radius_x(self) -> float:
293
+ return self._compute_radius(self.true_fov_x / 2)
256
294
 
257
- self.rotation = rotation
295
+ @computed_field
296
+ @property
297
+ def radius_y(self) -> float:
298
+ return self._compute_radius(self.true_fov_y / 2)
258
299
 
259
300
  def __str__(self):
260
- return f"{self.sensor_width}x{self.sensor_height} w/ {self.lens_focal_length}mm lens = {self.true_fov_x:.2f}\N{DEGREE SIGN} x {self.true_fov_y:.2f}\N{DEGREE SIGN}"
301
+ return f"{self.sensor_width}x{self.sensor_height} w/ {self.lens_focal_length:.0f}mm lens = {self.true_fov_x:.2f}\N{DEGREE SIGN} x {self.true_fov_y:.2f}\N{DEGREE SIGN}"
261
302
 
262
303
  @property
263
304
  def xlim(self):
@@ -12,9 +12,9 @@ from starplot.data import constellations as condata, constellation_lines as conl
12
12
  from starplot.data.stars import load as load_stars, StarCatalog
13
13
  from starplot.data.constellations import (
14
14
  CONSTELLATIONS_FULL_NAMES,
15
- CONSTELLATION_HIP_IDS,
16
15
  )
17
16
  from starplot.data.constellation_stars import CONSTELLATION_HIPS
17
+ from starplot.models import Star
18
18
  from starplot.models.constellation import from_tuple as constellation_from_tuple
19
19
  from starplot.projections import Projection
20
20
  from starplot.profile import profile
@@ -65,6 +65,7 @@ class ConstellationPlotterMixin:
65
65
  self,
66
66
  style: LineStyle = None,
67
67
  where: list = None,
68
+ sql: str = None,
68
69
  ):
69
70
  """Plots the constellation lines **only**. To plot constellation borders and/or labels, see separate functions for them.
70
71
 
@@ -73,6 +74,7 @@ class ConstellationPlotterMixin:
73
74
  Args:
74
75
  style: Styling of the constellations. If None, then the plot's style (specified when creating the plot) will be used
75
76
  where: A list of expressions that determine which constellations to plot. See [Selecting Objects](/reference-selecting-objects/) for details.
77
+ sql: SQL query for selecting constellations (table name is `_`). This query will be applied _after_ any filters in the `where` kwarg.
76
78
  """
77
79
  self.logger.debug("Plotting constellation lines...")
78
80
 
@@ -80,7 +82,7 @@ class ConstellationPlotterMixin:
80
82
  ctr = 0
81
83
 
82
84
  extent = self._extent_mask()
83
- results = condata.load(extent=extent, filters=where)
85
+ results = condata.load(extent=extent, filters=where, sql=sql)
84
86
  constellations_df = results.to_pandas()
85
87
 
86
88
  if constellations_df.empty:
@@ -262,11 +264,17 @@ class ConstellationPlotterMixin:
262
264
  self.ax.add_collection(line_collection)
263
265
 
264
266
  def _constellation_labels_auto(self, style, labels, settings):
267
+ hips = []
268
+ for c in self.objects.constellations:
269
+ hips.extend(c.star_hip_ids)
270
+
271
+ all_constellation_stars = Star.find(where=[_.hip.isin(hips)])
272
+
265
273
  for constellation in self.objects.constellations:
266
274
  constellation_line_stars = [
267
275
  s
268
- for s in self.objects.stars
269
- if s.hip in CONSTELLATION_HIP_IDS[constellation.iau_id]
276
+ for s in all_constellation_stars
277
+ if s.hip in constellation.star_hip_ids
270
278
  ]
271
279
  if not constellation_line_stars:
272
280
  continue
@@ -302,10 +310,11 @@ class ConstellationPlotterMixin:
302
310
  text = labels.get(con.lower())
303
311
  self.text(
304
312
  text,
305
- ra,
313
+ ra * 15,
306
314
  dec,
307
315
  style,
308
316
  hide_on_collision=self.hide_colliding_labels,
317
+ remove_on_constellation_collision=False,
309
318
  gid="constellations-label-name",
310
319
  )
311
320
 
@@ -319,7 +328,7 @@ class ConstellationPlotterMixin:
319
328
  auto_adjust_settings: dict = DEFAULT_AUTO_ADJUST_SETTINGS,
320
329
  ):
321
330
  """
322
- Plots constellation labels.
331
+ Plots constellation labels for all constellations that have been plotted. This means you must plot the constellations before plotting their labels.
323
332
 
324
333
  It's good to plot these last because they're area-based labels (vs point-based, like for star names), and area-based labels have more freedom to move around. If you plot area-based labels first, then it would limit the available space for point-based labels.
325
334
 
@@ -328,10 +337,6 @@ class ConstellationPlotterMixin:
328
337
  labels: A dictionary where the keys are each constellation's 3-letter IAU abbreviation, and the values are how the constellation will be labeled on the plot.
329
338
  auto_adjust: If True (the default), then labels will be automatically adjusted to avoid collisions with other labels and stars **Important: you must plot stars and constellations first for this to work**. This uses a fairly simple method: for each constellation it finds the centroid of all plotted constellation stars with lines and then generates random points in the constellation boundary starting at the centroid and then progressively increasing the distance from the centroid.
330
339
  auto_adjust_settings: Optional settings for the auto adjustment algorithm.
331
-
332
- TODO:
333
- make this work without plotting constellations first
334
-
335
340
  """
336
341
 
337
342
  if auto_adjust:
starplot/plotters/dsos.py CHANGED
@@ -44,7 +44,7 @@ class DsoPlotterMixin:
44
44
  This is just a small wrapper around the `dsos()` function, so any `kwargs` will be passed through.
45
45
  """
46
46
  where = kwargs.pop("where", [])
47
- where.append(_.type == DsoType.OPEN_CLUSTER)
47
+ where.append(_.type == DsoType.OPEN_CLUSTER.value)
48
48
  self.dsos(where=where, **kwargs)
49
49
 
50
50
  def globular_clusters(self, **kwargs):
@@ -54,7 +54,7 @@ class DsoPlotterMixin:
54
54
  This is just a small wrapper around the `dsos()` function, so any `kwargs` will be passed through.
55
55
  """
56
56
  where = kwargs.pop("where", [])
57
- where.append(_.type == DsoType.GLOBULAR_CLUSTER)
57
+ where.append(_.type == DsoType.GLOBULAR_CLUSTER.value)
58
58
  self.dsos(where=where, **kwargs)
59
59
 
60
60
  def galaxies(self, **kwargs):
@@ -68,9 +68,9 @@ class DsoPlotterMixin:
68
68
  This is just a small wrapper around the `dsos()` function, so any `kwargs` will be passed through.
69
69
  """
70
70
  galaxy_types = [
71
- DsoType.GALAXY,
72
- DsoType.GALAXY_PAIR,
73
- DsoType.GALAXY_TRIPLET,
71
+ DsoType.GALAXY.value,
72
+ DsoType.GALAXY_PAIR.value,
73
+ DsoType.GALAXY_TRIPLET.value,
74
74
  ]
75
75
  where = kwargs.pop("where", [])
76
76
  where.append(_.type.isin(galaxy_types))
@@ -85,15 +85,19 @@ class DsoPlotterMixin:
85
85
  - Emission Nebula
86
86
  - Star Cluster Nebula
87
87
  - Reflection Nebula
88
+ - HII Ionized Regions
89
+
90
+ * Note that this does NOT plot dark nebulae
88
91
 
89
92
  This is just a small wrapper around the `dsos()` function, so any `kwargs` will be passed through.
90
93
  """
91
94
  nebula_types = [
92
- DsoType.NEBULA,
93
- DsoType.PLANETARY_NEBULA,
94
- DsoType.EMISSION_NEBULA,
95
- DsoType.STAR_CLUSTER_NEBULA,
96
- DsoType.REFLECTION_NEBULA,
95
+ DsoType.NEBULA.value,
96
+ DsoType.PLANETARY_NEBULA.value,
97
+ DsoType.EMISSION_NEBULA.value,
98
+ DsoType.STAR_CLUSTER_NEBULA.value,
99
+ DsoType.REFLECTION_NEBULA.value,
100
+ DsoType.HII_IONIZED_REGION.value,
97
101
  ]
98
102
  where = kwargs.pop("where", [])
99
103
  where.append(_.type.isin(nebula_types))
@@ -109,6 +113,8 @@ class DsoPlotterMixin:
109
113
  legend_labels: Mapping[DsoType, str] = DSO_LEGEND_LABELS,
110
114
  alpha_fn: Callable[[DSO], float] = None,
111
115
  label_fn: Callable[[DSO], str] = None,
116
+ sql: str = None,
117
+ sql_labels: str = None,
112
118
  ):
113
119
  """
114
120
  Plots Deep Sky Objects (DSOs), from OpenNGC
@@ -121,6 +127,8 @@ class DsoPlotterMixin:
121
127
  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`.
122
128
  alpha_fn: Callable for calculating the alpha value (aka "opacity") of each DSO. If `None`, then the marker style's alpha will be used.
123
129
  label_fn: Callable for determining the label of each DSO. If `None`, then the names in the `labels` kwarg will be used.
130
+ sql: SQL query for selecting DSOs (table name is `_`). This query will be applied _after_ any filters in the `where` kwarg.
131
+ sql_labels: SQL query for selecting DSOs that will be labeled (table name is `_`). Applied _after_ any filters in the `where_labels` kwarg.
124
132
  """
125
133
 
126
134
  # TODO: add kwarg styles
@@ -139,12 +147,19 @@ class DsoPlotterMixin:
139
147
  legend_labels = {**DSO_LEGEND_LABELS, **legend_labels}
140
148
 
141
149
  extent = self._extent_mask()
142
- dso_results = load(extent=extent, filters=where)
150
+ dso_results = load(extent=extent, filters=where, sql=sql)
143
151
 
144
152
  dso_results_labeled = dso_results
145
153
  for f in where_labels:
146
154
  dso_results_labeled = dso_results_labeled.filter(f)
147
155
 
156
+ if sql_labels:
157
+ result = (
158
+ dso_results_labeled.alias("_").sql(sql_labels).select("sk").execute()
159
+ )
160
+ skids = result["sk"].to_list()
161
+ dso_results_labeled = dso_results_labeled.filter(_.sk.isin(skids))
162
+
148
163
  label_row_ids = dso_results_labeled.to_pandas()["rowid"].tolist()
149
164
 
150
165
  results_df = dso_results.to_pandas()
@@ -2,6 +2,7 @@ from typing import Callable, Mapping
2
2
 
3
3
  import rtree
4
4
  import numpy as np
5
+ from ibis import _ as ibis_table
5
6
  from skyfield.api import Star as SkyfieldStar, wgs84
6
7
 
7
8
  from starplot import callables
@@ -13,13 +14,14 @@ from starplot.profile import profile
13
14
 
14
15
 
15
16
  class StarPlotterMixin:
16
- def _load_stars(self, catalog, filters=None):
17
+ def _load_stars(self, catalog, filters=None, sql=None):
17
18
  extent = self._extent_mask()
18
19
 
19
20
  return stars.load(
20
21
  extent=extent,
21
22
  catalog=catalog,
22
23
  filters=filters,
24
+ sql=sql,
23
25
  )
24
26
 
25
27
  def _scatter_stars(self, ras, decs, sizes, alphas, colors, style=None, **kwargs):
@@ -165,6 +167,8 @@ class StarPlotterMixin:
165
167
  legend_label: str = "Star",
166
168
  bayer_labels: bool = False,
167
169
  flamsteed_labels: bool = False,
170
+ sql: str = None,
171
+ sql_labels: str = None,
168
172
  *args,
169
173
  **kwargs,
170
174
  ):
@@ -180,7 +184,7 @@ class StarPlotterMixin:
180
184
  Args:
181
185
  where: A list of expressions that determine which stars to plot. See [Selecting Objects](/reference-selecting-objects/) for details.
182
186
  where_labels: A list of expressions that determine which stars are labeled on the plot (this includes all labels: name, Bayer, and Flamsteed). If you want to hide **all** labels, then set this arg to `[False]`. See [Selecting Objects](/reference-selecting-objects/) for details.
183
- catalog: The catalog of stars to use: "big-sky-mag11", or "big-sky" -- see [`StarCatalog`](/reference-data/#starplot.data.stars.StarCatalog) for details
187
+ catalog: The catalog of stars to use: "big-sky-mag11", or "big-sky" -- see [star catalogs](/data/star-catalogs/) for details
184
188
  style: If `None`, then the plot's style for stars will be used
185
189
  size_fn: Callable for calculating the marker size of each star. If `None`, then the marker style's size will be used.
186
190
  alpha_fn: Callable for calculating the alpha value (aka "opacity") of each star. If `None`, then the marker style's alpha will be used.
@@ -190,6 +194,8 @@ class StarPlotterMixin:
190
194
  legend_label: Label for stars in the legend. If `None`, then they will not be in the legend.
191
195
  bayer_labels: If True, then Bayer labels for stars will be plotted.
192
196
  flamsteed_labels: If True, then Flamsteed number labels for stars will be plotted.
197
+ sql: SQL query for selecting stars (table name is `_`). This query will be applied _after_ any filters in the `where` kwarg.
198
+ sql_labels: SQL query for selecting stars that will be labeled (table name is `_`). Applied _after_ any filters in the `where_labels` kwarg.
193
199
  """
194
200
 
195
201
  # fallback to style if callables are None
@@ -205,12 +211,21 @@ class StarPlotterMixin:
205
211
  stars_to_index = []
206
212
  labels = labels or {}
207
213
 
208
- star_results = self._load_stars(catalog, filters=where)
214
+ star_results = self._load_stars(catalog, filters=where, sql=sql)
209
215
 
210
216
  star_results_labeled = star_results
211
217
  for f in where_labels:
212
218
  star_results_labeled = star_results_labeled.filter(f)
213
219
 
220
+ if sql_labels:
221
+ result = (
222
+ star_results_labeled.alias("_").sql(sql_labels).select("sk").execute()
223
+ )
224
+ skids = result["sk"].to_list()
225
+ star_results_labeled = star_results_labeled.filter(
226
+ ibis_table.sk.isin(skids)
227
+ )
228
+
214
229
  label_row_ids = star_results_labeled.to_pandas()["rowid"].tolist()
215
230
 
216
231
  stars_df = star_results.to_pandas()
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: starplot
3
- Version: 0.15.5
3
+ Version: 0.15.7
4
4
  Summary: Star charts and maps of the sky
5
5
  Keywords: astronomy,stars,charts,maps,constellations,sky,plotting
6
6
  Author-email: Steve Berardi <hello@steveberardi.com>
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3
10
10
  Classifier: Programming Language :: Python :: 3.10
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
+ License-File: LICENSE
13
14
  Requires-Dist: matplotlib >= 3.8.0
14
15
  Requires-Dist: numpy >= 1.26.2
15
16
  Requires-Dist: pandas >= 1.4.0