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.
Files changed (98) hide show
  1. starplot/__init__.py +59 -0
  2. starplot/callables.py +176 -0
  3. starplot/cli.py +29 -0
  4. starplot/config.py +80 -0
  5. starplot/coordinates.py +7 -0
  6. starplot/data/__init__.py +22 -0
  7. starplot/data/catalogs.py +356 -0
  8. starplot/data/constellations.py +71 -0
  9. starplot/data/db.py +33 -0
  10. starplot/data/dsos.py +85 -0
  11. starplot/data/ecliptic.py +201 -0
  12. starplot/data/library/constellation_names.parquet +0 -0
  13. starplot/data/library/dso_names.parquet +0 -0
  14. starplot/data/library/readme.md +1 -0
  15. starplot/data/library/star_designations.parquet +0 -0
  16. starplot/data/stars.py +79 -0
  17. starplot/data/translations.py +408 -0
  18. starplot/data/utils.py +35 -0
  19. starplot/geometry.py +319 -0
  20. starplot/mixins.py +360 -0
  21. starplot/models/__init__.py +21 -0
  22. starplot/models/base.py +77 -0
  23. starplot/models/comet.py +302 -0
  24. starplot/models/constellation.py +151 -0
  25. starplot/models/dso.py +317 -0
  26. starplot/models/milky_way.py +30 -0
  27. starplot/models/moon.py +130 -0
  28. starplot/models/objects.py +29 -0
  29. starplot/models/observer.py +125 -0
  30. starplot/models/optics.py +342 -0
  31. starplot/models/planet.py +137 -0
  32. starplot/models/satellite.py +138 -0
  33. starplot/models/star.py +253 -0
  34. starplot/models/sun.py +62 -0
  35. starplot/plots/__init__.py +7 -0
  36. starplot/plots/base.py +1023 -0
  37. starplot/plots/galaxy.py +372 -0
  38. starplot/plots/horizon.py +543 -0
  39. starplot/plots/map.py +515 -0
  40. starplot/plots/optic.py +469 -0
  41. starplot/plots/zenith.py +217 -0
  42. starplot/plotters/__init__.py +9 -0
  43. starplot/plotters/arrow.py +174 -0
  44. starplot/plotters/constellations.py +298 -0
  45. starplot/plotters/debug.py +21 -0
  46. starplot/plotters/dsos.py +294 -0
  47. starplot/plotters/experimental.py +722 -0
  48. starplot/plotters/gradients.py +153 -0
  49. starplot/plotters/legend.py +253 -0
  50. starplot/plotters/milkyway.py +51 -0
  51. starplot/plotters/stars.py +319 -0
  52. starplot/plotters/text.py +802 -0
  53. starplot/profile.py +16 -0
  54. starplot/projections.py +184 -0
  55. starplot/styles/__init__.py +6 -0
  56. starplot/styles/base.py +1344 -0
  57. starplot/styles/ext/antique.yml +175 -0
  58. starplot/styles/ext/blue_dark.yml +163 -0
  59. starplot/styles/ext/blue_gold.yml +147 -0
  60. starplot/styles/ext/blue_light.yml +123 -0
  61. starplot/styles/ext/blue_medium.yml +142 -0
  62. starplot/styles/ext/blue_night.yml +185 -0
  63. starplot/styles/ext/cb_wong.yml +124 -0
  64. starplot/styles/ext/color_print.yml +111 -0
  65. starplot/styles/ext/gradient_presets.yml +158 -0
  66. starplot/styles/ext/grayscale.yml +94 -0
  67. starplot/styles/ext/grayscale_dark.yml +136 -0
  68. starplot/styles/ext/map.yml +12 -0
  69. starplot/styles/ext/nord.yml +158 -0
  70. starplot/styles/ext/optic.yml +20 -0
  71. starplot/styles/ext/publication.yml +8 -0
  72. starplot/styles/extensions.py +129 -0
  73. starplot/styles/fonts-library/gfs-didot/DESCRIPTION.en_us.html +9 -0
  74. starplot/styles/fonts-library/gfs-didot/GFSDidot-Regular.ttf +0 -0
  75. starplot/styles/fonts-library/gfs-didot/METADATA.pb +16 -0
  76. starplot/styles/fonts-library/gfs-didot/OFL.txt +94 -0
  77. starplot/styles/fonts-library/inter/Inter-Bold.ttf +0 -0
  78. starplot/styles/fonts-library/inter/Inter-BoldItalic.ttf +0 -0
  79. starplot/styles/fonts-library/inter/Inter-ExtraBold.ttf +0 -0
  80. starplot/styles/fonts-library/inter/Inter-ExtraLight.ttf +0 -0
  81. starplot/styles/fonts-library/inter/Inter-ExtraLightItalic.ttf +0 -0
  82. starplot/styles/fonts-library/inter/Inter-Italic.ttf +0 -0
  83. starplot/styles/fonts-library/inter/Inter-Light.ttf +0 -0
  84. starplot/styles/fonts-library/inter/Inter-LightItalic.ttf +0 -0
  85. starplot/styles/fonts-library/inter/Inter-Regular.ttf +0 -0
  86. starplot/styles/fonts-library/inter/Inter-SemiBold.ttf +0 -0
  87. starplot/styles/fonts-library/inter/Inter-SemiBoldItalic.ttf +0 -0
  88. starplot/styles/fonts-library/inter/LICENSE.txt +92 -0
  89. starplot/styles/fonts.py +15 -0
  90. starplot/styles/helpers.py +93 -0
  91. starplot/styles/markers.py +308 -0
  92. starplot/utils.py +169 -0
  93. starplot/warnings.py +21 -0
  94. starplot-0.20.5.dist-info/METADATA +146 -0
  95. starplot-0.20.5.dist-info/RECORD +98 -0
  96. starplot-0.20.5.dist-info/WHEEL +4 -0
  97. starplot-0.20.5.dist-info/entry_points.txt +3 -0
  98. starplot-0.20.5.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,319 @@
