starplot 0.16.4__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.
- starplot/__init__.py +28 -2
- starplot/base.py +36 -56
- starplot/config.py +16 -0
- starplot/data/constellations.py +5 -676
- starplot/data/dsos.py +7 -23
- starplot/data/library/bigsky.0.4.0.stars.mag11.parquet +0 -0
- starplot/data/library/sky.db +0 -0
- starplot/data/stars.py +12 -2
- starplot/data/translations.py +161 -0
- starplot/geometry.py +7 -6
- starplot/horizon.py +9 -8
- starplot/map.py +1 -1
- starplot/models/__init__.py +19 -7
- starplot/models/base.py +1 -1
- starplot/models/comet.py +332 -0
- starplot/models/constellation.py +10 -0
- starplot/models/dso.py +24 -0
- starplot/{optics.py → models/optics.py} +4 -4
- starplot/models/satellite.py +158 -0
- starplot/models/star.py +10 -0
- starplot/optic.py +10 -9
- starplot/plotters/__init__.py +1 -0
- starplot/plotters/arrow.py +162 -0
- starplot/plotters/constellations.py +15 -51
- starplot/plotters/dsos.py +8 -14
- starplot/plotters/experimental.py +559 -6
- starplot/plotters/legend.py +5 -0
- starplot/plotters/stars.py +7 -16
- starplot/styles/base.py +20 -1
- starplot/styles/extensions.py +10 -1
- starplot/zenith.py +4 -1
- {starplot-0.16.4.dist-info → starplot-0.17.0.dist-info}/METADATA +19 -15
- {starplot-0.16.4.dist-info → starplot-0.17.0.dist-info}/RECORD +37 -33
- /starplot/{observer.py → models/observer.py} +0 -0
- {starplot-0.16.4.dist-info → starplot-0.17.0.dist-info}/WHEEL +0 -0
- {starplot-0.16.4.dist-info → starplot-0.17.0.dist-info}/entry_points.txt +0 -0
- {starplot-0.16.4.dist-info → starplot-0.17.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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,11 +12,8 @@ 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
18
|
from starplot.projections import (
|
|
20
19
|
StereoNorth,
|
|
@@ -24,7 +23,7 @@ from starplot.projections import (
|
|
|
24
23
|
LambertAzEqArea,
|
|
25
24
|
)
|
|
26
25
|
from starplot.profile import profile
|
|
27
|
-
from starplot.styles import
|
|
26
|
+
from starplot.styles import LineStyle, LabelStyle
|
|
28
27
|
from starplot.styles.helpers import use_style
|
|
29
28
|
from starplot.utils import points_on_line
|
|
30
29
|
from starplot.geometry import is_wrapped_polygon
|
|
@@ -197,40 +196,6 @@ class ConstellationPlotterMixin:
|
|
|
197
196
|
None,
|
|
198
197
|
)
|
|
199
198
|
|
|
200
|
-
def _plot_constellation_labels(
|
|
201
|
-
self,
|
|
202
|
-
style: PathStyle = None,
|
|
203
|
-
labels: dict[str, str] = CONSTELLATIONS_FULL_NAMES,
|
|
204
|
-
):
|
|
205
|
-
"""
|
|
206
|
-
TODO:
|
|
207
|
-
1. plot label, if removed then get size in display coords
|
|
208
|
-
2. generate random points in polygon, convert to display coords, test for intersections
|
|
209
|
-
3. plot best score
|
|
210
|
-
|
|
211
|
-
problem = constellations usually plotted first, so wont have star data (or could use stars from constellations only?)
|
|
212
|
-
|
|
213
|
-
constellation names CAN cross lines but not stars
|
|
214
|
-
|
|
215
|
-
"""
|
|
216
|
-
style = style or self.style.constellation.label
|
|
217
|
-
self._constellation_labels = []
|
|
218
|
-
|
|
219
|
-
for con in condata.iterator():
|
|
220
|
-
_, ra, dec = condata.get(con)
|
|
221
|
-
text = labels.get(con.lower())
|
|
222
|
-
label = self.text(
|
|
223
|
-
text,
|
|
224
|
-
ra,
|
|
225
|
-
dec,
|
|
226
|
-
style,
|
|
227
|
-
hide_on_collision=False,
|
|
228
|
-
# hide_on_collision=self.hide_colliding_labels,
|
|
229
|
-
gid="constellations-label-name",
|
|
230
|
-
)
|
|
231
|
-
if label is not None:
|
|
232
|
-
self._constellation_labels.append(label)
|
|
233
|
-
|
|
234
199
|
@profile
|
|
235
200
|
@use_style(LineStyle, "constellation_borders")
|
|
236
201
|
def constellation_borders(self, style: LineStyle = None):
|
|
@@ -275,7 +240,7 @@ class ConstellationPlotterMixin:
|
|
|
275
240
|
)
|
|
276
241
|
self.ax.add_collection(line_collection)
|
|
277
242
|
|
|
278
|
-
def _constellation_labels_auto(self, style,
|
|
243
|
+
def _constellation_labels_auto(self, style, label_fn, settings):
|
|
279
244
|
hips = []
|
|
280
245
|
for c in self.objects.constellations:
|
|
281
246
|
hips.extend(c.star_hip_ids)
|
|
@@ -303,7 +268,7 @@ class ConstellationPlotterMixin:
|
|
|
303
268
|
|
|
304
269
|
points_line = MultiPoint(starpoints)
|
|
305
270
|
centroid = points_line.centroid
|
|
306
|
-
text =
|
|
271
|
+
text = label_fn(constellation)
|
|
307
272
|
|
|
308
273
|
self.text(
|
|
309
274
|
text,
|
|
@@ -316,14 +281,13 @@ class ConstellationPlotterMixin:
|
|
|
316
281
|
gid="constellations-label-name",
|
|
317
282
|
)
|
|
318
283
|
|
|
319
|
-
def _constellation_labels_static(self, style,
|
|
320
|
-
for
|
|
321
|
-
|
|
322
|
-
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)
|
|
323
287
|
self.text(
|
|
324
288
|
text,
|
|
325
|
-
ra
|
|
326
|
-
dec,
|
|
289
|
+
constellation.ra,
|
|
290
|
+
constellation.dec,
|
|
327
291
|
style,
|
|
328
292
|
hide_on_collision=self.hide_colliding_labels,
|
|
329
293
|
remove_on_constellation_collision=False,
|
|
@@ -335,7 +299,7 @@ class ConstellationPlotterMixin:
|
|
|
335
299
|
def constellation_labels(
|
|
336
300
|
self,
|
|
337
301
|
style: LabelStyle = None,
|
|
338
|
-
|
|
302
|
+
label_fn: Callable[[Constellation], str] = Constellation.get_label,
|
|
339
303
|
auto_adjust: bool = True,
|
|
340
304
|
auto_adjust_settings: dict = DEFAULT_AUTO_ADJUST_SETTINGS,
|
|
341
305
|
):
|
|
@@ -346,7 +310,7 @@ class ConstellationPlotterMixin:
|
|
|
346
310
|
|
|
347
311
|
Args:
|
|
348
312
|
style: Styling of the constellation labels. If None, then the plot's style (specified when creating the plot) will be used
|
|
349
|
-
|
|
313
|
+
label_fn: Callable for determining the label for each constellation. The default function returns the constellation's name in uppercase.
|
|
350
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.
|
|
351
315
|
auto_adjust_settings: Optional settings for the auto adjustment algorithm.
|
|
352
316
|
"""
|
|
@@ -354,6 +318,6 @@ class ConstellationPlotterMixin:
|
|
|
354
318
|
if auto_adjust:
|
|
355
319
|
settings = DEFAULT_AUTO_ADJUST_SETTINGS
|
|
356
320
|
settings.update(auto_adjust_settings)
|
|
357
|
-
self._constellation_labels_auto(style,
|
|
321
|
+
self._constellation_labels_auto(style, label_fn, settings=settings)
|
|
358
322
|
else:
|
|
359
|
-
self._constellation_labels_static(style,
|
|
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
|
-
|
|
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] =
|
|
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.
|
|
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
|
-
|
|
169
|
+
|
|
170
|
+
label = label_fn(_dso)
|
|
177
171
|
|
|
178
172
|
if style is None:
|
|
179
173
|
continue
|