starplot 0.18.3__py2.py3-none-any.whl → 0.19.2__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 (38) hide show
  1. starplot/__init__.py +33 -27
  2. starplot/config.py +11 -0
  3. starplot/data/__init__.py +3 -5
  4. starplot/data/catalogs.py +24 -12
  5. starplot/data/constellations.py +1 -0
  6. starplot/data/db.py +1 -7
  7. starplot/geod.py +3 -4
  8. starplot/geometry.py +17 -1
  9. starplot/mixins.py +11 -0
  10. starplot/models/__init__.py +3 -1
  11. starplot/models/constellation.py +20 -3
  12. starplot/models/milky_way.py +30 -0
  13. starplot/models/moon.py +1 -1
  14. starplot/models/observer.py +11 -2
  15. starplot/models/planet.py +1 -1
  16. starplot/models/sun.py +1 -1
  17. starplot/plots/__init__.py +6 -0
  18. starplot/{base.py → plots/base.py} +107 -456
  19. starplot/{horizon.py → plots/horizon.py} +12 -10
  20. starplot/{map.py → plots/map.py} +11 -7
  21. starplot/{optic.py → plots/optic.py} +21 -30
  22. starplot/{zenith.py → plots/zenith.py} +37 -8
  23. starplot/plotters/__init__.py +9 -7
  24. starplot/plotters/arrow.py +1 -1
  25. starplot/plotters/constellations.py +46 -61
  26. starplot/plotters/dsos.py +33 -16
  27. starplot/plotters/experimental.py +0 -1
  28. starplot/plotters/milkyway.py +15 -6
  29. starplot/plotters/stars.py +19 -36
  30. starplot/plotters/text.py +524 -0
  31. starplot/styles/__init__.py +4 -4
  32. starplot/styles/base.py +1 -13
  33. {starplot-0.18.3.dist-info → starplot-0.19.2.dist-info}/METADATA +2 -1
  34. {starplot-0.18.3.dist-info → starplot-0.19.2.dist-info}/RECORD +37 -35
  35. starplot/data/library/sky.db +0 -0
  36. {starplot-0.18.3.dist-info → starplot-0.19.2.dist-info}/WHEEL +0 -0
  37. {starplot-0.18.3.dist-info → starplot-0.19.2.dist-info}/entry_points.txt +0 -0
  38. {starplot-0.18.3.dist-info → starplot-0.19.2.dist-info}/licenses/LICENSE +0 -0
starplot/__init__.py CHANGED
@@ -1,36 +1,42 @@
1
+ # ruff: noqa: F401,F403
2
+
1
3
  """Star charts and maps of the sky"""
2
4
 
3
- __version__ = "0.18.3"
5
+ __version__ = "0.19.2"
4
6
 
5
- from .base import BasePlot # noqa: F401
6
- from .map import MapPlot # noqa: F401
7
- from .horizon import HorizonPlot # noqa: F401
8
- from .optic import OpticPlot # noqa: F401
9
- from .zenith import ZenithPlot # noqa: F401
7
+ from .plots import (
8
+ MapPlot,
9
+ HorizonPlot,
10
+ OpticPlot,
11
+ ZenithPlot,
12
+ )
10
13
  from .models import (
11
- DSO, # noqa: F401
12
- DsoType, # noqa: F401
13
- Star, # noqa: F401
14
- Constellation, # noqa: F401
15
- Comet, # noqa: F401
16
- Planet, # noqa: F401
17
- Moon, # noqa: F401
18
- Sun, # noqa: F401
19
- ObjectList, # noqa: F401
20
- Scope, # noqa: F401
21
- Binoculars, # noqa: F401
22
- Reflector, # noqa: F401
23
- Refractor, # noqa: F401
24
- Camera, # noqa: F401
25
- Satellite, # noqa: F401
26
- Observer, # noqa: F401
14
+ DSO,
15
+ DsoType,
16
+ Star,
17
+ Constellation,
18
+ ConstellationBorder,
19
+ Comet,
20
+ Planet,
21
+ Moon,
22
+ Sun,
23
+ ObjectList,
24
+ Scope,
25
+ Binoculars,
26
+ Reflector,
27
+ Refractor,
28
+ Camera,
29
+ Satellite,
30
+ Observer,
31
+ MilkyWay,
27
32
  )
