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.
Files changed (74) hide show
  1. starplot/__init__.py +5 -2
  2. starplot/base.py +311 -197
  3. starplot/cli.py +33 -0
  4. starplot/coordinates.py +6 -0
  5. starplot/data/__init__.py +6 -24
  6. starplot/data/bigsky.py +58 -40
  7. starplot/data/constellation_lines.py +827 -0
  8. starplot/data/constellation_stars.py +1501 -0
  9. starplot/data/constellations.py +600 -27
  10. starplot/data/db.py +17 -0
  11. starplot/data/dsos.py +24 -141
  12. starplot/data/stars.py +45 -24
  13. starplot/geod.py +0 -6
  14. starplot/geometry.py +181 -0
  15. starplot/horizon.py +302 -231
  16. starplot/map.py +100 -463
  17. starplot/mixins.py +75 -14
  18. starplot/models/__init__.py +1 -1
  19. starplot/models/base.py +18 -129
  20. starplot/models/constellation.py +55 -32
  21. starplot/models/dso.py +132 -67
  22. starplot/models/moon.py +57 -78
  23. starplot/models/planet.py +44 -69
  24. starplot/models/star.py +91 -60
  25. starplot/models/sun.py +32 -53
  26. starplot/optic.py +21 -18
  27. starplot/plotters/__init__.py +2 -0
  28. starplot/plotters/constellations.py +342 -0
  29. starplot/plotters/dsos.py +49 -68
  30. starplot/plotters/experimental.py +171 -0
  31. starplot/plotters/milkyway.py +39 -0
  32. starplot/plotters/stars.py +126 -122
  33. starplot/profile.py +16 -0
  34. starplot/settings.py +26 -0
  35. starplot/styles/__init__.py +2 -0
  36. starplot/styles/base.py +56 -34
  37. starplot/styles/ext/antique.yml +11 -9
  38. starplot/styles/ext/blue_dark.yml +8 -10
  39. starplot/styles/ext/blue_gold.yml +135 -0
  40. starplot/styles/ext/blue_light.yml +14 -12
  41. starplot/styles/ext/blue_medium.yml +23 -20
  42. starplot/styles/ext/cb_wong.yml +9 -7
  43. starplot/styles/ext/grayscale.yml +4 -3
  44. starplot/styles/ext/grayscale_dark.yml +7 -5
  45. starplot/styles/ext/map.yml +9 -6
  46. starplot/styles/ext/nord.yml +7 -7
  47. starplot/styles/ext/optic.yml +1 -1
  48. starplot/styles/extensions.py +1 -0
  49. starplot/utils.py +19 -0
  50. starplot/warnings.py +21 -0
  51. {starplot-0.13.0.dist-info → starplot-0.15.0.dist-info}/METADATA +19 -18
  52. starplot-0.15.0.dist-info/RECORD +97 -0
  53. starplot-0.15.0.dist-info/entry_points.txt +3 -0
  54. starplot/data/bayer.py +0 -3499
  55. starplot/data/flamsteed.py +0 -2682
  56. starplot/data/library/constellation_borders_inv.gpkg +0 -0
  57. starplot/data/library/constellation_lines_hips.json +0 -709
  58. starplot/data/library/constellation_lines_inv.gpkg +0 -0
  59. starplot/data/library/constellations.gpkg +0 -0
  60. starplot/data/library/constellations_hip.fab +0 -88
  61. starplot/data/library/milkyway.gpkg +0 -0
  62. starplot/data/library/milkyway_inv.gpkg +0 -0
  63. starplot/data/library/ongc.gpkg.zip +0 -0
  64. starplot/data/library/stars.bigsky.mag11.parquet +0 -0
  65. starplot/data/library/stars.hipparcos.parquet +0 -0
  66. starplot/data/messier.py +0 -111
  67. starplot/data/prep/__init__.py +0 -0
  68. starplot/data/prep/constellations.py +0 -108
  69. starplot/data/prep/dsos.py +0 -299
  70. starplot/data/prep/utils.py +0 -16
  71. starplot/models/geometry.py +0 -44
  72. starplot-0.13.0.dist-info/RECORD +0 -101
  73. {starplot-0.13.0.dist-info → starplot-0.15.0.dist-info}/LICENSE +0 -0
  74. {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
+ )
@@ -1,51 +1,26 @@
1
1
  from typing import Callable, Mapping
