starplot 0.16.1__py2.py3-none-any.whl → 0.17.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 (40) hide show
  1. starplot/__init__.py +28 -2
  2. starplot/base.py +42 -60
  3. starplot/config.py +41 -9
  4. starplot/data/bigsky.py +1 -1
  5. starplot/data/constellations.py +9 -681
  6. starplot/data/db.py +2 -2
  7. starplot/data/dsos.py +11 -28
  8. starplot/data/library/bigsky.0.4.0.stars.mag11.parquet +0 -0
  9. starplot/data/library/sky.db +0 -0
  10. starplot/data/stars.py +15 -433
  11. starplot/data/translations.py +161 -0
  12. starplot/geometry.py +52 -6
  13. starplot/horizon.py +18 -12
  14. starplot/map.py +1 -5
  15. starplot/mixins.py +283 -0
  16. starplot/models/__init__.py +19 -7
  17. starplot/models/base.py +1 -1
  18. starplot/models/comet.py +332 -0
  19. starplot/models/constellation.py +10 -0
  20. starplot/models/dso.py +24 -0
  21. starplot/{optics.py → models/optics.py} +4 -4
  22. starplot/models/satellite.py +158 -0
  23. starplot/models/star.py +10 -0
  24. starplot/optic.py +24 -13
  25. starplot/plotters/__init__.py +1 -0
  26. starplot/plotters/arrow.py +162 -0
  27. starplot/plotters/constellations.py +34 -56
  28. starplot/plotters/dsos.py +8 -14
  29. starplot/plotters/experimental.py +560 -8
  30. starplot/plotters/legend.py +5 -0
  31. starplot/plotters/stars.py +7 -16
  32. starplot/styles/base.py +20 -1
  33. starplot/styles/extensions.py +10 -1
  34. starplot/zenith.py +4 -1
  35. {starplot-0.16.1.dist-info → starplot-0.17.0.dist-info}/METADATA +20 -17
  36. {starplot-0.16.1.dist-info → starplot-0.17.0.dist-info}/RECORD +40 -36
  37. /starplot/{observer.py → models/observer.py} +0 -0
  38. {starplot-0.16.1.dist-info → starplot-0.17.0.dist-info}/WHEEL +0 -0
  39. {starplot-0.16.1.dist-info → starplot-0.17.0.dist-info}/entry_points.txt +0 -0
  40. {starplot-0.16.1.dist-info → starplot-0.17.0.dist-info}/licenses/LICENSE +0 -0
starplot/optic.py CHANGED
@@ -1,7 +1,5 @@
1
1
  from typing import Callable, Mapping
2
2
 
3
- import pandas as pd
4
-
5
3
  from cartopy import crs as ccrs
6
4
  from matplotlib import pyplot as plt, patches, path
7
5
  from skyfield.api import wgs84, Star as SkyfieldStar
@@ -9,12 +7,16 @@ from skyfield.api import wgs84, Star as SkyfieldStar
9
7
  from starplot.coordinates import CoordinateSystem
10
8
  from starplot import callables
11
9
  from starplot.base import BasePlot, DPI
12
- from starplot.data.stars import StarCatalog, STAR_NAMES
10
+ from starplot.data.stars import StarCatalog
13
11
  from starplot.mixins import ExtentMaskMixin
