starplot 0.10.1__py2.py3-none-any.whl → 0.11.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 CHANGED
@@ -1,9 +1,17 @@
1
1
  """Star charts and maps"""
2
2
 
3
- __version__ = "0.10.1"
3
+ __version__ = "0.11.0"
4
4
 
5
5
  from .base import BasePlot # noqa: F401
6
6
  from .map import MapPlot, Projection # noqa: F401
7
- from .models import DSO, Star, Planet, Moon, Sun, ObjectList # noqa: F401
7
+ from .models import (
8
+ DSO, # noqa: F401
9
+ Star, # noqa: F401
10
+ Constellation, # noqa: F401
11
+ Planet, # noqa: F401
12
+ Moon, # noqa: F401
13
+ Sun, # noqa: F401
14
+ ObjectList, # noqa: F401
15
+ )
8
16
  from .optic import OpticPlot # noqa: F401
9
17
  from .styles import * # noqa: F401 F403
starplot/base.py CHANGED
@@ -22,6 +22,7 @@ from starplot.styles import (
22
22
  LabelStyle,
23
23
  LegendLocationEnum,
24
24
  LegendStyle,
25
+ MarkerSymbolEnum,
25
26
  PathStyle,
26
27
  PolygonStyle,
27
28
  )
@@ -143,12 +144,20 @@ class BasePlot(ABC):
143
144
  label=label,
144
145
  )
145
146
 
146
- def _text(self, ra: float, dec: float, text: str, *args, **kwargs) -> None:
147
+ def _text(
148
+ self,
149
+ ra: float,
150
+ dec: float,
151
+ text: str,
152
+ hide_on_collision: bool = True,
153
+ *args,
154
+ **kwargs,
155
+ ) -> None:
147
156
  if not text:
148
157
  return
149
158
 
150
159
  x, y = self._prepare_coords(ra, dec)
151
- kwargs["path_effects"] = kwargs.get("path_effects") or [self.text_border]
160
+ kwargs["path_effects"] = kwargs.get("path_effects", [self.text_border])
152
161
  label = self.ax.annotate(
153
162
  text,
154
163
  (x, y),
@@ -156,13 +165,24 @@ class BasePlot(ABC):
156
165
  **kwargs,
157
166
  **self._plot_kwargs(),
158
167
  )
168
+ if kwargs.get("clip_on") is False:
169
+ return
170
+
159
171
  label.set_clip_on(True)
160
172
  label.set_clip_path(self._background_clip_path)
161
173
 
162
- self._maybe_remove_label(label)
174
+ if hide_on_collision:
175
+ self._maybe_remove_label(label)
163
176
 
164
177
  @use_style(LabelStyle)
165
- def text(self, text: str, ra: float, dec: float, style: LabelStyle = None):
178
+ def text(
179
+ self,
180
+ text: str,
181
+ ra: float,
182
+ dec: float,
183
+ style: LabelStyle = None,
184
+ hide_on_collision: bool = True,
185
+ ):
166
186
  """
167
187
  Plots text
168
188
 
@@ -171,6 +191,7 @@ class BasePlot(ABC):
171
191
  ra: Right ascension of text (0...24)
172
192
  dec: Declination of text (-90...90)
173
193
  style: Styling of the text
194
+ hide_on_collision: If True, then the text will not be plotted if it collides with another label
174
195
  """
175
196
  style = style or LabelStyle()
176
197
  self._text(
@@ -178,6 +199,7 @@ class BasePlot(ABC):
178
199
  dec,
179
200
  text,
180
201
  **style.matplot_kwargs(self._size_multiplier),
202
+ hide_on_collision=hide_on_collision,
181
203
  xytext=(style.offset_x, style.offset_y),
182
204
  textcoords="offset pixels",
183
205
  )
@@ -213,30 +235,33 @@ class BasePlot(ABC):
213
235
  Args:
214
236
  style: Styling of the legend. If None, then the plot's style (specified when creating the plot) will be used