28
- from .data import Catalog # noqa: F401 F403
29
- from .styles import * # noqa: F401 F403
30
- from .projections import * # noqa: F401 F403
31
- from .config import settings # noqa: F401
33
+ from .data import Catalog
34
+ from .styles import *
35
+ from .projections import *
36
+ from .config import settings
37
+ from .plotters.text import CollisionHandler
32
38
 
33
- from ibis import _ # noqa: F401 F403
39
+ from ibis import _
34
40
 
35
41
 
36
42
  import contextlib
starplot/config.py CHANGED
@@ -13,6 +13,14 @@ def _get_path(var_name, default) -> Path:
13
13
  return _get
14
14
 
15
15
 
16
+ def _get_boolean(var_name, default) -> bool:
17
+ def _get():
18
+ value = os.environ.get(var_name)
19
+ return default if not value else bool(value.lower() == "true")
20
+
21
+ return _get
22
+
23
+
16
24
  class SvgTextType(str, Enum):
17
25
  PATH = "path"
18
26
  ELEMENT = "element"
@@ -64,5 +72,8 @@ class Settings:
64
72
  **🌐 Want to see another language available? Please help us add it! [Details here](https://github.com/steveberardi/starplot/tree/main/data/raw/translations).**
65
73
  """
66
74
 
75
+ debug: bool = field(default_factory=_get_boolean("STARPLOT_DEBUG", False))
76
+ """Global setting for debug mode. When this is enabled, Starplot will log debugging information and plot polygons for debugging text issues"""
77
+
67
78
 
68
79
  settings = Settings()
starplot/data/__init__.py CHANGED
@@ -1,9 +1,11 @@
1
+ # ruff: noqa: F401,F403
2
+
1
3
  from pathlib import Path
2
4
 
3
5
  from skyfield.api import Loader
4
6
 
5
7
  from starplot.config import settings
6
- from .catalogs import Catalog # noqa: F401
8
+ from .catalogs import Catalog
7
9
 
8
10
  load = Loader(settings.data_path) # used for loading ephemeris
9
11
 
@@ -16,9 +18,5 @@ INTERNAL_DATA_PATH = HERE / "library"
16
18
 
17
19
  class DataFiles:
18
20
  STAR_DESIGNATIONS = INTERNAL_DATA_PATH / "star_designations.parquet"
19
-
20
21
  CONSTELLATION_NAMES = INTERNAL_DATA_PATH / "constellation_names.parquet"
21
-
22
22
  DSO_NAMES = INTERNAL_DATA_PATH / "dso_names.parquet"
23
-
24
- DATABASE = INTERNAL_DATA_PATH / "sky.db"
starplot/data/catalogs.py CHANGED
@@ -290,51 +290,61 @@ class Catalog:
290
290
  # --------------------------------------------------------
291
291
 
292
292
  BIG_SKY = Catalog(
293
- path=settings.data_path / "stars.bigksy.0.1.2.mag16.parquet",
294
- url="https://github.com/steveberardi/starplot-bigsky/releases/download/v0.1.1/stars.bigksy.0.1.1.mag16.parquet",
293
+ path=settings.data_path / "stars.bigksy.0.1.3.mag16.parquet",
294
+ url="https://github.com/steveberardi/starplot-bigsky/releases/download/v0.1.3/stars.bigksy.0.1.3.mag16.parquet",
295
295
  )
296
296
  """
297
297
  [Big Sky Catalog](https://github.com/steveberardi/bigsky) ~ 2.5M stars
298
298
 
299
- This is the full version of the Big Sky Catalog, which includes 2,557,500 stars from Hipparcos, Tycho-1, and Tycho-2.
299
+ This is the full version of the Big Sky Catalog, which includes 2,557,501 stars from Hipparcos, Tycho-1, and Tycho-2.
300
300
  """
301
301
 
302
302
  BIG_SKY_MAG11 = Catalog(
303
- path=settings.data_path / "stars.bigksy.0.1.2.mag11.parquet",
304
- url="https://github.com/steveberardi/starplot-bigsky/releases/download/v0.1.1/stars.bigksy.0.1.1.mag11.parquet",
303
+ path=settings.data_path / "stars.bigksy.0.1.3.mag11.parquet",
304
+ url="https://github.com/steveberardi/starplot-bigsky/releases/download/v0.1.3/stars.bigksy.0.1.3.mag11.parquet",
305
305
  )
306
306
  """
307
- [Big Sky Catalog](https://github.com/steveberardi/bigsky) ~ 983,822 stars with limiting magnitude 11
307
+ [Big Sky Catalog](https://github.com/steveberardi/bigsky) ~ 983,823 stars with limiting magnitude 11
308
308
 
309
309
  This is an _abridged_ version of the Big Sky Catalog.
310
310
  """
311
311
 
312
312
  BIG_SKY_MAG9 = Catalog(
313
- path=settings.data_path / "stars.bigksy.0.1.2.mag9.parquet",
314
- url="https://github.com/steveberardi/starplot-bigsky/releases/download/v0.1.1/stars.bigksy.0.1.1.mag9.parquet",
313
+ path=settings.data_path / "stars.bigksy.0.1.3.mag9.parquet",
314
+ url="https://github.com/steveberardi/starplot-bigsky/releases/download/v0.1.3/stars.bigksy.0.1.3.mag9.parquet",
315
315
  )
316
316
  """
317
- [Big Sky Catalog](https://github.com/steveberardi/bigsky) ~ 136,125 stars with limiting magnitude 9
317
+ [Big Sky Catalog](https://github.com/steveberardi/bigsky) ~ 136,126 stars with limiting magnitude 9
318
318
 
319
319
  This is an _abridged_ version of the Big Sky Catalog.
320
320
  """
321
321
 
322
322
  OPEN_NGC = Catalog(
323
323
  path=settings.data_path / "ongc.0.1.2.parquet",
324
- url="https://github.com/steveberardi/starplot-ongc/releases/download/v0.1.1/ongc.0.1.1.parquet",
324
+ url="https://github.com/steveberardi/starplot-ongc/releases/download/v0.1.2/ongc.0.1.2.parquet",
325
325
  )
326
326
  """
327
327
  [OpenNGC](https://github.com/mattiaverga/OpenNGC) catalog, including nebulae outlines.
328
328
  """
329
329
 
330
330
  CONSTELLATIONS_IAU = Catalog(
331
- path=settings.data_path / "constellations-iau-0.1.2.parquet",
332
- url="https://github.com/steveberardi/starplot-constellations/releases/download/v0.1.1/constellations.0.1.1.parquet",
331
+ path=settings.data_path / "constellations.0.3.3.parquet",
332
+ url="https://github.com/steveberardi/starplot-constellations/releases/download/v0.3.3/constellations.0.3.3.parquet",
333
333
  )
334
334
  """
335
335
  Constellations recognized by IAU, with lines by Sky & Telescope.
336
336
  """
337
337
 
338
+ CONSTELLATION_BORDERS = Catalog(
339
+ path=settings.data_path / "constellations-borders-0.3.1.parquet",
340
+ url="https://github.com/steveberardi/starplot-constellations/releases/download/v0.3.1/constellations-borders.0.3.1.parquet",
341
+ )
342
+
343
+ MILKY_WAY = Catalog(
344
+ path=settings.data_path / "milky_way-0.1.0.parquet",
345
+ url="https://github.com/steveberardi/starplot-milkyway/releases/download/v0.1.0/milky_way.parquet",
346
+ )
347
+
338
348
 
339
349
  def download_all_catalogs(silent=False):
340
350
  BIG_SKY.download_if_not_exists(silent=silent)
@@ -342,3 +352,5 @@ def download_all_catalogs(silent=False):
342
352
  BIG_SKY_MAG11.download_if_not_exists(silent=silent)
343
353
  OPEN_NGC.download_if_not_exists(silent=silent)
344
354
  CONSTELLATIONS_IAU.download_if_not_exists(silent=silent)
355
+ CONSTELLATION_BORDERS.download_if_not_exists(silent=silent)
356
+ MILKY_WAY.download_if_not_exists(silent=silent)
@@ -43,6 +43,7 @@ def table(
43
43
 
44
44
  return c.mutate(
45
45
  boundary=_.boundary.cast("geometry"), # cast WKB to geometry type
46
+ border=_.border.cast("geometry"), # cast WKB to geometry type
46
47
  name=getattr(c, name_column),
47
48
  )
48
49
 
starplot/data/db.py CHANGED
@@ -12,13 +12,7 @@ NAME_TABLES = {
12
12
 
13
13
 
14
14
  def connect():
15
- connection = duckdb.connect(
16
- DataFiles.DATABASE,
17
- read_only=True,
18
- # threads=4,
19
- # memory_limit="1GB",
20
- )
21
-
15
+ connection = duckdb.connect()
22
16
  path = settings.data_path / "duckdb-extensions"
23
17
  connection.raw_sql(f"SET extension_directory = '{str(path)}';")
24
18
 
starplot/geod.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import math
2
2
 
3
3
  import pyproj
4
-
4
+ import numpy as np
5
5
 
6
6
  GEOD = pyproj.Geod("+a=6378137 +f=0.0", sphere=True)
7
7
 
@@ -70,11 +70,10 @@ def ellipse(
70
70
 
71
71
  height = distance_m(height_degrees / 2) # b
72
72
  width = distance_m(width_degrees / 2) # a
73
+ step_size = (end_angle - start_angle) / num_pts
73
74
 
74
75
  points = []
75
- for angle_pt in range(
76
- start_angle, end_angle + 1, int((end_angle - start_angle) / num_pts)
77
- ):
76
+ for angle_pt in np.arange(start_angle, end_angle + step_size, step_size):
78
77
  radians = math.radians(angle_pt)
79
78
  radius_a = (height * width) / math.sqrt(
80
79
  height**2 * (math.sin(radians)) ** 2
starplot/geometry.py CHANGED
@@ -2,7 +2,7 @@ import random
2
2
  import math
3
3
  from typing import Union
4
4
 
5
- from shapely import transform
5
+ from shapely import transform, union_all
6
6
  from shapely.geometry import Point, Polygon, MultiPolygon
7
7
 
8
8
  from starplot import geod
@@ -96,6 +96,22 @@ def unwrap_polygon_360(polygon: Polygon) -> Polygon:
96
96
  return polygon
97
97
 
98
98
 
99
+ def union_at_zero(a: Polygon, b: Polygon) -> Polygon:
100
+ """Returns union of two polygons"""
101
+ a_ra = list(a.exterior.coords.xy)[0]
102
+ b_ra = list(b.exterior.coords.xy)[0]
103
+
104
+ if max(a_ra) == 360 and min(b_ra) == 0:
105
+ points = list(zip(*b.exterior.coords.xy))
106
+ b = Polygon([[ra + 360, dec] for ra, dec in points])
107
+
108
+ elif min(a_ra) == 0 and max(b_ra) == 360:
109
+ points = list(zip(*a.exterior.coords.xy))
110
+ a = Polygon([[ra + 360, dec] for ra, dec in points])
111
+
112
+ return union_all([a, b])
113
+
114
+
99
115
  def split_polygon_at_zero(polygon: Polygon) -> list[Polygon]:
100
116
  """
101
117
  Splits a polygon at the first point of Aries (RA=0)
starplot/mixins.py CHANGED
@@ -12,6 +12,17 @@ class ExtentMaskMixin:
12
12
 
13
13
  If the extent crosses equinox, then a MultiPolygon will be returned
14
14
  """
15
+ if self._is_global_extent():
16
+ return Polygon(
17
+ [
18
+ (0, -90),
19
+ (360, -90),
20
+ (360, 90),
21
+ (0, 90),
22
+ (0, -90),
23
+ ]
24
+ )
25
+
15
26
  if self.ra_max <= 360:
16
27
  coords = [
17
28
  [self.ra_min, self.dec_min],
@@ -1,5 +1,6 @@
1
1
  # ruff: noqa: F401,F403
2
- from .constellation import Constellation
2
+
3
+ from .constellation import Constellation, ConstellationBorder
3
4
  from .comet import Comet
4
5
  from .dso import DSO, DsoType
5
6
  from .star import Star
@@ -17,3 +18,4 @@ from .optics import (
17
18
  from .objects import ObjectList
18
19
  from .satellite import Satellite
19
20
  from .observer import Observer
21
+ from .milky_way import MilkyWay
@@ -2,7 +2,7 @@ from typing import Iterator
2
2
  from dataclasses import dataclass
3
3
 
4
4
  from ibis import _
5
- from shapely import Polygon, MultiPolygon
5
+ from shapely import Polygon, MultiPolygon, LineString
6
6
 
7
7
  from starplot.models.base import SkyObject, CatalogObject
8
8
  from starplot.data.catalogs import Catalog, CONSTELLATIONS_IAU
@@ -16,7 +16,11 @@ class Constellation(CatalogObject, SkyObject):
16
16
  """
17
17
 
18
18
  boundary: Polygon | MultiPolygon
19
- """Shapely Polygon of the constellation's boundary. Right ascension coordinates are in degrees (0...360)."""
19
+ """
20
+ Shapely Polygon of the constellation's boundary. Right ascension coordinates are in degrees (0...360).
21
+
22
+ If the constellation's boundary crosses the 0-meridian, then this will be a MultiPolygon split at the meridian.
23
+ """
20
24
 
21
25
  star_hip_ids: list[int]
22
26
  """List of HIP ids for stars that are part of the _lines_ for this constellation."""
@@ -35,6 +39,13 @@ class Constellation(CatalogObject, SkyObject):
35
39
  Serpens Caput has the `iau_id` of `ser1` and Serpens Cauda is `ser2`
36
40
  """
37
41
 
42
+ border: LineString = None
43
+ """
44
+ Shapely LineString of the constellation's border. Right ascension coordinates are in degrees (0...360).
45
+
46
+ Coordinates in this geometry may extend past RA=360, if the border crosses the 0-meridian. This makes it ready to plot with the `line` function.
47
+ """
48
+
38
49
  def __repr__(self) -> str:
39
50
  return f"Constellation(iau_id={self.iau_id}, name={self.name}, ra={self.ra}, dec={self.dec})"
40
51
 
@@ -125,10 +136,16 @@ class Constellation(CatalogObject, SkyObject):
125
136
  Returns the uppercase name of the constellation.
126
137
 
127
138
  """
128
- return constellation.name.upper()
139
+ return constellation.name.upper().replace(" ", "\n")
129
140
 
130
141
 
131
142
  def from_tuple(c: tuple) -> Constellation:
132
143
  kwargs = {f: getattr(c, f) for f in Constellation._fields() if hasattr(c, f)}
133
144
  c = Constellation(**kwargs)
134
145
  return c
146
+
147
+
148
+ @dataclass(slots=True, kw_only=True)
149
+ class ConstellationBorder(CatalogObject, SkyObject):
150
+ geometry: LineString
151
+ """Shapely LineString of the border. Right ascension coordinates are in degrees (0...360)."""
@@ -0,0 +1,30 @@
1
+ from dataclasses import dataclass
2
+
3
+ import pyarrow as pa
4
+ from shapely import Polygon, MultiPolygon
5
+
6
+ from starplot.models.base import SkyObject, CatalogObject
7
+
8
+
9
+ @dataclass(slots=True, kw_only=True)
10
+ class MilkyWay(CatalogObject, SkyObject):
11
+ """
12
+ Milky Way model.
13
+ """
14
+
15
+ geometry: Polygon | MultiPolygon
16
+ """Shapely Polygon of the Milky Way's extent. Right ascension coordinates are in degrees (0...360)."""
17
+
18
+ @classmethod
19
+ def _pyarrow_schema(cls):
20
+ base_schema = super(MilkyWay, cls)._pyarrow_schema()
21
+ extra_fields = [
22
+ pa.field("pk", pa.int64(), nullable=False),
23
+ pa.field("geometry", pa.binary(), nullable=False),
24
+ ]
25
+ return pa.schema(list(base_schema) + extra_fields)
26
+
27
+
28
+ def from_tuple(d: tuple) -> MilkyWay:
29
+ kwargs = {f: getattr(d, f) for f in MilkyWay._fields() if hasattr(d, f)}
30
+ return MilkyWay(**kwargs)
starplot/models/moon.py CHANGED
@@ -37,7 +37,7 @@ class Moon(SkyObject):
37
37
  """Date/time of moon's position"""
38
38
 
39
39
  apparent_size: float
40
- """Apparent size in the sky (degrees)"""
40
+ """Apparent diameter in the sky (degrees)"""
41
41
 
42
42
  phase_angle: float
43
43
  """Angle of the moon from the Sun (degrees)"""
@@ -6,6 +6,8 @@ from skyfield.timelib import Timescale
6
6
 
7
7
  from starplot.data import load
8
8
 
9
+ ts = load.timescale()
10
+
9
11
 
10
12
  class Observer(BaseModel):
11
13
  """
@@ -19,8 +21,8 @@ class Observer(BaseModel):
19
21
  lat=33.363484,
20
22
  lon=-116.836394,
21
23
  )
22
-
23
24
  ```
25
+
24
26
  """
25
27
 
26
28
  dt: AwareDatetime = Field(default_factory=lambda: datetime.now(timezone.utc))
@@ -47,7 +49,7 @@ class Observer(BaseModel):
47
49
 
48
50
  Timescale instance of the specified datetime (used by Skyfield)
49
51
  """
50
- return load.timescale().from_datetime(self.dt)
52
+ return ts.from_datetime(self.dt)
51
53
 
52
54
  @computed_field
53
55
  @cached_property
@@ -59,6 +61,13 @@ class Observer(BaseModel):
59
61
  """
60
62
  return float(360.0 * self.timescale.gmst / 24.0 + self.lon) % 360.0
61
63
 
64
+ @classmethod
65
+ def at_epoch(cls, epoch: float) -> "Observer":
66
+ """
67
+ Returns an Observer for the specified epoch (Julian year)
68
+ """
69
+ return Observer(dt=ts.J(epoch).utc_datetime())
70
+
62
71
  # @computed_field
63
72
  # @cached_property
64
73
  # def location(self):
starplot/models/planet.py CHANGED
@@ -72,7 +72,7 @@ class Planet(SkyObject):
72
72
  """Date/time of planet's position"""
73
73
 
74
74
  apparent_size: float = 0
75
- """Apparent size (degrees)"""
75
+ """Apparent diameter (degrees)"""
76
76
 
77
77
  geometry: Polygon = None
78
78
  """Shapely Polygon of the planet's extent. Right ascension coordinates are in degrees (0...360)."""
starplot/models/sun.py CHANGED
@@ -22,7 +22,7 @@ class Sun(SkyObject):
22
22
  """Date/time of Sun's position"""
23
23
 
24
24
  apparent_size: float = 0
25
- """Apparent size (degrees)"""
25
+ """Apparent diameter (degrees)"""
26
26
 
27
27
  geometry: Polygon = None
28
28
  """Shapely Polygon of the Sun's extent. Right ascension coordinates are in degrees (0...360)."""
@@ -0,0 +1,6 @@
1
+ # ruff: noqa: F401,F403
2
+
3
+ from .map import MapPlot
4
+ from .horizon import HorizonPlot
5
+ from .zenith import ZenithPlot
6
+ from .optic import OpticPlot