2
- from functools import cache
3
2
 
4
- from skyfield.api import Star as SkyfieldStar
5
-
6
- # import numpy as np
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 bayer, stars, flamsteed
10
- from starplot.data.stars import StarCatalog, STAR_NAMES
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
- @cache
17
- def _load_stars(self, catalog, limiting_magnitude=None):
18
- stardata = stars.load(catalog)
19
-
20
- ra_buffer = (self.ra_max - self.ra_min) / 4
21
- dec_buffer = (self.dec_max - self.dec_min) / 4
22
-
23
- if limiting_magnitude is not None:
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
- where_labels: list,
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 where_labels and not all([e.evaluate(s) for e in where_labels]):
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
- label = labels.get(s.hip) if label_fn is None else label_fn(s)
112
- bayer_desig = bayer.hip.get(s.hip)
113
- flamsteed_num = flamsteed.hip.get(s.hip)
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], s.hip))
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], s.hip))
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, _ in _bayer:
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, hip in _flamsteed:
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
- mag: float = 6.0,
179
- catalog: StarCatalog = StarCatalog.HIPPARCOS,
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] = callables.alpha_by_magnitude,
161
+ alpha_fn: Callable[[Star], float] = None,
184
162
  color_fn: Callable[[Star], str] = None,
185
163
  label_fn: Callable[[Star], str] = None,
186
- where: list = None,
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
- mag: Limiting magnitude of stars to plot. For more control of what stars to plot, use the `where` kwarg. **Note:** if you pass `mag` and `where` then `mag` will be ignored
200
- catalog: The catalog of stars to use: "hipparcos", "big-sky-mag11", or "big-sky" -- see [`StarCatalog`](/reference-data/#starplot.data.stars.StarCatalog) for details
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
- where: A list of expressions that determine which stars to plot. See [Selecting Objects](/reference-selecting-objects/) for details.
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
- if where:
227
- mag = None
210
+ star_results_labeled = star_results
211
+ for f in where_labels:
212
+ star_results_labeled = star_results_labeled.filter(f)
228
213
 
229
- if labels is None:
230
- labels = {}
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
- labels = {**STAR_NAMES, **labels}
233
-
234
- nearby_stars_df = self._load_stars(catalog, mag)
235
- nearby_stars = SkyfieldStar.from_dataframe(nearby_stars_df)
236
- astrometric = self.ephemeris["earth"].at(self.timescale).observe(nearby_stars)
237
- stars_ra, stars_dec, _ = astrometric.radec()
238
- nearby_stars_df["ra"], nearby_stars_df["dec"] = (
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 nearby_stars_df.itertuples():
247
- obj = from_tuple(star, catalog)
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 not all([e.evaluate(obj) for e in where]):
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
- starz.append((star.x, star.y, size, alpha, color, obj))
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
- # Experimental code for keeping spatial index of plotted stars (for better label placement)
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 labels:
312
- self._star_labels(
313
- star_objects,
314
- sizes,
315
- where_labels,
316
- style,
317
- labels,
318
- bayer_labels,
319
- flamsteed_labels,
320
- label_fn,
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"
@@ -2,3 +2,5 @@ from .base import * # noqa: F401,F403
2
2
  from .helpers import * # noqa: F401,F403
3
3
 
4
4
  # from .extensions import * # noqa: F401
5
+
6
+ import starplot.styles.extensions as style_extensions # noqa: F401