starplot 0.13.0__py2.py3-none-any.whl → 0.14.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 +3 -2
- starplot/base.py +187 -185
- starplot/coordinates.py +6 -0
- starplot/data/constellations.py +564 -2
- starplot/geometry.py +82 -0
- starplot/horizon.py +237 -177
- starplot/map.py +57 -369
- starplot/models/base.py +9 -2
- starplot/models/constellation.py +1 -1
- starplot/optic.py +7 -1
- starplot/plotters/__init__.py +2 -0
- starplot/plotters/constellations.py +339 -0
- starplot/plotters/experimental.py +171 -0
- starplot/plotters/milkyway.py +41 -0
- starplot/plotters/stars.py +56 -27
- starplot/styles/base.py +49 -17
- starplot/styles/ext/antique.yml +11 -9
- starplot/styles/ext/blue_dark.yml +8 -10
- starplot/styles/ext/blue_light.yml +9 -8
- starplot/styles/ext/blue_medium.yml +12 -13
- 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/utils.py +19 -0
- starplot/warnings.py +16 -0
- {starplot-0.13.0.dist-info → starplot-0.14.0.dist-info}/METADATA +10 -9
- {starplot-0.13.0.dist-info → starplot-0.14.0.dist-info}/RECORD +32 -26
- {starplot-0.13.0.dist-info → starplot-0.14.0.dist-info}/LICENSE +0 -0
- {starplot-0.13.0.dist-info → starplot-0.14.0.dist-info}/WHEEL +0 -0
starplot/horizon.py
CHANGED
|
@@ -1,69 +1,93 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from
|
|
2
|
+
from functools import cache
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
|
+
import geopandas as gpd
|
|
5
6
|
|
|
6
7
|
from cartopy import crs as ccrs
|
|
7
|
-
from matplotlib import pyplot as plt, patches
|
|
8
|
+
from matplotlib import pyplot as plt, patches
|
|
9
|
+
from matplotlib.ticker import FixedLocator
|
|
8
10
|
from skyfield.api import wgs84, Star as SkyfieldStar
|
|
9
11
|
|
|
10
|
-
from starplot import
|
|
12
|
+
from starplot.coordinates import CoordinateSystem
|
|
11
13
|
from starplot.base import BasePlot, DPI
|
|
12
|
-
from starplot.data.stars import StarCatalog, STAR_NAMES
|
|
13
14
|
from starplot.mixins import ExtentMaskMixin
|
|
14
|
-
from starplot.
|
|
15
|
-
|
|
15
|
+
from starplot.plotters import (
|
|
16
|
+
ConstellationPlotterMixin,
|
|
17
|
+
StarPlotterMixin,
|
|
18
|
+
DsoPlotterMixin,
|
|
19
|
+
MilkyWayPlotterMixin,
|
|
20
|
+
)
|
|
16
21
|
from starplot.styles import (
|
|
17
22
|
PlotStyle,
|
|
18
|
-
ObjectStyle,
|
|
19
23
|
extensions,
|
|
20
24
|
use_style,
|
|
21
|
-
|
|
25
|
+
PathStyle,
|
|
22
26
|
)
|
|
23
27
|
|
|
24
28
|
pd.options.mode.chained_assignment = None # default='warn'
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
DEFAULT_HORIZON_STYLE = PlotStyle().extend(extensions.MAP)
|
|
31
|
+
|
|
32
|
+
DEFAULT_HORIZON_LABELS = {
|
|
33
|
+
0: "N",
|
|
34
|
+
45: "NE",
|
|
35
|
+
90: "E",
|
|
36
|
+
135: "SE",
|
|
37
|
+
180: "S",
|
|
38
|
+
225: "SW",
|
|
39
|
+
270: "W",
|
|
40
|
+
315: "NW",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class HorizonPlot(
|
|
45
|
+
BasePlot,
|
|
46
|
+
ExtentMaskMixin,
|
|
47
|
+
ConstellationPlotterMixin,
|
|
48
|
+
StarPlotterMixin,
|
|
49
|
+
DsoPlotterMixin,
|
|
50
|
+
MilkyWayPlotterMixin,
|
|
51
|
+
):
|
|
30
52
|
"""Creates a new horizon plot.
|
|
31
53
|
|
|
32
54
|
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
55
|
lat: Latitude of observer's location
|
|
37
56
|
lon: Longitude of observer's location
|
|
57
|
+
altitude: Tuple of altitude range to plot (min, max)
|
|
58
|
+
azimuth: Tuple of azimuth range to plot (min, max)
|
|
38
59
|
dt: Date/time of observation (*must be timezone-aware*). Default = current UTC time.
|
|
39
60
|
ephemeris: Ephemeris to use for calculating planet positions (see [Skyfield's documentation](https://rhodesmill.org/skyfield/planets.html) for details)
|
|
40
61
|
style: Styling for the plot (colors, sizes, fonts, etc)
|
|
41
62
|
resolution: Size (in pixels) of largest dimension of the map
|
|
42
63
|
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
64
|
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
65
|
autoscale: If True, then the scale will be automatically set based on resolution
|
|
66
|
+
suppress_warnings: If True (the default), then all warnings will be suppressed
|
|
46
67
|
|
|
47
68
|
Returns:
|
|
48
|
-
|
|
69
|
+
HorizonPlot: A new instance of an HorizonPlot
|
|
49
70
|
|
|
50
71
|
"""
|
|
51
72
|
|
|
73
|
+
_coordinate_system = CoordinateSystem.AZ_ALT
|
|
74
|
+
|
|
52
75
|
FIELD_OF_VIEW_MAX = 9.0
|
|
53
76
|
|
|
54
77
|
def __init__(
|
|
55
78
|
self,
|
|
56
79
|
lat: float,
|
|
57
80
|
lon: float,
|
|
58
|
-
altitude: tuple[float, float]
|
|
59
|
-
azimuth: tuple[float, float]
|
|
81
|
+
altitude: tuple[float, float],
|
|
82
|
+
azimuth: tuple[float, float],
|
|
60
83
|
dt: datetime = None,
|
|
61
84
|
ephemeris: str = "de421_2001.bsp",
|
|
62
|
-
style: PlotStyle =
|
|
63
|
-
resolution: int =
|
|
85
|
+
style: PlotStyle = DEFAULT_HORIZON_STYLE,
|
|
86
|
+
resolution: int = 4096,
|
|
64
87
|
hide_colliding_labels: bool = True,
|
|
65
88
|
scale: float = 1.0,
|
|
66
89
|
autoscale: bool = False,
|
|
90
|
+
suppress_warnings: bool = True,
|
|
67
91
|
*args,
|
|
68
92
|
**kwargs,
|
|
69
93
|
) -> "HorizonPlot":
|
|
@@ -75,16 +99,31 @@ class HorizonPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
75
99
|
hide_colliding_labels,
|
|
76
100
|
scale=scale,
|
|
77
101
|
autoscale=autoscale,
|
|
102
|
+
suppress_warnings=suppress_warnings,
|
|
78
103
|
*args,
|
|
79
104
|
**kwargs,
|
|
80
105
|
)
|
|
106
|
+
|
|
107
|
+
if azimuth[0] >= azimuth[1]:
|
|
108
|
+
raise ValueError("Azimuth min must be less than max")
|
|
109
|
+
if azimuth[1] - azimuth[0] > 180:
|
|
110
|
+
raise ValueError("Azimuth range cannot be greater than 180 degrees")
|
|
111
|
+
|
|
112
|
+
if altitude[0] >= altitude[1]:
|
|
113
|
+
raise ValueError("Altitude min must be less than max")
|
|
114
|
+
if altitude[1] - altitude[0] > 90:
|
|
115
|
+
raise ValueError("Altitude range cannot be greater than 90 degrees")
|
|
116
|
+
|
|
81
117
|
self.logger.debug("Creating HorizonPlot...")
|
|
82
118
|
self.alt = altitude
|
|
83
119
|
self.az = azimuth
|
|
120
|
+
self.center_alt = sum(altitude) / 2
|
|
84
121
|
self.center_az = sum(azimuth) / 2
|
|
85
122
|
self.lat = lat
|
|
86
123
|
self.lon = lon
|
|
87
124
|
|
|
125
|
+
self._geodetic = ccrs.Geodetic()
|
|
126
|
+
self._plate_carree = ccrs.PlateCarree()
|
|
88
127
|
self._crs = ccrs.CRS(
|
|
89
128
|
proj4_params=[
|
|
90
129
|
("proj", "latlong"),
|
|
@@ -97,17 +136,27 @@ class HorizonPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
97
136
|
self._init_plot()
|
|
98
137
|
self._adjust_radec_minmax()
|
|
99
138
|
|
|
139
|
+
@cache
|
|
100
140
|
def _prepare_coords(self, ra, dec) -> (float, float):
|
|
101
141
|
"""Converts RA/DEC to AZ/ALT"""
|
|
102
142
|
point = SkyfieldStar(ra_hours=ra, dec_degrees=dec)
|
|
103
|
-
position = self.observe(point)
|
|
104
|
-
|
|
105
|
-
pos_alt, pos_az, _ = pos_apparent.altaz()
|
|
143
|
+
position = self.observe(point).apparent()
|
|
144
|
+
pos_alt, pos_az, _ = position.altaz()
|
|
106
145
|
return pos_az.degrees, pos_alt.degrees
|
|
107
146
|
|
|
147
|
+
def _prepare_star_coords(self, df):
|
|
148
|
+
stars_apparent = self.observe(SkyfieldStar.from_dataframe(df)).apparent()
|
|
149
|
+
nearby_stars_alt, nearby_stars_az, _ = stars_apparent.altaz()
|
|
150
|
+
df["x"], df["y"] = (
|
|
151
|
+
nearby_stars_az.degrees,
|
|
152
|
+
nearby_stars_alt.degrees,
|
|
153
|
+
)
|
|
154
|
+
return df
|
|
155
|
+
|
|
108
156
|
def _plot_kwargs(self) -> dict:
|
|
109
157
|
return dict(transform=self._crs)
|
|
110
158
|
|
|
159
|
+
@cache
|
|
111
160
|
def in_bounds(self, ra, dec) -> bool:
|
|
112
161
|
"""Determine if a coordinate is within the bounds of the plot.
|
|
113
162
|
|
|
@@ -119,12 +168,7 @@ class HorizonPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
119
168
|
True if the coordinate is in bounds, otherwise False
|
|
120
169
|
"""
|
|
121
170
|
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
|
-
)
|
|
171
|
+
return self.in_bounds_altaz(alt, az)
|
|
128
172
|
|
|
129
173
|
def in_bounds_altaz(self, alt, az, scale: float = 1) -> bool:
|
|
130
174
|
"""Determine if a coordinate is within the bounds of the plot.
|
|
@@ -136,7 +180,9 @@ class HorizonPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
136
180
|
Returns:
|
|
137
181
|
True if the coordinate is in bounds, otherwise False
|
|
138
182
|
"""
|
|
139
|
-
|
|
183
|
+
if self.az[0] > 360 or self.az[1] > 360 and az < 90:
|
|
184
|
+
az += 360
|
|
185
|
+
|
|
140
186
|
return (
|
|
141
187
|
az < self.az[1]
|
|
142
188
|
and az > self.az[0]
|
|
@@ -153,12 +199,6 @@ class HorizonPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
153
199
|
self.location = earth + wgs84.latlon(self.lat, self.lon)
|
|
154
200
|
self.observe = self.location.at(self.timescale).observe
|
|
155
201
|
|
|
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
202
|
locations = [
|
|
163
203
|
self.location.at(self.timescale).from_altaz(
|
|
164
204
|
alt_degrees=self.alt[0], az_degrees=self.az[0]
|
|
@@ -169,6 +209,9 @@ class HorizonPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
169
209
|
self.location.at(self.timescale).from_altaz(
|
|
170
210
|
alt_degrees=self.alt[1], az_degrees=self.center_az
|
|
171
211
|
), # top center
|
|
212
|
+
self.location.at(self.timescale).from_altaz(
|
|
213
|
+
alt_degrees=self.center_alt, az_degrees=self.center_az
|
|
214
|
+
), # center
|
|
172
215
|
# self.location.at(self.timescale).from_altaz(alt_degrees=self.alt[1], az_degrees=self.az[0]), # upper left
|
|
173
216
|
# self.location.at(self.timescale).from_altaz(alt_degrees=self.alt[1], az_degrees=self.az[1]), # upper right
|
|
174
217
|
]
|
|
@@ -194,42 +237,19 @@ class HorizonPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
194
237
|
if self.dec_max is None or dec > self.dec_max:
|
|
195
238
|
self.dec_max = dec
|
|
196
239
|
|
|
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
240
|
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
241
|
if self.dec_max > 70 or self.dec_min < -70:
|
|
212
242
|
# naive method of getting all the stars near the poles
|
|
213
243
|
self.ra_min = 0
|
|
214
244
|
self.ra_max = 24
|
|
215
245
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
self.
|
|
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
|
|
246
|
+
self.dec_min -= 20
|
|
247
|
+
self.dec_max += 20
|
|
248
|
+
self.ra_min -= 4
|
|
249
|
+
self.ra_max += 4
|
|
230
250
|
|
|
231
|
-
|
|
232
|
-
self.
|
|
251
|
+
if self.ra_min < 0:
|
|
252
|
+
self.ra_min = 0
|
|
233
253
|
|
|
234
254
|
self.logger.debug(
|
|
235
255
|
f"Extent = RA ({self.ra_min:.2f}, {self.ra_max:.2f}) DEC ({self.dec_min:.2f}, {self.dec_max:.2f})"
|
|
@@ -238,122 +258,154 @@ class HorizonPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
238
258
|
def _in_bounds_xy(self, x: float, y: float) -> bool:
|
|
239
259
|
return self.in_bounds_altaz(y, x) # alt = y, az = x
|
|
240
260
|
|
|
241
|
-
def
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
261
|
+
def _read_geo_package(self, filename: str):
|
|
262
|
+
"""Returns GeoDataFrame of a GeoPackage file"""
|
|
263
|
+
|
|
264
|
+
# if self.ra_min <= 0 and self.ra_max >= 24:
|
|
265
|
+
# lon_min = -180
|
|
266
|
+
# lon_max = 180
|
|
267
|
+
# else:
|
|
268
|
+
# lon_min = self.ra_max * 15 - 180 # ra_to_lon(24 - self.ra_max)
|
|
269
|
+
# lon_max = self.ra_min * 15 - 180 # ra_to_lon(24 - self.ra_min)
|
|
270
|
+
|
|
271
|
+
# extent = self._extent_mask()
|
|
272
|
+
# extent = (
|
|
273
|
+
# lon_min,
|
|
274
|
+
# self.dec_min,
|
|
275
|
+
# lon_max,
|
|
276
|
+
# self.dec_max,
|
|
277
|
+
# )
|
|
278
|
+
|
|
279
|
+
return gpd.read_file(
|
|
280
|
+
filename,
|
|
281
|
+
engine="pyogrio",
|
|
282
|
+
use_arrow=True,
|
|
283
|
+
# bbox=extent,
|
|
247
284
|
)
|
|
248
|
-
return df
|
|
249
285
|
|
|
250
|
-
|
|
251
|
-
|
|
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(
|
|
286
|
+
@use_style(PathStyle, "horizon")
|
|
287
|
+
def horizon(
|
|
264
288
|
self,
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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,
|
|
289
|
+
style: PathStyle = None,
|
|
290
|
+
labels: dict[int, str] = DEFAULT_HORIZON_LABELS,
|
|
291
|
+
show_degree_labels: bool = True,
|
|
292
|
+
degree_step: int = 15,
|
|
293
|
+
show_ticks: bool = True,
|
|
294
|
+
tick_step: int = 5,
|
|
279
295
|
):
|
|
280
296
|
"""
|
|
281
|
-
Plots
|
|
297
|
+
Plots rectangle for horizon that shows cardinal directions and azimuth labels.
|
|
282
298
|
|
|
283
299
|
Args:
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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.
|
|
300
|
+
style: Style of the horizon path. If None, then the plot's style definition will be used.
|
|
301
|
+
labels: Dictionary that maps azimuth values (0...360) to their cardinal direction labels (e.g. "N"). Default is to label each 45deg direction (e.g. "N", "NE", "E", etc)
|
|
302
|
+
show_degree_labels: If True, then azimuth degree labels will be plotted on the horizon path
|
|
303
|
+
degree_step: Step size for degree labels
|
|
304
|
+
show_ticks: If True, then tick marks will be plotted on the horizon path for every `tick_step` degree that is not also a degree label
|
|
305
|
+
tick_step: Step size for tick marks
|
|
296
306
|
"""
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
307
|
+
bottom = patches.Polygon(
|
|
308
|
+
[
|
|
309
|
+
(0, 0),
|
|
310
|
+
(1, 0),
|
|
311
|
+
(1, -0.1 * self.scale),
|
|
312
|
+
(0, -0.1 * self.scale),
|
|
313
|
+
(0, 0),
|
|
314
|
+
],
|
|
315
|
+
color=style.line.color.as_hex(),
|
|
316
|
+
transform=self.ax.transAxes,
|
|
317
|
+
clip_on=False,
|
|
318
|
+
)
|
|
319
|
+
self.ax.add_patch(bottom)
|
|
320
|
+
|
|
321
|
+
def az_to_ax(d):
|
|
322
|
+
return self._to_ax(d, self.alt[0])[0]
|
|
323
|
+
|
|
324
|
+
for az in range(self.az[0] + 2, self.az[1], 1):
|
|
325
|
+
az = int(az)
|
|
326
|
+
|
|
327
|
+
if az >= 360:
|
|
328
|
+
az -= 360
|
|
329
|
+
|
|
330
|
+
if labels.get(az):
|
|
331
|
+
self.ax.annotate(
|
|
332
|
+
labels.get(az),
|
|
333
|
+
(az_to_ax(az), -0.074 * self.scale),
|
|
334
|
+
xycoords=self.ax.transAxes,
|
|
335
|
+
**style.label.matplot_kwargs(self.scale),
|
|
336
|
+
clip_on=False,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
if show_degree_labels and az % degree_step == 0:
|
|
340
|
+
self.ax.annotate(
|
|
341
|
+
str(az) + "\u00b0",
|
|
342
|
+
(az_to_ax(az), -0.011 * self.scale),
|
|
343
|
+
xycoords=self.ax.transAxes,
|
|
344
|
+
**self.style.gridlines.label.matplot_kwargs(self.scale),
|
|
345
|
+
clip_on=False,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
elif show_ticks and az % tick_step == 0:
|
|
349
|
+
self.ax.annotate(
|
|
350
|
+
"|",
|
|
351
|
+
(az_to_ax(az), -0.011 * self.scale),
|
|
352
|
+
xycoords=self.ax.transAxes,
|
|
353
|
+
**self.style.gridlines.label.matplot_kwargs(self.scale / 2),
|
|
354
|
+
clip_on=False,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
self.ax.plot(
|
|
358
|
+
[0, 1],
|
|
359
|
+
[-0.04 * self.scale, -0.04 * self.scale],
|
|
360
|
+
lw=1,
|
|
361
|
+
color=style.label.font_color.as_hex(),
|
|
362
|
+
clip_on=False,
|
|
363
|
+
transform=self.ax.transAxes,
|
|
317
364
|
)
|
|
318
365
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
366
|
+
@use_style(PathStyle, "gridlines")
|
|
367
|
+
def gridlines(
|
|
368
|
+
self,
|
|
369
|
+
style: PathStyle = None,
|
|
370
|
+
az_locations: list[float] = None,
|
|
371
|
+
alt_locations: list[float] = None,
|
|
372
|
+
):
|
|
373
|
+
"""
|
|
374
|
+
Plots gridlines
|
|
323
375
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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)
|
|
376
|
+
Args:
|
|
377
|
+
style: Styling of the gridlines. If None, then the plot's style (specified when creating the plot) will be used
|
|
378
|
+
az_locations: List of azimuth locations for the gridlines (in degrees, 0...360). Defaults to every 15 degrees
|
|
379
|
+
alt_locations: List of altitude locations for the gridlines (in degrees, -90...90). Defaults to every 10 degrees.
|
|
334
380
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
edgecolor=self.style.border_bg_color.as_hex(),
|
|
353
|
-
fill=False,
|
|
354
|
-
zorder=ZOrderEnum.LAYER_5,
|
|
381
|
+
"""
|
|
382
|
+
x_locations = az_locations or [x for x in range(0, 360, 15)]
|
|
383
|
+
x_locations = [x - 180 for x in x_locations]
|
|
384
|
+
y_locations = alt_locations or [d for d in range(-90, 90, 10)]
|
|
385
|
+
|
|
386
|
+
line_style_kwargs = style.line.matplot_kwargs()
|
|
387
|
+
gridlines = self.ax.gridlines(
|
|
388
|
+
draw_labels=False,
|
|
389
|
+
x_inline=False,
|
|
390
|
+
y_inline=False,
|
|
391
|
+
rotate_labels=False,
|
|
392
|
+
xpadding=12,
|
|
393
|
+
ypadding=12,
|
|
394
|
+
clip_on=True,
|
|
395
|
+
clip_path=self._background_clip_path,
|
|
396
|
+
gid="gridlines",
|
|
397
|
+
**line_style_kwargs,
|
|
355
398
|
)
|
|
356
|
-
|
|
399
|
+
gridlines.xlocator = FixedLocator(x_locations)
|
|
400
|
+
gridlines.ylocator = FixedLocator(y_locations)
|
|
401
|
+
|
|
402
|
+
@cache
|
|
403
|
+
def _to_ax(self, az: float, alt: float) -> tuple[float, float]:
|
|
404
|
+
"""Converts az/alt to axes coordinates"""
|
|
405
|
+
x, y = self._proj.transform_point(az, alt, self._crs)
|
|
406
|
+
data_to_axes = self.ax.transData + self.ax.transAxes.inverted()
|
|
407
|
+
x_axes, y_axes = data_to_axes.transform((x, y))
|
|
408
|
+
return x_axes, y_axes
|
|
357
409
|
|
|
358
410
|
def _fit_to_ax(self) -> None:
|
|
359
411
|
bbox = self.ax.get_window_extent().transformed(
|
|
@@ -362,6 +414,20 @@ class HorizonPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
362
414
|
width, height = bbox.width, bbox.height
|
|
363
415
|
self.fig.set_size_inches(width, height)
|
|
364
416
|
|
|
417
|
+
def _plot_background_clip_path(self):
|
|
418
|
+
self._background_clip_path = patches.Rectangle(
|
|
419
|
+
(0, 0),
|
|
420
|
+
width=1,
|
|
421
|
+
height=1,
|
|
422
|
+
facecolor=self.style.background_color.as_hex(),
|
|
423
|
+
linewidth=0,
|
|
424
|
+
fill=True,
|
|
425
|
+
zorder=-3_000,
|
|
426
|
+
transform=self.ax.transAxes,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
self.ax.add_patch(self._background_clip_path)
|
|
430
|
+
|
|
365
431
|
def _init_plot(self):
|
|
366
432
|
self._proj = ccrs.LambertAzimuthalEqualArea(
|
|
367
433
|
central_longitude=sum(self.az) / 2,
|
|
@@ -381,18 +447,12 @@ class HorizonPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
381
447
|
|
|
382
448
|
bounds = [
|
|
383
449
|
self.az[0],
|
|
384
|
-
self.az[1]
|
|
450
|
+
self.az[1],
|
|
385
451
|
self.alt[0],
|
|
386
452
|
self.alt[1],
|
|
387
453
|
]
|
|
388
|
-
print(bounds)
|
|
389
454
|
|
|
390
455
|
self.ax.set_extent(bounds, crs=ccrs.PlateCarree())
|
|
391
|
-
self.ax.gridlines()
|
|
392
456
|
|
|
393
|
-
|
|
457
|
+
self._plot_background_clip_path()
|
|
394
458
|
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)
|