starplot 0.13.0__py2.py3-none-any.whl → 0.15.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 +5 -2
- starplot/base.py +311 -197
- starplot/cli.py +33 -0
- starplot/coordinates.py +6 -0
- starplot/data/__init__.py +6 -24
- starplot/data/bigsky.py +58 -40
- starplot/data/constellation_lines.py +827 -0
- starplot/data/constellation_stars.py +1501 -0
- starplot/data/constellations.py +600 -27
- starplot/data/db.py +17 -0
- starplot/data/dsos.py +24 -141
- starplot/data/stars.py +45 -24
- starplot/geod.py +0 -6
- starplot/geometry.py +181 -0
- starplot/horizon.py +302 -231
- starplot/map.py +100 -463
- starplot/mixins.py +75 -14
- starplot/models/__init__.py +1 -1
- starplot/models/base.py +18 -129
- starplot/models/constellation.py +55 -32
- starplot/models/dso.py +132 -67
- starplot/models/moon.py +57 -78
- starplot/models/planet.py +44 -69
- starplot/models/star.py +91 -60
- starplot/models/sun.py +32 -53
- starplot/optic.py +21 -18
- starplot/plotters/__init__.py +2 -0
- starplot/plotters/constellations.py +342 -0
- starplot/plotters/dsos.py +49 -68
- starplot/plotters/experimental.py +171 -0
- starplot/plotters/milkyway.py +39 -0
- starplot/plotters/stars.py +126 -122
- starplot/profile.py +16 -0
- starplot/settings.py +26 -0
- starplot/styles/__init__.py +2 -0
- starplot/styles/base.py +56 -34
- starplot/styles/ext/antique.yml +11 -9
- starplot/styles/ext/blue_dark.yml +8 -10
- starplot/styles/ext/blue_gold.yml +135 -0
- starplot/styles/ext/blue_light.yml +14 -12
- starplot/styles/ext/blue_medium.yml +23 -20
- starplot/styles/ext/cb_wong.yml +9 -7
- starplot/styles/ext/grayscale.yml +4 -3
- starplot/styles/ext/grayscale_dark.yml +7 -5
- starplot/styles/ext/map.yml +9 -6
- starplot/styles/ext/nord.yml +7 -7
- starplot/styles/ext/optic.yml +1 -1
- starplot/styles/extensions.py +1 -0
- starplot/utils.py +19 -0
- starplot/warnings.py +21 -0
- {starplot-0.13.0.dist-info → starplot-0.15.0.dist-info}/METADATA +19 -18
- starplot-0.15.0.dist-info/RECORD +97 -0
- starplot-0.15.0.dist-info/entry_points.txt +3 -0
- starplot/data/bayer.py +0 -3499
- starplot/data/flamsteed.py +0 -2682
- starplot/data/library/constellation_borders_inv.gpkg +0 -0
- starplot/data/library/constellation_lines_hips.json +0 -709
- starplot/data/library/constellation_lines_inv.gpkg +0 -0
- starplot/data/library/constellations.gpkg +0 -0
- starplot/data/library/constellations_hip.fab +0 -88
- starplot/data/library/milkyway.gpkg +0 -0
- starplot/data/library/milkyway_inv.gpkg +0 -0
- starplot/data/library/ongc.gpkg.zip +0 -0
- starplot/data/library/stars.bigsky.mag11.parquet +0 -0
- starplot/data/library/stars.hipparcos.parquet +0 -0
- starplot/data/messier.py +0 -111
- starplot/data/prep/__init__.py +0 -0
- starplot/data/prep/constellations.py +0 -108
- starplot/data/prep/dsos.py +0 -299
- starplot/data/prep/utils.py +0 -16
- starplot/models/geometry.py +0 -44
- starplot-0.13.0.dist-info/RECORD +0 -101
- {starplot-0.13.0.dist-info → starplot-0.15.0.dist-info}/LICENSE +0 -0
- {starplot-0.13.0.dist-info → starplot-0.15.0.dist-info}/WHEEL +0 -0
starplot/map.py
CHANGED
|
@@ -1,63 +1,48 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import math
|
|
3
|
-
import warnings
|
|
4
3
|
from typing import Callable
|
|
4
|
+
from functools import cache
|
|
5
5
|
|
|
6
6
|
from cartopy import crs as ccrs
|
|
7
7
|
from matplotlib import pyplot as plt
|
|
8
8
|
from matplotlib import path, patches, ticker
|
|
9
9
|
from matplotlib.ticker import FuncFormatter, FixedLocator
|
|
10
|
-
from shapely import
|
|
11
|
-
from
|
|
12
|
-
from skyfield.api import Star as SkyfieldStar, wgs84
|
|
13
|
-
import geopandas as gpd
|
|
10
|
+
from shapely import Polygon
|
|
11
|
+
from skyfield.api import wgs84
|
|
14
12
|
import numpy as np
|
|
15
13
|
|
|
14
|
+
from starplot.coordinates import CoordinateSystem
|
|
16
15
|
from starplot import geod
|
|
17
16
|
from starplot.base import BasePlot, DPI
|
|
18
|
-
from starplot.data import DataFiles, constellations as condata, stars
|
|
19
|
-
from starplot.data.constellations import CONSTELLATIONS_FULL_NAMES
|
|
20
17
|
from starplot.mixins import ExtentMaskMixin
|
|
21
|
-
from starplot.
|
|
22
|
-
|
|
18
|
+
from starplot.plotters import (
|
|
19
|
+
ConstellationPlotterMixin,
|
|
20
|
+
StarPlotterMixin,
|
|
21
|
+
DsoPlotterMixin,
|
|
22
|
+
MilkyWayPlotterMixin,
|
|
23
|
+
)
|
|
23
24
|
from starplot.projections import Projection
|
|
24
25
|
from starplot.styles import (
|
|
25
26
|
ObjectStyle,
|
|
26
27
|
LabelStyle,
|
|
27
|
-
LineStyle,
|
|
28
28
|
PlotStyle,
|
|
29
|
-
PolygonStyle,
|
|
30
29
|
PathStyle,
|
|
31
30
|
)
|
|
32
31
|
from starplot.styles.helpers import use_style
|
|
33
32
|
from starplot.utils import lon_to_ra, ra_to_lon
|
|
34
33
|
|
|
35
|
-
# Silence noisy cartopy warnings
|
|
36
|
-
warnings.filterwarnings("ignore", module="cartopy")
|
|
37
|
-
warnings.filterwarnings("ignore", module="shapely")
|
|
38
34
|
|
|
39
35
|
DEFAULT_MAP_STYLE = PlotStyle() # .extend(extensions.MAP)
|
|
40
36
|
|
|
41
37
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
Returns:
|
|
51
|
-
list: List of (x, y) coordinates of the generated points.
|
|
52
|
-
"""
|
|
53
|
-
|
|
54
|
-
x_coords = np.linspace(start[0], end[0], num_points)
|
|
55
|
-
y_coords = np.linspace(start[1], end[1], num_points)
|
|
56
|
-
|
|
57
|
-
return list(zip(x_coords, y_coords))
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
38
|
+
class MapPlot(
|
|
39
|
+
BasePlot,
|
|
40
|
+
ExtentMaskMixin,
|
|
41
|
+
StarPlotterMixin,
|
|
42
|
+
DsoPlotterMixin,
|
|
43
|
+
MilkyWayPlotterMixin,
|
|
44
|
+
ConstellationPlotterMixin,
|
|
45
|
+
):
|
|
61
46
|
"""Creates a new map plot.
|
|
62
47
|
|
|
63
48
|
!!! star "Note"
|
|
@@ -65,8 +50,8 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
65
50
|
|
|
66
51
|
Args:
|
|
67
52
|
projection: Projection of the map
|
|
68
|
-
ra_min: Minimum right ascension of the map's extent, in
|
|
69
|
-
ra_max: Maximum right ascension of the map's extent, in
|
|
53
|
+
ra_min: Minimum right ascension of the map's extent, in degrees (0...360)
|
|
54
|
+
ra_max: Maximum right ascension of the map's extent, in degrees (0...360)
|
|
70
55
|
dec_min: Minimum declination of the map's extent, in degrees (-90...90)
|
|
71
56
|
dec_max: Maximum declination of the map's extent, in degrees (-90...90)
|
|
72
57
|
lat: Latitude for perspective projections: Orthographic, Stereographic, and Zenith
|
|
@@ -79,17 +64,20 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
79
64
|
clip_path: An optional Shapely Polygon that specifies the clip path of the plot -- only objects inside the polygon will be plotted. If `None` (the default), then the clip path will be the extent of the map you specified with the RA/DEC parameters.
|
|
80
65
|
scale: Scaling factor that will be applied to all sizes in styles (e.g. font size, marker size, line widths, etc). For example, if you want to make everything 2x bigger, then set the scale to 2. At `scale=1` and `resolution=4096` (the default), all sizes are optimized visually for a map that covers 1-3 constellations. So, if you're creating a plot of a _larger_ extent, then it'd probably be good to decrease the scale (i.e. make everything smaller) -- and _increase_ the scale if you're plotting a very small area.
|
|
81
66
|
autoscale: If True, then the scale will be set automatically based on resolution.
|
|
67
|
+
suppress_warnings: If True (the default), then all warnings will be suppressed
|
|
82
68
|
|
|
83
69
|
Returns:
|
|
84
70
|
MapPlot: A new instance of a MapPlot
|
|
85
71
|
|
|
86
72
|
"""
|
|
87
73
|
|
|
74
|
+
_coordinate_system = CoordinateSystem.RA_DEC
|
|
75
|
+
|
|
88
76
|
def __init__(
|
|
89
77
|
self,
|
|
90
78
|
projection: Projection,
|
|
91
79
|
ra_min: float = 0,
|
|
92
|
-
ra_max: float =
|
|
80
|
+
ra_max: float = 360,
|
|
93
81
|
dec_min: float = -90,
|
|
94
82
|
dec_max: float = 90,
|
|
95
83
|
lat: float = None,
|
|
@@ -102,6 +90,7 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
102
90
|
clip_path: Polygon = None,
|
|
103
91
|
scale: float = 1.0,
|
|
104
92
|
autoscale: bool = False,
|
|
93
|
+
suppress_warnings: bool = True,
|
|
105
94
|
*args,
|
|
106
95
|
**kwargs,
|
|
107
96
|
) -> "MapPlot":
|
|
@@ -113,6 +102,7 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
113
102
|
hide_colliding_labels,
|
|
114
103
|
scale=scale,
|
|
115
104
|
autoscale=autoscale,
|
|
105
|
+
suppress_warnings=suppress_warnings,
|
|
116
106
|
*args,
|
|
117
107
|
**kwargs,
|
|
118
108
|
)
|
|
@@ -145,7 +135,7 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
145
135
|
|
|
146
136
|
if self.projection == Projection.ZENITH and not self._is_global_extent():
|
|
147
137
|
raise ValueError(
|
|
148
|
-
"Zenith projection requires a global extent: ra_min=0, ra_max=
|
|
138
|
+
"Zenith projection requires a global extent: ra_min=0, ra_max=360, dec_min=-90, dec_max=90"
|
|
149
139
|
)
|
|
150
140
|
|
|
151
141
|
self._geodetic = ccrs.Geodetic()
|
|
@@ -163,27 +153,25 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
163
153
|
def _plot_kwargs(self) -> dict:
|
|
164
154
|
return dict(transform=self._crs)
|
|
165
155
|
|
|
166
|
-
|
|
167
|
-
return ra * 15, dec
|
|
168
|
-
|
|
156
|
+
@cache
|
|
169
157
|
def in_bounds(self, ra: float, dec: float) -> bool:
|
|
170
158
|
"""Determine if a coordinate is within the bounds of the plot.
|
|
171
159
|
|
|
172
160
|
Args:
|
|
173
|
-
ra: Right ascension, in
|
|
161
|
+
ra: Right ascension, in degrees (0...360)
|
|
174
162
|
dec: Declination, in degrees (-90...90)
|
|
175
163
|
|
|
176
164
|
Returns:
|
|
177
165
|
True if the coordinate is in bounds, otherwise False
|
|
178
166
|
"""
|
|
179
167
|
# TODO : try using pyproj transformer directly
|
|
180
|
-
x, y = self._proj.transform_point(ra
|
|
168
|
+
x, y = self._proj.transform_point(ra, dec, self._crs)
|
|
181
169
|
data_to_axes = self.ax.transData + self.ax.transAxes.inverted()
|
|
182
170
|
x_axes, y_axes = data_to_axes.transform((x, y))
|
|
183
171
|
return 0 <= x_axes <= 1 and 0 <= y_axes <= 1
|
|
184
172
|
|
|
185
173
|
def _in_bounds_xy(self, x: float, y: float) -> bool:
|
|
186
|
-
return self.in_bounds(x
|
|
174
|
+
return self.in_bounds(x, y)
|
|
187
175
|
|
|
188
176
|
def _polygon(self, points, style, **kwargs):
|
|
189
177
|
super()._polygon(points, style, transform=self._crs, **kwargs)
|
|
@@ -191,409 +179,45 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
191
179
|
def _latlon_bounds(self):
|
|
192
180
|
# convert the RA/DEC bounds to lat/lon bounds
|
|
193
181
|
return [
|
|
194
|
-
-1 * self.ra_min
|
|
195
|
-
-1 * self.ra_max
|
|
182
|
+
-1 * self.ra_min,
|
|
183
|
+
-1 * self.ra_max,
|
|
196
184
|
self.dec_min,
|
|
197
185
|
self.dec_max,
|
|
198
186
|
]
|
|
199
187
|
|
|
200
188
|
def _adjust_radec_minmax(self):
|
|
189
|
+
# adjust declination to match extent
|
|
190
|
+
extent = self.ax.get_extent(crs=self._plate_carree)
|
|
191
|
+
self.dec_min = extent[2]
|
|
192
|
+
self.dec_max = extent[3]
|
|
193
|
+
|
|
201
194
|
# adjust the RA min/max if the DEC bounds is near the poles
|
|
202
195
|
if self.projection in [Projection.STEREO_NORTH, Projection.STEREO_SOUTH] and (
|
|
203
196
|
self.dec_max > 80 or self.dec_min < -80
|
|
204
197
|
):
|
|
205
198
|
self.ra_min = 0
|
|
206
|
-
self.ra_max =
|
|
207
|
-
|
|
208
|
-
# adjust declination to match extent
|
|
209
|
-
extent = self.ax.get_extent(crs=self._plate_carree)
|
|
210
|
-
self.dec_min = extent[2]
|
|
211
|
-
self.dec_max = extent[3]
|
|
199
|
+
self.ra_max = 360
|
|
212
200
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
ra_min =
|
|
216
|
-
ra_max =
|
|
201
|
+
elif self.ra_max < 360:
|
|
202
|
+
# adjust right ascension to match extent
|
|
203
|
+
ra_min = extent[1] * -1
|
|
204
|
+
ra_max = extent[0] * -1
|
|
217
205
|
|
|
218
206
|
if ra_min < 0 or ra_max < 0:
|
|
219
|
-
ra_min +=
|
|
220
|
-
ra_max +=
|
|
207
|
+
ra_min += 360
|
|
208
|
+
ra_max += 360
|
|
221
209
|
|
|
222
210
|
self.ra_min = ra_min
|
|
223
211
|
self.ra_max = ra_max
|
|
224
212
|
|
|
225
|
-
self.logger.debug(
|
|
226
|
-
f"Extent = RA ({self.ra_min:.2f}, {self.ra_max:.2f}) DEC ({self.dec_min:.2f}, {self.dec_max:.2f})"
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
def _read_geo_package(self, filename: str):
|
|
230
|
-
"""Returns GeoDataFrame of a GeoPackage file"""
|
|
231
|
-
extent = self.ax.get_extent(crs=self._plate_carree)
|
|
232
|
-
bbox = (extent[0], extent[2], extent[1], extent[3])
|
|
233
|
-
|
|
234
|
-
return gpd.read_file(
|
|
235
|
-
filename,
|
|
236
|
-
engine="pyogrio",
|
|
237
|
-
use_arrow=True,
|
|
238
|
-
bbox=bbox,
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
def _load_stars(self, catalog, limiting_magnitude):
|
|
242
|
-
df = super()._load_stars(catalog, limiting_magnitude)
|
|
243
|
-
|
|
244
|
-
if self.projection == Projection.ZENITH:
|
|
245
|
-
# filter stars for zenith plots to only include those above horizon
|
|
246
|
-
earth = self.ephemeris["earth"]
|
|
247
|
-
self.location = earth + wgs84.latlon(self.lat, self.lon)
|
|
248
|
-
|
|
249
|
-
stars_apparent = (
|
|
250
|
-
self.location.at(self.timescale)
|
|
251
|
-
.observe(SkyfieldStar.from_dataframe(df))
|
|
252
|
-
.apparent()
|
|
253
|
-
)
|
|
254
|
-
# we only need altitude
|
|
255
|
-
stars_alt, _, _ = stars_apparent.altaz()
|
|
256
|
-
df["alt"] = stars_alt.degrees
|
|
257
|
-
df = df[df["alt"] > 0]
|
|
258
|
-
|
|
259
|
-
return df
|
|
260
|
-
|
|
261
|
-
@use_style(LineStyle, "constellation_borders")
|
|
262
|
-
def constellation_borders(self, style: LineStyle = None):
|
|
263
|
-
"""Plots the constellation borders
|
|
264
|
-
|
|
265
|
-
Args:
|
|
266
|
-
style: Styling of the constellation borders. If None, then the plot's style (specified when creating the plot) will be used
|
|
267
|
-
"""
|
|
268
|
-
constellation_borders = self._read_geo_package(
|
|
269
|
-
DataFiles.CONSTELLATION_BORDERS.value
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
if constellation_borders.empty:
|
|
273
|
-
return
|
|
274
|
-
|
|
275
|
-
style_kwargs = style.matplot_kwargs(self.scale)
|
|
276
|
-
|
|
277
|
-
geometries = []
|
|
278
|
-
|
|
279
|
-
for _, c in constellation_borders.iterrows():
|
|
280
|
-
for ls in c.geometry.geoms:
|
|
281
|
-
geometries.append(ls)
|
|
282
|
-
|
|
283
|
-
for ls in geometries:
|
|
284
|
-
x, y = ls.xy
|
|
285
|
-
self.ax.plot(
|
|
286
|
-
list(x),
|
|
287
|
-
list(y),
|
|
288
|
-
transform=self._plate_carree,
|
|
289
|
-
clip_on=True,
|
|
290
|
-
clip_path=self._background_clip_path,
|
|
291
|
-
gid="constellations-border",
|
|
292
|
-
**style_kwargs,
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
def _plot_constellation_borders(self):
|
|
296
|
-
"""work in progress"""
|
|
297
|
-
constellation_borders = gpd.read_file(
|
|
298
|
-
DataFiles.CONSTELLATIONS.value,
|
|
299
|
-
engine="pyogrio",
|
|
300
|
-
use_arrow=True,
|
|
301
|
-
bbox=self._extent_mask(),
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
if constellation_borders.empty:
|
|
305
|
-
return
|
|
306
|
-
|
|
307
|
-
geometries = []
|
|
308
|
-
|
|
309
|
-
for i, constellation in constellation_borders.iterrows():
|
|
310
|
-
geometry_types = constellation.geometry.geom_type
|
|
311
|
-
|
|
312
|
-
# equinox = LineString([[0, 90], [0, -90]])
|
|
313
|
-
"""
|
|
314
|
-
Problems:
|
|
315
|
-
- Need to handle multipolygon borders too (SER)
|
|
316
|
-
- Shapely's union doesn't handle geodesy (e.g. TRI + AND)
|
|
317
|
-
- ^^ TRI is plotted with ra < 360, but AND has ra > 360
|
|
318
|
-
- ^^ idea: create union first and then remove duplicate lines?
|
|
319
|
-
|
|
320
|
-
TODO: create new static data file of constellation border lines
|
|
321
|
-
"""
|
|
322
|
-
|
|
323
|
-
if "Polygon" in geometry_types and "MultiPolygon" not in geometry_types:
|
|
324
|
-
polygons = [constellation.geometry]
|
|
325
|
-
|
|
326
|
-
elif "MultiPolygon" in geometry_types:
|
|
327
|
-
polygons = constellation.geometry.geoms
|
|
328
|
-
|
|
329
|
-
for p in polygons:
|
|
330
|
-
coords = list(zip(*p.exterior.coords.xy))
|
|
331
|
-
# coords = [(ra * -1, dec) for ra, dec in coords]
|
|
332
|
-
|
|
333
|
-
new_coords = []
|
|
334
|
-
|
|
335
|
-
for i, c in enumerate(coords):
|
|
336
|
-
ra, dec = c
|
|
337
|
-
if i > 0:
|
|
338
|
-
if new_coords[i - 1][0] - ra > 60:
|
|
339
|
-
ra += 360
|
|
340
|
-
|
|
341
|
-
elif ra - new_coords[i - 1][0] > 60:
|
|
342
|
-
new_coords[i - 1][0] += 360
|
|
343
|
-
|
|
344
|
-
new_coords.append([ra, dec])
|
|
345
|
-
|
|
346
|
-
ls = LineString(new_coords)
|
|
347
|
-
geometries.append(ls)
|
|
348
|
-
|
|
349
|
-
mls = MultiLineString(geometries)
|
|
350
|
-
geometries = unary_union(mls)
|
|
351
|
-
|
|
352
|
-
style_kwargs = self.style.constellation_borders.matplot_kwargs(self.scale)
|
|
353
|
-
|
|
354
|
-
for ls in list(geometries.geoms):
|
|
355
|
-
# print(ls)
|
|
356
|
-
x, y = ls.xy
|
|
357
|
-
newx = [xx * -1 for xx in list(x)]
|
|
358
|
-
self.ax.plot(
|
|
359
|
-
# list(x),
|
|
360
|
-
newx,
|
|
361
|
-
list(y),
|
|
362
|
-
# **self._plot_kwargs(),
|
|
363
|
-
# transform=self._geodetic,
|
|
364
|
-
transform=self._plate_carree,
|
|
365
|
-
**style_kwargs,
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
@use_style(PathStyle, "constellation")
|
|
369
|
-
def constellations(
|
|
370
|
-
self,
|
|
371
|
-
style: PathStyle = None,
|
|
372
|
-
labels: dict[str, str] = CONSTELLATIONS_FULL_NAMES,
|
|
373
|
-
where: list = None,
|
|
374
|
-
):
|
|
375
|
-
"""Plots the constellation lines and/or labels.
|
|
376
|
-
|
|
377
|
-
**Important:** If you're plotting the constellation lines, then it's good to plot them _first_, because Starplot will use the constellation lines to determine where to place labels that are plotted afterwards (labels will look better if they're not crossing a constellation line).
|
|
378
|
-
|
|
379
|
-
Args:
|
|
380
|
-
style: Styling of the constellations. If None, then the plot's style (specified when creating the plot) will be used
|
|
381
|
-
labels: A dictionary where the keys are each constellation's 3-letter abbreviation, and the values are how the constellation will be labeled on the plot.
|
|
382
|
-
where: A list of expressions that determine which constellations to plot. See [Selecting Objects](/reference-selecting-objects/) for details.
|
|
383
|
-
"""
|
|
384
|
-
self.logger.debug("Plotting constellations...")
|
|
385
|
-
|
|
386
|
-
labels = labels or {}
|
|
387
|
-
where = where or []
|
|
388
|
-
|
|
389
|
-
constellations_gdf = gpd.read_file(
|
|
390
|
-
DataFiles.CONSTELLATIONS.value,
|
|
391
|
-
engine="pyogrio",
|
|
392
|
-
use_arrow=True,
|
|
393
|
-
bbox=self._extent_mask(),
|
|
394
|
-
)
|
|
395
|
-
stars_df = stars.load("hipparcos")
|
|
396
|
-
|
|
397
|
-
if constellations_gdf.empty:
|
|
398
|
-
return
|
|
399
|
-
|
|
400
|
-
if self.projection in [Projection.MERCATOR, Projection.MILLER]:
|
|
401
|
-
transform = self._plate_carree
|
|
402
213
|
else:
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
conline_hips = condata.lines()
|
|
406
|
-
style_kwargs = style.line.matplot_kwargs(self.scale)
|
|
407
|
-
|
|
408
|
-
for c in constellations_gdf.itertuples():
|
|
409
|
-
obj = constellation_from_tuple(c)
|
|
410
|
-
|
|
411
|
-
if not all([e.evaluate(obj) for e in where]):
|
|
412
|
-
continue
|
|
413
|
-
|
|
414
|
-
hiplines = conline_hips[c.iau_id]
|
|
415
|
-
inbounds = False
|
|
416
|
-
|
|
417
|
-
for s1_hip, s2_hip in hiplines:
|
|
418
|
-
s1 = stars_df.loc[s1_hip]
|
|
419
|
-
s2 = stars_df.loc[s2_hip]
|
|
420
|
-
|
|
421
|
-
s1_ra = s1.ra_hours * 15
|
|
422
|
-
s2_ra = s2.ra_hours * 15
|
|
423
|
-
|
|
424
|
-
s1_dec = s1.dec_degrees
|
|
425
|
-
s2_dec = s2.dec_degrees
|
|
426
|
-
|
|
427
|
-
if s1_ra - s2_ra > 60:
|
|
428
|
-
s2_ra += 360
|
|
429
|
-
|
|
430
|
-
elif s2_ra - s1_ra > 60:
|
|
431
|
-
s1_ra += 360
|
|
432
|
-
|
|
433
|
-
if self.in_bounds(s1_ra / 15, s1_dec):
|
|
434
|
-
inbounds = True
|
|
435
|
-
|
|
436
|
-
s1_ra *= -1
|
|
437
|
-
s2_ra *= -1
|
|
438
|
-
|
|
439
|
-
# make lines straight
|
|
440
|
-
# s1_ra, s1_dec = self._proj.transform_point(s1_ra, s1.dec_degrees, self._geodetic)
|
|
441
|
-
# s2_ra, s2_dec = self._proj.transform_point(s2_ra, s2.dec_degrees, self._geodetic)
|
|
442
|
-
|
|
443
|
-
constellation_line = self.ax.plot(
|
|
444
|
-
[s1_ra, s2_ra],
|
|
445
|
-
[s1_dec, s2_dec],
|
|
446
|
-
transform=transform,
|
|
447
|
-
**style_kwargs,
|
|
448
|
-
clip_on=True,
|
|
449
|
-
clip_path=self._background_clip_path,
|
|
450
|
-
gid="constellations-line",
|
|
451
|
-
)[0]
|
|
452
|
-
|
|
453
|
-
extent = constellation_line.get_window_extent(
|
|
454
|
-
renderer=self.fig.canvas.get_renderer()
|
|
455
|
-
)
|
|
456
|
-
|
|
457
|
-
if extent.xmin < 0:
|
|
458
|
-
continue
|
|
459
|
-
|
|
460
|
-
start = self._proj.transform_point(s1_ra, s1_dec, self._geodetic)
|
|
461
|
-
end = self._proj.transform_point(s2_ra, s2_dec, self._geodetic)
|
|
462
|
-
radius = style_kwargs.get("linewidth") or 1
|
|
463
|
-
|
|
464
|
-
if any([np.isnan(n) for n in start + end]):
|
|
465
|
-
continue
|
|
466
|
-
|
|
467
|
-
for x, y in points(start, end, 25):
|
|
468
|
-
x0, y0 = self.ax.transData.transform((x, y))
|
|
469
|
-
if x0 < 0 or y0 < 0:
|
|
470
|
-
continue
|
|
471
|
-
self._constellations_rtree.insert(
|
|
472
|
-
0,
|
|
473
|
-
np.array((x0 - radius, y0 - radius, x0 + radius, y0 + radius)),
|
|
474
|
-
obj=obj.name,
|
|
475
|
-
)
|
|
476
|
-
|
|
477
|
-
if inbounds:
|
|
478
|
-
self._objects.constellations.append(obj)
|
|
479
|
-
|
|
480
|
-
self._plot_constellation_labels(style.label, labels)
|
|
481
|
-
# self._plot_constellation_labels_experimental(style.label, labels)
|
|
482
|
-
|
|
483
|
-
def _plot_constellation_labels(
|
|
484
|
-
self,
|
|
485
|
-
style: PathStyle = None,
|
|
486
|
-
labels: dict[str, str] = CONSTELLATIONS_FULL_NAMES,
|
|
487
|
-
):
|
|
488
|
-
style = style or self.style.constellation.label
|
|
489
|
-
|
|
490
|
-
for con in condata.iterator():
|
|
491
|
-
_, ra, dec = condata.get(con)
|
|
492
|
-
text = labels.get(con.lower())
|
|
493
|
-
self.text(
|
|
494
|
-
text,
|
|
495
|
-
ra,
|
|
496
|
-
dec,
|
|
497
|
-
style,
|
|
498
|
-
hide_on_collision=False,
|
|
499
|
-
gid="constellations-label-name",
|
|
500
|
-
)
|
|
214
|
+
self.ra_min = lon_to_ra(extent[1]) * 15
|
|
215
|
+
self.ra_max = lon_to_ra(extent[0]) * 15 + 360
|
|
501
216
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
style: PathStyle = None,
|
|
505
|
-
labels: dict[str, str] = CONSTELLATIONS_FULL_NAMES,
|
|
506
|
-
):
|
|
507
|
-
from shapely import (
|
|
508
|
-
MultiPoint,
|
|
509
|
-
intersection,
|
|
510
|
-
delaunay_triangles,
|
|
511
|
-
distance,
|
|
217
|
+
self.logger.debug(
|
|
218
|
+
f"Extent = RA ({self.ra_min:.2f}, {self.ra_max:.2f}) DEC ({self.dec_min:.2f}, {self.dec_max:.2f})"
|
|
512
219
|
)
|
|
513
220
|
|
|
514
|
-
def sorter(g):
|
|
515
|
-
d = distance(g.centroid, points.centroid)
|
|
516
|
-
# d = distance(g.centroid, constellation.boundary.centroid)
|
|
517
|
-
extent = abs(g.bounds[2] - g.bounds[0])
|
|
518
|
-
area = g.area / constellation.boundary.area
|
|
519
|
-
return (extent**2 + area) - (d**2)
|
|
520
|
-
|
|
521
|
-
for constellation in self.objects.constellations:
|
|
522
|
-
constellation_stars = [
|
|
523
|
-
s
|
|
524
|
-
for s in self.objects.stars
|
|
525
|
-
if s.constellation_id == constellation.iau_id
|
|
526
|
-
]
|
|
527
|
-
points = MultiPoint([(s.ra, s.dec) for s in constellation_stars])
|
|
528
|
-
|
|
529
|
-
triangles = delaunay_triangles(
|
|
530
|
-
geometry=points,
|
|
531
|
-
# tolerance=2,
|
|
532
|
-
)
|
|
533
|
-
|
|
534
|
-
polygons = []
|
|
535
|
-
for t in triangles.geoms:
|
|
536
|
-
try:
|
|
537
|
-
inter = intersection(t, constellation.boundary)
|
|
538
|
-
except Exception:
|
|
539
|
-
continue
|
|
540
|
-
if (
|
|
541
|
-
inter.geom_type == "Polygon"
|
|
542
|
-
and len(list(zip(*inter.exterior.coords.xy))) > 2
|
|
543
|
-
):
|
|
544
|
-
polygons.append(inter)
|
|
545
|
-
|
|
546
|
-
p_by_area = {pg.area: pg for pg in polygons}
|
|
547
|
-
polygons_sorted = [
|
|
548
|
-
p_by_area[k] for k in sorted(p_by_area.keys(), reverse=True)
|
|
549
|
-
]
|
|
550
|
-
|
|
551
|
-
# sort by combination of horizontal extent and area
|
|
552
|
-
polygons_sorted = sorted(polygons_sorted, key=sorter, reverse=True)
|
|
553
|
-
|
|
554
|
-
if len(polygons_sorted) > 0:
|
|
555
|
-
i = 0
|
|
556
|
-
ra, dec = polygons_sorted[i].centroid.x, polygons_sorted[i].centroid.y
|
|
557
|
-
else:
|
|
558
|
-
ra, dec = constellation.ra, constellation.dec
|
|
559
|
-
|
|
560
|
-
text = labels.get(constellation.iau_id)
|
|
561
|
-
style = style or self.style.constellation.label
|
|
562
|
-
self.text(text, ra, dec, style)
|
|
563
|
-
|
|
564
|
-
@use_style(PolygonStyle, "milky_way")
|
|
565
|
-
def milky_way(self, style: PolygonStyle = None):
|
|
566
|
-
"""Plots the Milky Way
|
|
567
|
-
|
|
568
|
-
Args:
|
|
569
|
-
style: Styling of the Milky Way. If None, then the plot's style (specified when creating the plot) will be used
|
|
570
|
-
"""
|
|
571
|
-
mw = self._read_geo_package(DataFiles.MILKY_WAY.value)
|
|
572
|
-
|
|
573
|
-
if mw.empty:
|
|
574
|
-
return
|
|
575
|
-
|
|
576
|
-
def _prepare_polygon(p):
|
|
577
|
-
points = list(zip(*p.boundary.coords.xy))
|
|
578
|
-
# convert lon to RA and reverse so the coordinates are counterclockwise order
|
|
579
|
-
return [(lon_to_ra(lon) * 15, dec) for lon, dec in reversed(points)]
|
|
580
|
-
|
|
581
|
-
# create union of all Milky Way patches
|
|
582
|
-
gs = mw.geometry.to_crs(self._plate_carree)
|
|
583
|
-
mw_union = gs.buffer(0.1).unary_union.buffer(-0.1)
|
|
584
|
-
polygons = []
|
|
585
|
-
|
|
586
|
-
if mw_union.geom_type == "MultiPolygon":
|
|
587
|
-
polygons.extend([_prepare_polygon(polygon) for polygon in mw_union.geoms])
|
|
588
|
-
else:
|
|
589
|
-
polygons.append(_prepare_polygon(mw_union))
|
|
590
|
-
|
|
591
|
-
for polygon_points in polygons:
|
|
592
|
-
self._polygon(
|
|
593
|
-
polygon_points,
|
|
594
|
-
style=style,
|
|
595
|
-
)
|
|
596
|
-
|
|
597
221
|
@use_style(ObjectStyle, "zenith")
|
|
598
222
|
def zenith(
|
|
599
223
|
self,
|
|
@@ -618,7 +242,7 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
618
242
|
ra, dec, _ = zenith.radec()
|
|
619
243
|
|
|
620
244
|
self.marker(
|
|
621
|
-
ra=ra.hours,
|
|
245
|
+
ra=ra.hours * 15,
|
|
622
246
|
dec=dec.degrees,
|
|
623
247
|
style=style,
|
|
624
248
|
label=label,
|
|
@@ -647,44 +271,60 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
647
271
|
ra, dec, _ = zenith.radec()
|
|
648
272
|
|
|
649
273
|
points = geod.ellipse(
|
|
650
|
-
center=(ra.hours, dec.degrees),
|
|
274
|
+
center=(ra.hours * 15, dec.degrees),
|
|
651
275
|
height_degrees=180,
|
|
652
276
|
width_degrees=180,
|
|
653
277
|
num_pts=100,
|
|
654
278
|
)
|
|
655
279
|
x = []
|
|
656
280
|
y = []
|
|
657
|
-
verts = []
|
|
658
|
-
|
|
659
|
-
# TODO : handle map edges better
|
|
660
281
|
|
|
661
282
|
for ra, dec in points:
|
|
662
|
-
ra = ra / 15
|
|
663
283
|
x0, y0 = self._prepare_coords(ra, dec)
|
|
664
284
|
x.append(x0)
|
|
665
285
|
y.append(y0)
|
|
666
|
-
verts.append((x0, y0))
|
|
667
286
|
|
|
668
287
|
style_kwargs = {}
|
|
669
288
|
if self.projection == Projection.ZENITH:
|
|
670
289
|
"""
|
|
671
|
-
For zenith projections, we plot the horizon as a patch
|
|
672
|
-
plottting as a line results in extra pixels on bottom.
|
|
673
|
-
|
|
674
|
-
TODO : investigate why line is extra thick on bottom when plotting line
|
|
290
|
+
For zenith projections, we plot the horizon as a patch to make a more perfect circle
|
|
675
291
|
"""
|
|
676
292
|
style_kwargs = style.line.matplot_kwargs(self.scale)
|
|
677
293
|
style_kwargs["clip_on"] = False
|
|
678
294
|
style_kwargs["edgecolor"] = style_kwargs.pop("color")
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
295
|
+
patch = patches.Circle(
|
|
296
|
+
(0.50, 0.50),
|
|
297
|
+
radius=0.454,
|
|
682
298
|
facecolor=None,
|
|
683
299
|
fill=False,
|
|
684
|
-
transform=self.
|
|
300
|
+
transform=self.ax.transAxes,
|
|
685
301
|
**style_kwargs,
|
|
686
302
|
)
|
|
687
303
|
self.ax.add_patch(patch)
|
|
304
|
+
self._background_clip_path = patch
|
|
305
|
+
self._update_clip_path_polygon(
|
|
306
|
+
buffer=style.line.width / 2 + 2 * style.line.edge_width + 20
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
if not labels:
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
label_ax_coords = [
|
|
313
|
+
(0.5, 0.95), # north
|
|
314
|
+
(0.045, 0.5), # east
|
|
315
|
+
(0.5, 0.045), # south
|
|
316
|
+
(0.954, 0.5), # west
|
|
317
|
+
]
|
|
318
|
+
for label, coords in zip(labels, label_ax_coords):
|
|
319
|
+
self.ax.annotate(
|
|
320
|
+
label,
|
|
321
|
+
coords,
|
|
322
|
+
xycoords=self.ax.transAxes,
|
|
323
|
+
clip_on=False,
|
|
324
|
+
**style.label.matplot_kwargs(self.scale),
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
return
|
|
688
328
|
|
|
689
329
|
else:
|
|
690
330
|
style_kwargs["clip_on"] = True
|
|
@@ -698,13 +338,6 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
698
338
|
**self._plot_kwargs(),
|
|
699
339
|
)
|
|
700
340
|
|
|
701
|
-
# self.circle(
|
|
702
|
-
# (ra.hours, dec.degrees),
|
|
703
|
-
# 90,
|
|
704
|
-
# style,
|
|
705
|
-
# num_pts=200,
|
|
706
|
-
# )
|
|
707
|
-
|
|
708
341
|
if not labels:
|
|
709
342
|
return
|
|
710
343
|
|
|
@@ -717,21 +350,19 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
717
350
|
|
|
718
351
|
text_kwargs = dict(
|
|
719
352
|
**style.label.matplot_kwargs(self.scale),
|
|
720
|
-
hide_on_collision=False,
|
|
721
353
|
xytext=(
|
|
722
354
|
style.label.offset_x * self.scale,
|
|
723
355
|
style.label.offset_y * self.scale,
|
|
724
356
|
),
|
|
725
357
|
textcoords="offset points",
|
|
726
358
|
path_effects=[],
|
|
359
|
+
clip_on=True,
|
|
727
360
|
)
|
|
728
361
|
|
|
729
|
-
if self.projection == Projection.ZENITH:
|
|
730
|
-
text_kwargs["clip_on"] = False
|
|
731
|
-
|
|
732
362
|
for i, position in enumerate(cardinal_directions):
|
|
733
363
|
ra, dec, _ = position.radec()
|
|
734
|
-
self.
|
|
364
|
+
x, y = self._prepare_coords(ra.hours * 15, dec.degrees)
|
|
365
|
+
self._text(x, y, labels[i], **text_kwargs)
|
|
735
366
|
|
|
736
367
|
@use_style(PathStyle, "gridlines")
|
|
737
368
|
def gridlines(
|
|
@@ -751,12 +382,12 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
751
382
|
Args:
|
|
752
383
|
style: Styling of the gridlines. If None, then the plot's style (specified when creating the plot) will be used
|
|
753
384
|
labels: If True, then labels for each gridline will be plotted on the outside of the axes.
|
|
754
|
-
ra_locations: List of Right Ascension locations for the gridlines (in
|
|
385
|
+
ra_locations: List of Right Ascension locations for the gridlines (in degrees, 0...360). Defaults to every 15 degrees.
|
|
755
386
|
dec_locations: List of Declination locations for the gridlines (in degrees, -90...90). Defaults to every 10 degrees.
|
|
756
387
|
ra_formatter_fn: Callable for creating labels of right ascension gridlines
|
|
757
388
|
dec_formatter_fn: Callable for creating labels of declination gridlines
|
|
758
389
|
tick_marks: If True, then tick marks will be plotted outside the axis. **Only supported for rectangular projections (e.g. Mercator, Miller)**
|
|
759
|
-
ra_tick_locations: List of Right Ascension locations for the tick marks (in
|
|
390
|
+
ra_tick_locations: List of Right Ascension locations for the tick marks (in degrees, 0...260)
|
|
760
391
|
dec_tick_locations: List of Declination locations for the tick marks (in degrees, -90...90)
|
|
761
392
|
"""
|
|
762
393
|
|
|
@@ -773,7 +404,7 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
773
404
|
def dec_formatter(x, pos) -> str:
|
|
774
405
|
return dec_formatter_fn(x)
|
|
775
406
|
|
|
776
|
-
ra_locations = ra_locations or [x for x in range(
|
|
407
|
+
ra_locations = ra_locations or [x for x in range(0, 360, 15)]
|
|
777
408
|
dec_locations = dec_locations or [d for d in range(-80, 90, 10)]
|
|
778
409
|
|
|
779
410
|
line_style_kwargs = style.line.matplot_kwargs()
|
|
@@ -802,14 +433,14 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
802
433
|
# because cartopy does not extend lines to poles
|
|
803
434
|
for ra in ra_locations:
|
|
804
435
|
self.ax.plot(
|
|
805
|
-
(ra
|
|
436
|
+
(ra, ra),
|
|
806
437
|
(-90, 90),
|
|
807
438
|
gid="gridlines",
|
|
808
439
|
**line_style_kwargs,
|
|
809
440
|
**self._plot_kwargs(),
|
|
810
441
|
)
|
|
811
442
|
|
|
812
|
-
gridlines.xlocator = FixedLocator([ra_to_lon(r) for r in ra_locations])
|
|
443
|
+
gridlines.xlocator = FixedLocator([ra_to_lon(r / 15) for r in ra_locations])
|
|
813
444
|
gridlines.xformatter = FuncFormatter(ra_formatter)
|
|
814
445
|
gridlines.xlabel_style = label_style_kwargs
|
|
815
446
|
|
|
@@ -824,10 +455,10 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
824
455
|
def in_axes(ra):
|
|
825
456
|
return self.in_bounds(ra, (self.dec_max + self.dec_min) / 2)
|
|
826
457
|
|
|
827
|
-
xticks = ra_tick_locations or [x for x in np.arange(0,
|
|
458
|
+
xticks = ra_tick_locations or [x for x in np.arange(0, 360, 1.875)]
|
|
828
459
|
yticks = dec_tick_locations or [x for x in np.arange(-90, 90, 1)]
|
|
829
460
|
|
|
830
|
-
inbound_xticks = [ra_to_lon(ra) for ra in xticks if in_axes(ra)]
|
|
461
|
+
inbound_xticks = [ra_to_lon(ra / 15) for ra in xticks if in_axes(ra)]
|
|
831
462
|
self.ax.set_xticks(inbound_xticks, crs=self._plate_carree)
|
|
832
463
|
self.ax.xaxis.set_major_formatter(ticker.NullFormatter())
|
|
833
464
|
|
|
@@ -869,7 +500,7 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
869
500
|
Projection.STEREOGRAPHIC,
|
|
870
501
|
Projection.ZENITH,
|
|
871
502
|
]:
|
|
872
|
-
# Calculate LST to shift RA DEC to be in line with current date and time
|
|
503
|
+
# Calculate local sidereal time (LST) to shift RA DEC to be in line with current date and time
|
|
873
504
|
lst = -(360.0 * self.timescale.gmst / 24.0 + self.lon) % 360.0
|
|
874
505
|
self._proj = Projection.crs(self.projection, lon=lst, lat=self.lat)
|
|
875
506
|
elif self.projection == Projection.LAMBERT_AZ_EQ_AREA:
|
|
@@ -901,9 +532,8 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
901
532
|
|
|
902
533
|
self.logger.debug(f"Projection = {self.projection.value.upper()}")
|
|
903
534
|
|
|
904
|
-
self._plot_background_clip_path()
|
|
905
|
-
|
|
906
535
|
self._fit_to_ax()
|
|
536
|
+
self._plot_background_clip_path()
|
|
907
537
|
|
|
908
538
|
@use_style(LabelStyle, "info_text")
|
|
909
539
|
def info(self, style: LabelStyle = None):
|
|
@@ -928,12 +558,18 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
928
558
|
**style.matplot_kwargs(self.scale),
|
|
929
559
|
)
|
|
930
560
|
|
|
561
|
+
def _ax_to_radec(self, x, y):
|
|
562
|
+
trans = self.ax.transAxes + self.ax.transData.inverted()
|
|
563
|
+
x_projected, y_projected = trans.transform((x, y)) # axes to data
|
|
564
|
+
x_ra, y_ra = self._crs.transform_point(x_projected, y_projected, self._proj)
|
|
565
|
+
return (x_ra + 360), y_ra
|
|
566
|
+
|
|
931
567
|
def _plot_background_clip_path(self):
|
|
932
568
|
def to_axes(points):
|
|
933
569
|
ax_points = []
|
|
934
570
|
|
|
935
571
|
for ra, dec in points:
|
|
936
|
-
x, y = self._proj.transform_point(ra
|
|
572
|
+
x, y = self._proj.transform_point(ra, dec, self._crs)
|
|
937
573
|
data_to_axes = self.ax.transData + self.ax.transAxes.inverted()
|
|
938
574
|
x_axes, y_axes = data_to_axes.transform((x, y))
|
|
939
575
|
ax_points.append([x_axes, y_axes])
|
|
@@ -974,3 +610,4 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
974
610
|
)
|
|
975
611
|
|
|
976
612
|
self.ax.add_patch(self._background_clip_path)
|
|
613
|
+
self._update_clip_path_polygon()
|