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
starplot/data/dsos.py CHANGED
@@ -1,131 +1,7 @@
1
- from enum import Enum
2
-
3
-
4
- ZENITH_BASE = [
5
- "M5",
6
- "M13",
7
- "M23",
8
- "M31",
9
- "M42",
10
- "M44",
11
- "M45",
12
- "M47",
13
- "M51",
14
- "M55",
15
- "M83",
16
- "M93",
17
- "M104",
18
- ]
19
-
20
-
21
- class DsoType(str, Enum):
22
- """
23
- Types of deep sky objects (DSOs), as designated in OpenNGC
24
- """
1
+ import ibis
2
+ from ibis import _
25
3
 
26
- STAR = "Star"
27
- DOUBLE_STAR = "Double star"
28
- ASSOCIATION_OF_STARS = "Association of stars"
29
-
30
- OPEN_CLUSTER = "Open Cluster"
31
- GLOBULAR_CLUSTER = "Globular Cluster"
32
-
33
- GALAXY = "Galaxy"
34
- GALAXY_PAIR = "Galaxy Pair"
35
- GALAXY_TRIPLET = "Galaxy Triplet"
36
- GROUP_OF_GALAXIES = "Group of galaxies"
37
-
38
- NEBULA = "Nebula"
39
- PLANETARY_NEBULA = "Planetary Nebula"
40
- EMISSION_NEBULA = "Emission Nebula"
41
- STAR_CLUSTER_NEBULA = "Star cluster + Nebula"
42
- REFLECTION_NEBULA = "Reflection Nebula"
43
-
44
- DARK_NEBULA = "Dark Nebula"
45
- HII_IONIZED_REGION = "HII Ionized region"
46
- SUPERNOVA_REMNANT = "Supernova remnant"
47
- NOVA_STAR = "Nova star"
48
- NONEXISTENT = "Nonexistent object"
49
- UNKNOWN = "Object of other/unknown type"
50
- DUPLICATE_RECORD = "Duplicated record"
51
-
52
-
53
- ONGC_TYPE = {
54
- # Star Clusters ----------
55
- DsoType.OPEN_CLUSTER: "OCl",
56
- DsoType.GLOBULAR_CLUSTER: "GCl",
57
- # Galaxies ----------
58
- DsoType.GALAXY: "G",
59
- DsoType.GALAXY_PAIR: "GPair",
60
- DsoType.GALAXY_TRIPLET: "GTrpl",
61
- DsoType.GROUP_OF_GALAXIES: "GGroup",
62
- # Nebulas ----------
63
- DsoType.NEBULA: "Neb",
64
- DsoType.PLANETARY_NEBULA: "PN",
65
- DsoType.EMISSION_NEBULA: "EmN",
66
- DsoType.STAR_CLUSTER_NEBULA: "Cl+N",
67
- DsoType.REFLECTION_NEBULA: "RfN",
68
- # Stars ----------
69
- DsoType.STAR: "*",
70
- DsoType.DOUBLE_STAR: "**",
71
- DsoType.ASSOCIATION_OF_STARS: "*Ass",
72
- # Others
73
- DsoType.HII_IONIZED_REGION: "HII",
74
- DsoType.DARK_NEBULA: "DrkN",
75
- DsoType.SUPERNOVA_REMNANT: "SNR",
76
- DsoType.NOVA_STAR: "Nova",
77
- DsoType.NONEXISTENT: "NonEx",
78
- DsoType.UNKNOWN: "Other",
79
- DsoType.DUPLICATE_RECORD: "Dup",
80
- }
81
-
82
- ONGC_TYPE_MAP = {v: k.value for k, v in ONGC_TYPE.items()}
83
-
84
- BASIC_DSO_TYPES = [
85
- # Star Clusters ----------
86
- DsoType.OPEN_CLUSTER,
87
- DsoType.GLOBULAR_CLUSTER,
88
- # Galaxies ----------
89
- DsoType.GALAXY,
90
- DsoType.GALAXY_PAIR,
91
- DsoType.GALAXY_TRIPLET,
92
- DsoType.GROUP_OF_GALAXIES,
93
- # Nebulas ----------
94
- DsoType.NEBULA,
95
- DsoType.PLANETARY_NEBULA,
96
- DsoType.EMISSION_NEBULA,
97
- DsoType.STAR_CLUSTER_NEBULA,
98
- DsoType.REFLECTION_NEBULA,
99
- # Stars ----------
100
- # DsoType.DOUBLE_STAR,
101
- DsoType.ASSOCIATION_OF_STARS,
102
- ]
103
- """Default types of Deep Sky Objects (DSOs) that are plotted when you call `dsos()` on a plot"""
104
-
105
- DSO_LEGEND_LABELS = {
106
- # Galaxies ----------
107
- DsoType.GALAXY: "Galaxy",
108
- DsoType.GALAXY_PAIR: "Galaxy",
109
- DsoType.GALAXY_TRIPLET: "Galaxy",
110
- DsoType.GROUP_OF_GALAXIES: "Galaxy",
111
- # Nebulas ----------
112
- DsoType.NEBULA: "Nebula",
113
- DsoType.PLANETARY_NEBULA: "Nebula",
114
- DsoType.EMISSION_NEBULA: "Nebula",
115
- DsoType.STAR_CLUSTER_NEBULA: "Nebula",
116
- DsoType.REFLECTION_NEBULA: "Nebula",
117
- # Star Clusters ----------
118
- DsoType.OPEN_CLUSTER: "Open Cluster",
119
- DsoType.GLOBULAR_CLUSTER: "Globular Cluster",
120
- # Stars ----------
121
- DsoType.DOUBLE_STAR: "Double Star",
122
- DsoType.ASSOCIATION_OF_STARS: "Association of stars",
123
- DsoType.NOVA_STAR: "Nova Star",
124
- # Others
125
- DsoType.HII_IONIZED_REGION: "HII Ionized Region",
126
- DsoType.DARK_NEBULA: "Dark Nebula",
127
- DsoType.SUPERNOVA_REMNANT: "Supernova Remnant",
128
- }
4
+ from starplot.data import db
129
5
 
