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.
- starplot/__init__.py +59 -0
- starplot/callables.py +176 -0
- starplot/cli.py +29 -0
- starplot/config.py +80 -0
- starplot/coordinates.py +7 -0
- starplot/data/__init__.py +22 -0
- starplot/data/catalogs.py +356 -0
- starplot/data/constellations.py +71 -0
- starplot/data/db.py +33 -0
- starplot/data/dsos.py +85 -0
- starplot/data/ecliptic.py +201 -0
- starplot/data/library/constellation_names.parquet +0 -0
- starplot/data/library/dso_names.parquet +0 -0
- starplot/data/library/readme.md +1 -0
- starplot/data/library/star_designations.parquet +0 -0
- starplot/data/stars.py +79 -0
- starplot/data/translations.py +408 -0
- starplot/data/utils.py +35 -0
- starplot/geometry.py +319 -0
- starplot/mixins.py +360 -0
- starplot/models/__init__.py +21 -0
- starplot/models/base.py +77 -0
- starplot/models/comet.py +302 -0
- starplot/models/constellation.py +151 -0
- starplot/models/dso.py +317 -0
- starplot/models/milky_way.py +30 -0
- starplot/models/moon.py +130 -0
- starplot/models/objects.py +29 -0
- starplot/models/observer.py +125 -0
- starplot/models/optics.py +342 -0
- starplot/models/planet.py +137 -0
- starplot/models/satellite.py +138 -0
- starplot/models/star.py +253 -0
- starplot/models/sun.py +62 -0
- starplot/plots/__init__.py +7 -0
- starplot/plots/base.py +1023 -0
- starplot/plots/galaxy.py +372 -0
- starplot/plots/horizon.py +543 -0
- starplot/plots/map.py +515 -0
- starplot/plots/optic.py +469 -0
- starplot/plots/zenith.py +217 -0
- starplot/plotters/__init__.py +9 -0
- starplot/plotters/arrow.py +174 -0
- starplot/plotters/constellations.py +298 -0
- starplot/plotters/debug.py +21 -0
- starplot/plotters/dsos.py +294 -0
- starplot/plotters/experimental.py +722 -0
- starplot/plotters/gradients.py +153 -0
- starplot/plotters/legend.py +253 -0
- starplot/plotters/milkyway.py +51 -0
- starplot/plotters/stars.py +319 -0
- starplot/plotters/text.py +802 -0
- starplot/profile.py +16 -0
- starplot/projections.py +184 -0
- starplot/styles/__init__.py +6 -0
- starplot/styles/base.py +1344 -0
- starplot/styles/ext/antique.yml +175 -0
- starplot/styles/ext/blue_dark.yml +163 -0
- starplot/styles/ext/blue_gold.yml +147 -0
- starplot/styles/ext/blue_light.yml +123 -0
- starplot/styles/ext/blue_medium.yml +142 -0
- starplot/styles/ext/blue_night.yml +185 -0
- starplot/styles/ext/cb_wong.yml +124 -0
- starplot/styles/ext/color_print.yml +111 -0
- starplot/styles/ext/gradient_presets.yml +158 -0
- starplot/styles/ext/grayscale.yml +94 -0
- starplot/styles/ext/grayscale_dark.yml +136 -0
- starplot/styles/ext/map.yml +12 -0
- starplot/styles/ext/nord.yml +158 -0
- starplot/styles/ext/optic.yml +20 -0
- starplot/styles/ext/publication.yml +8 -0
- starplot/styles/extensions.py +129 -0
- starplot/styles/fonts-library/gfs-didot/DESCRIPTION.en_us.html +9 -0
- starplot/styles/fonts-library/gfs-didot/GFSDidot-Regular.ttf +0 -0
- starplot/styles/fonts-library/gfs-didot/METADATA.pb +16 -0
- starplot/styles/fonts-library/gfs-didot/OFL.txt +94 -0
- starplot/styles/fonts-library/inter/Inter-Bold.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-BoldItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ExtraBold.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ExtraLight.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ExtraLightItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Italic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Light.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-LightItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Regular.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-SemiBold.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-SemiBoldItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/LICENSE.txt +92 -0
- starplot/styles/fonts.py +15 -0
- starplot/styles/helpers.py +93 -0
- starplot/styles/markers.py +308 -0
- starplot/utils.py +169 -0
- starplot/warnings.py +21 -0
- starplot-0.20.5.dist-info/METADATA +146 -0
- starplot-0.20.5.dist-info/RECORD +98 -0
- starplot-0.20.5.dist-info/WHEEL +4 -0
- starplot-0.20.5.dist-info/entry_points.txt +3 -0
- starplot-0.20.5.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from matplotlib.colors import LinearSegmentedColormap
|
|
3
|
+
from starplot.profile import profile
|
|
4
|
+
from starplot.styles import GradientDirection
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GradientBackgroundMixin:
|
|
8
|
+
"""
|
|
9
|
+
Mixin class to handle adding gradients to plots.
|
|
10
|
+
|
|
11
|
+
Handles a variety of projections and can be inherited by HorizonPlot, OpticPlot,
|
|
12
|
+
and MapPlot for vertical, radial, and mollweide gradients. However, some more
|
|
13
|
+
obscure projections may throw errors if attempting to plot a gradient with them.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@profile
|
|
17
|
+
def _plot_gradient_background(
|
|
18
|
+
self, gradient_preset: list[tuple[float, str]]
|
|
19
|
+
) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Adds a gradient background to the plot, beneath the GeoAxes.
|
|
22
|
+
The background_color of the map must be set as a RGBA value with full
|
|
23
|
+
transparency (e.g. #ffffff00) for this function to render the desired
|
|
24
|
+
result.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
gradient_preset: A list of tuples (e.g. [(0.0, '#000000'), (1.0, '#000080')])
|
|
28
|
+
where each tuple contains a position value [0-1] and a color
|
|
29
|
+
value to describe the range of colors in the gradient.
|
|
30
|
+
"""
|
|
31
|
+
direction = self._gradient_direction
|
|
32
|
+
reverse = True if direction == GradientDirection.RADIAL else False
|
|
33
|
+
cmap = self._create_colormap(gradient_preset, reverse=reverse)
|
|
34
|
+
background_ax = self._create_background_ax()
|
|
35
|
+
background_ax.set_axis_off()
|
|
36
|
+
self._background_ax = background_ax
|
|
37
|
+
|
|
38
|
+
X, Y, gradient = self._create_gradient_arrays()
|
|
39
|
+
|
|
40
|
+
# Radial specific axes adjustments
|
|
41
|
+
if self._gradient_direction == GradientDirection.RADIAL:
|
|
42
|
+
background_ax.set_ylim(Y.min(), Y.max() * 1.06)
|
|
43
|
+
|
|
44
|
+
# if getattr(self, "optic", None):
|
|
45
|
+
# self._camera_optic_transform(background_ax)
|
|
46
|
+
# Camera specific axes adjustments
|
|
47
|
+
# if gradient_shape == "camera":
|
|
48
|
+
# self._camera_optic_transform(background_ax)
|
|
49
|
+
|
|
50
|
+
# Render gradient
|
|
51
|
+
background_ax.pcolormesh(
|
|
52
|
+
X,
|
|
53
|
+
Y,
|
|
54
|
+
gradient,
|
|
55
|
+
cmap=cmap,
|
|
56
|
+
shading="gouraud",
|
|
57
|
+
rasterized=True,
|
|
58
|
+
zorder=0,
|
|
59
|
+
clip_path=self._background_clip_path,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Set plot in self.ax's zorder to 1 so it appears above the gradient
|
|
63
|
+
self.ax.zorder = 1
|
|
64
|
+
|
|
65
|
+
# Event driven function so background_ax matches GeoAxes when plotted
|
|
66
|
+
self.ax.figure.canvas.mpl_connect(
|
|
67
|
+
"draw_event",
|
|
68
|
+
lambda event: background_ax.set_position(self.ax.get_position()),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def _create_colormap(
|
|
72
|
+
self, gradient_preset: list[tuple[float, str]], reverse: bool = False
|
|
73
|
+
) -> LinearSegmentedColormap:
|
|
74
|
+
"""Creates a matplotlib colormap from a gradient preset."""
|
|
75
|
+
positions, colors = zip(*gradient_preset)
|
|
76
|
+
|
|
77
|
+
if self._gradient_direction == GradientDirection.RADIAL:
|
|
78
|
+
positions = [p / 2 for p in positions]
|
|
79
|
+
positions[-1] = 1
|
|
80
|
+
|
|
81
|
+
from pydantic.color import Color
|
|
82
|
+
|
|
83
|
+
colors = [Color(c).as_hex() for c in colors]
|
|
84
|
+
|
|
85
|
+
cmap = LinearSegmentedColormap.from_list(
|
|
86
|
+
"custom_gradient", list(zip(positions, colors)), N=750
|
|
87
|
+
)
|
|
88
|
+
return cmap.reversed() if reverse else cmap
|
|
89
|
+
|
|
90
|
+
def _create_background_ax(self):
|
|
91
|
+
"""Adds a set of axes to take the gradient image."""
|
|
92
|
+
bbox = self.ax.get_position()
|
|
93
|
+
projection = None
|
|
94
|
+
|
|
95
|
+
if self._gradient_direction == GradientDirection.RADIAL:
|
|
96
|
+
projection = "polar"
|
|
97
|
+
elif self._gradient_direction == GradientDirection.MOLLWEIDE:
|
|
98
|
+
projection = "mollweide"
|
|
99
|
+
|
|
100
|
+
return self.ax.figure.add_axes(bbox, zorder=0, projection=projection)
|
|
101
|
+
|
|
102
|
+
def _create_gradient_arrays(self):
|
|
103
|
+
"""Creates arrays for the gradient placement and the gradient meshgrid."""
|
|
104
|
+
# Radial gradient
|
|
105
|
+
if self._gradient_direction == GradientDirection.RADIAL:
|
|
106
|
+
rad = np.linspace(0, 1, 50)
|
|
107
|
+
azm = np.linspace(0, 2 * np.pi, 100)
|
|
108
|
+
Y, X = np.meshgrid(rad, azm)
|
|
109
|
+
gradient = Y**2.0
|
|
110
|
+
return X, Y, gradient
|
|
111
|
+
|
|
112
|
+
# Mollweide gradient
|
|
113
|
+
if self._gradient_direction == GradientDirection.MOLLWEIDE:
|
|
114
|
+
return self._create_mollweide_gradient()
|
|
115
|
+
|
|
116
|
+
# Default Vertical Gradient
|
|
117
|
+
x_array = np.linspace(0, 1, 2)
|
|
118
|
+
y_array = np.linspace(0, 1, 750)
|
|
119
|
+
X, Y = np.meshgrid(x_array, y_array)
|
|
120
|
+
gradient = np.linspace(0, 1, 750).reshape(-1, 1)
|
|
121
|
+
gradient = np.repeat(gradient, 2, axis=1)
|
|
122
|
+
return X, Y, gradient
|
|
123
|
+
|
|
124
|
+
def _create_mollweide_gradient(self):
|
|
125
|
+
"""Generate meshgrid and gradient for a mollweide projection."""
|
|
126
|
+
x = np.linspace(-np.pi, np.pi, 250)
|
|
127
|
+
y = np.linspace(-np.pi / 2, np.pi / 2, 250)
|
|
128
|
+
X, Y = np.meshgrid(x, y)
|
|
129
|
+
# Rotation matrix (ICRS → Galactic)
|
|
130
|
+
R = np.array(
|
|
131
|
+
[
|
|
132
|
+
[-0.0548755604162154, -0.8734370902348850, -0.4838350155487132],
|
|
133
|
+
[0.4941094278755837, -0.4448296299600112, 0.7469822444972189],
|
|
134
|
+
[-0.8676661490190047, -0.1980763734312015, 0.4559837761750669],
|
|
135
|
+
]
|
|
136
|
+
)
|
|
137
|
+
# Equatorial unit vectors
|
|
138
|
+
cos_y = np.cos(Y)
|
|
139
|
+
eq = np.stack(
|
|
140
|
+
[cos_y * np.cos(X), cos_y * np.sin(X) * -1, np.sin(Y) * -1], axis=-1
|
|
141
|
+
)
|
|
142
|
+
# Rotate into Galactic coords
|
|
143
|
+
gal = eq @ R.T
|
|
144
|
+
# Gradient follows galactic latitude
|
|
145
|
+
gradient = np.arcsin(gal[..., 2])
|
|
146
|
+
return X, Y, gradient
|
|
147
|
+
|
|
148
|
+
def _camera_optic_transform(self, background_ax) -> None:
|
|
149
|
+
"""Apply camera-specific axes transformations for the gradient."""
|
|
150
|
+
background_ax.set_xlim(-0.11, 1.11)
|
|
151
|
+
background_ax.set_ylim(-0.07, 1.07)
|
|
152
|
+
if self.optic.rotation == 0:
|
|
153
|
+
return
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from matplotlib.legend import Legend
|
|
5
|
+
from matplotlib.lines import Line2D
|
|
6
|
+
|
|
7
|
+
from starplot import callables
|
|
8
|
+
from starplot.data.translations import translate
|
|
9
|
+
from starplot.models.star import Star
|
|
10
|
+
from starplot.styles import (
|
|
11
|
+
MarkerStyle,
|
|
12
|
+
LegendLocationEnum,
|
|
13
|
+
LegendStyle,
|
|
14
|
+
)
|
|
15
|
+
from starplot.styles.helpers import use_style
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LegendPlotterMixin:
|
|
19
|
+
def _create_legend(self, handles, labels, title, style, set_anchor: bool = False):
|
|
20
|
+
style_kwargs = style.matplot_kwargs(self.scale)
|
|
21
|
+
|
|
22
|
+
target = self.ax
|
|
23
|
+
|
|
24
|
+
if style.location.startswith("outside"):
|
|
25
|
+
target = self.fig
|
|
26
|
+
|
|
27
|
+
style_kwargs["borderaxespad"] = -1 * style.padding
|
|
28
|
+
|
|
29
|
+
legend = Legend(
|
|
30
|
+
target,
|
|
31
|
+
handles=handles,
|
|
32
|
+
labels=labels,
|
|
33
|
+
title=title,
|
|
34
|
+
**style_kwargs,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
legend.set_zorder(
|
|
38
|
+
# zorder is not a valid kwarg to legend(), so we have to set it afterwards
|
|
39
|
+
style.zorder
|
|
40
|
+
)
|
|
41
|
+
legend.get_title().set_color(style.font_color.as_hex())
|
|
42
|
+
|
|
43
|
+
if not set_anchor:
|
|
44
|
+
return legend
|
|
45
|
+
|
|
46
|
+
display_to_axes_transform = self.ax.transAxes.inverted()
|
|
47
|
+
origin_x, origin_y = self.ax.transAxes.transform((0, 0))
|
|
48
|
+
padding_x, padding_y = display_to_axes_transform.transform(
|
|
49
|
+
(origin_x + style.padding_x / 2, origin_y + style.padding_y / 2)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if style.location.startswith("outside"):
|
|
53
|
+
extent = legend.get_window_extent(renderer=self.fig.canvas.get_renderer())
|
|
54
|
+
min_x, min_y = display_to_axes_transform.transform(extent.min)
|
|
55
|
+
max_x, max_y = display_to_axes_transform.transform(extent.max)
|
|
56
|
+
|
|
57
|
+
baseline, _ = display_to_axes_transform.transform(
|
|
58
|
+
(origin_x + 200, origin_y + style.padding_y / 2)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
padding_x += baseline
|
|
62
|
+
width = max_x - min_x + padding_x
|
|
63
|
+
# height = max_y - min_y
|
|
64
|
+
# top_x, top_y = display_to_figure_transform.transform(self.ax.transAxes.transform((1, 1)))
|
|
65
|
+
|
|
66
|
+
bbox = {
|
|
67
|
+
LegendLocationEnum.OUTSIDE_TOP_RIGHT: (1 + width, 1 - padding_y),
|
|
68
|
+
LegendLocationEnum.OUTSIDE_TOP_LEFT: (-1 * width, 1 - padding_y),
|
|
69
|
+
LegendLocationEnum.OUTSIDE_BOTTOM_RIGHT: (1 + width, padding_y),
|
|
70
|
+
LegendLocationEnum.OUTSIDE_BOTTOM_LEFT: (-1 * width, padding_y),
|
|
71
|
+
}.get(style.location)
|
|
72
|
+
|
|
73
|
+
else:
|
|
74
|
+
bbox = {
|
|
75
|
+
LegendLocationEnum.INSIDE_TOP_LEFT: (padding_x, 1 - padding_y),
|
|
76
|
+
LegendLocationEnum.INSIDE_TOP_RIGHT: (1 - padding_x, 1 - padding_y),
|
|
77
|
+
LegendLocationEnum.INSIDE_TOP: (0.5, 1 - padding_y),
|
|
78
|
+
LegendLocationEnum.INSIDE_BOTTOM_LEFT: (padding_x, padding_y),
|
|
79
|
+
LegendLocationEnum.INSIDE_BOTTOM_RIGHT: (1 - padding_x, padding_y),
|
|
80
|
+
LegendLocationEnum.INSIDE_BOTTOM: (0.5, padding_y),
|
|
81
|
+
}.get(style.location)
|
|
82
|
+
|
|
83
|
+
legend.set_bbox_to_anchor(
|
|
84
|
+
bbox=bbox,
|
|
85
|
+
transform=self.ax.transAxes,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return legend
|
|
89
|
+
|
|
90
|
+
@use_style(LegendStyle, "legend")
|
|
91
|
+
def legend(self, title: str = "Legend", style: LegendStyle = None):
|
|
92
|
+
"""
|
|
93
|
+
Plots the legend.
|
|
94
|
+
|
|
95
|
+
If the legend is already plotted, then it'll be removed first and then plotted again. So, it's safe to call this function multiple times if you need to 'refresh' the legend.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
title: Title of the legend, which will be plotted at the top
|
|
99
|
+
style: Styling of the legend. If None, then the plot's style (specified when creating the plot) will be used
|
|
100
|
+
"""
|
|
101
|
+
if not self._legend_handles:
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
if self._legend:
|
|
105
|
+
self._legend.remove()
|
|
106
|
+
|
|
107
|
+
target = self.ax
|
|
108
|
+
|
|
109
|
+
title = translate(title, self.language)
|
|
110
|
+
|
|
111
|
+
if style.location.startswith("outside"):
|
|
112
|
+
target = self.fig
|
|
113
|
+
|
|
114
|
+
legend = self._create_legend(
|
|
115
|
+
handles=self._legend_handles.values(),
|
|
116
|
+
labels=self._legend_handles.keys(),
|
|
117
|
+
title=title,
|
|
118
|
+
style=style,
|
|
119
|
+
set_anchor=True,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
target.add_artist(legend)
|
|
123
|
+
|
|
124
|
+
self._legend = legend
|
|
125
|
+
self._legend_target = target
|
|
126
|
+
|
|
127
|
+
def _add_to_legend(self, legend):
|
|
128
|
+
if not self._legend:
|
|
129
|
+
self.legend()
|
|
130
|
+
|
|
131
|
+
target = self._legend_target
|
|
132
|
+
|
|
133
|
+
legend_base = self._legend.get_children()[0]
|
|
134
|
+
legend_2 = legend.get_children()[0]
|
|
135
|
+
|
|
136
|
+
# empty legend for padding
|
|
137
|
+
empty = Legend(
|
|
138
|
+
target,
|
|
139
|
+
handles=[],
|
|
140
|
+
labels=[],
|
|
141
|
+
title="",
|
|
142
|
+
# **style_kwargs,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# add empty legend for padding between legend and scale
|
|
146
|
+
legend_base.get_children().extend(empty.get_children()[0].get_children())
|
|
147
|
+
|
|
148
|
+
legend_base.get_children().extend(legend_2.get_children())
|
|
149
|
+
|
|
150
|
+
legend_base.get_children().extend(empty.get_children()[0].get_children())
|
|
151
|
+
|
|
152
|
+
# target.add_artist(self._legend)
|
|
153
|
+
|
|
154
|
+
@use_style(LegendStyle, "legend")
|
|
155
|
+
def star_magnitude_scale(
|
|
156
|
+
self,
|
|
157
|
+
title: str = "Star Magnitude",
|
|
158
|
+
style: LegendStyle = None,
|
|
159
|
+
size_fn: Callable[[Star], float] = callables.size_by_magnitude,
|
|
160
|
+
label_fn: Callable[float, str] = lambda m: str(m),
|
|
161
|
+
start: float = -1,
|
|
162
|
+
stop: float = 9,
|
|
163
|
+
step: float = 1,
|
|
164
|
+
add_to_legend: bool = False,
|
|
165
|
+
):
|
|
166
|
+
"""
|
|
167
|
+
Plots a magnitude scale for stars.
|
|
168
|
+
|
|
169
|
+
!!! example "Experimental"
|
|
170
|
+
|
|
171
|
+
This is currently an "experimental" feature, which means it's likely to be changed and improved in upcoming versions of Starplot.
|
|
172
|
+
It also means the feature likely has limitations.
|
|
173
|
+
|
|
174
|
+
**Help us improve this feature by submitting feedback on [GitHub (open an issue)](https://github.com/steveberardi/starplot/issues) or chat with us on [Discord](https://discord.gg/WewJJjshFu). Thanks!**
|
|
175
|
+
|
|
176
|
+
!!! note "Current Limitations"
|
|
177
|
+
- Only supports size functions that determine size based on magnitude only
|
|
178
|
+
- Only supports default marker for stars (point)
|
|
179
|
+
- Labels can only be plotted to the right of the marker
|
|
180
|
+
- Does not automatically determine the magnitude range of the stars you already plotted
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
title: Title of the legend, which will be plotted at the top
|
|
184
|
+
style: Styling of the magnitude scale. If None, then the plot's `legend` style will be used
|
|
185
|
+
size_fn: Size function for the star markers
|
|
186
|
+
label_fn: Function to determine the label for each magnitude
|
|
187
|
+
start: Starting magnitude
|
|
188
|
+
stop: Stop point (exclusive)
|
|
189
|
+
step: Step-size of each scale entry (i.e. how much to increment each step)
|
|
190
|
+
add_to_legend: If True, the scale will be added to the bottom of the legend (and if the legend isn't already plotted, then it'll plot the legend)
|
|
191
|
+
"""
|
|
192
|
+
target = self.ax
|
|
193
|
+
|
|
194
|
+
if style.location.startswith("outside"):
|
|
195
|
+
target = self.fig
|
|
196
|
+
|
|
197
|
+
title = translate(title, self.language)
|
|
198
|
+
|
|
199
|
+
def scale(
|
|
200
|
+
size_fn,
|
|
201
|
+
style: MarkerStyle,
|
|
202
|
+
label_fn,
|
|
203
|
+
start: float,
|
|
204
|
+
stop: float,
|
|
205
|
+
step: float = 1,
|
|
206
|
+
):
|
|
207
|
+
for mag in np.arange(start, stop, step):
|
|
208
|
+
s = style.matplot_kwargs()
|
|
209
|
+
s["markersize"] = (
|
|
210
|
+
size_fn(Star(pk=1, ra=0, dec=0, magnitude=mag, geometry=None))
|
|
211
|
+
** 0.5
|
|
212
|
+
) * self.scale
|
|
213
|
+
label = label_fn(mag)
|
|
214
|
+
yield Line2D(
|
|
215
|
+
[],
|
|
216
|
+
[],
|
|
217
|
+
**s,
|
|
218
|
+
linestyle="None",
|
|
219
|
+
label=label,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
handles = [
|
|
223
|
+
h
|
|
224
|
+
for h in scale(
|
|
225
|
+
size_fn=size_fn,
|
|
226
|
+
style=self.style.star.marker,
|
|
227
|
+
label_fn=label_fn,
|
|
228
|
+
start=start,
|
|
229
|
+
stop=stop,
|
|
230
|
+
step=step,
|
|
231
|
+
)
|
|
232
|
+
]
|
|
233
|
+
labels = [str(m) for m in np.arange(start, stop, step)]
|
|
234
|
+
|
|
235
|
+
scale = self._create_legend(
|
|
236
|
+
handles=handles,
|
|
237
|
+
labels=labels,
|
|
238
|
+
title=title,
|
|
239
|
+
style=style,
|
|
240
|
+
set_anchor=True if not add_to_legend else False,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
if add_to_legend:
|
|
244
|
+
self._add_to_legend(scale)
|
|
245
|
+
else:
|
|
246
|
+
target.add_artist(scale)
|
|
247
|
+
|
|
248
|
+
# for text in magnitude_scale.get_texts():
|
|
249
|
+
# text.set_ha("center") # horizontal alignment of text item
|
|
250
|
+
# text.set_x(-85) # x-position
|
|
251
|
+
# text.set_y(-90) # y-position
|
|
252
|
+
|
|
253
|
+
# self.ax.add_artist(magnitude_scale)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from shapely.ops import unary_union
|
|
2
|
+
|
|
3
|
+
from ibis import _
|
|
4
|
+
|
|
5
|
+
from starplot.data import db
|
|
6
|
+
from starplot.data.catalogs import Catalog, MILKY_WAY
|
|
7
|
+
from starplot.styles import PolygonStyle
|
|
8
|
+
from starplot.styles.helpers import use_style
|
|
9
|
+
from starplot.geometry import split_polygon_at_zero
|
|
10
|
+
from starplot.profile import profile
|
|
11
|
+
from starplot.models.milky_way import from_tuple
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MilkyWayPlotterMixin:
|
|
15
|
+
@profile
|
|
16
|
+
@use_style(PolygonStyle, "milky_way")
|
|
17
|
+
def milky_way(self, style: PolygonStyle = None, catalog: Catalog = MILKY_WAY):
|
|
18
|
+
"""
|
|
19
|
+
Plots the Milky Way
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
style: Styling of the Milky Way. If None, then the plot's style (specified when creating the plot) will be used
|
|
23
|
+
catalog: Catalog to use for Milky Way polygons
|
|
24
|
+
"""
|
|
25
|
+
con = db.connect()
|
|
26
|
+
mw = catalog._load(connection=con, table_name="milky_way")
|
|
27
|
+
mw = mw.mutate(
|
|
28
|
+
geometry=_.geometry.cast("geometry"), # cast WKB to geometry type
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
extent = self._extent_mask()
|
|
32
|
+
df = mw.filter(_.geometry.intersects(extent)).to_pandas()
|
|
33
|
+
|
|
34
|
+
milky_ways = [from_tuple(m) for m in df.itertuples()]
|
|
35
|
+
|
|
36
|
+
polygons = []
|
|
37
|
+
for milky_way in milky_ways:
|
|
38
|
+
polygons.extend(split_polygon_at_zero(milky_way.geometry))
|
|
39
|
+
|
|
40
|
+
mw_union = unary_union(polygons)
|
|
41
|
+
|
|
42
|
+
if mw_union.geom_type == "MultiPolygon":
|
|
43
|
+
polygons = mw_union.geoms
|
|
44
|
+
else:
|
|
45
|
+
polygons = [mw_union]
|
|
46
|
+
|
|
47
|
+
for p in polygons:
|
|
48
|
+
self.polygon(
|
|
49
|
+
geometry=p.buffer(0.001),
|
|
50
|
+
style=style,
|
|
51
|
+
)
|