215
237
  """
216
- if not self._legend_handles:
217
- return
218
-
219
238
  if self._legend is not None:
220
239
  self._legend.remove()
221
240
 
241
+ if not self._legend_handles:
242
+ return
243
+
222
244
  bbox_kwargs = {}
223
245
 
224
246
  if style.location == LegendLocationEnum.OUTSIDE_BOTTOM:
225
- # to plot legends outside the map area, you have to target the figure
226
- target = self.fig
247
+ style.location = "lower center"
248
+ offset_y = -0.08
249
+ if getattr(self, "_axis_labels", False):
250
+ offset_y -= 0.05
227
251
  bbox_kwargs = dict(
228
- bbox_to_anchor=(0.5, 0.13), bbox_transform=self.fig.transFigure
252
+ bbox_to_anchor=(0.5, offset_y),
229
253
  )
230
254
 
231
255
  elif style.location == LegendLocationEnum.OUTSIDE_TOP:
232
- target = self.fig
256
+ style.location = "upper center"
257
+ offset_y = 1.08
258
+ if getattr(self, "_axis_labels", False):
259
+ offset_y += 0.05
233
260
  bbox_kwargs = dict(
234
- bbox_to_anchor=(0.5, 0.87), bbox_transform=self.fig.transFigure
261
+ bbox_to_anchor=(0.5, offset_y),
235
262
  )
236
- else:
237
- target = self.ax
238
263
 
239
- self._legend = target.legend(
264
+ self._legend = self.ax.legend(
240
265
  handles=self._legend_handles.values(),
241
266
  **style.matplot_kwargs(size_multiplier=self._size_multiplier),
242
267
  **bbox_kwargs,
@@ -267,7 +292,7 @@ class BasePlot(ABC):
267
292
 
268
293
  Args:
269
294
  filename: Filename of exported file
270
- format: Format of file: "png" or "svg"
295
+ format: Format of file (options are "png", "jpeg", or "svg")
271
296
  padding: Padding (in inches) around the image
272
297
  **kwargs: Any keyword arguments to pass through to matplotlib's `savefig` method
273
298
 
@@ -388,7 +413,7 @@ class BasePlot(ABC):
388
413
 
389
414
  Args:
390
415
  style: Styling of the Sun. If None, then the plot's style (specified when creating the plot) will be used
391
- true_size: If True, then the Sun's true apparent size in the sky will be plotted. If False, then the style's marker size will be used.
416
+ true_size: If True, then the Sun's true apparent size in the sky will be plotted as a circle (the marker style's symbol will be ignored). If False, then the style's marker size will be used.
392
417
  label: How the Sun will be labeled on the plot and legend
393
418
  """
394
419
  s = models.Sun.get(
@@ -413,6 +438,7 @@ class BasePlot(ABC):
413
438
  style=polygon_style,
414
439
  )
415
440
 
441
+ style.marker.symbol = MarkerSymbolEnum.CIRCLE
416
442
  self._add_legend_handle_marker(legend_label, style.marker)
417
443
 
418
444
  if label:
@@ -584,7 +610,7 @@ class BasePlot(ABC):
584
610
 
585
611
  Args:
586
612
  style: Styling of the Moon. If None, then the plot's style (specified when creating the plot) will be used
587
- true_size: If True, then the Moon's true apparent size in the sky will be plotted. If False, then the style's marker size will be used.
613
+ true_size: If True, then the Moon's true apparent size in the sky will be plotted as a circle (the marker style's symbol will be ignored). If False, then the style's marker size will be used.
588
614
  show_phase: If True, and if `true_size = True`, then the approximate phase of the moon will be illustrated. The dark side of the moon will be colored with the marker's `edge_color`.
589
615
  label: How the Moon will be labeled on the plot and legend