1
+ from pathlib import Path
2
+ from typing import Callable
3
+
4
+ import rtree
5
+ import numpy as np
6
+ from ibis import _ as ibis_table
7
+ from skyfield.api import Star as SkyfieldStar
8
+
9
+ from starplot import callables
10
+ from starplot.data import stars
11
+ from starplot.data.catalogs import Catalog, BIG_SKY_MAG11
12
+ from starplot.data.translations import translate
13
+ from starplot.models.star import Star, from_tuple
14
+ from starplot.styles import ObjectStyle, use_style
15
+ from starplot.profile import profile
16
+ from starplot.plotters.text import CollisionHandler
17
+
18
+
19
+ class StarPlotterMixin:
20
+ def _load_stars(self, catalog, filters=None, sql=None):
21
+ extent = self._extent_mask()
22
+
23
+ return stars.load(
24
+ extent=extent,
25
+ catalog=catalog,
26
+ filters=filters,
27
+ sql=sql,
28
+ )
29
+
30
+ def _scatter_stars(self, ras, decs, sizes, alphas, colors, style=None, **kwargs):
31
+ style = style or self.style.star
32
+ edge_colors = kwargs.pop("edgecolors", None)
33
+
34
+ if not edge_colors:
35
+ if style.marker.edge_color:
36
+ edge_colors = style.marker.edge_color.as_hex()
37
+ else:
38
+ edge_colors = "none"
39
+
40
+ plotted = self.ax.scatter(
41
+ ras,
42
+ decs,
43
+ s=sizes,
44
+ c=colors,
45
+ marker=kwargs.pop("symbol", None) or style.marker.symbol_matplot,
46
+ zorder=kwargs.pop("zorder", None) or style.marker.zorder,
47
+ edgecolors=edge_colors,
48
+ alpha=alphas,
49
+ gid="stars",
50
+ **self._plot_kwargs(),
51
+ **kwargs,
52
+ )
53
+
54
+ if self._background_clip_path is not None:
55
+ plotted.set_clip_on(True)
56
+ plotted.set_clip_path(self._background_clip_path)
57
+
58
+ return plotted
59
+
60
+ def _star_labels(
61
+ self,
62
+ star_objects: list[Star],
63
+ star_sizes: list[float],
64
+ label_pks: list,
65
+ style: ObjectStyle,
66
+ bayer_labels: bool,
67
+ flamsteed_labels: bool,
68
+ label_fn: Callable[[Star], str],
69
+ collision_handler: CollisionHandler,
70
+ ):
71
+ _bayer = []
72
+ _flamsteed = []
73
+
74
+ # Plot all star common names first
75
+ for i, s in enumerate(star_objects):
76
+ if s.pk not in label_pks:
77
+ continue
78
+
79
+ if (
80
+ s.hip
81
+ and s.hip in self._labeled_stars
82
+ or s.tyc
83
+ and s.tyc in self._labeled_stars
84
+ ):
85
+ continue
86
+ elif s.hip:
87
+ self._labeled_stars.append(s.hip)
88
+ elif s.tyc:
89
+ self._labeled_stars.append(s.tyc)
90
+
91
+ label = label_fn(s)
92
+ bayer_desig = s.bayer
93
+ flamsteed_num = s.flamsteed
94
+
95
+ if label:
96
+ self.text(
97
+ label,
98
+ s.ra,
99
+ s.dec,
100
+ style=style.label.offset_from_marker(
101
+ marker_symbol=style.marker.symbol,
102
+ marker_size=star_sizes[i],
103
+ scale=self.scale,
104
+ ),
105
+ collision_handler=collision_handler,
106
+ gid="stars-label-name",
107
+ )
108
+
109
+ if bayer_labels and bayer_desig and s.is_primary:
110
+ _bayer.append((bayer_desig, s.ra, s.dec, star_sizes[i]))
111
+
112
+ if flamsteed_labels and flamsteed_num and not bayer_desig and s.is_primary:
113
+ _flamsteed.append((flamsteed_num, s.ra, s.dec, star_sizes[i]))
114
+
115
+ # Plot bayer/flamsteed
116
+ for bayer_desig, ra, dec, star_size in _bayer:
117
+ self.text(
118
+ bayer_desig,
119
+ ra,
120
+ dec,
121
+ style=self.style.bayer_labels.offset_from_marker(
122
+ marker_symbol=style.marker.symbol,
123
+ marker_size=star_size,
124
+ scale=self.scale,
125
+ ),
126
+ collision_handler=collision_handler,
127
+ gid="stars-label-bayer",
128
+ )
129
+
130
+ for flamsteed_num, ra, dec, star_size in _flamsteed:
131
+ self.text(
132
+ flamsteed_num,
133
+ ra,
134
+ dec,
135
+ style=self.style.flamsteed_labels.offset_from_marker(
136
+ marker_symbol=style.marker.symbol,
137
+ marker_size=star_size,
138
+ scale=self.scale,
139
+ ),
140
+ collision_handler=collision_handler,
141
+ gid="stars-label-flamsteed",
142
+ )
143
+
144
+ def _prepare_star_coords(self, df, limit_by_altaz=False):
145
+ df["x"], df["y"] = (
146
+ df["ra"],
147
+ df["dec"],
148
+ )
149
+ return df
150
+
151
+ @profile
152
+ @use_style(ObjectStyle, "star")
153
+ def stars(
154
+ self,
155
+ where: list = None,
156
+ where_labels: list = None,
157
+ catalog: Catalog | Path | str = BIG_SKY_MAG11,
158
+ style: ObjectStyle = None,
159
+ size_fn: Callable[[Star], float] = callables.size_by_magnitude,
160
+ alpha_fn: Callable[[Star], float] = None,
161
+ color_fn: Callable[[Star], str] = None,
162
+ label_fn: Callable[[Star], str] = Star.get_label,
163
+ legend_label: str = "Star",
164
+ bayer_labels: bool = False,
165
+ flamsteed_labels: bool = False,
166
+ sql: str = None,
167
+ sql_labels: str = None,
168
+ collision_handler: CollisionHandler = None,
169
+ ):
170
+ """
171
+ Plots stars
172
+
173
+ Args:
174
+ where: A list of expressions that determine which stars to plot. See [Selecting Objects](/reference-selecting-objects/) for details.
175
+ 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.
176
+ catalog: The catalog of stars to use -- see [catalogs overview](/data/overview/) for details
177
+ style: If `None`, then the plot's style for stars will be used
178
+ size_fn: Callable for calculating the marker size of each star. If `None`, then the marker style's size will be used.
179
+ alpha_fn: Callable for calculating the alpha value (aka "opacity") of each star. If `None`, then the marker style's alpha will be used.
180
+ color_fn: Callable for calculating the color of each star. If `None`, then the marker style's color will be used.
181
+ label_fn: Callable for determining the label of each star.
182
+ legend_label: Label for stars in the legend. If `None`, then they will not be in the legend.
183
+ bayer_labels: If True, then Bayer labels for stars will be plotted.
184
+ flamsteed_labels: If True, then Flamsteed number labels for stars will be plotted.
185
+ sql: SQL query for selecting stars (table name is `_`). This query will be applied _after_ any filters in the `where` kwarg.
186
+ sql_labels: SQL query for selecting stars that will be labeled (table name is `_`). Applied _after_ any filters in the `where_labels` kwarg.
187
+ collision_handler: An instance of [CollisionHandler][starplot.CollisionHandler] that describes what to do on label collisions with other labels, markers, etc. If `None`, then the collision handler of the plot will be used.
188
+ """
189
+
190
+ # fallback to style if callables are None
191
+ color_hex = (
192
+ style.marker.color.as_hex()
193
+ ) # calculate color hex once here to avoid repeated calls in color_fn()
194
+ size_fn = size_fn or (lambda d: style.marker.size)
195
+ alpha_fn = alpha_fn or (lambda d: style.marker.alpha)
196
+ color_fn = color_fn or (lambda d: color_hex)
197
+
198
+ handler = collision_handler or self.point_label_handler
199
+ where = where or []
200
+ where_labels = where_labels or []
201
+ stars_to_index = []
202
+
203
+ star_results = self._load_stars(catalog, filters=where, sql=sql)
204
+
205
+ star_results_labeled = star_results
206
+ for f in where_labels:
207
+ star_results_labeled = star_results_labeled.filter(f)
208
+
209
+ if sql_labels:
210
+ result = (
211
+ star_results_labeled.alias("_").sql(sql_labels).select("pk").execute()
212
+ )
213
+ pks = result["pk"].to_list()
214
+ star_results_labeled = star_results_labeled.filter(ibis_table.pk.isin(pks))
215
+
216
+ label_pks = star_results_labeled.to_pandas()["pk"].tolist()
217
+
218
+ stars_df = star_results.to_pandas()
219
+ stars_df["ra_hours"], stars_df["dec_degrees"] = (stars_df.ra / 15, stars_df.dec)
220
+
221
+ nearby_stars = SkyfieldStar.from_dataframe(stars_df)
222
+ astrometric = self.earth.at(self.observer.timescale).observe(nearby_stars)
223
+ stars_ra, stars_dec, _ = astrometric.radec()
224
+ stars_df["ra"], stars_df["dec"] = (
225
+ stars_ra.hours * 15,
226
+ stars_dec.degrees,
227
+ )
228
+ stars_df = self._prepare_star_coords(stars_df)
229
+
230
+ starz = []
231
+ rtree_id = 1
232
+
233
+ transformed = self._proj.transform_points(
234
+ self._crs,
235
+ stars_df["x"].to_numpy(),
236
+ stars_df["y"].to_numpy(),
237
+ )
238
+ stars_df["data_x"] = transformed[:, 0]
239
+ stars_df["data_y"] = transformed[:, 1]
240
+ stars_df[["display_x", "display_y"]] = self.ax.transData.transform(
241
+ stars_df[["data_x", "data_y"]].to_numpy()
242
+ )
243
+ stars_df = stars_df[(stars_df["display_x"] >= 0) & (stars_df["display_y"] >= 0)]
244
+
245
+ for star in stars_df.itertuples():
246
+ display_x, display_y = star.display_x, star.display_y
247
+
248
+ obj = from_tuple(star)
249
+ size = size_fn(obj) * self.scale**2
250
+ alpha = alpha_fn(obj)
251
+ color = color_fn(obj) or style.marker.color.as_hex()
252
+
253
+ if obj.magnitude < 5:
254
+ rtree_id += 1
255
+ radius = size**0.5 / 5
256
+ bbox = np.array(
257
+ (
258
+ display_x - radius,
259
+ display_y - radius,
260
+ display_x + radius,
261
+ display_y + radius,
262
+ )
263
+ )
264
+ if self.debug_text:
265
+ self._debug_bbox(bbox, color="#39FF14", width=1)
266
+ if self._stars_rtree.get_size() > 0:
267
+ self._stars_rtree.insert(
268
+ 0,
269
+ bbox,
270
+ None,
271
+ )
272
+ else:
273
+ # if the index has no stars yet, then wait until end to load for better performance
274
+ stars_to_index.append((rtree_id, bbox, None))
275
+
276
+ starz.append((star.x, star.y, size, alpha, color, obj))
277
+
278
+ starz.sort(key=lambda s: s[2], reverse=True) # sort by descending size
279
+
280
+ if not starz:
281
+ self.logger.debug(f"Star count = {len(starz)}")
282
+ return
283
+
284
+ x, y, sizes, alphas, colors, star_objects = zip(*starz)
285
+
286
+ self._objects.stars.extend(star_objects)
287
+
288
+ self.logger.debug(f"Star count = {len(star_objects)}")
289
+
290
+ # Plot Stars
291
+ self._scatter_stars(
292
+ x,
293
+ y,
294
+ sizes,
295
+ alphas,
296
+ colors,
297
+ style=style,
298
+ zorder=style.marker.zorder,
299
+ edgecolors=style.marker.edge_color.as_hex()
300
+ if style.marker.edge_color
301
+ else "none",
302
+ )
303
+
304
+ _legend_label = translate(legend_label, self.language) or legend_label
305
+ self._add_legend_handle_marker(_legend_label, style.marker)
306
+
307
+ if stars_to_index:
308
+ self._stars_rtree = rtree.index.Index(stars_to_index)
309
+
310
+ self._star_labels(
311
+ star_objects,
312
+ sizes,
313
+ label_pks,
314
+ style,
315
+ bayer_labels,
316
+ flamsteed_labels,
317
+ label_fn,
318
+ handler,
319
+ )