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/map.py
CHANGED
|
@@ -1,40 +1,37 @@
|
|
|
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 shapely.ops import unary_union
|
|
10
|
+
from shapely import Polygon
|
|
12
11
|
from skyfield.api import Star as SkyfieldStar, wgs84
|
|
13
12
|
import geopandas as gpd
|
|
14
13
|
import numpy as np
|
|
15
14
|
|
|
15
|
+
from starplot.coordinates import CoordinateSystem
|
|
16
16
|
from starplot import geod
|
|
17
17
|
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
18
|
from starplot.mixins import ExtentMaskMixin
|
|
21
|
-
from starplot.
|
|
22
|
-
|
|
19
|
+
from starplot.plotters import (
|
|
20
|
+
ConstellationPlotterMixin,
|
|
21
|
+
StarPlotterMixin,
|
|
22
|
+
DsoPlotterMixin,
|
|
23
|
+
MilkyWayPlotterMixin,
|
|
24
|
+
)
|
|
23
25
|
from starplot.projections import Projection
|
|
24
26
|
from starplot.styles import (
|
|
25
27
|
ObjectStyle,
|
|
26
28
|
LabelStyle,
|
|
27
|
-
LineStyle,
|
|
28
29
|
PlotStyle,
|
|
29
|
-
PolygonStyle,
|
|
30
30
|
PathStyle,
|
|
31
31
|
)
|
|
32
32
|
from starplot.styles.helpers import use_style
|
|
33
33
|
from starplot.utils import lon_to_ra, ra_to_lon
|
|
34
34
|
|
|
35
|
-
# Silence noisy cartopy warnings
|
|
36
|
-
warnings.filterwarnings("ignore", module="cartopy")
|
|
37
|
-
warnings.filterwarnings("ignore", module="shapely")
|
|
38
35
|
|
|
39
36
|
DEFAULT_MAP_STYLE = PlotStyle() # .extend(extensions.MAP)
|
|
40
37
|
|
|
@@ -57,7 +54,14 @@ def points(start, end, num_points=100):
|
|
|
57
54
|
return list(zip(x_coords, y_coords))
|
|
58
55
|
|
|
59
56
|
|
|
60
|
-
class MapPlot(
|
|
57
|
+
class MapPlot(
|
|
58
|
+
BasePlot,
|
|
59
|
+
ExtentMaskMixin,
|
|
60
|
+
StarPlotterMixin,
|
|
61
|
+
DsoPlotterMixin,
|
|
62
|
+
MilkyWayPlotterMixin,
|
|
63
|
+
ConstellationPlotterMixin,
|
|
64
|
+
):
|
|
61
65
|
"""Creates a new map plot.
|
|
62
66
|
|
|
63
67
|
!!! star "Note"
|
|
@@ -79,12 +83,15 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
79
83
|
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
84
|
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
85
|
autoscale: If True, then the scale will be set automatically based on resolution.
|
|
86
|
+
suppress_warnings: If True (the default), then all warnings will be suppressed
|
|
82
87
|
|
|
83
88
|
Returns:
|
|
84
89
|
MapPlot: A new instance of a MapPlot
|
|
85
90
|
|
|
86
91
|
"""
|
|
87
92
|
|
|
93
|
+
_coordinate_system = CoordinateSystem.RA_DEC
|
|
94
|
+
|
|
88
95
|
def __init__(
|
|
89
96
|
self,
|
|
90
97
|
projection: Projection,
|
|
@@ -102,6 +109,7 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
102
109
|
clip_path: Polygon = None,
|
|
103
110
|
scale: float = 1.0,
|
|
104
111
|
autoscale: bool = False,
|
|
112
|
+
suppress_warnings: bool = True,
|
|
105
113
|
*args,
|
|
106
114
|
**kwargs,
|
|
107
115
|
) -> "MapPlot":
|
|
@@ -113,6 +121,7 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
113
121
|
hide_colliding_labels,
|
|
114
122
|
scale=scale,
|
|
115
123
|
autoscale=autoscale,
|
|
124
|
+
suppress_warnings=suppress_warnings,
|
|
116
125
|
*args,
|
|
117
126
|
**kwargs,
|
|
118
127
|
)
|
|
@@ -166,6 +175,7 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
166
175
|
def _prepare_coords(self, ra: float, dec: float) -> (float, float):
|
|
167
176
|
return ra * 15, dec
|
|
168
177
|
|
|
178
|
+
@cache
|
|
169
179
|
def in_bounds(self, ra: float, dec: float) -> bool:
|
|
170
180
|
"""Determine if a coordinate is within the bounds of the plot.
|
|
171
181
|
|
|
@@ -258,342 +268,6 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
258
268
|
|
|
259
269
|
return df
|
|
260
270
|
|
|
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
|
-
else:
|
|
403
|
-
transform = self._geodetic
|
|
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
|
-
)
|
|
501
|
-
|
|
502
|
-
def _plot_constellation_labels_experimental(
|
|
503
|
-
self,
|
|
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,
|
|
512
|
-
)
|
|
513
|
-
|
|
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
271
|
@use_style(ObjectStyle, "zenith")
|
|
598
272
|
def zenith(
|
|
599
273
|
self,
|
|
@@ -654,37 +328,51 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
654
328
|
)
|
|
655
329
|
x = []
|
|
656
330
|
y = []
|
|
657
|
-
verts = []
|
|
658
|
-
|
|
659
|
-
# TODO : handle map edges better
|
|
660
331
|
|
|
661
332
|
for ra, dec in points:
|
|
662
333
|
ra = ra / 15
|
|
663
334
|
x0, y0 = self._prepare_coords(ra, dec)
|
|
664
335
|
x.append(x0)
|
|
665
336
|
y.append(y0)
|
|
666
|
-
verts.append((x0, y0))
|
|
667
337
|
|
|
668
338
|
style_kwargs = {}
|
|
669
339
|
if self.projection == Projection.ZENITH:
|
|
670
340
|
"""
|
|
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
|
|
341
|
+
For zenith projections, we plot the horizon as a patch to make a more perfect circle
|
|
675
342
|
"""
|
|
676
343
|
style_kwargs = style.line.matplot_kwargs(self.scale)
|
|
677
344
|
style_kwargs["clip_on"] = False
|
|
678
345
|
style_kwargs["edgecolor"] = style_kwargs.pop("color")
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
346
|
+
patch = patches.Circle(
|
|
347
|
+
(0.50, 0.50),
|
|
348
|
+
radius=0.454,
|
|
682
349
|
facecolor=None,
|
|
683
350
|
fill=False,
|
|
684
|
-
transform=self.
|
|
351
|
+
transform=self.ax.transAxes,
|
|
685
352
|
**style_kwargs,
|
|
686
353
|
)
|
|
687
354
|
self.ax.add_patch(patch)
|
|
355
|
+
self._background_clip_path = patch
|
|
356
|
+
|
|
357
|
+
if not labels:
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
label_ax_coords = [
|
|
361
|
+
(0.5, 0.95), # north
|
|
362
|
+
(0.045, 0.5), # east
|
|
363
|
+
(0.5, 0.045), # south
|
|
364
|
+
(0.954, 0.5), # west
|
|
365
|
+
]
|
|
366
|
+
for label, coords in zip(labels, label_ax_coords):
|
|
367
|
+
self.ax.annotate(
|
|
368
|
+
label,
|
|
369
|
+
coords,
|
|
370
|
+
xycoords=self.ax.transAxes,
|
|
371
|
+
clip_on=False,
|
|
372
|
+
**style.label.matplot_kwargs(self.scale),
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
return
|
|
688
376
|
|
|
689
377
|
else:
|
|
690
378
|
style_kwargs["clip_on"] = True
|
|
@@ -698,13 +386,6 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
698
386
|
**self._plot_kwargs(),
|
|
699
387
|
)
|
|
700
388
|
|
|
701
|
-
# self.circle(
|
|
702
|
-
# (ra.hours, dec.degrees),
|
|
703
|
-
# 90,
|
|
704
|
-
# style,
|
|
705
|
-
# num_pts=200,
|
|
706
|
-
# )
|
|
707
|
-
|
|
708
389
|
if not labels:
|
|
709
390
|
return
|
|
710
391
|
|
|
@@ -731,7 +412,8 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
731
412
|
|
|
732
413
|
for i, position in enumerate(cardinal_directions):
|
|
733
414
|
ra, dec, _ = position.radec()
|
|
734
|
-
self.
|
|
415
|
+
x, y = self._prepare_coords(ra, dec)
|
|
416
|
+
self._text(x, y, labels[i], **text_kwargs)
|
|
735
417
|
|
|
736
418
|
@use_style(PathStyle, "gridlines")
|
|
737
419
|
def gridlines(
|
|
@@ -928,6 +610,12 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
928
610
|
**style.matplot_kwargs(self.scale),
|
|
929
611
|
)
|
|
930
612
|
|
|
613
|
+
def _ax_to_radec(self, x, y):
|
|
614
|
+
trans = self.ax.transAxes + self.ax.transData.inverted()
|
|
615
|
+
x_projected, y_projected = trans.transform((x, y)) # axes to data
|
|
616
|
+
x_ra, y_ra = self._crs.transform_point(x_projected, y_projected, self._proj)
|
|
617
|
+
return (x_ra + 360) / 15, y_ra
|
|
618
|
+
|
|
931
619
|
def _plot_background_clip_path(self):
|
|
932
620
|
def to_axes(points):
|
|
933
621
|
ax_points = []
|
starplot/models/base.py
CHANGED
|
@@ -113,6 +113,8 @@ class SkyObject(CreateMapMixin, CreateOpticMixin, metaclass=Meta):
|
|
|
113
113
|
dec: float
|
|
114
114
|
"""Declination, in degrees (-90...90)"""
|
|
115
115
|
|
|
116
|
+
_constellation_id = None
|
|
117
|
+
|
|
116
118
|
constellation_id: Optional[str] = None
|
|
117
119
|
"""Identifier of the constellation that contains this object. The ID is the three-letter (all lowercase) abbreviation from the International Astronomical Union (IAU)."""
|
|
118
120
|
|
|
@@ -120,8 +122,13 @@ class SkyObject(CreateMapMixin, CreateOpticMixin, metaclass=Meta):
|
|
|
120
122
|
self.ra = ra
|
|
121
123
|
self.dec = dec
|
|
122
124
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
@property
|
|
126
|
+
def constellation_id(self):
|
|
127
|
+
"""Identifier of the constellation that contains this object. The ID is the three-letter (all lowercase) abbreviation from the International Astronomical Union (IAU)."""
|
|
128
|
+
if not self._constellation_id:
|
|
129
|
+
pos = position_of_radec(self.ra, self.dec)
|
|
130
|
+
self._constellation_id = constellation_at(pos).lower()
|
|
131
|
+
return self._constellation_id
|
|
125
132
|
|
|
126
133
|
def constellation(self):
|
|
127
134
|
"""Returns an instance of the [`Constellation`][starplot.models.Constellation] that contains this object"""
|
starplot/models/constellation.py
CHANGED
|
@@ -39,7 +39,7 @@ class Constellation(SkyObject):
|
|
|
39
39
|
) -> None:
|
|
40
40
|
super().__init__(ra, dec)
|
|
41
41
|
self.iau_id = iau_id.lower()
|
|
42
|
-
self.
|
|
42
|
+
self._constellation_id = self.iau_id # override from super()
|
|
43
43
|
self.name = name
|
|
44
44
|
self.boundary = boundary
|
|
45
45
|
|
starplot/optic.py
CHANGED
|
@@ -7,6 +7,7 @@ from cartopy import crs as ccrs
|
|
|
7
7
|
from matplotlib import pyplot as plt, patches, path
|
|
8
8
|
from skyfield.api import wgs84, Star as SkyfieldStar
|
|
9
9
|
|
|
10
|
+
from starplot.coordinates import CoordinateSystem
|
|
10
11
|
from starplot import callables
|
|
11
12
|
from starplot.base import BasePlot, DPI
|
|
12
13
|
from starplot.data.stars import StarCatalog, STAR_NAMES
|
|
@@ -46,12 +47,15 @@ class OpticPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
46
47
|
raise_on_below_horizon: If True, then a ValueError will be raised if the target is below the horizon at the observing time/location
|
|
47
48
|
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.
|
|
48
49
|
autoscale: If True, then the scale will be set automatically based on resolution.
|
|
50
|
+
suppress_warnings: If True (the default), then all warnings will be suppressed
|
|
49
51
|
|
|
50
52
|
Returns:
|
|
51
53
|
OpticPlot: A new instance of an OpticPlot
|
|
52
54
|
|
|
53
55
|
"""
|
|
54
56
|
|
|
57
|
+
_coordinate_system = CoordinateSystem.AZ_ALT
|
|
58
|
+
|
|
55
59
|
FIELD_OF_VIEW_MAX = 9.0
|
|
56
60
|
|
|
57
61
|
def __init__(
|
|
@@ -69,6 +73,7 @@ class OpticPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
69
73
|
raise_on_below_horizon: bool = True,
|
|
70
74
|
scale: float = 1.0,
|
|
71
75
|
autoscale: bool = False,
|
|
76
|
+
suppress_warnings: bool = True,
|
|
72
77
|
*args,
|
|
73
78
|
**kwargs,
|
|
74
79
|
) -> "OpticPlot":
|
|
@@ -80,6 +85,7 @@ class OpticPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
80
85
|
hide_colliding_labels,
|
|
81
86
|
scale=scale,
|
|
82
87
|
autoscale=autoscale,
|
|
88
|
+
suppress_warnings=suppress_warnings,
|
|
83
89
|
*args,
|
|
84
90
|
**kwargs,
|
|
85
91
|
)
|
|
@@ -242,7 +248,7 @@ class OpticPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
|
|
|
242
248
|
if size_fn is not None:
|
|
243
249
|
|
|
244
250
|
def size_fn_mx(s):
|
|
245
|
-
return size_fn(s) * optic_star_multiplier
|
|
251
|
+
return size_fn(s) * optic_star_multiplier * 0.68
|
|
246
252
|
|
|
247
253
|
super().stars(
|
|
248
254
|
mag=mag,
|
starplot/plotters/__init__.py
CHANGED