starplot 0.11.4__py2.py3-none-any.whl → 0.12.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.

Potentially problematic release.


This version of starplot might be problematic. Click here for more details.

Files changed (42) hide show
  1. starplot/__init__.py +1 -1
  2. starplot/base.py +21 -1
  3. starplot/data/constellations.py +16 -1
  4. starplot/data/dsos.py +1 -1
  5. starplot/data/library/constellations.gpkg +0 -0
  6. starplot/data/library/ongc.gpkg.zip +0 -0
  7. starplot/data/prep/__init__.py +0 -0
  8. starplot/data/prep/constellations.py +108 -0
  9. starplot/data/prep/dsos.py +299 -0
  10. starplot/data/prep/utils.py +16 -0
  11. starplot/map.py +77 -81
  12. starplot/models/__init__.py +1 -1
  13. starplot/models/base.py +9 -3
  14. starplot/models/constellation.py +27 -5
  15. starplot/models/dso.py +47 -1
  16. starplot/models/geometry.py +44 -0
  17. starplot/models/moon.py +8 -0
  18. starplot/models/objects.py +5 -1
  19. starplot/models/planet.py +14 -1
  20. starplot/models/star.py +47 -7
  21. starplot/models/sun.py +14 -1
  22. starplot/optic.py +11 -4
  23. starplot/plotters/dsos.py +58 -28
  24. starplot/plotters/stars.py +15 -29
  25. starplot/styles/base.py +98 -52
  26. starplot/styles/ext/antique.yml +29 -1
  27. starplot/styles/ext/blue_dark.yml +20 -2
  28. starplot/styles/ext/blue_light.yml +29 -1
  29. starplot/styles/ext/blue_medium.yml +30 -1
  30. starplot/styles/ext/cb_wong.yml +28 -1
  31. starplot/styles/ext/color_print.yml +3 -0
  32. starplot/styles/ext/grayscale.yml +18 -1
  33. starplot/styles/ext/grayscale_dark.yml +20 -1
  34. starplot/styles/ext/nord.yml +33 -7
  35. starplot/styles/markers.py +107 -0
  36. starplot/utils.py +1 -1
  37. {starplot-0.11.4.dist-info → starplot-0.12.0.dist-info}/METADATA +4 -13
  38. starplot-0.12.0.dist-info/RECORD +67 -0
  39. starplot/data/library/de440s.bsp +0 -0
  40. starplot-0.11.4.dist-info/RECORD +0 -62
  41. {starplot-0.11.4.dist-info → starplot-0.12.0.dist-info}/LICENSE +0 -0
  42. {starplot-0.11.4.dist-info → starplot-0.12.0.dist-info}/WHEEL +0 -0
