starplot 0.20.5__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 (98) hide show
  1. starplot/__init__.py +59 -0
  2. starplot/callables.py +176 -0
  3. starplot/cli.py +29 -0
  4. starplot/config.py +80 -0
  5. starplot/coordinates.py +7 -0
  6. starplot/data/__init__.py +22 -0
  7. starplot/data/catalogs.py +356 -0
  8. starplot/data/constellations.py +71 -0
  9. starplot/data/db.py +33 -0
  10. starplot/data/dsos.py +85 -0
  11. starplot/data/ecliptic.py +201 -0
  12. starplot/data/library/constellation_names.parquet +0 -0
  13. starplot/data/library/dso_names.parquet +0 -0
  14. starplot/data/library/readme.md +1 -0
  15. starplot/data/library/star_designations.parquet +0 -0
  16. starplot/data/stars.py +79 -0
  17. starplot/data/translations.py +408 -0
  18. starplot/data/utils.py +35 -0
  19. starplot/geometry.py +319 -0
  20. starplot/mixins.py +360 -0
  21. starplot/models/__init__.py +21 -0
  22. starplot/models/base.py +77 -0
  23. starplot/models/comet.py +302 -0
  24. starplot/models/constellation.py +151 -0
  25. starplot/models/dso.py +317 -0
  26. starplot/models/milky_way.py +30 -0
  27. starplot/models/moon.py +130 -0
  28. starplot/models/objects.py +29 -0
  29. starplot/models/observer.py +125 -0
  30. starplot/models/optics.py +342 -0
  31. starplot/models/planet.py +137 -0
  32. starplot/models/satellite.py +138 -0
  33. starplot/models/star.py +253 -0
  34. starplot/models/sun.py +62 -0
  35. starplot/plots/__init__.py +7 -0
  36. starplot/plots/base.py +1023 -0
  37. starplot/plots/galaxy.py +372 -0
  38. starplot/plots/horizon.py +543 -0
  39. starplot/plots/map.py +515 -0
  40. starplot/plots/optic.py +469 -0
  41. starplot/plots/zenith.py +217 -0
  42. starplot/plotters/__init__.py +9 -0
  43. starplot/plotters/arrow.py +174 -0
  44. starplot/plotters/constellations.py +298 -0
  45. starplot/plotters/debug.py +21 -0
  46. starplot/plotters/dsos.py +294 -0
  47. starplot/plotters/experimental.py +722 -0
  48. starplot/plotters/gradients.py +153 -0
  49. starplot/plotters/legend.py +253 -0
  50. starplot/plotters/milkyway.py +51 -0
  51. starplot/plotters/stars.py +319 -0
  52. starplot/plotters/text.py +802 -0
  53. starplot/profile.py +16 -0
  54. starplot/projections.py +184 -0
  55. starplot/styles/__init__.py +6 -0
  56. starplot/styles/base.py +1344 -0
  57. starplot/styles/ext/antique.yml +175 -0
  58. starplot/styles/ext/blue_dark.yml +163 -0
  59. starplot/styles/ext/blue_gold.yml +147 -0
  60. starplot/styles/ext/blue_light.yml +123 -0
  61. starplot/styles/ext/blue_medium.yml +142 -0
  62. starplot/styles/ext/blue_night.yml +185 -0
  63. starplot/styles/ext/cb_wong.yml +124 -0
  64. starplot/styles/ext/color_print.yml +111 -0
  65. starplot/styles/ext/gradient_presets.yml +158 -0
  66. starplot/styles/ext/grayscale.yml +94 -0
  67. starplot/styles/ext/grayscale_dark.yml +136 -0
  68. starplot/styles/ext/map.yml +12 -0
  69. starplot/styles/ext/nord.yml +158 -0
  70. starplot/styles/ext/optic.yml +20 -0
  71. starplot/styles/ext/publication.yml +8 -0
  72. starplot/styles/extensions.py +129 -0
  73. starplot/styles/fonts-library/gfs-didot/DESCRIPTION.en_us.html +9 -0
  74. starplot/styles/fonts-library/gfs-didot/GFSDidot-Regular.ttf +0 -0
  75. starplot/styles/fonts-library/gfs-didot/METADATA.pb +16 -0
  76. starplot/styles/fonts-library/gfs-didot/OFL.txt +94 -0
  77. starplot/styles/fonts-library/inter/Inter-Bold.ttf +0 -0
  78. starplot/styles/fonts-library/inter/Inter-BoldItalic.ttf +0 -0
  79. starplot/styles/fonts-library/inter/Inter-ExtraBold.ttf +0 -0
  80. starplot/styles/fonts-library/inter/Inter-ExtraLight.ttf +0 -0
  81. starplot/styles/fonts-library/inter/Inter-ExtraLightItalic.ttf +0 -0
  82. starplot/styles/fonts-library/inter/Inter-Italic.ttf +0 -0
  83. starplot/styles/fonts-library/inter/Inter-Light.ttf +0 -0
  84. starplot/styles/fonts-library/inter/Inter-LightItalic.ttf +0 -0
  85. starplot/styles/fonts-library/inter/Inter-Regular.ttf +0 -0
  86. starplot/styles/fonts-library/inter/Inter-SemiBold.ttf +0 -0
  87. starplot/styles/fonts-library/inter/Inter-SemiBoldItalic.ttf +0 -0
  88. starplot/styles/fonts-library/inter/LICENSE.txt +92 -0
  89. starplot/styles/fonts.py +15 -0
  90. starplot/styles/helpers.py +93 -0
  91. starplot/styles/markers.py +308 -0
  92. starplot/utils.py +169 -0
  93. starplot/warnings.py +21 -0
  94. starplot-0.20.5.dist-info/METADATA +146 -0
  95. starplot-0.20.5.dist-info/RECORD +98 -0
  96. starplot-0.20.5.dist-info/WHEEL +4 -0
  97. starplot-0.20.5.dist-info/entry_points.txt +3 -0
  98. starplot-0.20.5.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,92 @@
