starplot 0.12.5__py2.py3-none-any.whl → 0.13.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.
Potentially problematic release.
This version of starplot might be problematic. Click here for more details.
- starplot/__init__.py +1 -1
- starplot/base.py +368 -57
- starplot/callables.py +61 -7
- starplot/data/bayer.py +1532 -3
- starplot/data/flamsteed.py +2682 -0
- starplot/data/library/constellation_borders_inv.gpkg +0 -0
- starplot/data/library/constellation_lines_hips.json +3 -1
- starplot/data/stars.py +408 -87
- starplot/horizon.py +398 -0
- starplot/map.py +144 -19
- starplot/optic.py +26 -14
- starplot/plotters/dsos.py +5 -1
- starplot/plotters/stars.py +114 -13
- starplot/styles/base.py +263 -156
- starplot/styles/ext/antique.yml +45 -39
- starplot/styles/ext/blue_dark.yml +32 -36
- starplot/styles/ext/blue_light.yml +43 -25
- starplot/styles/ext/blue_medium.yml +48 -44
- starplot/styles/ext/cb_wong.yml +7 -0
- starplot/styles/ext/grayscale.yml +13 -7
- starplot/styles/ext/grayscale_dark.yml +12 -4
- starplot/styles/ext/map.yml +4 -4
- starplot/styles/ext/nord.yml +32 -32
- starplot/styles/ext/optic.yml +7 -5
- 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/hind/DESCRIPTION.en_us.html +28 -0
- starplot/styles/fonts-library/hind/Hind-Bold.ttf +0 -0
- starplot/styles/fonts-library/hind/Hind-Light.ttf +0 -0
- starplot/styles/fonts-library/hind/Hind-Medium.ttf +0 -0
- starplot/styles/fonts-library/hind/Hind-Regular.ttf +0 -0
- starplot/styles/fonts-library/hind/Hind-SemiBold.ttf +0 -0
- starplot/styles/fonts-library/hind/METADATA.pb +58 -0
- starplot/styles/fonts-library/hind/OFL.txt +93 -0
- starplot/styles/fonts-library/inter/Inter-Black.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-BlackItalic.ttf +0 -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-ExtraBoldItalic.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-Medium.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-MediumItalic.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/Inter-Thin.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ThinItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/LICENSE.txt +92 -0
- starplot/styles/fonts.py +15 -0
- starplot/styles/markers.py +207 -6
- {starplot-0.12.5.dist-info → starplot-0.13.0.dist-info}/METADATA +7 -8
- starplot-0.13.0.dist-info/RECORD +101 -0
- starplot-0.12.5.dist-info/RECORD +0 -67
- {starplot-0.12.5.dist-info → starplot-0.13.0.dist-info}/LICENSE +0 -0
- {starplot-0.12.5.dist-info → starplot-0.13.0.dist-info}/WHEEL +0 -0
starplot/horizon.py
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Callable, Mapping
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from cartopy import crs as ccrs
|
|
7
|
+
from matplotlib import pyplot as plt, patches, path
|
|
8
|
+
from skyfield.api import wgs84, Star as SkyfieldStar
|
|
9
|
+
|
|
10
|
+
from starplot import callables
|
|
11
|
+
from starplot.base import BasePlot, DPI
|
|
12
|
+
from starplot.data.stars import StarCatalog, STAR_NAMES
|
|
13
|
+
from starplot.mixins import ExtentMaskMixin
|
|
14
|
+
from starplot.models import Star
|
|
15
|
+
from starplot.plotters import StarPlotterMixin, DsoPlotterMixin
|
|
16
|
+
from starplot.styles import (
|
|
17
|
+
PlotStyle,
|
|
18
|
+
ObjectStyle,
|
|
19
|
+
extensions,
|
|
20
|
+
use_style,
|
|
21
|
+
ZOrderEnum,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
pd.options.mode.chained_assignment = None # default='warn'
|
|
25
|
+
|
|
26
|
+
DEFAULT_OPTIC_STYLE = PlotStyle().extend(extensions.OPTIC)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class HorizonPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
30
|
+
"""Creates a new horizon plot.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
optic: Optic instance that defines optical parameters
|
|
34
|
+
ra: Right ascension of target center, in hours (0...24)
|
|
35
|
+
dec: Declination of target center, in degrees (-90...90)
|
|
36
|
+
lat: Latitude of observer's location
|
|
37
|
+
lon: Longitude of observer's location
|
|
38
|
+
dt: Date/time of observation (*must be timezone-aware*). Default = current UTC time.
|
|
39
|
+
ephemeris: Ephemeris to use for calculating planet positions (see [Skyfield's documentation](https://rhodesmill.org/skyfield/planets.html) for details)
|
|
40
|
+
style: Styling for the plot (colors, sizes, fonts, etc)
|
|
41
|
+
resolution: Size (in pixels) of largest dimension of the map
|
|
42
|
+
hide_colliding_labels: If True, then labels will not be plotted if they collide with another existing label
|
|
43
|
+
raise_on_below_horizon: If True, then a ValueError will be raised if the target is below the horizon at the observing time/location
|
|
44
|
+
scale: Scaling factor that will be applied to all relevant sizes in styles (e.g. font size, marker size, line widths, etc). For example, if you want to make everything 2x bigger, then set scale to 2.
|
|
45
|
+
autoscale: If True, then the scale will be automatically set based on resolution
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
OpticPlot: A new instance of an OpticPlot
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
FIELD_OF_VIEW_MAX = 9.0
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
lat: float,
|
|
57
|
+
lon: float,
|
|
58
|
+
altitude: tuple[float, float] = (0, 60),
|
|
59
|
+
azimuth: tuple[float, float] = (0, 90),
|
|
60
|
+
dt: datetime = None,
|
|
61
|
+
ephemeris: str = "de421_2001.bsp",
|
|
62
|
+
style: PlotStyle = DEFAULT_OPTIC_STYLE,
|
|
63
|
+
resolution: int = 2048,
|
|
64
|
+
hide_colliding_labels: bool = True,
|
|
65
|
+
scale: float = 1.0,
|
|
66
|
+
autoscale: bool = False,
|
|
67
|
+
*args,
|
|
68
|
+
**kwargs,
|
|
69
|
+
) -> "HorizonPlot":
|
|
70
|
+
super().__init__(
|
|
71
|
+
dt,
|
|
72
|
+
ephemeris,
|
|
73
|
+
style,
|
|
74
|
+
resolution,
|
|
75
|
+
hide_colliding_labels,
|
|
76
|
+
scale=scale,
|
|
77
|
+
autoscale=autoscale,
|
|
78
|
+
*args,
|
|
79
|
+
**kwargs,
|
|
80
|
+
)
|
|
81
|
+
self.logger.debug("Creating HorizonPlot...")
|
|
82
|
+
self.alt = altitude
|
|
83
|
+
self.az = azimuth
|
|
84
|
+
self.center_az = sum(azimuth) / 2
|
|
85
|
+
self.lat = lat
|
|
86
|
+
self.lon = lon
|
|
87
|
+
|
|
88
|
+
self._crs = ccrs.CRS(
|
|
89
|
+
proj4_params=[
|
|
90
|
+
("proj", "latlong"),
|
|
91
|
+
("a", "6378137"),
|
|
92
|
+
],
|
|
93
|
+
globe=ccrs.Globe(ellipse="sphere", flattening=0),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
self._calc_position()
|
|
97
|
+
self._init_plot()
|
|
98
|
+
self._adjust_radec_minmax()
|
|
99
|
+
|
|
100
|
+
def _prepare_coords(self, ra, dec) -> (float, float):
|
|
101
|
+
"""Converts RA/DEC to AZ/ALT"""
|
|
102
|
+
point = SkyfieldStar(ra_hours=ra, dec_degrees=dec)
|
|
103
|
+
position = self.observe(point)
|
|
104
|
+
pos_apparent = position.apparent()
|
|
105
|
+
pos_alt, pos_az, _ = pos_apparent.altaz()
|
|
106
|
+
return pos_az.degrees, pos_alt.degrees
|
|
107
|
+
|
|
108
|
+
def _plot_kwargs(self) -> dict:
|
|
109
|
+
return dict(transform=self._crs)
|
|
110
|
+
|
|
111
|
+
def in_bounds(self, ra, dec) -> bool:
|
|
112
|
+
"""Determine if a coordinate is within the bounds of the plot.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
ra: Right ascension, in hours (0...24)
|
|
116
|
+
dec: Declination, in degrees (-90...90)
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
True if the coordinate is in bounds, otherwise False
|
|
120
|
+
"""
|
|
121
|
+
az, alt = self._prepare_coords(ra, dec)
|
|
122
|
+
return (
|
|
123
|
+
az < self.az[1]
|
|
124
|
+
and az > self.az[0]
|
|
125
|
+
and alt < self.alt[1]
|
|
126
|
+
and alt > self.alt[0]
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def in_bounds_altaz(self, alt, az, scale: float = 1) -> bool:
|
|
130
|
+
"""Determine if a coordinate is within the bounds of the plot.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
alt: Altitude angle in degrees (0...90)
|
|
134
|
+
az: Azimuth angle in degrees (0...360)
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
True if the coordinate is in bounds, otherwise False
|
|
138
|
+
"""
|
|
139
|
+
# x, y = self._proj.transform_point(az, alt, self._crs)
|
|
140
|
+
return (
|
|
141
|
+
az < self.az[1]
|
|
142
|
+
and az > self.az[0]
|
|
143
|
+
and alt < self.alt[1]
|
|
144
|
+
and alt > self.alt[0]
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def _polygon(self, points, style, **kwargs):
|
|
148
|
+
super()._polygon(points, style, transform=self._crs, **kwargs)
|
|
149
|
+
|
|
150
|
+
def _calc_position(self):
|
|
151
|
+
earth = self.ephemeris["earth"]
|
|
152
|
+
|
|
153
|
+
self.location = earth + wgs84.latlon(self.lat, self.lon)
|
|
154
|
+
self.observe = self.location.at(self.timescale).observe
|
|
155
|
+
|
|
156
|
+
# get radec at center horizon
|
|
157
|
+
center = self.location.at(self.timescale).from_altaz(
|
|
158
|
+
alt_degrees=0, az_degrees=self.center_az
|
|
159
|
+
)
|
|
160
|
+
print(self.center_az)
|
|
161
|
+
print(center.radec())
|
|
162
|
+
locations = [
|
|
163
|
+
self.location.at(self.timescale).from_altaz(
|
|
164
|
+
alt_degrees=self.alt[0], az_degrees=self.az[0]
|
|
165
|
+
), # lower left
|
|
166
|
+
self.location.at(self.timescale).from_altaz(
|
|
167
|
+
alt_degrees=self.alt[0], az_degrees=self.az[1]
|
|
168
|
+
), # lower right
|
|
169
|
+
self.location.at(self.timescale).from_altaz(
|
|
170
|
+
alt_degrees=self.alt[1], az_degrees=self.center_az
|
|
171
|
+
), # top center
|
|
172
|
+
# self.location.at(self.timescale).from_altaz(alt_degrees=self.alt[1], az_degrees=self.az[0]), # upper left
|
|
173
|
+
# self.location.at(self.timescale).from_altaz(alt_degrees=self.alt[1], az_degrees=self.az[1]), # upper right
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
self.ra_min = None
|
|
177
|
+
self.ra_max = None
|
|
178
|
+
self.dec_max = None
|
|
179
|
+
self.dec_min = None
|
|
180
|
+
|
|
181
|
+
for location in locations:
|
|
182
|
+
ra, dec, _ = location.radec()
|
|
183
|
+
ra = ra.hours
|
|
184
|
+
dec = dec.degrees
|
|
185
|
+
if self.ra_min is None or ra < self.ra_min:
|
|
186
|
+
self.ra_min = ra
|
|
187
|
+
|
|
188
|
+
if self.ra_max is None or ra > self.ra_max:
|
|
189
|
+
self.ra_max = ra
|
|
190
|
+
|
|
191
|
+
if self.dec_min is None or dec < self.dec_min:
|
|
192
|
+
self.dec_min = dec
|
|
193
|
+
|
|
194
|
+
if self.dec_max is None or dec > self.dec_max:
|
|
195
|
+
self.dec_max = dec
|
|
196
|
+
|
|
197
|
+
# self.star = SkyfieldStar(ra_hours=self.ra, dec_degrees=self.dec)
|
|
198
|
+
# self.position = self.observe(self.star)
|
|
199
|
+
# self.pos_apparent = self.position.apparent()
|
|
200
|
+
# self.pos_alt, self.pos_az, _ = self.pos_apparent.altaz()
|
|
201
|
+
|
|
202
|
+
# if self.pos_alt.degrees < 0 and self.raise_on_below_horizon:
|
|
203
|
+
# raise ValueError("Target is below horizon at specified time/location.")
|
|
204
|
+
|
|
205
|
+
def _adjust_radec_minmax(self):
|
|
206
|
+
# self.ra_min = self.ra - self.optic.true_fov / 15 * 1.08
|
|
207
|
+
# self.ra_max = self.ra + self.optic.true_fov / 15 * 1.08
|
|
208
|
+
# self.dec_max = self.dec + self.optic.true_fov / 2 * 1.03
|
|
209
|
+
# self.dec_min = self.dec - self.optic.true_fov / 2 * 1.03
|
|
210
|
+
|
|
211
|
+
if self.dec_max > 70 or self.dec_min < -70:
|
|
212
|
+
# naive method of getting all the stars near the poles
|
|
213
|
+
self.ra_min = 0
|
|
214
|
+
self.ra_max = 24
|
|
215
|
+
|
|
216
|
+
# TODO : below are in ra/dec - need to convert to alt/az
|
|
217
|
+
# adjust declination to match extent
|
|
218
|
+
extent = self.ax.get_extent(crs=ccrs.PlateCarree())
|
|
219
|
+
self.dec_min = extent[2]
|
|
220
|
+
self.dec_max = extent[3]
|
|
221
|
+
|
|
222
|
+
# adjust right ascension to match extent
|
|
223
|
+
if self.ra_max < 24:
|
|
224
|
+
ra_min = (-1 * extent[1]) / 15
|
|
225
|
+
ra_max = (-1 * extent[0]) / 15
|
|
226
|
+
|
|
227
|
+
if ra_min < 0 or ra_max < 0:
|
|
228
|
+
ra_min += 24
|
|
229
|
+
ra_max += 24
|
|
230
|
+
|
|
231
|
+
self.ra_min = ra_min
|
|
232
|
+
self.ra_max = ra_max
|
|
233
|
+
|
|
234
|
+
self.logger.debug(
|
|
235
|
+
f"Extent = RA ({self.ra_min:.2f}, {self.ra_max:.2f}) DEC ({self.dec_min:.2f}, {self.dec_max:.2f})"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def _in_bounds_xy(self, x: float, y: float) -> bool:
|
|
239
|
+
return self.in_bounds_altaz(y, x) # alt = y, az = x
|
|
240
|
+
|
|
241
|
+
def _prepare_star_coords(self, df):
|
|
242
|
+
stars_apparent = self.observe(SkyfieldStar.from_dataframe(df)).apparent()
|
|
243
|
+
nearby_stars_alt, nearby_stars_az, _ = stars_apparent.altaz()
|
|
244
|
+
df["x"], df["y"] = (
|
|
245
|
+
nearby_stars_az.degrees,
|
|
246
|
+
nearby_stars_alt.degrees,
|
|
247
|
+
)
|
|
248
|
+
return df
|
|
249
|
+
|
|
250
|
+
def _scatter_stars(self, ras, decs, sizes, alphas, colors, style=None, **kwargs):
|
|
251
|
+
plotted = super()._scatter_stars(
|
|
252
|
+
ras, decs, sizes, alphas, colors, style, **kwargs
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
if type(self._background_clip_path) == patches.Rectangle:
|
|
256
|
+
# convert to generic path to handle possible rotation angle:
|
|
257
|
+
clip_path = path.Path(self._background_clip_path.get_corners())
|
|
258
|
+
plotted.set_clip_path(clip_path, transform=self.ax.transData)
|
|
259
|
+
else:
|
|
260
|
+
plotted.set_clip_path(self._background_clip_path)
|
|
261
|
+
|
|
262
|
+
@use_style(ObjectStyle, "star")
|
|
263
|
+
def stars(
|
|
264
|
+
self,
|
|
265
|
+
mag: float = 6.0,
|
|
266
|
+
catalog: StarCatalog = StarCatalog.HIPPARCOS,
|
|
267
|
+
style: ObjectStyle = None,
|
|
268
|
+
rasterize: bool = False,
|
|
269
|
+
size_fn: Callable[[Star], float] = callables.size_by_magnitude,
|
|
270
|
+
alpha_fn: Callable[[Star], float] = callables.alpha_by_magnitude,
|
|
271
|
+
color_fn: Callable[[Star], str] = None,
|
|
272
|
+
where: list = None,
|
|
273
|
+
where_labels: list = None,
|
|
274
|
+
labels: Mapping[int, str] = STAR_NAMES,
|
|
275
|
+
legend_label: str = "Star",
|
|
276
|
+
bayer_labels: bool = False,
|
|
277
|
+
*args,
|
|
278
|
+
**kwargs,
|
|
279
|
+
):
|
|
280
|
+
"""
|
|
281
|
+
Plots stars
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
mag: Limiting magnitude of stars to plot
|
|
285
|
+
catalog: The catalog of stars to use
|
|
286
|
+
style: If `None`, then the plot's style for stars will be used
|
|
287
|
+
rasterize: If True, then the stars will be rasterized when plotted, which can speed up exporting to SVG and reduce the file size but with a loss of image quality
|
|
288
|
+
size_fn: Callable for calculating the marker size of each star. If `None`, then the marker style's size will be used.
|
|
289
|
+
alpha_fn: Callable for calculating the alpha value (aka "opacity") of each star. If `None`, then the marker style's alpha will be used.
|
|
290
|
+
color_fn: Callable for calculating the color of each star. If `None`, then the marker style's color will be used.
|
|
291
|
+
where: A list of expressions that determine which stars to plot. See [Selecting Objects](/reference-selecting-objects/) for details.
|
|
292
|
+
where_labels: A list of expressions that determine which stars are labeled on the plot. See [Selecting Objects](/reference-selecting-objects/) for details.
|
|
293
|
+
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`.
|
|
294
|
+
legend_label: Label for stars in the legend. If `None`, then they will not be in the legend.
|
|
295
|
+
bayer_labels: If True, then Bayer labels for stars will be plotted. Set this to False if you want to hide Bayer labels.
|
|
296
|
+
"""
|
|
297
|
+
# optic_star_multiplier = 0.57 * (self.FIELD_OF_VIEW_MAX / self.optic.true_fov)
|
|
298
|
+
|
|
299
|
+
# def size_fn_mx(st: Star) -> float:
|
|
300
|
+
# return size_fn(st) * optic_star_multiplier
|
|
301
|
+
|
|
302
|
+
super().stars(
|
|
303
|
+
mag=mag,
|
|
304
|
+
catalog=catalog,
|
|
305
|
+
style=style,
|
|
306
|
+
rasterize=rasterize,
|
|
307
|
+
size_fn=size_fn,
|
|
308
|
+
alpha_fn=alpha_fn,
|
|
309
|
+
color_fn=color_fn,
|
|
310
|
+
where=where,
|
|
311
|
+
where_labels=where_labels,
|
|
312
|
+
labels=labels,
|
|
313
|
+
legend_label=legend_label,
|
|
314
|
+
bayer_labels=bayer_labels,
|
|
315
|
+
*args,
|
|
316
|
+
**kwargs,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
def _plot_border(self):
|
|
320
|
+
# since we're using AzimuthalEquidistant projection, the center will always be (0, 0)
|
|
321
|
+
x = 0
|
|
322
|
+
y = 0
|
|
323
|
+
|
|
324
|
+
# Background of Viewable Area
|
|
325
|
+
self._background_clip_path = self.optic.patch(
|
|
326
|
+
x,
|
|
327
|
+
y,
|
|
328
|
+
facecolor=self.style.background_color.as_hex(),
|
|
329
|
+
linewidth=0,
|
|
330
|
+
fill=True,
|
|
331
|
+
zorder=ZOrderEnum.LAYER_1,
|
|
332
|
+
)
|
|
333
|
+
self.ax.add_patch(self._background_clip_path)
|
|
334
|
+
|
|
335
|
+
# Inner Border
|
|
336
|
+
inner_border = self.optic.patch(
|
|
337
|
+
x,
|
|
338
|
+
y,
|
|
339
|
+
linewidth=2 * self.scale,
|
|
340
|
+
edgecolor=self.style.border_line_color.as_hex(),
|
|
341
|
+
fill=False,
|
|
342
|
+
zorder=ZOrderEnum.LAYER_5 + 100,
|
|
343
|
+
)
|
|
344
|
+
self.ax.add_patch(inner_border)
|
|
345
|
+
|
|
346
|
+
# Outer border
|
|
347
|
+
outer_border = self.optic.patch(
|
|
348
|
+
x,
|
|
349
|
+
y,
|
|
350
|
+
padding=0.05,
|
|
351
|
+
linewidth=20 * self.scale,
|
|
352
|
+
edgecolor=self.style.border_bg_color.as_hex(),
|
|
353
|
+
fill=False,
|
|
354
|
+
zorder=ZOrderEnum.LAYER_5,
|
|
355
|
+
)
|
|
356
|
+
self.ax.add_patch(outer_border)
|
|
357
|
+
|
|
358
|
+
def _fit_to_ax(self) -> None:
|
|
359
|
+
bbox = self.ax.get_window_extent().transformed(
|
|
360
|
+
self.fig.dpi_scale_trans.inverted()
|
|
361
|
+
)
|
|
362
|
+
width, height = bbox.width, bbox.height
|
|
363
|
+
self.fig.set_size_inches(width, height)
|
|
364
|
+
|
|
365
|
+
def _init_plot(self):
|
|
366
|
+
self._proj = ccrs.LambertAzimuthalEqualArea(
|
|
367
|
+
central_longitude=sum(self.az) / 2,
|
|
368
|
+
central_latitude=0,
|
|
369
|
+
)
|
|
370
|
+
self._proj.threshold = 100
|
|
371
|
+
self.fig = plt.figure(
|
|
372
|
+
figsize=(self.figure_size, self.figure_size),
|
|
373
|
+
facecolor=self.style.figure_background_color.as_hex(),
|
|
374
|
+
layout="constrained",
|
|
375
|
+
dpi=DPI,
|
|
376
|
+
)
|
|
377
|
+
self.ax = plt.axes(projection=self._proj)
|
|
378
|
+
self.ax.xaxis.set_visible(False)
|
|
379
|
+
self.ax.yaxis.set_visible(False)
|
|
380
|
+
self.ax.axis("off")
|
|
381
|
+
|
|
382
|
+
bounds = [
|
|
383
|
+
self.az[0],
|
|
384
|
+
self.az[1] * 1.2,
|
|
385
|
+
self.alt[0],
|
|
386
|
+
self.alt[1],
|
|
387
|
+
]
|
|
388
|
+
print(bounds)
|
|
389
|
+
|
|
390
|
+
self.ax.set_extent(bounds, crs=ccrs.PlateCarree())
|
|
391
|
+
self.ax.gridlines()
|
|
392
|
+
|
|
393
|
+
# self._plot_border()
|
|
394
|
+
self._fit_to_ax()
|
|
395
|
+
|
|
396
|
+
# self.ax.set_xlim(-1.06 * self.optic.xlim, 1.06 * self.optic.xlim)
|
|
397
|
+
# self.ax.set_ylim(-1.06 * self.optic.ylim, 1.06 * self.optic.ylim)
|
|
398
|
+
# self.optic.transform(self.ax)
|