starplot/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Star charts and maps"""
2
2
 
3
- __version__ = "0.11.4"
3
+ __version__ = "0.12.0"
4
4
 
5
5
  from .base import BasePlot # noqa: F401
6
6
  from .map import MapPlot, Projection # noqa: F401
starplot/base.py CHANGED
@@ -22,6 +22,7 @@ from starplot.styles import (
22
22
  LabelStyle,
23
23
  LegendLocationEnum,
24
24
  LegendStyle,
25
+ LineStyle,
25
26
  MarkerSymbolEnum,
26
27
  PathStyle,
27
28
  PolygonStyle,
@@ -84,7 +85,7 @@ class BasePlot(ABC):
84
85
 
85
86
  self.text_border = patheffects.withStroke(
86
87
  linewidth=self.style.text_border_width,
87
- foreground=self.style.background_color.as_hex(),
88
+ foreground=self.style.text_border_color.as_hex(),
88
89
  )
89
90
  self._size_multiplier = self.resolution / 3000
90
91
  self.timescale = load.timescale().from_datetime(self.dt)
@@ -594,6 +595,25 @@ class BasePlot(ABC):
594
595
  num_pts=num_pts,
595
596
  )
596
597
 
598
+ @use_style(LineStyle)
599
+ def line(self, coordinates: list[tuple[float, float]], style: LineStyle):
600
+ """Plots a line
601
+
602
+ Args:
603
+ coordinates: List of coordinates, e.g. `[(ra, dec), (ra, dec)]`
604
+ style: Style of the line
605
+ """
606
+ x, y = zip(*[self._prepare_coords(*p) for p in coordinates])
607
+
608
+ self.ax.plot(
609
+ x,
610
+ y,
611
+ clip_on=True,
612
+ clip_path=self._background_clip_path,
613
+ **style.matplot_kwargs(self._size_multiplier),
614
+ **self._plot_kwargs(),
615
+ )
616
+
597
617
  @use_style(ObjectStyle, "moon")
598
618
  def moon(
599
619
  self,
@@ -112,12 +112,27 @@ CONSTELLATIONS_ABBREVIATIONS = {k.lower(): k.upper() for k in properties.keys()}
112
112
  """Constellation 3-letter abbreviations"""
113
113
 
114
114
 
115
- def load():
115
+ def _load_deprecated():
116
116
  with _load.open("constellations_hip.fab") as f:
117
117
  consdata = stellarium.parse_constellations(f)
118
118
  return consdata
119
119
 
120
120
 
121
+ def load(**kwargs):
122
+ import geopandas as gpd
123
+ import numpy as np
124
+ from starplot.data import DataFiles
125
+
126
+ cons = gpd.read_file(
127
+ DataFiles.CONSTELLATIONS.value,
128
+ engine="pyogrio",
129
+ use_arrow=True,
130
+ **kwargs,
131
+ )
132
+ cons = cons.replace({np.nan: None})
133
+ return cons
134
+
135
+
121
136
  def get(constellation_id: str):
122
137
  return properties.get(constellation_id)
123
138
 
starplot/data/dsos.py CHANGED
@@ -81,7 +81,7 @@ ONGC_TYPE = {
81
81
 
82
82
  ONGC_TYPE_MAP = {v: k.value for k, v in ONGC_TYPE.items()}
83
83
 
84
- DEFAULT_DSO_TYPES = [
84
+ BASIC_DSO_TYPES = [
85
85
  # Star Clusters ----------
86
86
  DsoType.OPEN_CLUSTER,
87
87
  DsoType.GLOBULAR_CLUSTER,
Binary file
Binary file
File without changes
@@ -0,0 +1,108 @@
1
+ import geopandas as gpd
2
+ import pandas as pd
3
+
4
+ from shapely.geometry import Polygon, MultiPolygon
5
+
6
+ from starplot.data import constellations
7
+ from starplot.data.prep.utils import RAW_DATA_PATH, DATA_LIBRARY
8
+
9
+
10
+ DATA_PATH = RAW_DATA_PATH / "iau"
11
+ CRS = "+ellps=sphere +f=0 +proj=latlong +axis=wnu +a=6378137 +no_defs"
12
+
13
+ # - Three letter (index)
14
+ # - Full name
15
+ # - Centroid
16
+ # - Min bounding box of lines
17
+ # - Polygon of bounds
18
+ # - List of HIP ids for lines
19
+
20
+ # constellation_dict = {
21
+ # "id": [],
22
+ # "name": [],
23
+ # "centroid": [],
24
+ # "lines_bounding_box": [],
25
+ # "lines_hip_ids": [],
26
+ # "borders": [],
27
+ # }
28
+
29
+
30
+ def parse_ra(ra_str):
31
+ """Parses RA from border file HH MM SS to 0...360 degree float"""
32
+ h, m, s = ra_str.strip().split(" ")
33
+ return round(15 * (float(h) + float(m) / 60 + float(s) / 3600), 4)
34
+
35
+
36
+ def parse_dec(dec_str):
37
+ """Parses DEC from ONGC CSV from HH:MM:SS to -90...90 degree float"""
38
+ return round(float(dec_str), 4)
39
+
40
+
41
+ def parse_borders(lines):
42
+ coords = []
43
+ for line in lines:
44
+ if "|" not in line:
45
+ continue
46
+ ra_str, dec_str, _ = line.split("|")
47
+ ra = parse_ra(ra_str)
48
+ dec = parse_dec(dec_str)
49
+ coords.append((ra, dec))
50
+ return coords
51
+
52
+
53
+ def build_constellations():
54
+ constellation_records = []
55
+ con_lines = constellations.lines()
56
+
57
+ for cid, props in constellations.properties.items():
58
+ constellation_dict = {
59
+ "id": cid.lower(),
60
+ "iau_id": cid.lower(),
61
+ "name": props[0],
62
+ "center_ra": props[1] * 15,
63
+ "center_dec": props[2],
64
+ "lines_hip_ids": ",".join(
65
+ "-".join([str(h) for h in hips]) for hips in con_lines[cid.lower()]
66
+ ),
67
+ }
68
+ # print(cid)
69
+
70
+ if cid == "Ser":
71
+ ser1_coords = []
72
+ ser2_coords = []
73
+ with open(DATA_PATH / "ser1.txt", "r") as ser1:
74
+ ser1_coords = parse_borders(ser1.readlines())
75
+
76
+ with open(DATA_PATH / "ser2.txt", "r") as ser2:
77
+ ser2_coords = parse_borders(ser2.readlines())
78
+
79
+ constellation_dict["geometry"] = MultiPolygon(
80
+ [Polygon(ser1_coords), Polygon(ser2_coords)]
81
+ )
82
+
83
+ else:
84
+ with open(DATA_PATH / f"{cid.lower()}.txt", "r") as borderfile:
85
+ coords = parse_borders(borderfile.readlines())
86
+ constellation_dict["geometry"] = Polygon(coords)
87
+
88
+ constellation_records.append(constellation_dict)
89
+
90
+ return constellation_records
91
+
92
+
93
+ constellation_records = build_constellations()
94
+ df = pd.DataFrame.from_records(constellation_records)
95
+
96
+ gdf = gpd.GeoDataFrame(
97
+ df,
98
+ geometry=df["geometry"],
99
+ crs=CRS,
100
+ )
101
+ gdf = gdf.set_index("id")
102
+ gdf.to_file(
103
+ DATA_LIBRARY / "constellations.gpkg", driver="GPKG", engine="pyogrio", index=True
104
+ )
105
+
106
+ print(gdf.loc["uma"])
107
+
108
+ print("Total Constellations: " + str(len(constellation_records)))
@@ -0,0 +1,299 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ import geopandas as gpd
5
+ import pandas as pd
6
+ import numpy as np
7
+
8
+ from shapely.geometry import Polygon, MultiPolygon, Point
9
+
10
+ from starplot import geod
11
+ from starplot.data.prep.utils import RAW_DATA_PATH, DATA_LIBRARY, zip_file
12
+
13
+ DATA_PATH = RAW_DATA_PATH / "ongc" / "outlines"
14
+ CRS = "+ellps=sphere +f=0 +proj=latlong +axis=wnu +a=6378137 +no_defs"
15
+ IGNORE_OUTLINES = [
16
+ "IC0424", # seems too big?
17
+ ]
18
+ MIN_SIZE = 0.1
19
+
20
+
21
+ def _size(d):
22
+ """Returns size (in sq degrees) of minimum bounding rectangle of a DSO"""
23
+ size = None
24
+ geometry_types = d["geometry"].geom_type
25
+
26
+ if "Polygon" in geometry_types and "MultiPolygon" not in geometry_types:
27
+ size = d.geometry.envelope.area
28
+
29
+ elif "MultiPolygon" in geometry_types:
30
+ size = sum([p.envelope.area for p in d.geometry.geoms])
31
+
32
+ elif d.maj_ax and not np.isnan(d.min_ax):
33
+ size = (d.maj_ax / 60) * (d.min_ax / 60)
34
+
35
+ elif d.maj_ax:
36
+ size = (d.maj_ax / 60) ** 2
37
+
38
+ if size:
39
+ size = round(size, 8)
40
+
41
+ return size
42
+
43
+
44
+ def read_csv():
45
+ df = pd.read_csv(
46
+ "raw/ongc/NGC.csv",
47
+ sep=";",
48
+ )
49
+ df_addendum = pd.read_csv(
50
+ "raw/ongc/addendum.csv",
51
+ sep=";",
52
+ )
53
+ df = pd.concat([df, df_addendum])
54
+
55
+ df["ra_degrees"] = df.apply(parse_ra, axis=1)
56
+ df["dec_degrees"] = df.apply(parse_dec, axis=1)
57
+ df["M"] = df.apply(parse_m, axis=1)
58
+ df["IC"] = df.apply(parse_ic, axis=1)
59
+ df["NGC"] = df.apply(parse_ngc, axis=1)
60
+
61
+ df = df.rename(
62
+ columns={
63
+ "Name": "name",
64
+ "Type": "type",
65
+ "M": "m",
66
+ "NGC": "ngc",
67
+ "IC": "ic",
68
+ "MajAx": "maj_ax",
69
+ "MinAx": "min_ax",
70
+ "PosAng": "angle",
71
+ "B-Mag": "mag_b",
72
+ "V-Mag": "mag_v",
73
+ "NED notes": "ned_notes",
74
+ "Common names": "common_names",
75
+ "Const": "constellation",
76
+ }
77
+ )
78
+
79
+ df = df.drop(
80
+ columns=[
81
+ "Cstar U-Mag",
82
+ "Cstar B-Mag",
83
+ "Cstar V-Mag",
84
+ "Cstar Names",
85
+ "ned_notes",
86
+ "constellation",
87
+ "OpenNGC notes",
88
+ "Sources",
89
+ "Hubble",
90
+ "Identifiers",
91
+ "J-Mag",
92
+ "H-Mag",
93
+ "K-Mag",
94
+ "RA",
95
+ "Dec",
96
+ ],
97
+ axis=1,
98
+ )
99
+
100
+ gdf = gpd.GeoDataFrame(
101
+ df,
102
+ geometry=gpd.points_from_xy(df.ra_degrees, df.dec_degrees),
103
+ crs=CRS,
104
+ )
105
+
106
+ gdf["geometry"] = gdf.apply(create_ellipse, axis=1)
107
+ # gdf["geometry"] = gdf.apply(create_point, axis=1)
108
+
109
+ return gdf
110
+
111
+
112
+ def create_point(d):
113
+ return Point([d.ra_degrees, d.dec_degrees])
114
+
115
+
116
+ def parse_m(row):
117
+ """Parses messier number"""
118
+ if not np.isnan(row.M):
119
+ return str(int(row.M)).lstrip("0")
120
+
121
+ return None
122
+
123
+
124
+ def parse_ic(row):
125
+ """Parses IC number if name starts with IC"""
126
+ if row.Name.startswith("IC"):
127
+ return row.Name[2:].lstrip("0")
128
+
129
+ if str(row.IC) != "nan":
130
+ return str(row.IC).lstrip("0")
131
+
132
+ return None
133
+
134
+
135
+ def parse_ngc(row):
136
+ """Parses NGC number if name starts with NGC"""
137
+ if row.Name.startswith("NGC"):
138
+ return row.Name[3:].lstrip("0")
139
+
140
+ if str(row.NGC) != "nan":
141
+ return str(row.NGC).lstrip("0")
142
+
143
+ return None
144
+
145
+
146
+ def parse_ra(row):
147
+ """Parses RA from ONGC CSV from HH:MM:SS to 0...360 degree float"""
148
+ if row.Type == "NonEx":
149
+ print(f"Non Existent object, ignoring... {row.Name}")
150
+ return
151
+
152
+ ra = row.RA
153
+ h, m, s = ra.split(":")
154
+ return round(15 * (float(h) + float(m) / 60 + float(s) / 3600), 4)
155
+
156
+
157
+ def parse_dec(row):
158
+ """Parses DEC from ONGC CSV from HH:MM:SS to -90...90 degree float"""
159
+ if row.Type == "NonEx":
160
+ print(f"Non Existent object, ignoring... {row.Name}")
161
+ return
162
+
163
+ dec = row.Dec
164
+ if dec[0] == "-":
165
+ mx = -1
166
+ else:
167
+ mx = 1
168
+ h, m, s = dec[1:].split(":")
169
+ return round(mx * (float(h) + float(m) / 60 + float(s) / 3600), 4)
170
+
171
+
172
+ def create_ellipse(d):
173
+ maj_ax, min_ax, angle = d.maj_ax, d.min_ax, d.angle
174
+
175
+ if np.isnan(maj_ax):
176
+ return d.geometry
177
+
178
+ if np.isnan(angle):
179
+ angle = 0
180
+
181
+ maj_ax_degrees = (maj_ax / 60) / 2
182
+
183
+ if np.isnan(min_ax):
184
+ min_ax_degrees = maj_ax_degrees
185
+ else:
186
+ min_ax_degrees = (min_ax / 60) / 2
187
+
188
+ points = geod.ellipse(
189
+ (d.ra_degrees / 15, d.dec_degrees),
190
+ min_ax_degrees * 2,
191
+ maj_ax_degrees * 2,
192
+ angle,
193
+ num_pts=100,
194
+ )
195
+
196
+ points360 = []
197
+ for lon, dec in points:
198
+ if lon < 0:
199
+ lon += 360
200
+ points360.append([round(lon, 4), round(dec, 4)])
201
+
202
+ return Polygon(points360)
203
+
204
+
205
+ def parse_designation_from_filename(filename):
206
+ designation, level = filename.split("_")
207
+
208
+ if designation.startswith("IC"):
209
+ ic = designation[2:]
210
+ else:
211
+ ic = None
212
+
213
+ if designation.startswith("NGC"):
214
+ ngc = designation[3:]
215
+ else:
216
+ ngc = None
217
+
218
+ return designation, ic, ngc, level
219
+
220
+
221
+ def walk_files(path=DATA_PATH):
222
+ for dirpath, dirnames, filenames in os.walk(path):
223
+ for filename in sorted(filenames):
224
+ yield Path(os.path.join(dirpath, filename))
225
+
226
+
227
+ gdf = read_csv()
228
+ gdf = gdf.set_index("name")
229
+
230
+ outlines = {}
231
+
232
+ for f in walk_files():
233
+ designation, ic, ngc, level = parse_designation_from_filename(f.name)
234
+ name, _ = f.name.split("_")
235
+
236
+ if level == "lv3" or name in IGNORE_OUTLINES:
237
+ continue
238
+ # if designation in d['designation']:
239
+ # continue
240
+
241
+ dso_df = pd.read_csv(f, sep="\t")
242
+ polygons = []
243
+ current_poly = []
244
+
245
+ for i, row in dso_df.iterrows():
246
+ cont_flag = row["Cont_Flag"]
247
+ ra = row["RAJ2000"]
248
+ dec = row["DEJ2000"]
249
+ current_poly.append([ra, dec])
250
+
251
+ if cont_flag == "*":
252
+ # a * indicates this is the last point in the current polygon
253
+ polygons.append(current_poly)
254
+ current_poly = []
255
+
256
+ if len(polygons) > 1:
257
+ dso_geom = MultiPolygon([Polygon(p) for p in polygons])
258
+ else:
259
+ dso_geom = Polygon(polygons[0])
260
+
261
+ if dso_geom.area > MIN_SIZE:
262
+ outlines[designation] = dso_geom
263
+
264
+ if not ic and not ngc:
265
+ print(designation)
266
+ centroid = dso_geom.centroid
267
+ gdf.loc[name, "geometry"] = dso_geom
268
+ gdf.loc[name, "ra_degrees"] = round(centroid.x, 4)
269
+ gdf.loc[name, "dec_degrees"] = round(centroid.y, 4)
270
+ gdf.loc[name, "type"] = "Neb"
271
+ else:
272
+ if gdf.loc[name].empty:
273
+ print(f"NGC/IC object not found: {name}")
274
+ elif dso_geom.area > MIN_SIZE:
275
+ gdf.loc[name, "geometry"] = dso_geom
276
+
277
+
278
+ # add size column
279
+ gdf["size_deg2"] = gdf.apply(_size, axis=1)
280
+
281
+ # print(gdf.loc["NGC1976"])
282
+
283
+ print(gdf.loc["NGC6720"]) # ring nebula
284
+ print(gdf.loc["Mel022"]) # M45
285
+ # print("INDEX")
286
+ # print(gdf.sindex.size)
287
+
288
+ # gdf.to_file(BUILD_PATH / "ongc.gpkg", driver="GPKG", crs=CRS, index=True)
289
+
290
+ gdf.set_crs(CRS, inplace=True)
291
+ gdf.to_file(DATA_LIBRARY / "ongc.gpkg", driver="GPKG", engine="pyogrio", index=True)
292
+ # crs=CRS, engine="pyogrio",
293
+
294
+ print("Total nebula outlines: " + str(len(outlines)))
295
+ # result = gpd.read_file(HERE.parent / "build" / "ngc.gpkg")
296
+ # print(result)
297
+
298
+ # Zip it up!
299
+ zip_file(DATA_LIBRARY / "ongc.gpkg")
@@ -0,0 +1,16 @@
1
+ import zipfile
2
+ from pathlib import Path
3
+
4
+
5
+ HERE = Path(__file__).resolve().parent
6
+ ROOT = HERE.parent.parent.parent.parent
7
+ RAW_DATA_PATH = ROOT / "raw"
8
+ BUILD_PATH = ROOT / "build"
9
+ DATA_LIBRARY = HERE.parent / "library"
10
+
11
+ CRS = "+ellps=sphere +f=0 +proj=latlong +axis=wnu +a=6378137 +no_defs"
12
+
13
+
14
+ def zip_file(filename):
15
+ zipped = zipfile.ZipFile(f"{filename}.zip", "w", zipfile.ZIP_DEFLATED)
16
+ zipped.write(filename)