590
616
  """
@@ -620,6 +646,7 @@ class BasePlot(ABC):
620
646
  style=polygon_style,
621
647
  )
622
648
 
649
+ style.marker.symbol = MarkerSymbolEnum.CIRCLE
623
650
  self._add_legend_handle_marker(legend_label, style.marker)
624
651
 
625
652
  if label:
starplot/data/__init__.py CHANGED
@@ -1,3 +1,5 @@
1
+ import os
2
+
1
3
  from enum import Enum
2
4
  from pathlib import Path
3
5
 
@@ -9,12 +11,21 @@ DATA_PATH = HERE / "library"
9
11
  load = Loader(DATA_PATH)
10
12
 
11
13
 
14
+ def env(name, default):
15
+ return os.environ.get(name) or default
16
+
17
+
12
18
  class DataFiles(str, Enum):
19
+ # Built-In Files
13
20
  CONSTELLATION_LINES = DATA_PATH / "constellation_lines_inv.gpkg"
14
21
  CONSTELLATION_LINES_HIP = DATA_PATH / "constellation_lines_hips.json"
15
22
  CONSTELLATION_BORDERS = DATA_PATH / "constellation_borders_inv.gpkg"
16
23
  MILKY_WAY = DATA_PATH / "milkyway.gpkg"
17
24
  HIPPARCOS = DATA_PATH / "stars.hipparcos.parquet"
18
- TYCHO_1 = DATA_PATH / "stars.tycho-1.gz.parquet"
25
+ BIG_SKY_MAG11 = DATA_PATH / "stars.bigsky.mag11.parquet"
19
26
  ONGC = DATA_PATH / "ongc.gpkg.zip"
20
27
  CONSTELLATIONS = DATA_PATH / "constellations.gpkg"
28
+
29
+ # Downloaded Files
30
+ _DOWNLOAD_PATH = Path(env("STARPLOT_DOWNLOAD_PATH", str(DATA_PATH)))
31
+ BIG_SKY = _DOWNLOAD_PATH / "stars.bigsky.parquet"
@@ -0,0 +1,97 @@
1
+ import os
2
+
3
+ import pandas as pd
4
+
5
+ from starplot.data import DATA_PATH, DataFiles, utils
6
+
7
+
8
+ BIG_SKY_VERSION = "0.1.0"
9
+
10
+ BIG_SKY_FILENAME = "bigsky.stars.csv.gz"
11
+
12
+ BIG_SKY_URL = f"https://github.com/steveberardi/bigsky/releases/download/v{BIG_SKY_VERSION}/{BIG_SKY_FILENAME}"
13
+
14
+ DOWNLOADED_PATH = DATA_PATH / BIG_SKY_FILENAME
15
+
16
+ DIGITS = 4
17
+
18
+ BIG_SKY_ASSETS = {
19
+ DataFiles.BIG_SKY: "bigsky.stars.csv.gz",
20
+ DataFiles.BIG_SKY_MAG11: "bigsky.stars.mag11.csv.gz",
21
+ }
22
+
23
+
24
+ def url(filename: str, version: str):
25
+ return f"https://github.com/steveberardi/bigsky/releases/download/v{version}/{filename}"
26
+
27
+
28
+ def download(
29
+ filename: str = BIG_SKY_FILENAME,
30
+ version: str = BIG_SKY_VERSION,
31
+ download_path: str = None,
32
+ digits: int = 4,
33
+ ):
34
+ download_path = download_path or str(DATA_PATH / filename)
35
+ utils.download(
36
+ url(filename, version),
37
+ download_path,
38
+ "Big Sky Star Catalog",
39
+ )
40
+ to_parquet(
41
+ download_path,
42
+ DataFiles.BIG_SKY,
43
+ digits,
44
+ )
45
+
46
+
47
+ def to_parquet(source_path: str, destination_path: str, digits: int = DIGITS):
48
+ print("Preparing Big Sky Catalog for Starplot...")
49
+
50
+ df = pd.read_csv(
51
+ source_path,
52
+ header=0,
53
+ names=[
54
+ "tyc_id",
55
+ "hip_id",
56
+ "ccdm",
57
+ "magnitude",
58
+ "bv",
59
+ "ra_degrees_j2000",
60
+ "dec_degrees_j2000",
61
+ "ra_mas_per_year",
62
+ "dec_mas_per_year",
63
+ "parallax_mas",
64
+ ],
65
+ compression="gzip",
66
+ )
67
+
68
+ df["ra_hours"] = df.apply(
69
+ lambda row: round(row.ra_degrees_j2000 / 15, digits), axis=1
70
+ )
71
+
72
+ df = df.assign(epoch_year=2000)
73
+
74
+ df = df.rename(
75
+ columns={
76
+ "hip_id": "hip",
77
+ "ra_degrees_j2000": "ra_degrees",
78
+ "dec_degrees_j2000": "dec_degrees",
79
+ }
80
+ )
81
+
82
+ df.to_parquet(destination_path, compression="gzip")
83
+
84
+ print(f"Done! {destination_path.value}")
85
+
86
+
87
+ def load(path):
88
+ if not exists(path):
89
+ download(filename=BIG_SKY_ASSETS.get(path))
90
+
91
+ df = pd.read_parquet(path)
92
+
93
+ return df.set_index("tyc_id")
94
+
95
+
96
+ def exists(path) -> bool:
97
+ return os.path.isfile(path)
starplot/data/stars.py CHANGED
@@ -2,7 +2,7 @@ from enum import Enum
2
2
 
3
3
  from pandas import read_parquet
4
4
 
5
- from starplot.data import DataFiles
5
+ from starplot.data import bigsky, DataFiles
6
6
 
7
7
  STAR_NAMES = {
8
8
  7588: "Achernar",
@@ -117,27 +117,41 @@ class StarCatalog(str, Enum):
117
117
  HIPPARCOS = "hipparcos"
118
118
  """Hipparcos Catalog = 118,218 stars"""
119
119
 
120
- TYCHO_1 = "tycho-1"
121
- """Tycho-1 Catalog = 1,055,115 stars"""
120
+ BIG_SKY_MAG11 = "big-sky-mag11"
121
+ """
122
+ [Big Sky Catalog](https://github.com/steveberardi/bigsky) ~ 900k stars up to magnitude 11
123
+
124
+ This is an _abridged_ version of the Big Sky Catalog, including stars up to a limiting magnitude of 11 (total = 981,852).
122
125
 
126
+ This catalog is included with Starplot, so does not require downloading any files.
127
+ """
123
128
 
124
- def load_hipparcos():
125
- return read_parquet(DataFiles.HIPPARCOS)
129
+ BIG_SKY = "big-sky"
130
+ """
131
+ [Big Sky Catalog](https://github.com/steveberardi/bigsky) ~ 2.5M stars
126
132
 
133
+ This is the full version of the Big Sky Catalog, which includes 2,557,499 stars from Hipparcos, Tycho-1, and Tycho-2.
127
134
 
128
- def load_tycho1():
129
- df = read_parquet(DataFiles.TYCHO_1)
130
- df = df.assign(
131
- ra_degrees=df["ra_hours"] * 15.0,
132
- epoch_year=1991.25,
133
- )
134
- return df.set_index("hip")
135
+ This catalog is very large (50+ MB), so it's not built-in to Starplot. When you plot stars and specify this catalog, the catalog
136
+ will be downloaded from the [Big Sky GitHub repository](https://github.com/steveberardi/bigsky) and saved to Starplot's data library
137
+ directory. You can override this download path with the environment variable `STARPLOT_DOWNLOAD_PATH`
138
+
139
+ """
140
+
141
+
142
+ def load_bigsky():
143
+ if not bigsky.exists():
144
+ bigsky.download()
145
+
146
+ return bigsky.load(DataFiles.BIG_SKY)
135
147
 
136
148
 
137
149
  def load(catalog: StarCatalog = StarCatalog.HIPPARCOS):
138
- if catalog == StarCatalog.TYCHO_1:
139
- return load_tycho1()
140
- elif catalog == StarCatalog.HIPPARCOS:
141
- return load_hipparcos()
150
+ if catalog == StarCatalog.HIPPARCOS:
151
+ return read_parquet(DataFiles.HIPPARCOS)
152
+ elif catalog == StarCatalog.BIG_SKY_MAG11:
153
+ return bigsky.load(DataFiles.BIG_SKY_MAG11)
154
+ elif catalog == StarCatalog.BIG_SKY:
155
+ return bigsky.load(DataFiles.BIG_SKY)
142
156
  else:
143
157
  raise ValueError("Unrecognized star catalog.")
starplot/data/utils.py ADDED
@@ -0,0 +1,27 @@
1
+ import sys
2
+
3
+ import requests
4
+
5
+
6
+ def download(url, download_path, description=""):
7
+ with open(download_path, "wb") as f:
8
+ print(f"Downloading {description}...")
9
+
10
+ response = requests.get(url, stream=True)
11
+ total_size = response.headers.get("content-length")
12
+
13
+ if total_size is None:
14
+ f.write(response.content)
15
+ return
16
+
17
+ bytes_written = 0
18
+ total_size = int(total_size)
19
+ for chunk in response.iter_content(chunk_size=4096):
20
+ bytes_written += len(chunk)
21
+ f.write(chunk)
22
+
23
+ progress = int(25 * bytes_written / total_size)
24
+ sys.stdout.write("\r[%s%s]" % ("=" * progress, " " * (25 - progress)))
25
+ sys.stdout.flush()
26
+
27
+ print("Download complete!")