1
+ Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+ -----------------------------------------------------------
8
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
9
+ -----------------------------------------------------------
10
+
11
+ PREAMBLE
12
+ The goals of the Open Font License (OFL) are to stimulate worldwide
13
+ development of collaborative font projects, to support the font creation
14
+ efforts of academic and linguistic communities, and to provide a free and
15
+ open framework in which fonts may be shared and improved in partnership
16
+ with others.
17
+
18
+ The OFL allows the licensed fonts to be used, studied, modified and
19
+ redistributed freely as long as they are not sold by themselves. The
20
+ fonts, including any derivative works, can be bundled, embedded,
21
+ redistributed and/or sold with any software provided that any reserved
22
+ names are not used by derivative works. The fonts and derivatives,
23
+ however, cannot be released under any other type of license. The
24
+ requirement for fonts to remain under this license does not apply
25
+ to any document created using the fonts or their derivatives.
26
+
27
+ DEFINITIONS
28
+ "Font Software" refers to the set of files released by the Copyright
29
+ Holder(s) under this license and clearly marked as such. This may
30
+ include source files, build scripts and documentation.
31
+
32
+ "Reserved Font Name" refers to any names specified as such after the
33
+ copyright statement(s).
34
+
35
+ "Original Version" refers to the collection of Font Software components as
36
+ distributed by the Copyright Holder(s).
37
+
38
+ "Modified Version" refers to any derivative made by adding to, deleting,
39
+ or substituting -- in part or in whole -- any of the components of the
40
+ Original Version, by changing formats or by porting the Font Software to a
41
+ new environment.
42
+
43
+ "Author" refers to any designer, engineer, programmer, technical
44
+ writer or other person who contributed to the Font Software.
45
+
46
+ PERMISSION AND CONDITIONS
47
+ Permission is hereby granted, free of charge, to any person obtaining
48
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
49
+ redistribute, and sell modified and unmodified copies of the Font
50
+ Software, subject to the following conditions:
51
+
52
+ 1) Neither the Font Software nor any of its individual components,
53
+ in Original or Modified Versions, may be sold by itself.
54
+
55
+ 2) Original or Modified Versions of the Font Software may be bundled,
56
+ redistributed and/or sold with any software, provided that each copy
57
+ contains the above copyright notice and this license. These can be
58
+ included either as stand-alone text files, human-readable headers or
59
+ in the appropriate machine-readable metadata fields within text or
60
+ binary files as long as those fields can be easily viewed by the user.
61
+
62
+ 3) No Modified Version of the Font Software may use the Reserved Font
63
+ Name(s) unless explicit written permission is granted by the corresponding
64
+ Copyright Holder. This restriction only applies to the primary font name as
65
+ presented to the users.
66
+
67
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
68
+ Software shall not be used to promote, endorse or advertise any
69
+ Modified Version, except to acknowledge the contribution(s) of the
70
+ Copyright Holder(s) and the Author(s) or with their explicit written
71
+ permission.
72
+
73
+ 5) The Font Software, modified or unmodified, in part or in whole,
74
+ must be distributed entirely under this license, and must not be
75
+ distributed under any other license. The requirement for fonts to
76
+ remain under this license does not apply to any document created
77
+ using the Font Software.
78
+
79
+ TERMINATION
80
+ This license becomes null and void if any of the above conditions are
81
+ not met.
82
+
83
+ DISCLAIMER
84
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
85
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
86
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
87
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
88
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
89
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
90
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
91
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
92
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,15 @@
1
+ from pathlib import Path
2
+
3
+ from matplotlib import font_manager
4
+
5
+ HERE = Path(__file__).resolve().parent
6
+ FONTS_PATH = HERE / "fonts-library"
7
+
8
+
9
+ def load():
10
+ """Loads all fonts in ./fonts-library"""
11
+ font_dirs = [FONTS_PATH]
12
+ font_files = font_manager.findSystemFonts(fontpaths=font_dirs)
13
+
14
+ for font_file in font_files:
15
+ font_manager.fontManager.addfont(font_file)
@@ -0,0 +1,93 @@
1
+ import inspect
2
+ import json
3
+
4
+ from functools import wraps
5
+
6
+
7
+ def merge_dict(dict_1: dict, dict_2: dict) -> None:
8
+ """
9
+
10
+ Args:
11
+ dict_1: Base dictionary to merge into
12
+ dict_2: Dictionary to merge into the base (dict_1)
13
+
14
+ Returns:
15
+ None (dict_1 is modified directly)
16
+ """
17
+ for k in dict_2.keys():
18
+ if k in dict_1 and isinstance(dict_1[k], dict) and isinstance(dict_2[k], dict):
19
+ merge_dict(dict_1[k], dict_2[k])
20
+ else:
21
+ dict_1[k] = dict_2[k]
22
+
23
+
24
+ def use_style(style_class, style_attr: str = None):
25
+ def decorator(func):
26
+ params = list(inspect.signature(func).parameters.keys())
27
+ style_pos = params.index("style") if "style" in params else -1
28
+
29
+ @wraps(func)
30
+ def wrapper(*args, **kwargs):
31
+ style_in_positional = 0 <= style_pos < len(args)
32
+ style = args[style_pos] if style_in_positional else kwargs.get("style")
33
+ style_kwargs = {
34
+ kw: value for kw, value in kwargs.items() if kw.startswith("style__")
35
+ }
36
+
37
+ if style and style_kwargs:
38
+ raise ValueError(
39
+ "Too many style arguments types. Specify only style or style kwargs"
40
+ )
41
+
42
+ if style and isinstance(style, dict):
43
+ if style_attr is not None:
44
+ # if style is a dict and there's a base style, then we just want to merge the changes
45
+ base_style = getattr(args[0].style, style_attr).model_dump_json()
46
+ base_style = json.loads(base_style)
47
+
48
+ merge_dict(base_style, style)
49
+
50
+ kwargs["style"] = style_class(**base_style)
51
+ else:
52
+ kwargs["style"] = style_class(**style)
53
+
54
+ elif style_kwargs:
55
+ # if style kwargs are used - build style dict and pass as "style" variable
56
+ styling_overrides = {}
57
+ for key, val in style_kwargs.items():
58
+ t = styling_overrides
59
+ prev = None
60
+ for part in key.split("__")[1:]:
61
+ if prev is not None:
62
+ t = t.setdefault(prev, {})
63
+ prev = part
64
+ t.setdefault(prev, val)
65
+ if style_attr is not None:
66
+ base_style = getattr(args[0].style, style_attr).model_dump_json()
67
+ base_style = json.loads(base_style)
68
+
69
+ merge_dict(base_style, styling_overrides)
70
+
71
+ kwargs["style"] = style_class(**base_style)
72
+ else:
73
+ kwargs["style"] = style_class(**styling_overrides)
74
+
75
+ # remove 'style__' kwargs so they're not passed to wrapped function
76
+ kwargs = {
77
+ kw: value
78
+ for kw, value in kwargs.items()
79
+ if not kw.startswith("style__")
80
+ }
81
+
82
+ elif style is None and style_attr is not None:
83
+ # if no style overrides and there's a base style, then just pass the base style
84
+ kwargs["style"] = getattr(args[0].style, style_attr, None)
85
+
86
+ elif style is None and not style_in_positional:
87
+ kwargs["style"] = style_class()
88
+
89
+ return func(*args, **kwargs)
90
+
91
+ return wrapper
92
+
93
+ return decorator
@@ -0,0 +1,308 @@
1
+ from functools import cache
2
+
3
+ from matplotlib import transforms
4
+ from matplotlib.path import Path
5
+
6
+ import numpy as np
7
+
8
+
9
+ def ellipse_points(
10
+ center_x, center_y, semi_major_axis, semi_minor_axis, num_points=100
11
+ ):
12
+ """Generates points on an ellipse.
13
+
14
+ Args:
15
+ center_x: X coordinate of the center.
16
+ center_y: Y coordinate of the center.
17
+ semi_major_axis: Length of the semi-major axis.
18
+ semi_minor_axis: Length of the semi-minor axis.
19
+ num_points: Number of points to generate.
20
+
21
+ Returns:
22
+ A list of (x, y) coordinates of the ellipse points.
23
+ """
24
+
25
+ theta = np.linspace(0, 2 * np.pi, num_points)
26
+ x = center_x + semi_major_axis * np.cos(theta)
27
+ y = center_y + semi_minor_axis * np.sin(theta)
28
+
29
+ return list(zip(x, y))
30
+
31
+
32
+ @cache
33
+ def ellipse():
34
+ verts = ellipse_points(0, 0, 1, 0.5)
35
+
36
+ codes = [
37
+ Path.MOVETO,
38
+ ]
39
+ codes.extend([Path.LINETO] * 98)
40
+ codes.extend([Path.CLOSEPOLY])
41
+ p = Path(verts, codes).transformed(transforms.Affine2D().rotate_deg(15))
42
+
43
+ return p
44
+
45
+
46
+ @cache
47
+ def circle_cross():
48
+ verts = ellipse_points(0, 0, 1, 1)
49
+
50
+ codes = [
51
+ Path.MOVETO,
52
+ ]
53
+ codes.extend([Path.LINETO] * 98)
54
+ codes.extend([Path.CLOSEPOLY])
55
+
56
+ verts.extend(
57
+ [
58
+ (-1, 0),
59
+ (1, 0),
60
+ (0, 1),
61
+ (0, -1),
62
+ ]
63
+ )
64
+ codes.extend(
65
+ [
66
+ Path.MOVETO,
67
+ Path.LINETO,
68
+ Path.MOVETO,
69
+ Path.LINETO,
70
+ ]
71
+ )
72
+
73
+ return Path(verts, codes)
74
+
75
+
76
+ @cache
77
+ def circle_crosshair():
78
+ verts = ellipse_points(0, 0, 1, 1)
79
+
80
+ codes = [
81
+ Path.MOVETO,
82
+ ]
83
+ codes.extend([Path.LINETO] * 98)
84
+ codes.extend([Path.CLOSEPOLY])
85
+
86
+ verts.extend(
87
+ [
88
+ (-1, 0),
89
+ (-1.7, 0),
90
+ (0, 1),
91
+ (0, 1.7),
92
+ (1, 0),
93
+ (1.7, 0),
94
+ (0, -1),
95
+ (0, -1.7),
96
+ ]
97
+ )
98
+ codes.extend(
99
+ [
100
+ Path.MOVETO,
101
+ Path.LINETO,
102
+ Path.MOVETO,
103
+ Path.LINETO,
104
+ Path.MOVETO,
105
+ Path.LINETO,
106
+ Path.MOVETO,
107
+ Path.LINETO,
108
+ ]
109
+ )
110
+
111
+ return Path(verts, codes)
112
+
113
+
114
+ @cache
115
+ def circle_dot():
116
+ verts = ellipse_points(0, 0, 1, 1)
117
+
118
+ codes = [
119
+ Path.MOVETO,
120
+ ]
121
+ codes.extend([Path.LINETO] * 98)
122
+ codes.extend([Path.CLOSEPOLY])
123
+
124
+ verts.extend(ellipse_points(0, 0, 0.6, 0.6))
125
+
126
+ codes.extend([Path.MOVETO])
127
+ codes.extend([Path.LINETO] * 98)
128
+ codes.extend([Path.CLOSEPOLY])
129
+
130
+ return Path(verts, codes)
131
+
132
+
133
+ @cache
134
+ def circle_dotted_rings(num_rings=2):
135
+ verts = ellipse_points(0, 0, 1, 1)
136
+
137
+ codes = [
138
+ Path.MOVETO,
139
+ ]
140
+ codes.extend([Path.LINETO] * 98)
141
+ codes.extend([Path.CLOSEPOLY])
142
+
143
+ step = 2
144
+ for n in range(num_rings):
145
+ radius = 1.4 + n * 0.3
146
+ ring_verts = ellipse_points(0, 0, radius, radius)
147
+
148
+ for k in range(step, 100, step):
149
+ if k % (step * 2):
150
+ verts.extend(ring_verts[k : k + step])
151
+ codes.extend([Path.MOVETO])
152
+ codes.extend([Path.LINETO] * (step - 1))
153
+
154
+ return Path(verts, codes)
155
+
156
+
157
+ @cache
158
+ def circle_line():
159
+ # verts = list(ellipse_points(0, 0, 1, 1))
160
+ # size = len(verts)
161
+ # top = verts[2 : int(size / 2 - 2)]
162
+ # bottom = verts[int(size / 2 + 2) : size - 2]
163
+ # y = top[-1][1]
164
+ # top = [[2, 0], [2, y]] + top + [[-2, top[-1][1]], [-2, 0]]
165
+ # bottom = [[-2, 0], [-2, -1 * y]] + bottom + [[2, -1 * y], [2, 0], [2, 0]]
166
+
167
+ points = [
168
+ [2, 0],
169
+ [2, 0.18925124436040974],
170
+ (0.9819286972627067, 0.18925124436041021),
171
+ (0.9679487013963562, 0.2511479871810792),
172
+ (0.9500711177409454, 0.3120334456984871),
173
+ (0.9283679330160726, 0.3716624556603276),
174
+ (0.9029265382866212, 0.42979491208917164),
175
+ (0.8738493770697849, 0.4861967361004687),
176
+ (0.8412535328311812, 0.5406408174555976),
177
+ (0.8052702575310586, 0.5929079290546404),
178
+ (0.766044443118978, 0.6427876096865393),
179
+ (0.7237340381050701, 0.690079011482112),
180
+ (0.6785094115571322, 0.7345917086575333),
181
+ (0.6305526670845225, 0.7761464642917568),
182
+ (0.5800569095711982, 0.8145759520503357),
183
+ (0.5272254676105024, 0.8497254299495144),
184
+ (0.4722710747726827, 0.8814533634475821),
185
+ (0.41541501300188644, 0.9096319953545183),
186
+ (0.3568862215918719, 0.9341478602651067),
187
+ (0.2969203753282749, 0.9549022414440739),
188
+ (0.23575893550942728, 0.9718115683235417),
189
+ (0.17364817766693041, 0.984807753012208),
190
+ (0.1108381999010111, 0.9938384644612541),
191
+ (0.04758191582374218, 0.998867339183008),
192
+ (-0.01586596383480803, 0.9998741276738751),
193
+ (-0.07924995685678854, 0.9968547759519424),
194
+ (-0.14231483827328523, 0.9898214418809327),
195
+ (-0.20480666806519074, 0.9788024462147787),
196
+ (-0.26647381369003503, 0.963842158559942),
197
+ (-0.32706796331742166, 0.9450008187146685),
198
+ (-0.3863451256931287, 0.9223542941045814),
199
+ (-0.4440666126057741, 0.8959937742913359),
200
+ (-0.5000000000000002, 0.8660254037844385),
201
+ (-0.5539200638661103, 0.8325698546347714),
202
+ (-0.6056096871376668, 0.795761840530832),
203
+ (-0.654860733945285, 0.7557495743542583),
204
+ (-0.7014748877063214, 0.7126941713788627),
205
+ (-0.7452644496757547, 0.6667690005162917),
206
+ (-0.7860530947427875, 0.6181589862206051),
207
+ (-0.8236765814298327, 0.5670598638627709),
208
+ (-0.8579834132349771, 0.5136773915734063),
209
+ (-0.8888354486549234, 0.4582265217274105),
210
+ (-0.9161084574320696, 0.4009305354066136),
211
+ (-0.9396926207859083, 0.3420201433256689),
212
+ (-0.9594929736144974, 0.28173255684142967),
213
+ (-0.975429786885407, 0.2511479871810792),
214
+ (-0.9874388886763943, 0.18925124436040974),
215
+ [-2, 0.18925124436040974],
216
+ [-2, 0],
217
+ [-2, 0],
218
+ [-2, -0.18925124436040974],
219
+ (-0.9874388886763943, -0.18925124436040974),
220
+ (-0.975429786885407, -0.2511479871810792),
221
+ (-0.9594929736144974, -0.28173255684142984),
222
+ (-0.9396926207859084, -0.34202014332566866),
223
+ (-0.9161084574320696, -0.4009305354066138),
224
+ (-0.8888354486549235, -0.4582265217274103),
225
+ (-0.857983413234977, -0.5136773915734064),
226
+ (-0.8236765814298328, -0.5670598638627706),
227
+ (-0.7860530947427874, -0.6181589862206053),
228
+ (-0.7452644496757548, -0.6667690005162915),
229
+ (-0.7014748877063213, -0.7126941713788629),
230
+ (-0.6548607339452852, -0.7557495743542582),
231
+ (-0.6056096871376666, -0.7957618405308321),
232
+ (-0.5539200638661105, -0.8325698546347713),
233
+ (-0.4999999999999996, -0.8660254037844388),
234
+ (-0.44406661260577396, -0.895993774291336),
235
+ (-0.3863451256931287, -0.9223542941045814),
236
+ (-0.3270679633174219, -0.9450008187146683),
237
+ (-0.26647381369003464, -0.9638421585599422),
238
+ (-0.20480666806519054, -0.9788024462147787),
239
+ (-0.14231483827328523, -0.9898214418809327),
240
+ (-0.07924995685678879, -0.9968547759519423),
241
+ (-0.01586596383480761, -0.9998741276738751),
242
+ (0.04758191582374238, -0.998867339183008),
243
+ (0.11083819990101086, -0.9938384644612541),
244
+ (0.17364817766692997, -0.9848077530122081),
245
+ (0.23575893550942748, -0.9718115683235417),
246
+ (0.2969203753282749, -0.9549022414440739),
247
+ (0.35688622159187167, -0.9341478602651068),
248
+ (0.4154150130018868, -0.9096319953545182),
249
+ (0.4722710747726829, -0.881453363447582),
250
+ (0.5272254676105024, -0.8497254299495144),
251
+ (0.5800569095711979, -0.8145759520503358),
252
+ (0.6305526670845228, -0.7761464642917566),
253
+ (0.6785094115571323, -0.7345917086575332),
254
+ (0.7237340381050701, -0.690079011482112),
255
+ (0.7660444431189778, -0.6427876096865396),
256
+ (0.8052702575310587, -0.5929079290546402),
257
+ (0.8412535328311812, -0.5406408174555974),
258
+ (0.8738493770697849, -0.4861967361004688),
259
+ (0.9029265382866211, -0.4297949120891719),
260
+ (0.9283679330160727, -0.37166245566032724),
261
+ (0.9500711177409454, -0.31203344569848707),
262
+ (0.9679487013963562, -0.2511479871810794),
263
+ (0.9819286972627068, -0.18925124436040974),
264
+ [2, -0.18925124436040974],
265
+ [2, 0],
266
+ [2, 0],
267
+ ]
268
+
269
+ codes = [
270
+ Path.MOVETO,
271
+ ]
272
+ codes.extend([Path.LINETO] * (len(points) - 2))
273
+ codes.extend([Path.CLOSEPOLY])
274
+
275
+ return Path(points, codes)
276
+
277
+
278
+ def circle_line_deprecated():
279
+ verts = ellipse_points(0, 0, 1, 1)
280
+
281
+ codes = [
282
+ Path.MOVETO,
283
+ ]
284
+ codes.extend([Path.LINETO] * 98)
285
+ codes.extend([Path.CLOSEPOLY])
286
+
287
+ verts.extend(
288
+ [
289
+ (-2, 0),
290
+ (2, 0),
291
+ (-2, 0.04),
292
+ (2, 0.04),
293
+ (-2, -0.04),
294
+ (2, -0.04),
295
+ ]
296
+ )
297
+ codes.extend(
298
+ [
299
+ Path.MOVETO,
300
+ Path.LINETO,
301
+ Path.MOVETO,
302
+ Path.LINETO,
303
+ Path.MOVETO,
304
+ Path.LINETO,
305
+ ]
306
+ )
307
+
308
+ return Path(verts, codes)
starplot/utils.py ADDED
@@ -0,0 +1,169 @@
1
+ import math
2
+ from datetime import datetime, timezone
3
+
4
+ import numpy as np
5
+
6
+
7
+ def in_circle(x, y, center_x=0, center_y=0, radius=0.9) -> bool:
8
+ """Determine if a point (x,y) is inside a circle"""
9
+ return (x - center_x) ** 2 + (y - center_y) ** 2 < (radius**2)
10
+
11
+
12
+ def lon_to_ra(lon: float):
13
+ pos_lon = lon + 180
14
+ ra = 12 - (24 * pos_lon / 360)
15
+ if ra < 0:
16
+ ra += 24
17
+ return ra
18
+
19
+
20
+ def ra_to_lon(ra):
21
+ lon = ra * -15
22
+ if lon < -180:
23
+ lon += 360
24
+
25
+ return lon
26
+
27
+
28
+ def lon_to_ra_hms(lon: float) -> (int, int, int):
29
+ """Converts longitude back to right ascension
30
+
31
+ Args:
32
+ lon: Longitude to convert
33
+
34
+ Returns:
35
+ Tuple of ints: (hours, minutes, seconds)
36
+ """
37
+ pos_lon = lon + 180
38
+ ra_decimal = 12 - (24 * pos_lon / 360)
39
+
40
+ hour = math.floor(ra_decimal)
41
+
42
+ min_decimal = 60 * (ra_decimal - hour)
43
+ minutes = math.floor(min_decimal)
44
+
45
+ sec_decimal = 60 * (min_decimal - minutes)
46
+ seconds = math.floor(sec_decimal)
47
+
48
+ if hour < 0:
49
+ hour += 24
50
+
51
+ if seconds >= 60:
52
+ minutes += 1
53
+ seconds -= 60
54
+
55
+ return hour, minutes, seconds
56
+
57
+
58
+ def dec_str_to_float(dec_str):
59
+ """
60
+ Converts declination strings to a single float:
61
+
62
+ >> dec_str_to_float("-05:20:30")
63
+ >> -5.341667
64
+
65
+ """
66
+ multiplier = 1
67
+ dec_d, dec_m, dec_s = [float(d) for d in dec_str.split(":")]
68
+
69
+ if dec_str.startswith("-"):
70
+ multiplier = -1
71
+
72
+ dec_f = dec_d + multiplier * ((dec_m / 60) + (dec_s / 3600))
73
+
74
+ return round(dec_f, 6)
75
+
76
+
77
+ def bv_to_hex_color(bv_index):
78
+ """
79
+ Returns hex color for a BV Index
80
+
81
+ List of BV colors from -0.40 -> 2.00 (with 0.05 increments)
82
+ source: http://www.vendian.org/mncharity/dir3/starcolor/details.html
83
+ """
84
+ bv_colors = [
85
+ "#9bb2ff",
86
+ "#9eb5ff",
87
+ "#a3b9ff",
88
+ "#aabfff",
89
+ "#b2c5ff",
90
+ "#bbccff",
91
+ "#c4d2ff",
92
+ "#ccd8ff",
93
+ "#d3ddff",
94
+ "#dae2ff",
95
+ "#dfe5ff",
96
+ "#e4e9ff",
97
+ "#e9ecff",
98
+ "#eeefff",
99
+ "#f3f2ff",
100
+ "#f8f6ff",
101
+ "#fef9ff",
102
+ "#fff9fb",
103
+ "#fff7f5",
104
+ "#fff5ef",
105
+ "#fff3ea",
106
+ "#fff1e5",
107
+ "#ffefe0",
108
+ "#ffeddb",
109
+ "#ffebd6",
110
+ "#ffe9d2",
111
+ "#ffe8ce",
112
+ "#ffe6ca",
113
+ "#ffe5c6",
114
+ "#ffe3c3",
115
+ "#ffe2bf",
116
+ "#ffe0bb",
117
+ "#ffdfb8",
118
+ "#ffddb4",
119
+ "#ffdbb0",
120
+ "#ffdaad",
121
+ "#ffd8a9",
122
+ "#ffd6a5",
123
+ "#ffd5a1",
124
+ "#ffd29c",
125
+ "#ffd096",
126
+ "#ffcc8f",
127
+ "#ffc885",
128
+ "#ffc178",
129
+ "#ffb765",
130
+ "#ffa94b",
131
+ "#ff9523",
132
+ "#ff7b00",
133
+ "#ff5200",
134
+ ]
135
+ color_index = round((bv_index + 0.4) / 0.05)
136
+
137
+ if color_index < 0 or color_index > len(bv_colors) - 1:
138
+ return None
139
+
140
+ return bv_colors[color_index]
141
+
142
+
143
+ def azimuth_to_string(azimuth_degrees: int):
144
+ if azimuth_degrees >= 360:
145
+ azimuth_degrees -= 360
146
+ direction_strings = ["N", "NE", "E", "SE", "S", "SW", "W", "NW", "N"]
147
+ return direction_strings[int(azimuth_degrees / 40)]
148
+
149
+
150
+ def dt_or_now(dt):
151
+ return dt or datetime.now(tz=timezone.utc)
152
+
153
+
154
+ def points_on_line(start, end, num_points=100):
155
+ """Generates points along a line segment.
156
+
157
+ Args:
158
+ start (tuple): (x, y) coordinates of the starting point.
159
+ end (tuple): (x, y) coordinates of the ending point.
160
+ num_points (int): Number of points to generate.
161
+
162
+ Returns:
163
+ list: List of (x, y) coordinates of the generated points.
164
+ """
165
+
166
+ x_coords = np.linspace(start[0], end[0], num_points)
167
+ y_coords = np.linspace(start[1], end[1], num_points)
168
+
169
+ return list(zip(x_coords, y_coords))
starplot/warnings.py ADDED
@@ -0,0 +1,21 @@
1
+ import warnings
2
+
3
+ import matplotlib
4
+
5
+
6
+ def suppress():
7
+ # ignore noisy matplotlib warnings
8
+ warnings.filterwarnings(
9
+ "ignore",
10
+ message="Setting the 'color' property will override the edgecolor or facecolor properties",
11
+ )
12
+
13
+ # Silence various matplotlib logs (e.g. building font cache)
14
+ matplotlib.set_loglevel("critical")
15
+
16
+ # Silence all user warnings
17
+ warnings.filterwarnings("ignore", category=UserWarning)
18
+
19
+ # Silence noisy cartopy warnings
20
+ warnings.filterwarnings("ignore", module="cartopy")
21
+ warnings.filterwarnings("ignore", module="shapely")