130
6
 
131
7
  class DsoLabelMaker(dict):
@@ -149,19 +25,26 @@ class DsoLabelMaker(dict):
149
25
  DSO_LABELS_DEFAULT = DsoLabelMaker()
150
26
 
151
27
 
152
- def load_ongc(**kwargs):
153
- import geopandas as gpd
154
- import numpy as np
155
- from starplot.data import DataFiles
28
+ def load(extent=None, filters=None):
29
+ filters = filters or []
30
+ con = db.connect()
31
+ dsos = con.table("deep_sky_objects")
156
32
 
157
- all_dsos = gpd.read_file(
158
- DataFiles.ONGC.value,
159
- engine="pyogrio",
160
- use_arrow=True,
161
- **kwargs,
33
+ dsos = dsos.mutate(
34
+ ra=_.ra_degrees,
35
+ dec=_.dec_degrees,
36
+ constellation_id=_.constellation,
37
+ magnitude=ibis.coalesce(_.mag_v, _.mag_b, None),
38
+ size=_.size_deg2,
39
+ rowid=ibis.row_number(),
162
40
  )
163
- all_dsos = all_dsos.replace({np.nan: None})
164
- all_dsos = all_dsos[
165
- all_dsos["ra_degrees"].notnull() & all_dsos["dec_degrees"].notnull()
166
- ]
167
- return all_dsos
41
+
42
+ if extent:
43
+ dsos = dsos.filter(_.geometry.intersects(extent))
44
+
45
+ filters.extend([_.ra_degrees.notnull() & _.dec_degrees.notnull()])
46
+
47
+ if filters:
48
+ return dsos.filter(*filters)
49
+
50
+ return dsos
starplot/data/stars.py CHANGED
@@ -1,8 +1,7 @@
1
- from enum import Enum
1
+ import ibis
2
+ from ibis import _
2
3
 
3
- from pandas import read_parquet
4
-
5
- from starplot.data import bigsky, DataFiles
4
+ from starplot.data import bigsky, DataFiles, db
6
5
 
7
6
  STAR_NAMES = {
8
7
  677: "Alpheratz",
@@ -432,17 +431,14 @@ STAR_NAMES = {
432
431
  """Star names by their HIP id. You can override these values when calling `stars()`"""
433
432
 
434
433
 
435
- class StarCatalog(str, Enum):
434
+ class StarCatalog:
436
435
  """Built-in star catalogs"""
437
436
 
438
- HIPPARCOS = "hipparcos"
439
- """Hipparcos Catalog = 118,218 stars"""
440
-
441
437
  BIG_SKY_MAG11 = "big-sky-mag11"
442
438
  """
443
- [Big Sky Catalog](https://github.com/steveberardi/bigsky) ~ 900k stars up to magnitude 11
439
+ [Big Sky Catalog](https://github.com/steveberardi/bigsky) ~ 370k stars up to magnitude 10
444
440
 
445
- This is an _abridged_ version of the Big Sky Catalog, including stars up to a limiting magnitude of 11 (total = 981,852).
441
+ This is an _abridged_ version of the Big Sky Catalog, including stars up to a limiting magnitude of 10 (total = 368,330 981,852).
446
442
 
447
443
  This catalog is included with Starplot, so does not require downloading any files.
448
444
  """
@@ -451,28 +447,53 @@ class StarCatalog(str, Enum):
451
447
  """
452
448
  [Big Sky Catalog](https://github.com/steveberardi/bigsky) ~ 2.5M stars
453
449
 
454
- This is the full version of the Big Sky Catalog, which includes 2,557,499 stars from Hipparcos, Tycho-1, and Tycho-2.
450
+ This is the full version of the Big Sky Catalog, which includes 2,557,500 stars from Hipparcos, Tycho-1, and Tycho-2.
455
451
 
456
- 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
452
+ This catalog is very large (approx 100 MB), so it's not built-in to Starplot. When you plot stars and specify this catalog, the catalog
457
453
  will be downloaded from the [Big Sky GitHub repository](https://github.com/steveberardi/bigsky) and saved to Starplot's data library
458
454
  directory. You can override this download path with the environment variable `STARPLOT_DOWNLOAD_PATH`
459
455
 
460
456
  """
461
457
 
462
458
 
463
- def load_bigsky():
464
- if not bigsky.exists():
465
- bigsky.download()
466
-
467
- return bigsky.load(DataFiles.BIG_SKY)
459
+ def load(extent=None, catalog: StarCatalog = StarCatalog.BIG_SKY_MAG11, filters=None):
460
+ filters = filters or []
461
+ con = db.connect()
468
462
 
469
-
470
- def load(catalog: StarCatalog = StarCatalog.HIPPARCOS):
471
- if catalog == StarCatalog.HIPPARCOS:
472
- return read_parquet(DataFiles.HIPPARCOS)
473
- elif catalog == StarCatalog.BIG_SKY_MAG11:
474
- return bigsky.load(DataFiles.BIG_SKY_MAG11)
463
+ if catalog == StarCatalog.BIG_SKY_MAG11:
464
+ stars = con.read_parquet(DataFiles.BIG_SKY_MAG11, "stars")
475
465
  elif catalog == StarCatalog.BIG_SKY:
476
- return bigsky.load(DataFiles.BIG_SKY)
466
+ bigsky.download_if_not_exists()
467
+ stars = con.read_parquet(DataFiles.BIG_SKY, "stars")
477
468
  else:
478
469
  raise ValueError("Unrecognized star catalog.")
470
+
471
+ designations = con.table("star_designations")
472
+
473
+ stars = stars.mutate(
474
+ epoch_year=2000,
475
+ ra=_.ra_degrees,
476
+ dec=_.dec_degrees,
477
+ constellation_id=_.constellation,
478
+ ra_hours=_.ra_degrees / 15,
479
+ # stars parquet does not have geometry field
480
+ geometry=_.ra_degrees.point(_.dec_degrees),
481
+ rowid=ibis.row_number(),
482
+ )
483
+
484
+ if extent:
485
+ stars = stars.filter(stars.geometry.intersects(extent))
486
+
487
+ stars = stars.join(
488
+ designations,
489
+ [
490
+ stars.hip == designations.hip,
491
+ (stars.ccdm == "A") | (stars.ccdm == "") | (stars.ccdm.isnull()),
492
+ ],
493
+ how="left",
494
+ )
495
+
496
+ if filters:
497
+ return stars.filter(*filters)
498
+
499
+ return stars
starplot/geod.py CHANGED
@@ -11,10 +11,6 @@ def distance_m(distance_degrees: float, lat: float = 0, lon: float = 0):
11
11
  return distance
12
12
 
13
13
 
14
- def to_radec(p) -> tuple:
15
- return (p[0] / 15, p[1])
16
-
17
-
18
14
  def away_from_poles(dec):
19
15
  # for some reason cartopy does not like plotting things EXACTLY at the poles
20
16
  # so, this is a little hack to avoid the bug (or maybe a misconception?) by
@@ -34,7 +30,6 @@ def rectangle(
34
30
  angle: float = 0,
35
31
  ) -> list:
36
32
  ra, dec = center
37
- ra = ra * 15
38
33
  dec = away_from_poles(dec)
39
34
  angle = 180 - angle
40
35
 
@@ -70,7 +65,6 @@ def ellipse(
70
65
  end_angle: int = 360,
71
66
  ) -> list:
72
67
  ra, dec = center
73
- ra = ra * 15
74
68
  dec = away_from_poles(dec)
75
69
  angle = 180 - angle
76
70
 
starplot/geometry.py ADDED
@@ -0,0 +1,181 @@
1
+ import random
2
+ import math
3
+ from typing import Union
4
+
5
+ from shapely import transform
6
+ from shapely.geometry import Point, Polygon, MultiPolygon
7
+
8
+ from starplot import geod, utils
9
+
10
+ GLOBAL_EXTENT = Polygon(
11
+ [
12
+ [0, -90],
13
+ [360, -90],
14
+ [360, 90],
15
+ [0, 90],
16
+ [0, -90],
17
+ ]
18
+ )
19
+
20
+
21
+ def circle(center, diameter_degrees):
22
+ points = geod.ellipse(
23
+ center,
24
+ diameter_degrees,
25
+ diameter_degrees,
26
+ angle=0,
27
+ num_pts=100,
28
+ )
29
+ points = [
30
+ (round(24 - utils.lon_to_ra(lon), 4), round(dec, 4)) for lon, dec in points
31
+ ]
32
+ return Polygon(points)
33
+
34
+
35
+ def to_24h(geometry: Union[Point, Polygon, MultiPolygon]):
36
+ geometry_type = str(geometry.geom_type)
37
+
38
+ if geometry_type == "MultiPolygon":
39
+ polygons = [transform(p, lambda c: c * [1 / 15, 1]) for p in geometry.geoms]
40
+ return MultiPolygon(polygons)
41
+
42
+ return transform(geometry, lambda c: c * [1 / 15, 1])
43
+
44
+
45
+ def unwrap_polygon(polygon: Polygon) -> Polygon:
46
+ points = list(zip(*polygon.exterior.coords.xy))
47
+ new_points = []
48
+ prev = None
49
+
50
+ for x, y in points:
51
+ if prev is not None and prev > 20 and x < 12:
52
+ x += 24
53
+ elif prev is not None and prev < 12 and x > 20:
54
+ x -= 24
55
+ new_points.append((x, y))
56
+ prev = x
57
+
58
+ return Polygon(new_points)
59
+
60
+
61
+ def unwrap_polygon_360_old(polygon: Polygon) -> Polygon:
62
+ points = list(zip(*polygon.exterior.coords.xy))
63
+ new_points = []
64
+ prev = None
65
+
66
+ for x, y in points:
67
+ if prev is not None and prev > 300 and x < 180:
68
+ x -= 360
69
+ elif prev is not None and prev < 180 and x > 300:
70
+ x += 360
71
+ new_points.append((x, y))
72
+ prev = x
73
+
74
+ return Polygon(new_points)
75
+
76
+
77
+ def unwrap_polygon_360_inverse(polygon: Polygon) -> Polygon:
78
+ ra, dec = [p for p in polygon.exterior.coords.xy]
79
+
80
+ if min(ra) < 180 and max(ra) > 300:
81
+ new_ra = [r + 360 if r < 50 else r for r in ra]
82
+ points = list(zip(new_ra, dec))
83
+ return Polygon(points)
84
+
85
+ return polygon
86
+
87
+
88
+ def unwrap_polygon_360(polygon: Polygon) -> Polygon:
89
+ ra, dec = [p for p in polygon.exterior.coords.xy]
90
+
91
+ if min(ra) < 180 and max(ra) > 300:
92
+ new_ra = [r - 360 if r > 300 else r for r in ra]
93
+ return Polygon(list(zip(new_ra, dec)))
94
+
95
+ return polygon
96
+
97
+
98
+ def random_point_in_polygon(
99
+ polygon: Polygon, max_iterations: int = 100, seed: int = None
100
+ ) -> Point:
101
+ """Returns a random point inside a shapely polygon"""
102
+ if seed:
103
+ random.seed(seed)
104
+
105
+ ctr = 0
106
+ x0, y0, x1, y1 = polygon.bounds
107
+
108
+ while ctr < max_iterations:
109
+ x = random.uniform(x0, x1)
110
+ y = random.uniform(y0, y1)
111
+ point = Point(x, y)
112
+ if polygon.contains(point):
113
+ return point
114
+
115
+ return None
116
+
117
+
118
+ def random_point_in_polygon_at_distance(
119
+ polygon: Polygon,
120
+ origin_point: Point,
121
+ distance: int,
122
+ max_iterations: int = 100,
123
+ seed: int = None,
124
+ ) -> Point:
125
+ """Returns a random point inside a polygon, at a specified distance from the origin point"""
126
+ if seed:
127
+ random.seed(seed)
128
+
129
+ ctr = 0
130
+ while ctr < max_iterations:
131
+ ctr += 1
132
+ angle = random.uniform(0, 2 * math.pi)
133
+ x = origin_point.x + distance * math.cos(angle)
134
+ y = origin_point.y + distance * math.sin(angle)
135
+ point = Point(x, y)
136
+
137
+ if polygon.contains(point):
138
+ return point
139
+
140
+ return None
141
+
142
+
143
+ def wrapped_polygon_adjustment_old(polygon: Polygon) -> int:
144
+ if "MultiPolygon" == str(polygon.geom_type):
145
+ return 0
146
+
147
+ points = list(zip(*polygon.exterior.coords.xy))
148
+ prev = None
149
+
150
+ for ra, _ in points:
151
+ if prev is not None and prev > 300 and ra < 180:
152
+ return 360
153
+ elif prev is not None and prev < 180 and ra > 300:
154
+ return -360
155
+ prev = ra
156
+
157
+ return 0
158
+
159
+
160
+ def wrapped_polygon_adjustment(polygon: Polygon) -> int:
161
+ if "MultiPolygon" == str(polygon.geom_type):
162
+ return 0
163
+
164
+ ra, _ = [p for p in polygon.exterior.coords.xy]
165
+
166
+ if min(ra) < 180 and max(ra) > 300:
167
+ return 360
168
+
169
+ return 0
170
+
171
+
172
+ def is_wrapped_polygon(polygon: Polygon) -> bool:
173
+ if "MultiPolygon" == str(polygon.geom_type):
174
+ return False
175
+
176
+ ra, _ = [p for p in polygon.exterior.coords.xy]
177
+
178
+ if min(ra) < 180 and max(ra) > 300:
179
+ return True
180
+
181
+ return False