14
- from starplot.models import Star
15
- from starplot.observer import Observer
16
- from starplot.optics import Optic, Camera
17
- from starplot.plotters import StarPlotterMixin, DsoPlotterMixin, GradientBackgroundMixin
12
+ from starplot.models import Star, Optic, Camera
13
+ from starplot.models.observer import Observer
14
+ from starplot.plotters import (
15
+ StarPlotterMixin,
16
+ DsoPlotterMixin,
17
+ GradientBackgroundMixin,
18
+ LegendPlotterMixin,
19
+ )
18
20
  from starplot.styles import (
19
21
  PlotStyle,
20
22
  ObjectStyle,
@@ -26,8 +28,6 @@ from starplot.styles import (
26
28
  )
27
29
  from starplot.utils import azimuth_to_string
28
30
 
29
- pd.options.mode.chained_assignment = None # default='warn'
30
-
31
31
  DEFAULT_OPTIC_STYLE = PlotStyle().extend(extensions.OPTIC)
32
32
 
33
33
 
@@ -37,6 +37,7 @@ class OpticPlot(
37
37
  StarPlotterMixin,
38
38
  DsoPlotterMixin,
39
39
  GradientBackgroundMixin,
40
+ LegendPlotterMixin,
40
41
  ):
41
42
  """Creates a new optic plot.
42
43
 
@@ -234,7 +235,8 @@ class OpticPlot(
234
235
  size_fn: Callable[[Star], float] = callables.size_by_magnitude_for_optic,
235
236
  alpha_fn: Callable[[Star], float] = callables.alpha_by_magnitude,
236
237
  color_fn: Callable[[Star], str] = None,
237
- labels: Mapping[int, str] = STAR_NAMES,
238
+ label_fn: Callable[[Star], str] = Star.get_label,
239
+ labels: Mapping[int, str] = None,
238
240
  legend_label: str = "Star",
239
241
  bayer_labels: bool = False,
240
242
  flamsteed_labels: bool = False,
@@ -245,6 +247,12 @@ class OpticPlot(
245
247
  """
246
248
  Plots stars
247
249
 
250
+ Labels for stars are determined in this order:
251
+
252
+ 1. Return value from `label_fn`
253
+ 2. Value for star's HIP id in `labels`
254
+ 3. IAU-designated name, as listed in the [data reference](/data/star-designations/)
255
+
248
256
  Args:
249
257
  where: A list of expressions that determine which stars to plot. See [Selecting Objects](/reference-selecting-objects/) for details.
250
258
  where_labels: A list of expressions that determine which stars are labeled on the plot. See [Selecting Objects](/reference-selecting-objects/) for details.
@@ -254,7 +262,8 @@ class OpticPlot(
254
262
  size_fn: Callable for calculating the marker size of each star. If `None`, then the marker style's size will be used.
255
263
  alpha_fn: Callable for calculating the alpha value (aka "opacity") of each star. If `None`, then the marker style's alpha will be used.
256
264
  color_fn: Callable for calculating the color of each star. If `None`, then the marker style's color will be used.
257
- 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`.
265
+ label_fn: Callable for determining the label of each star. If `None`, then the names in the `labels` kwarg will be used.
266
+ labels: A dictionary that maps a star's HIP id to the label that'll be plotted for that star. If `None`, then the star's IAU-designated name will be used.
258
267
  legend_label: Label for stars in the legend. If `None`, then they will not be in the legend.
259
268
  bayer_labels: If True, then Bayer labels for stars will be plotted.
260
269
  flamsteed_labels: If True, then Flamsteed number labels for stars will be plotted.
@@ -269,18 +278,20 @@ class OpticPlot(
269
278
  return size_fn(s) * optic_star_multiplier * 0.68
270
279
 
271
280
  super().stars(
281
+ where=where,
282
+ where_labels=where_labels,
272
283
  catalog=catalog,
273
284
  style=style,
274
285
  rasterize=rasterize,
275
286
  size_fn=size_fn_mx,
276
287
  alpha_fn=alpha_fn,
277
288
  color_fn=color_fn,
278
- where=where,
279
- where_labels=where_labels,
289
+ label_fn=label_fn,
280
290
  labels=labels,
281
291
  legend_label=legend_label,
282
292
  bayer_labels=bayer_labels,
283
293
  flamsteed_labels=flamsteed_labels,
294
+ sql=sql,
284
295
  *args,
285
296
  **kwargs,
286
297
  )
@@ -4,3 +4,4 @@ from .dsos import DsoPlotterMixin # noqa: F401
4
4
  from .milkyway import MilkyWayPlotterMixin # noqa: F401
5
5
  from .legend import LegendPlotterMixin # noqa: F401
6
6
  from .gradients import GradientBackgroundMixin # noqa: F401
7
+ from .arrow import ArrowPlotterMixin # noqa: F401
@@ -0,0 +1,162 @@
1
+ import numpy as np
2
+
3
+ from starplot.geometry import circle
4
+ from starplot.profile import profile
5
+
6
+
7
+ class ArrowPlotterMixin:
8
+ def _to_axes(self, points):
9
+ ax_points = []
10
+
11
+ for ra, dec in points:
12
+ x, y = self._proj.transform_point(ra, dec, self._crs)
13
+ data_to_axes = self.ax.transData + self.ax.transAxes.inverted()
14
+ x_axes, y_axes = data_to_axes.transform((x, y))
15
+ ax_points.append([x_axes, y_axes])
16
+ return ax_points
17
+
18
+ @profile
19
+ def arrow(
20
+ self,
21
+ origin: tuple[float, float] = None,
22
+ target: tuple[float, float] = None,
23
+ scale: float = 0.9,
24
+ length: float = 1,
25
+ ):
26
+ """
27
+ Plots an arrow from one point to another.
28
+
29
+ Args:
30
+ origin: Starting point (ra, dec)
31
+ target: Target of the arrow (ra, dec)
32
+ scale: Scaling factor for the arrow, to make it offset from the origin/target
33
+ length: If you only specify a target, then this will be the length of the arrow (in degrees). This value is ignored if you're plotting an arrow from one point to another.
34
+ style: Style of the arrow
35
+
36
+ TODO : add style kwarg
37
+
38
+ Draw as polygon instead
39
+ """
40
+ # self._text(x, y, labels[i], **text_kwargs)
41
+ # self.ax.annotate("", xytext=(0, 0), xy=(0.5, 0.5),
42
+ # )
43
+ # # y = mx + b
44
+ # def _slope(x1, y1, x2, y2):
45
+ # return (y2-y1)/(x2-x1)
46
+ # slope = _slope(x, y, target_x, target_y)
47
+ # b = y - slope * x
48
+
49
+ """
50
+ Do this in display coords
51
+
52
+ 1. Create LineString
53
+ 2. Segmentize
54
+ 3. Buffer
55
+ 4. Create arrow head (triangle)
56
+ """
57
+
58
+ if origin and target:
59
+ # TODO : prepare coords
60
+ ax_coords = self._to_axes([origin, target])
61
+ x, y = ax_coords[0]
62
+ target_x, target_y = ax_coords[1]
63
+
64
+ angle_radians = np.atan2(target_y - y, target_x - x)
65
+ angle_degrees = np.degrees(angle_radians)
66
+
67
+ line_x = [
68
+ (1 - scale) * ax_coords[1][0] + scale * ax_coords[0][0],
69
+ (1 - scale) * ax_coords[0][0] + scale * ax_coords[1][0],
70
+ ]
71
+ line_y = [
72
+ (1 - scale) * ax_coords[1][1] + scale * ax_coords[0][1],
73
+ (1 - scale) * ax_coords[0][1] + scale * ax_coords[1][1],
74
+ ]
75
+
76
+ # plot line
77
+ self.ax.plot(
78
+ line_x,
79
+ line_y,
80
+ clip_on=True,
81
+ clip_path=self._background_clip_path,
82
+ color="red",
83
+ linewidth=5,
84
+ transform=self.ax.transAxes,
85
+ )
86
+
87
+ arrow_x = line_x[-1]
88
+ arrow_y = line_y[-1]
89
+
90
+ # plot arrowhead
91
+ self.ax.plot(
92
+ arrow_x,
93
+ arrow_y,
94
+ marker=(3, 0, angle_degrees + 270),
95
+ markersize=30,
96
+ color="red",
97
+ linestyle="None",
98
+ transform=self.ax.transAxes,
99
+ )
100
+ elif target:
101
+ polygon = circle(
102
+ center=target,
103
+ diameter_degrees=length,
104
+ num_pts=200,
105
+ )
106
+
107
+ list(zip(*polygon.exterior.coords.xy))
108
+
109
+ """
110
+ TODO :
111
+ add support for target only:
112
+ length param (in degrees)
113
+ 1. create circle
114
+ 2. try random points on circle
115
+ 3. generate points on line from origin to target
116
+ 4. if points collide with label, try another random point
117
+ 5. stop when no collisions with labels
118
+ """
119
+
120
+ else:
121
+ raise ValueError(
122
+ "To plot an arrow you must specify a target or a target and origin."
123
+ )
124
+
125
+ # Convert to display coordinates
126
+ # display_x, display_y = ax.transAxes.transform((axes_x, axes_y))
127
+
128
+ # points = geod.ellipse(
129
+ # center=(),
130
+ # height_degrees,
131
+ # width_degrees,
132
+ # num_pts=200,
133
+ # )
134
+
135
+ # self.ax.arrow(
136
+ # x,
137
+ # y,
138
+ # target_x - x,
139
+ # target_y - y,
140
+ # color="red",
141
+ # head_length=1,
142
+ # head_width=1,
143
+ # transform=self.ax.transAxes,
144
+ # )
145
+
146
+ # ra, dec = to
147
+ # x, y = self._prepare_coords(ra, dec)
148
+
149
+ # self.ax.annotate(
150
+ # "",
151
+ # xy=(x, y),
152
+ # xytext=(0, 0),
153
+ # arrowprops=dict(
154
+ # # headlength=10,
155
+ # # headwidth=16,
156
+ # color="red",
157
+ # # shrink=0.9,
158
+ # mutation_scale=5,
159
+ # arrowstyle="-|>, head_length=10, head_width=5",
160
+ # ),
161
+ # **self._plot_kwargs(),
162
+ # )
@@ -1,3 +1,5 @@
1
+ from typing import Callable
2
+
1
3
  import numpy as np
2
4
 
3
5
  import rtree
@@ -10,15 +12,18 @@ from ibis import _
10
12
  from starplot.coordinates import CoordinateSystem
11
13
  from starplot.data import constellations as condata, constellation_lines as conlines
12
14
  from starplot.data.stars import load as load_stars, StarCatalog
13
- from starplot.data.constellations import (
14
- CONSTELLATIONS_FULL_NAMES,
15
- )
16
15
  from starplot.data.constellation_stars import CONSTELLATION_HIPS
17
- from starplot.models import Star
16
+ from starplot.models import Star, Constellation
18
17
  from starplot.models.constellation import from_tuple as constellation_from_tuple
19
- from starplot.projections import Mercator, Miller
18
+ from starplot.projections import (
19
+ StereoNorth,
20
+ StereoSouth,
21
+ Stereographic,
22
+ Equidistant,
23
+ LambertAzEqArea,
24
+ )
20
25
  from starplot.profile import profile
21
- from starplot.styles import PathStyle, LineStyle, LabelStyle
26
+ from starplot.styles import LineStyle, LabelStyle
22
27
  from starplot.styles.helpers import use_style
23
28
  from starplot.utils import points_on_line
24
29
  from starplot.geometry import is_wrapped_polygon
@@ -34,6 +39,14 @@ DEFAULT_AUTO_ADJUST_SETTINGS = {
34
39
  }
35
40
  """Default settings for auto-adjusting constellation labels"""
36
41
 
42
+ GEODETIC_PROJECTIONS = (
43
+ Equidistant,
44
+ LambertAzEqArea,
45
+ Stereographic,
46
+ StereoNorth,
47
+ StereoSouth,
48
+ )
49
+
37
50
 
38
51
  class ConstellationPlotterMixin:
39
52
  def inbounds_temp(self, x, y):
@@ -89,10 +102,10 @@ class ConstellationPlotterMixin:
89
102
  return
90
103
 
91
104
  projection = getattr(self, "projection", None)
92
- if isinstance(projection, Mercator) or isinstance(projection, Miller):
93
- transform = self._plate_carree
94
- else:
105
+ if isinstance(projection, GEODETIC_PROJECTIONS):
95
106
  transform = self._geodetic
107
+ else:
108
+ transform = self._plate_carree
96
109
 
97
110
  style_kwargs = style.matplot_kwargs(self.scale)
98
111
  constellation_points_to_index = []
@@ -136,7 +149,7 @@ class ConstellationPlotterMixin:
136
149
  if any([np.isnan(n) for n in start + end]):
137
150
  continue
138
151
 
139
- for x, y in points_on_line(start, end, 25):
152
+ for x, y in points_on_line(start, end, num_points=25):
140
153
  display_x, display_y = self.ax.transData.transform((x, y))
141
154
  if display_x < 0 or display_y < 0:
142
155
  continue
@@ -183,40 +196,6 @@ class ConstellationPlotterMixin:
183
196
  None,
184
197
  )
185
198
 
186
- def _plot_constellation_labels(
187
- self,
188
- style: PathStyle = None,
189
- labels: dict[str, str] = CONSTELLATIONS_FULL_NAMES,
190
- ):
191
- """
192
- TODO:
193
- 1. plot label, if removed then get size in display coords
194
- 2. generate random points in polygon, convert to display coords, test for intersections
195
- 3. plot best score
196
-
197
- problem = constellations usually plotted first, so wont have star data (or could use stars from constellations only?)
198
-
199
- constellation names CAN cross lines but not stars
200
-
201
- """
202
- style = style or self.style.constellation.label
203
- self._constellation_labels = []
204
-
205
- for con in condata.iterator():
206
- _, ra, dec = condata.get(con)
207
- text = labels.get(con.lower())
208
- label = self.text(
209
- text,
210
- ra,
211
- dec,
212
- style,
213
- hide_on_collision=False,
214
- # hide_on_collision=self.hide_colliding_labels,
215
- gid="constellations-label-name",
216
- )
217
- if label is not None:
218
- self._constellation_labels.append(label)
219
-
220
199
  @profile
221
200
  @use_style(LineStyle, "constellation_borders")
222
201
  def constellation_borders(self, style: LineStyle = None):
@@ -261,7 +240,7 @@ class ConstellationPlotterMixin:
261
240
  )
262
241
  self.ax.add_collection(line_collection)
263
242
 
264
- def _constellation_labels_auto(self, style, labels, settings):
243
+ def _constellation_labels_auto(self, style, label_fn, settings):
265
244
  hips = []
266
245
  for c in self.objects.constellations:
267
246
  hips.extend(c.star_hip_ids)
@@ -289,7 +268,7 @@ class ConstellationPlotterMixin:
289
268
 
290
269
  points_line = MultiPoint(starpoints)
291
270
  centroid = points_line.centroid
292
- text = labels.get(constellation.iau_id)
271
+ text = label_fn(constellation)
293
272
 
294
273
  self.text(
295
274
  text,
@@ -302,14 +281,13 @@ class ConstellationPlotterMixin:
302
281
  gid="constellations-label-name",
303
282
  )
304
283
 
305
- def _constellation_labels_static(self, style, labels):
306
- for con in condata.iterator():
307
- _, ra, dec = condata.get(con)
308
- text = labels.get(con.lower())
284
+ def _constellation_labels_static(self, style, label_fn):
285
+ for constellation in self.objects.constellations:
286
+ text = label_fn(constellation)
309
287
  self.text(
310
288
  text,
311
- ra * 15,
312
- dec,
289
+ constellation.ra,
290
+ constellation.dec,
313
291
  style,
314
292
  hide_on_collision=self.hide_colliding_labels,
315
293
  remove_on_constellation_collision=False,
@@ -321,7 +299,7 @@ class ConstellationPlotterMixin:
321
299
  def constellation_labels(
322
300
  self,
323
301
  style: LabelStyle = None,
324
- labels: dict[str, str] = CONSTELLATIONS_FULL_NAMES,
302
+ label_fn: Callable[[Constellation], str] = Constellation.get_label,
325
303
  auto_adjust: bool = True,
326
304
  auto_adjust_settings: dict = DEFAULT_AUTO_ADJUST_SETTINGS,
327
305
  ):
@@ -332,7 +310,7 @@ class ConstellationPlotterMixin:
332
310
 
333
311
  Args:
334
312
  style: Styling of the constellation labels. If None, then the plot's style (specified when creating the plot) will be used
335
- 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.
313
+ label_fn: Callable for determining the label for each constellation. The default function returns the constellation's name in uppercase.
336
314
  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.
337
315
  auto_adjust_settings: Optional settings for the auto adjustment algorithm.
338
316
  """
@@ -340,6 +318,6 @@ class ConstellationPlotterMixin:
340
318
  if auto_adjust:
341
319
  settings = DEFAULT_AUTO_ADJUST_SETTINGS
342
320
  settings.update(auto_adjust_settings)
343
- self._constellation_labels_auto(style, labels, settings=settings)
321
+ self._constellation_labels_auto(style, label_fn, settings=settings)
344
322
  else:
345
- self._constellation_labels_static(style, labels)
323
+ self._constellation_labels_static(style, label_fn)
starplot/plotters/dsos.py CHANGED
@@ -3,11 +3,8 @@ from typing import Callable, Mapping
3
3
  from ibis import _
4
4
  import numpy as np
5
5
 
6
- from starplot.data.dsos import (
7
- DSO_LABELS_DEFAULT,
8
- DsoLabelMaker,
9
- load,
10
- )
6
+ from starplot.data.dsos import load
7
+ from starplot.data.translations import translate
11
8
  from starplot.models.dso import (
12
9
  DSO,
13
10
  DsoType,
@@ -109,10 +106,9 @@ class DsoPlotterMixin:
109
106
  where: list = None,
110
107
  where_labels: list = None,
111
108
  true_size: bool = True,
112
- labels: Mapping[str, str] = DSO_LABELS_DEFAULT,
113
109
  legend_labels: Mapping[DsoType, str] = DSO_LEGEND_LABELS,
114
110
  alpha_fn: Callable[[DSO], float] = None,
115
- label_fn: Callable[[DSO], str] = None,
111
+ label_fn: Callable[[DSO], str] = DSO.get_label,
116
112
  sql: str = None,
117
113
  sql_labels: str = None,
118
114
  ):
@@ -126,7 +122,7 @@ class DsoPlotterMixin:
126
122
  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`.
127
123
  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`.
128
124
  alpha_fn: Callable for calculating the alpha value (aka "opacity") of each DSO. If `None`, then the marker style's alpha will be used.
129
- label_fn: Callable for determining the label of each DSO. If `None`, then the names in the `labels` kwarg will be used.
125
+ label_fn: Callable for determining the label of each DSO.
130
126
  sql: SQL query for selecting DSOs (table name is `_`). This query will be applied _after_ any filters in the `where` kwarg.
131
127
  sql_labels: SQL query for selecting DSOs that will be labeled (table name is `_`). Applied _after_ any filters in the `where_labels` kwarg.
132
128
  """
@@ -136,11 +132,6 @@ class DsoPlotterMixin:
136
132
  where = where or []
137
133
  where_labels = where_labels or []
138
134
 
139
- if labels is None:
140
- labels = {}
141
- elif type(labels) != DsoLabelMaker:
142
- labels = DsoLabelMaker(overrides=labels)
143
-
144
135
  if legend_labels is None:
145
136
  legend_labels = {}
146
137
  else:
@@ -172,8 +163,11 @@ class DsoPlotterMixin:
172
163
  style = self.style.get_dso_style(dso_type)
173
164
  maj_ax, min_ax, angle = d.maj_ax, d.min_ax, d.angle
174
165
  legend_label = legend_labels.get(dso_type)
166
+ if legend_label:
167
+ legend_label = translate(legend_label, self.language) or legend_label
175
168
  _dso = from_tuple(d)
176
- label = labels.get(d.name) if label_fn is None else label_fn(_dso)
169
+
170
+ label = label_fn(_dso)
177
171
 
178
172
  if style is None:
179
173
  continue