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
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import geopandas as gpd
|
|
2
|
+
|
|
3
|
+
from shapely import MultiPolygon
|
|
4
|
+
from shapely import (
|
|
5
|
+
MultiPoint,
|
|
6
|
+
intersection,
|
|
7
|
+
delaunay_triangles,
|
|
8
|
+
distance,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from starplot.data import DataFiles
|
|
12
|
+
from starplot.data.constellations import (
|
|
13
|
+
CONSTELLATIONS_FULL_NAMES,
|
|
14
|
+
CONSTELLATION_HIP_IDS,
|
|
15
|
+
)
|
|
16
|
+
from starplot.styles import PathStyle
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ExperimentalPlotterMixin:
|
|
20
|
+
def _constellation_borders(self):
|
|
21
|
+
from shapely import LineString, MultiLineString
|
|
22
|
+
from shapely.ops import unary_union
|
|
23
|
+
|
|
24
|
+
constellation_borders = gpd.read_file(
|
|
25
|
+
DataFiles.CONSTELLATIONS,
|
|
26
|
+
engine="pyogrio",
|
|
27
|
+
use_arrow=True,
|
|
28
|
+
bbox=self._extent_mask(),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if constellation_borders.empty:
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
geometries = []
|
|
35
|
+
|
|
36
|
+
for i, constellation in constellation_borders.iterrows():
|
|
37
|
+
geometry_types = constellation.geometry.geom_type
|
|
38
|
+
|
|
39
|
+
# equinox = LineString([[0, 90], [0, -90]])
|
|
40
|
+
"""
|
|
41
|
+
Problems:
|
|
42
|
+
- Need to handle multipolygon borders too (SER)
|
|
43
|
+
- Shapely's union doesn't handle geodesy (e.g. TRI + AND)
|
|
44
|
+
- ^^ TRI is plotted with ra < 360, but AND has ra > 360
|
|
45
|
+
- ^^ idea: create union first and then remove duplicate lines?
|
|
46
|
+
|
|
47
|
+
TODO: create new static data file of constellation border lines
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
if "Polygon" in geometry_types and "MultiPolygon" not in geometry_types:
|
|
51
|
+
polygons = [constellation.geometry]
|
|
52
|
+
|
|
53
|
+
elif "MultiPolygon" in geometry_types:
|
|
54
|
+
polygons = constellation.geometry.geoms
|
|
55
|
+
|
|
56
|
+
for p in polygons:
|
|
57
|
+
coords = list(zip(*p.exterior.coords.xy))
|
|
58
|
+
# coords = [(ra * -1, dec) for ra, dec in coords]
|
|
59
|
+
|
|
60
|
+
new_coords = []
|
|
61
|
+
|
|
62
|
+
for i, c in enumerate(coords):
|
|
63
|
+
ra, dec = c
|
|
64
|
+
if i > 0:
|
|
65
|
+
if new_coords[i - 1][0] - ra > 60:
|
|
66
|
+
ra += 360
|
|
67
|
+
|
|
68
|
+
elif ra - new_coords[i - 1][0] > 60:
|
|
69
|
+
new_coords[i - 1][0] += 360
|
|
70
|
+
|
|
71
|
+
new_coords.append([ra, dec])
|
|
72
|
+
|
|
73
|
+
ls = LineString(new_coords)
|
|
74
|
+
geometries.append(ls)
|
|
75
|
+
|
|
76
|
+
mls = MultiLineString(geometries)
|
|
77
|
+
geometries = unary_union(mls)
|
|
78
|
+
|
|
79
|
+
for ls in list(geometries.geoms):
|
|
80
|
+
x, y = ls.xy
|
|
81
|
+
|
|
82
|
+
self.line(zip(x, y), self.style.constellation_borders)
|
|
83
|
+
# x, y = ls.xy
|
|
84
|
+
# newx = [xx * -1 for xx in list(x)]
|
|
85
|
+
# self.ax.plot(
|
|
86
|
+
# # list(x),
|
|
87
|
+
# newx,
|
|
88
|
+
# list(y),
|
|
89
|
+
# # **self._plot_kwargs(),
|
|
90
|
+
# # transform=self._geodetic,
|
|
91
|
+
# transform=self._plate_carree,
|
|
92
|
+
# **style_kwargs,
|
|
93
|
+
# )
|
|
94
|
+
|
|
95
|
+
def _plot_constellation_labels_experimental(
|
|
96
|
+
self,
|
|
97
|
+
style: PathStyle = None,
|
|
98
|
+
labels: dict[str, str] = CONSTELLATIONS_FULL_NAMES,
|
|
99
|
+
):
|
|
100
|
+
def sorter(g):
|
|
101
|
+
# higher score is better
|
|
102
|
+
d = distance(g.centroid, points_line.centroid)
|
|
103
|
+
# d = distance(g.centroid, constellation.boundary.centroid)
|
|
104
|
+
extent = abs(g.bounds[2] - g.bounds[0])
|
|
105
|
+
area = g.area / constellation.boundary.area
|
|
106
|
+
# return ((extent**3)) * area**2
|
|
107
|
+
# return ((extent**2) - (d/2)) * area**2
|
|
108
|
+
# print(str(extent) + " " + str(area) + " " + str(d))
|
|
109
|
+
return d**2 * -1
|
|
110
|
+
return (extent / 2 + area) - (d / 5)
|
|
111
|
+
|
|
112
|
+
for constellation in self.objects.constellations:
|
|
113
|
+
constellation_stars = [
|
|
114
|
+
s
|
|
115
|
+
for s in self.objects.stars
|
|
116
|
+
if s.constellation_id == constellation.iau_id and s.magnitude < 5
|
|
117
|
+
]
|
|
118
|
+
constellation_line_stars = [
|
|
119
|
+
s
|
|
120
|
+
for s in self.objects.stars
|
|
121
|
+
if s.constellation_id == constellation.iau_id
|
|
122
|
+
and s.hip in CONSTELLATION_HIP_IDS[constellation.iau_id]
|
|
123
|
+
]
|
|
124
|
+
points = MultiPoint([(s.ra, s.dec) for s in constellation_stars])
|
|
125
|
+
points_line = MultiPoint([(s.ra, s.dec) for s in constellation_line_stars])
|
|
126
|
+
|
|
127
|
+
triangles = delaunay_triangles(
|
|
128
|
+
geometry=points,
|
|
129
|
+
tolerance=2,
|
|
130
|
+
)
|
|
131
|
+
print(constellation.name + " " + str(len(triangles.geoms)))
|
|
132
|
+
|
|
133
|
+
polygons = []
|
|
134
|
+
for t in triangles.geoms:
|
|
135
|
+
try:
|
|
136
|
+
inter = intersection(t, constellation.boundary)
|
|
137
|
+
except Exception:
|
|
138
|
+
continue
|
|
139
|
+
if (
|
|
140
|
+
inter.geom_type == "Polygon"
|
|
141
|
+
and len(list(zip(*inter.exterior.coords.xy))) > 2
|
|
142
|
+
):
|
|
143
|
+
polygons.append(inter)
|
|
144
|
+
|
|
145
|
+
p_by_area = {pg.area: pg for pg in polygons}
|
|
146
|
+
polygons_sorted = [
|
|
147
|
+
p_by_area[k] for k in sorted(p_by_area.keys(), reverse=True)
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
# sort by combination of horizontal extent and area
|
|
151
|
+
polygons_sorted = sorted(polygons_sorted, key=sorter, reverse=True)
|
|
152
|
+
|
|
153
|
+
if len(polygons_sorted) > 0:
|
|
154
|
+
i = 0
|
|
155
|
+
ra, dec = polygons_sorted[i].centroid.x, polygons_sorted[i].centroid.y
|
|
156
|
+
else:
|
|
157
|
+
ra, dec = constellation.ra, constellation.dec
|
|
158
|
+
|
|
159
|
+
text = labels.get(constellation.iau_id)
|
|
160
|
+
style = style or self.style.constellation.label
|
|
161
|
+
style.anchor_point = "center"
|
|
162
|
+
self.text(
|
|
163
|
+
text,
|
|
164
|
+
ra,
|
|
165
|
+
dec,
|
|
166
|
+
style,
|
|
167
|
+
hide_on_collision=self.hide_colliding_labels,
|
|
168
|
+
area=MultiPolygon(polygons_sorted[:3])
|
|
169
|
+
if len(polygons_sorted)
|
|
170
|
+
else constellation.boundary,
|
|
171
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from shapely.ops import unary_union
|
|
2
|
+
|
|
3
|
+
from starplot.data import db
|
|
4
|
+
from starplot.styles import PolygonStyle
|
|
5
|
+
from starplot.styles.helpers import use_style
|
|
6
|
+
from starplot.geometry import unwrap_polygon_360
|
|
7
|
+
from starplot.profile import profile
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MilkyWayPlotterMixin:
|
|
11
|
+
@profile
|
|
12
|
+
@use_style(PolygonStyle, "milky_way")
|
|
13
|
+
def milky_way(self, style: PolygonStyle = None):
|
|
14
|
+
"""
|
|
15
|
+
Plots the Milky Way
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
style: Styling of the Milky Way. If None, then the plot's style (specified when creating the plot) will be used
|
|
19
|
+
"""
|
|
20
|
+
con = db.connect()
|
|
21
|
+
mw = con.table("milky_way")
|
|
22
|
+
|
|
23
|
+
extent = self._extent_mask()
|
|
24
|
+
result = mw.filter(mw.geometry.intersects(extent)).to_pandas()
|
|
25
|
+
|
|
26
|
+
mw_union = unary_union(
|
|
27
|
+
[unwrap_polygon_360(row.geometry) for row in result.itertuples()]
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if mw_union.geom_type == "MultiPolygon":
|
|
31
|
+
polygons = mw_union.geoms
|
|
32
|
+
else:
|
|
33
|
+
polygons = [mw_union]
|
|
34
|
+
|
|
35
|
+
for p in polygons:
|
|
36
|
+
self.polygon(
|
|
37
|
+
geometry=p,
|
|
38
|
+
style=style,
|
|
39
|
+
)
|
starplot/plotters/stars.py
CHANGED
|
@@ -1,51 +1,26 @@
|
|
|
1
1
|
from typing import Callable, Mapping
|
|
2
|
-
from functools import cache
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import rtree
|
|
4
|
+
import numpy as np
|
|
5
|
+
from skyfield.api import Star as SkyfieldStar, wgs84
|
|
7
6
|
|
|
8
7
|
from starplot import callables
|
|
9
|
-
from starplot.data import
|
|
10
|
-
from starplot.data.stars import StarCatalog
|
|
8
|
+
from starplot.data import stars
|
|
9
|
+
from starplot.data.stars import StarCatalog
|
|
11
10
|
from starplot.models.star import Star, from_tuple
|
|
12
11
|
from starplot.styles import ObjectStyle, use_style
|
|
12
|
+
from starplot.profile import profile
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class StarPlotterMixin:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
stardata = stardata[(stardata["magnitude"] <= limiting_magnitude)]
|
|
25
|
-
|
|
26
|
-
stardata = stardata[
|
|
27
|
-
(stardata["dec_degrees"] < self.dec_max + dec_buffer)
|
|
28
|
-
& (stardata["dec_degrees"] > self.dec_min - dec_buffer)
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
if self.ra_max <= 24 and self.ra_min >= 0:
|
|
32
|
-
stardata = stardata[
|
|
33
|
-
(stardata["ra_hours"] < self.ra_max + ra_buffer)
|
|
34
|
-
& (stardata["ra_hours"] > self.ra_min - ra_buffer)
|
|
35
|
-
]
|
|
36
|
-
elif self.ra_max > 24:
|
|
37
|
-
# handle wrapping
|
|
38
|
-
stardata = stardata[
|
|
39
|
-
(stardata["ra_hours"] > self.ra_min - ra_buffer)
|
|
40
|
-
| (stardata["ra_hours"] < self.ra_max - 24 + ra_buffer)
|
|
41
|
-
]
|
|
42
|
-
elif self.ra_min < 0:
|
|
43
|
-
stardata = stardata[
|
|
44
|
-
(stardata["ra_hours"] > 24 + self.ra_min - ra_buffer)
|
|
45
|
-
| (stardata["ra_hours"] < self.ra_max + ra_buffer)
|
|
46
|
-
]
|
|
47
|
-
|
|
48
|
-
return stardata
|
|
16
|
+
def _load_stars(self, catalog, filters=None):
|
|
17
|
+
extent = self._extent_mask()
|
|
18
|
+
|
|
19
|
+
return stars.load(
|
|
20
|
+
extent=extent,
|
|
21
|
+
catalog=catalog,
|
|
22
|
+
filters=filters,
|
|
23
|
+
)
|
|
49
24
|
|
|
50
25
|
def _scatter_stars(self, ras, decs, sizes, alphas, colors, style=None, **kwargs):
|
|
51
26
|
style = style or self.style.star
|
|
@@ -81,7 +56,7 @@ class StarPlotterMixin:
|
|
|
81
56
|
self,
|
|
82
57
|
star_objects: list[Star],
|
|
83
58
|
star_sizes: list[float],
|
|
84
|
-
|
|
59
|
+
label_row_ids: list,
|
|
85
60
|
style: ObjectStyle,
|
|
86
61
|
labels: Mapping[str, str],
|
|
87
62
|
bayer_labels: bool,
|
|
@@ -93,7 +68,7 @@ class StarPlotterMixin:
|
|
|
93
68
|
|
|
94
69
|
# Plot all star common names first
|
|
95
70
|
for i, s in enumerate(star_objects):
|
|
96
|
-
if
|
|
71
|
+
if s._row_id not in label_row_ids:
|
|
97
72
|
continue
|
|
98
73
|
|
|
99
74
|
if (
|
|
@@ -108,17 +83,21 @@ class StarPlotterMixin:
|
|
|
108
83
|
elif s.tyc:
|
|
109
84
|
self._labeled_stars.append(s.tyc)
|
|
110
85
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
86
|
+
if label_fn is not None:
|
|
87
|
+
label = label_fn(s)
|
|
88
|
+
elif s.hip in labels:
|
|
89
|
+
label = labels.get(s.hip)
|
|
90
|
+
else:
|
|
91
|
+
label = s.name
|
|
92
|
+
|
|
93
|
+
bayer_desig = s.bayer
|
|
94
|
+
flamsteed_num = s.flamsteed
|
|
114
95
|
|
|
115
96
|
if label:
|
|
116
97
|
self.text(
|
|
117
98
|
label,
|
|
118
99
|
s.ra,
|
|
119
100
|
s.dec,
|
|
120
|
-
# style,
|
|
121
|
-
# _offset(style, star_sizes[i]),
|
|
122
101
|
style=style.label.offset_from_marker(
|
|
123
102
|
marker_symbol=style.marker.symbol,
|
|
124
103
|
marker_size=star_sizes[i],
|
|
@@ -129,13 +108,13 @@ class StarPlotterMixin:
|
|
|
129
108
|
)
|
|
130
109
|
|
|
131
110
|
if bayer_labels and bayer_desig:
|
|
132
|
-
_bayer.append((bayer_desig, s.ra, s.dec, star_sizes[i]
|
|
111
|
+
_bayer.append((bayer_desig, s.ra, s.dec, star_sizes[i]))
|
|
133
112
|
|
|
134
|
-
if flamsteed_labels and flamsteed_num:
|
|
135
|
-
_flamsteed.append((flamsteed_num, s.ra, s.dec, star_sizes[i]
|
|
113
|
+
if flamsteed_labels and flamsteed_num and not bayer_desig:
|
|
114
|
+
_flamsteed.append((flamsteed_num, s.ra, s.dec, star_sizes[i]))
|
|
136
115
|
|
|
137
116
|
# Plot bayer/flamsteed
|
|
138
|
-
for bayer_desig, ra, dec, star_size
|
|
117
|
+
for bayer_desig, ra, dec, star_size in _bayer:
|
|
139
118
|
self.text(
|
|
140
119
|
bayer_desig,
|
|
141
120
|
ra,
|
|
@@ -149,9 +128,7 @@ class StarPlotterMixin:
|
|
|
149
128
|
gid="stars-label-bayer",
|
|
150
129
|
)
|
|
151
130
|
|
|
152
|
-
for flamsteed_num, ra, dec, star_size
|
|
153
|
-
if hip in bayer.hip:
|
|
154
|
-
continue
|
|
131
|
+
for flamsteed_num, ra, dec, star_size in _flamsteed:
|
|
155
132
|
self.text(
|
|
156
133
|
flamsteed_num,
|
|
157
134
|
ra,
|
|
@@ -165,27 +142,26 @@ class StarPlotterMixin:
|
|
|
165
142
|
gid="stars-label-flamsteed",
|
|
166
143
|
)
|
|
167
144
|
|
|
168
|
-
def _prepare_star_coords(self, df):
|
|
145
|
+
def _prepare_star_coords(self, df, limit_by_altaz=False):
|
|
169
146
|
df["x"], df["y"] = (
|
|
170
147
|
df["ra"],
|
|
171
148
|
df["dec"],
|
|
172
149
|
)
|
|
173
150
|
return df
|
|
174
151
|
|
|
152
|
+
@profile
|
|
175
153
|
@use_style(ObjectStyle, "star")
|
|
176
154
|
def stars(
|
|
177
155
|
self,
|
|
178
|
-
|
|
179
|
-
|
|
156
|
+
where: list = None,
|
|
157
|
+
where_labels: list = None,
|
|
158
|
+
catalog: StarCatalog = StarCatalog.BIG_SKY_MAG11,
|
|
180
159
|
style: ObjectStyle = None,
|
|
181
|
-
rasterize: bool = False,
|
|
182
160
|
size_fn: Callable[[Star], float] = callables.size_by_magnitude,
|
|
183
|
-
alpha_fn: Callable[[Star], float] =
|
|
161
|
+
alpha_fn: Callable[[Star], float] = None,
|
|
184
162
|
color_fn: Callable[[Star], str] = None,
|
|
185
163
|
label_fn: Callable[[Star], str] = None,
|
|
186
|
-
|
|
187
|
-
where_labels: list = None,
|
|
188
|
-
labels: Mapping[int, str] = STAR_NAMES,
|
|
164
|
+
labels: Mapping[int, str] = None,
|
|
189
165
|
legend_label: str = "Star",
|
|
190
166
|
bayer_labels: bool = False,
|
|
191
167
|
flamsteed_labels: bool = False,
|
|
@@ -195,23 +171,26 @@ class StarPlotterMixin:
|
|
|
195
171
|
"""
|
|
196
172
|
Plots stars
|
|
197
173
|
|
|
174
|
+
Labels for stars are determined in this order:
|
|
175
|
+
|
|
176
|
+
1. Return value from `label_fn`
|
|
177
|
+
2. Value for star's HIP id in `labels`
|
|
178
|
+
3. IAU-designated name, as listed in the [data reference](/data/star-designations/)
|
|
179
|
+
|
|
198
180
|
Args:
|
|
199
|
-
|
|
200
|
-
|
|
181
|
+
where: A list of expressions that determine which stars to plot. See [Selecting Objects](/reference-selecting-objects/) for details.
|
|
182
|
+
where_labels: A list of expressions that determine which stars are labeled on the plot (this includes all labels: name, Bayer, and Flamsteed). If you want to hide **all** labels, then set this arg to `[False]`. See [Selecting Objects](/reference-selecting-objects/) for details.
|
|
183
|
+
catalog: The catalog of stars to use: "big-sky-mag11", or "big-sky" -- see [`StarCatalog`](/reference-data/#starplot.data.stars.StarCatalog) for details
|
|
201
184
|
style: If `None`, then the plot's style for stars will be used
|
|
202
|
-
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
|
|
203
185
|
size_fn: Callable for calculating the marker size of each star. If `None`, then the marker style's size will be used.
|
|
204
186
|
alpha_fn: Callable for calculating the alpha value (aka "opacity") of each star. If `None`, then the marker style's alpha will be used.
|
|
205
187
|
color_fn: Callable for calculating the color of each star. If `None`, then the marker style's color will be used.
|
|
206
188
|
label_fn: Callable for determining the label of each star. If `None`, then the names in the `labels` kwarg will be used.
|
|
207
|
-
|
|
208
|
-
where_labels: A list of expressions that determine which stars are labeled on the plot. See [Selecting Objects](/reference-selecting-objects/) for details.
|
|
209
|
-
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`.
|
|
189
|
+
labels: A dictionary that maps a star's HIP id to the label that'll be plotted for that star. If `None`, then the star's IAU-designated name will be used.
|
|
210
190
|
legend_label: Label for stars in the legend. If `None`, then they will not be in the legend.
|
|
211
191
|
bayer_labels: If True, then Bayer labels for stars will be plotted.
|
|
212
192
|
flamsteed_labels: If True, then Flamsteed number labels for stars will be plotted.
|
|
213
193
|
"""
|
|
214
|
-
self.logger.debug("Plotting stars...")
|
|
215
194
|
|
|
216
195
|
# fallback to style if callables are None
|
|
217
196
|
color_hex = (
|
|
@@ -222,62 +201,86 @@ class StarPlotterMixin:
|
|
|
222
201
|
color_fn = color_fn or (lambda d: color_hex)
|
|
223
202
|
|
|
224
203
|
where = where or []
|
|
204
|
+
where_labels = where_labels or []
|
|
205
|
+
stars_to_index = []
|
|
206
|
+
labels = labels or {}
|
|
207
|
+
|
|
208
|
+
star_results = self._load_stars(catalog, filters=where)
|
|
225
209
|
|
|
226
|
-
|
|
227
|
-
|
|
210
|
+
star_results_labeled = star_results
|
|
211
|
+
for f in where_labels:
|
|
212
|
+
star_results_labeled = star_results_labeled.filter(f)
|
|
228
213
|
|
|
229
|
-
|
|
230
|
-
|
|
214
|
+
label_row_ids = star_results_labeled.to_pandas()["rowid"].tolist()
|
|
215
|
+
|
|
216
|
+
stars_df = star_results.to_pandas()
|
|
217
|
+
|
|
218
|
+
if getattr(self, "projection", None) == "zenith":
|
|
219
|
+
# filter stars for zenith plots to only include those above horizon
|
|
220
|
+
self.location = self.earth + wgs84.latlon(self.lat, self.lon)
|
|
221
|
+
stars_apparent = (
|
|
222
|
+
self.location.at(self.timescale)
|
|
223
|
+
.observe(SkyfieldStar.from_dataframe(stars_df))
|
|
224
|
+
.apparent()
|
|
225
|
+
)
|
|
226
|
+
# we only need altitude
|
|
227
|
+
stars_alt, _, _ = stars_apparent.altaz()
|
|
228
|
+
stars_df["alt"] = stars_alt.degrees
|
|
229
|
+
stars_df = stars_df[stars_df["alt"] > 0]
|
|
231
230
|
else:
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
stars_ra.hours * 15,
|
|
240
|
-
stars_dec.degrees,
|
|
241
|
-
)
|
|
242
|
-
self._prepare_star_coords(nearby_stars_df)
|
|
231
|
+
nearby_stars = SkyfieldStar.from_dataframe(stars_df)
|
|
232
|
+
astrometric = self.earth.at(self.timescale).observe(nearby_stars)
|
|
233
|
+
stars_ra, stars_dec, _ = astrometric.radec()
|
|
234
|
+
stars_df["ra"], stars_df["dec"] = (
|
|
235
|
+
stars_ra.hours * 15,
|
|
236
|
+
stars_dec.degrees,
|
|
237
|
+
)
|
|
243
238
|
|
|
239
|
+
stars_df = self._prepare_star_coords(stars_df)
|
|
244
240
|
starz = []
|
|
241
|
+
rtree_id = 1
|
|
245
242
|
|
|
246
|
-
for star in
|
|
247
|
-
|
|
243
|
+
for star in stars_df.itertuples():
|
|
244
|
+
data_xy = self._proj.transform_point(star.x, star.y, self._crs)
|
|
245
|
+
display_x, display_y = self.ax.transData.transform(data_xy)
|
|
248
246
|
|
|
249
|
-
if
|
|
247
|
+
if (
|
|
248
|
+
display_x < 0
|
|
249
|
+
or display_y < 0
|
|
250
|
+
or np.isnan(display_x)
|
|
251
|
+
or np.isnan(display_y)
|
|
252
|
+
or self._is_clipped([(display_x, display_y)])
|
|
253
|
+
):
|
|
250
254
|
continue
|
|
251
255
|
|
|
256
|
+
obj = from_tuple(star)
|
|
252
257
|
size = size_fn(obj) * self.scale**2
|
|
253
258
|
alpha = alpha_fn(obj)
|
|
254
259
|
color = color_fn(obj) or style.marker.color.as_hex()
|
|
255
260
|
|
|
256
|
-
|
|
261
|
+
if obj.magnitude < 5:
|
|
262
|
+
rtree_id += 1
|
|
263
|
+
# radius = ((size**0.5 / 2) / self.scale) #/ 3.14
|
|
264
|
+
radius = size**0.5 / 5
|
|
265
|
+
bbox = np.array(
|
|
266
|
+
(
|
|
267
|
+
display_x - radius,
|
|
268
|
+
display_y - radius,
|
|
269
|
+
display_x + radius,
|
|
270
|
+
display_y + radius,
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
if self._stars_rtree.get_size() > 0:
|
|
274
|
+
self._stars_rtree.insert(
|
|
275
|
+
0,
|
|
276
|
+
bbox,
|
|
277
|
+
None,
|
|
278
|
+
)
|
|
279
|
+
else:
|
|
280
|
+
# if the index has no stars yet, then wait until end to load for better performance
|
|
281
|
+
stars_to_index.append((rtree_id, bbox, None))
|
|
257
282
|
|
|
258
|
-
|
|
259
|
-
# if getattr(self, "_geodetic", None):
|
|
260
|
-
# # TODO : clean up!
|
|
261
|
-
# x, y = self._proj.transform_point(
|
|
262
|
-
# star.ra * -1, star.dec, self._geodetic
|
|
263
|
-
# )
|
|
264
|
-
# x0, y0 = self.ax.transData.transform((x, y))
|
|
265
|
-
|
|
266
|
-
# if (
|
|
267
|
-
# x0 < 0
|
|
268
|
-
# or y0 < 0
|
|
269
|
-
# or obj.magnitude > 5
|
|
270
|
-
# or np.isnan(x0)
|
|
271
|
-
# or np.isnan(y0)
|
|
272
|
-
# ):
|
|
273
|
-
# continue
|
|
274
|
-
# radius = 1 + (5 - obj.magnitude)
|
|
275
|
-
# # radius = max(((size**0.5 / 2) / self.scale)/1.44 - 6, 0) #size / self.scale**2 / 200
|
|
276
|
-
# self._stars_rtree.insert(
|
|
277
|
-
# 0,
|
|
278
|
-
# np.array((x0 - radius, y0 - radius, x0 + radius, y0 + radius)),
|
|
279
|
-
# obj=star.x,
|
|
280
|
-
# )
|
|
283
|
+
starz.append((star.x, star.y, size, alpha, color, obj))
|
|
281
284
|
|
|
282
285
|
starz.sort(key=lambda s: s[2], reverse=True) # sort by descending size
|
|
283
286
|
|
|
@@ -303,19 +306,20 @@ class StarPlotterMixin:
|
|
|
303
306
|
edgecolors=style.marker.edge_color.as_hex()
|
|
304
307
|
if style.marker.edge_color
|
|
305
308
|
else "none",
|
|
306
|
-
rasterized=rasterize,
|
|
307
309
|
)
|
|
308
310
|
|
|
309
311
|
self._add_legend_handle_marker(legend_label, style.marker)
|
|
310
312
|
|
|
311
|
-
if
|
|
312
|
-
self.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
313
|
+
if stars_to_index:
|
|
314
|
+
self._stars_rtree = rtree.index.Index(stars_to_index)
|
|
315
|
+
|
|
316
|
+
self._star_labels(
|
|
317
|
+
star_objects,
|
|
318
|
+
sizes,
|
|
319
|
+
label_row_ids,
|
|
320
|
+
style,
|
|
321
|
+
labels,
|
|
322
|
+
bayer_labels,
|
|
323
|
+
flamsteed_labels,
|
|
324
|
+
label_fn,
|
|
325
|
+
)
|
starplot/profile.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def profile(func):
|
|
5
|
+
def wrapper(*args, **kwargs):
|
|
6
|
+
start = time.time()
|
|
7
|
+
|
|
8
|
+
result = func(*args, **kwargs)
|
|
9
|
+
|
|
10
|
+
duration = round(time.time() - start, 4)
|
|
11
|
+
|
|
12
|
+
args[0].logger.debug(f"{func.__name__} = {str(duration)} sec")
|
|
13
|
+
|
|
14
|
+
return result
|
|
15
|
+
|
|
16
|
+
return wrapper
|
starplot/settings.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def env(name, default):
|
|
7
|
+
return os.environ.get(name) or default
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
STARPLOT_PATH = Path(__file__).resolve().parent
|
|
11
|
+
"""Path of starplot source"""
|
|
12
|
+
|
|
13
|
+
DATA_PATH = STARPLOT_PATH / "data" / "library"
|
|
14
|
+
"""Path of starplot data"""
|
|
15
|
+
|
|
16
|
+
DOWNLOAD_PATH = Path(env("STARPLOT_DOWNLOAD_PATH", str(DATA_PATH / "downloads")))
|
|
17
|
+
"""Path for downloaded data"""
|
|
18
|
+
|
|
19
|
+
DUCKDB_EXTENSION_PATH = Path(
|
|
20
|
+
env("STARPLOT_DUCKDB_EXTENSIONS_PATH", str(DATA_PATH / "duckdb-extensions"))
|
|
21
|
+
)
|
|
22
|
+
"""Path for DuckDB extensions"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
RAW_DATA_PATH = STARPLOT_PATH.parent.parent / "raw"
|
|
26
|
+
BUILD_PATH = STARPLOT_PATH.parent.parent / "build"
|