diff --git a/docs/tutorial/08.md b/docs/tutorial/08.md
index 4f110d90..4202a84e 100644
--- a/docs/tutorial/08.md
+++ b/docs/tutorial/08.md
@@ -11,7 +11,7 @@ title: 8 - Using Callables
[Callables](/reference-callables/) allow you to define your own functions for calculating a few of the style properties for stars: size, alpha (opacity), color, and their labels. DSOs support callables for alpha and labels only. Starplot has a few basic callables built-in, but you can also create your own.
-Here's an example that uses the built-in callable `color_by_bv` to create an optic plot of [Antares](https://en.wikipedia.org/wiki/Antares) that colorizes the stars based on their B-V index (Antares B-V is 1.83 so it appears red/orange in the sky):
+Here's an example that uses the built-in callable `color_by_bv` to create an optic plot of [Antares](https://en.wikipedia.org/wiki/Antares) that colorizes the stars based on their B-V index (Antares' B-V is 1.83 so it appears red/orange in the sky):
```python linenums="1"
--8<-- "tutorial/tutorial_08.py"
diff --git a/examples/examples.py b/examples/examples.py
index 2901dd2a..4ca9b2e2 100755
--- a/examples/examples.py
+++ b/examples/examples.py
@@ -5,10 +5,15 @@
import time
from PIL import Image
-from multiprocessing import Pool, Process
+from multiprocessing import Pool
start = time.time()
+skip = [
+ # "map_milky_way_stars.py",
+ # "stuff.py",
+]
+
def thumbnail(filename, max_dimension=900):
print(filename)
@@ -23,16 +28,17 @@ def get_example_names():
for filename in glob.iglob("*.py"):
if filename.endswith("examples.py"):
continue
+
+ if filename in skip:
+ continue
+
filenames.append(filename)
return filenames
def run_example(filename):
- import subprocess
-
print(f"Running {filename}")
-
subprocess.call(["python", filename])
@@ -42,16 +48,6 @@ def run_example(filename):
with Pool(5) as pool:
pool.map(run_example, example_files)
-# Run all examples
-# for filename in glob.iglob("*.py"):
-# if filename.endswith("examples.py"):
-# continue
-
-# # if filename != "map_lyra.py":
-# # continue
-# print(f"Running {filename}")
-# subprocess.call(f"python {filename}", shell=True)
-
# Create thumbnail images for the examples list page
image_files = glob.glob("*.png")
pool = Pool(5)
diff --git a/examples/map_big.py b/examples/map_big.py
index 4060362e..dc74e471 100644
--- a/examples/map_big.py
+++ b/examples/map_big.py
@@ -1,5 +1,4 @@
-from starplot import MapPlot, Projection
-from starplot.models import DSO
+from starplot import MapPlot, Projection, DSO, Star
from starplot.styles import PlotStyle, extensions
@@ -8,10 +7,6 @@
extensions.MAP,
)
-style.star.label.font_size = 4
-style.constellation.label.font_size = 6
-style.constellation.line.width = 2
-
p = MapPlot(
projection=Projection.MILLER,
ra_min=0,
@@ -20,18 +15,42 @@
dec_max=80,
style=style,
resolution=6000,
+ # since this map has a very large extent, let's scale everything down
+ scale=0.8,
+)
+p.gridlines()
+p.constellations(
+ style__label__font_size=28,
)
-p.stars(mag=8)
-p.dsos(
+p.stars(mag=6, where_labels=[Star.magnitude < 2.1])
+p.open_clusters(
labels=None,
where=[
DSO.magnitude <= 8,
- DSO.size > 0.05,
],
+ true_size=False,
)
-p.gridlines()
+p.globular_clusters(
+ labels=None,
+ where=[
+ DSO.magnitude <= 9,
+ ],
+ true_size=False,
+)
+p.galaxies(
+ labels=None,
+ where=[
+ DSO.magnitude <= 10,
+ ],
+ true_size=False,
+)
+p.nebula(
+ labels=None,
+ where=[(DSO.magnitude <= 10) | (DSO.magnitude.is_null()), DSO.size > 0.05],
+)
+
p.milky_way()
-p.ecliptic(style={"line": {"style": "dashed"}})
+p.ecliptic()
p.celestial_equator()
p.export("map_big.png", padding=0.5)
diff --git a/examples/map_big_dipper.py b/examples/map_big_dipper.py
index 04f8a4bb..6e775f81 100644
--- a/examples/map_big_dipper.py
+++ b/examples/map_big_dipper.py
@@ -21,16 +21,9 @@
Star.dec > 45,
Star.dec < 64,
],
- # By default, star sizes are calculated based on their magnitude first,
- # but then that result will be multiplied by the star's marker size in the PlotStyle
- # so, adjusting the star marker size is a way to make all stars bigger or smaller
- style__marker__size=96,
style__marker__symbol="star",
style__marker__color="#ffff73",
- style__label__font_size=13,
+ style__label__font_size=20,
style__label__font_weight="normal",
- style__label__anchor_point="bottom right",
- style__label__offset_x=16,
- style__label__offset_y=-52,
)
p.export("map_big_dipper.png", transparent=True)
diff --git a/examples/map_canis_major.py b/examples/map_canis_major.py
index 343e7146..a9cc5ed0 100644
--- a/examples/map_canis_major.py
+++ b/examples/map_canis_major.py
@@ -2,17 +2,8 @@
from starplot.styles import PlotStyle, extensions
style = PlotStyle().extend(
- extensions.CB_WONG,
+ extensions.BLUE_LIGHT,
extensions.MAP,
- {
- "star": {"marker": {"size": 40}, "label": {"font_size": 13}},
- "bayer_labels": {
- "font_name": "GFS Didot", # use a better font for Greek letters
- "font_size": 10,
- "font_alpha": 0.9,
- },
- "dso_open_cluster": {"marker": {"size": 20}, "label": {"font_size": 10}},
- },
)
canis_major = Constellation.get(name="Canis Major")
p = MapPlot(
@@ -22,16 +13,15 @@
dec_min=-35,
dec_max=-9,
style=style,
- resolution=2000,
+ resolution=3600,
clip_path=canis_major.boundary,
+ scale=1.3,
)
-p.open_clusters(mag=8, true_size=False, label_fn=lambda d: f"{d.ngc}")
-p.stars(mag=9, bayer_labels=True, catalog="big-sky-mag11")
p.constellations(
where=[Constellation.iau_id == "cma"],
- style__line__width=7,
- style__label__font_size=18,
)
p.constellation_borders()
+p.open_clusters(mag=8, true_size=False, label_fn=lambda d: f"{d.ngc}")
+p.stars(mag=9, bayer_labels=True, catalog="big-sky-mag11")
p.ax.set_axis_off()
p.export("map_canis_major.png", padding=0, transparent=True)
diff --git a/examples/map_comet_neowise.py b/examples/map_comet_neowise.py
index 0d026774..1fd16c47 100644
--- a/examples/map_comet_neowise.py
+++ b/examples/map_comet_neowise.py
@@ -2,7 +2,7 @@
from skyfield.data import mpc
from skyfield.constants import GM_SUN_Pitjeva_2005_km3_s2 as GM_SUN
-from starplot import MapPlot, Projection
+from starplot import MapPlot, Projection, Star
from starplot.styles import PlotStyle, extensions
# First, we use Skyfield to get comet data
@@ -41,7 +41,6 @@
{
"star": {
"label": {
- "font_size": 9,
"font_weight": "normal",
}
},
@@ -60,6 +59,7 @@
dec_max=80,
style=style,
resolution=2800,
+ autoscale=True,
)
# Plot the comet markers first, to ensure their labels are plotted
@@ -70,7 +70,7 @@
dec=dec,
style={
"marker": {
- "size": 16,
+ "size": 40,
"symbol": "circle",
"fill": "full",
"color": "hsl(358, 78%, 58%)",
@@ -79,7 +79,7 @@
"zorder": 4096,
},
"label": {
- "font_size": 17,
+ "font_size": 46,
"font_weight": "bold",
"font_color": "hsl(60, 70%, 72%)",
"zorder": 4096,
@@ -90,9 +90,9 @@
)
p.gridlines(labels=False)
-p.stars(mag=6)
p.constellations()
p.constellation_borders()
+p.stars(mag=6, where_labels=[Star.magnitude < 2])
p.milky_way()
p.legend()
diff --git a/examples/map_milky_way_stars.py b/examples/map_milky_way_stars.py
index 2be89ab3..b9941051 100644
--- a/examples/map_milky_way_stars.py
+++ b/examples/map_milky_way_stars.py
@@ -20,7 +20,7 @@ def alpha(s):
p = MapPlot(
projection=Projection.MOLLWEIDE,
style=style,
- resolution=4000,
+ resolution=4800,
)
p.stars(
diff --git a/examples/map_orion.py b/examples/map_orion.py
index 41ff9930..a906f674 100755
--- a/examples/map_orion.py
+++ b/examples/map_orion.py
@@ -1,15 +1,10 @@
-from starplot import MapPlot, Projection
-from starplot.styles import PlotStyle, PolygonStyle, extensions
+from starplot import MapPlot, Projection, Star, DSO
+from starplot.styles import PlotStyle, extensions
style = PlotStyle().extend(
extensions.BLUE_LIGHT,
extensions.MAP,
{
- "bayer_labels": {
- "font_name": "GFS Didot", # use a better font for Greek letters
- "font_size": 7,
- "font_alpha": 0.9,
- },
"legend": {
"location": "lower right", # show legend inside map
"num_columns": 1,
@@ -26,28 +21,30 @@
dec_max=25,
style=style,
resolution=3600,
+ autoscale=True,
)
p.gridlines()
-p.stars(mag=9, bayer_labels=True)
-p.open_clusters(mag=9, labels=None)
-p.nebula(mag=9, labels=None)
p.constellations()
p.constellation_borders()
-p.milky_way()
-p.ecliptic()
-p.ellipse(
- (5.6, -1.2),
- height_degrees=3,
- width_degrees=5,
- style=PolygonStyle(
- fill_color="#ed7eed",
- edge_color="#000",
- alpha=0.2,
- ),
- angle=-22,
+p.stars(mag=8, bayer_labels=True, where_labels=[Star.magnitude < 5])
+
+p.open_clusters(
+ where=[DSO.size < 1, DSO.magnitude < 9],
+ labels=None,
+ label_fn=lambda d: d.ngc,
+ true_size=False,
)
+p.open_clusters(
+ # plot larger clusters as their true apparent size
+ where=[DSO.size > 1, (DSO.magnitude < 9) | (DSO.magnitude.is_null())],
+ labels=None,
+)
+
+p.nebula(mag=9, labels=None, label_fn=lambda d: d.ngc)
+p.milky_way()
+p.ecliptic()
p.legend()
p.export("map_orion.png", padding=0.3, transparent=True)
diff --git a/examples/map_orthographic.py b/examples/map_orthographic.py
index 789b3c77..a3b9c0b0 100644
--- a/examples/map_orthographic.py
+++ b/examples/map_orthographic.py
@@ -2,7 +2,6 @@
from pytz import timezone
from starplot import MapPlot, Projection, Star, DSO
-from starplot.data import constellations
from starplot.styles import PlotStyle, extensions
@@ -24,19 +23,19 @@
dec_min=-90,
dec_max=90,
style=style,
- resolution=3200,
+ scale=0.9, # lower the scale since it shows a large area
)
p.gridlines(labels=False)
-p.stars(mag=7.86, where_labels=[Star.magnitude < 6])
+p.constellations(
+ style={"label": {"font_alpha": 0.4}},
+)
+p.constellation_borders()
+p.stars(mag=7.86, where_labels=[Star.magnitude < 6])
p.open_clusters(where=[DSO.magnitude < 12], true_size=False, labels=None)
p.galaxies(where=[DSO.magnitude < 12], true_size=False, labels=None)
p.nebula(where=[DSO.magnitude < 12], true_size=False, labels=None)
-p.constellations(
- style={"label": {"font_size": 9, "font_alpha": 0.8}},
-)
-p.constellation_borders()
p.ecliptic()
p.celestial_equator()
p.milky_way()
diff --git a/examples/map_sagittarius.py b/examples/map_sagittarius.py
index 05fc68e0..de1ad6a3 100644
--- a/examples/map_sagittarius.py
+++ b/examples/map_sagittarius.py
@@ -1,37 +1,31 @@
-from starplot import MapPlot, Projection, DSO, Star
+from starplot import MapPlot, Projection, DSO, Star, callables
from starplot.styles import PlotStyle, extensions
style = PlotStyle().extend(
extensions.ANTIQUE,
extensions.MAP,
- {
- "bayer_labels": {
- "font_name": "GFS Didot",
- "font_size": 7,
- },
- "dso_open_cluster": {
- "marker": {
- "size": 17,
- },
- },
- },
)
p = MapPlot(
projection=Projection.MILLER,
ra_min=15.6,
ra_max=19.8,
- dec_min=-51.6,
+ dec_min=-45.2,
dec_max=-3,
style=style,
- resolution=3000,
+ resolution=4000,
+ autoscale=True,
)
p.constellations()
+p.constellation_borders()
p.stars(
where=[Star.magnitude <= 3],
- style__marker__size=72,
+ size_fn=lambda d: callables.size_by_magnitude(d) * 2, # make them 2x bigger
style__marker__symbol="star_8",
- style__marker__zorder=200,
+ style__label__offset_x=8,
+ style__label__offset_y=-8,
+ style__label__border_width=2,
+ style__label__border_color="#fefaed",
)
p.stars(
where=[
@@ -39,6 +33,7 @@
Star.magnitude < 9,
],
bayer_labels=True,
+ flamsteed_labels=True,
catalog="big-sky-mag11",
)
@@ -56,7 +51,13 @@
true_size=False,
label_fn=lambda d: d.ngc,
)
-p.constellation_borders()
+p.globular_clusters(
+ where=[
+ DSO.magnitude.is_null() | (DSO.magnitude < 12),
+ ],
+ true_size=False,
+ label_fn=lambda d: d.ngc,
+)
p.ecliptic()
p.celestial_equator()
p.milky_way()
diff --git a/examples/optic_m45.py b/examples/optic_m45.py
index 929b5bf0..c1413122 100644
--- a/examples/optic_m45.py
+++ b/examples/optic_m45.py
@@ -26,7 +26,8 @@
),
dt=dt,
style=style,
- resolution=1600,
+ resolution=2048,
+ autoscale=True,
)
p.stars(
mag=12,
diff --git a/examples/optic_moon_saturn.py b/examples/optic_moon_saturn.py
index bb0e8b78..8b27843c 100644
--- a/examples/optic_moon_saturn.py
+++ b/examples/optic_moon_saturn.py
@@ -19,6 +19,7 @@
optic=optics.Binoculars(magnification=15, fov=65),
style=PlotStyle().extend(extensions.GRAYSCALE_DARK, extensions.OPTIC),
resolution=2000,
+ autoscale=True,
)
op.moon(
true_size=True,
@@ -27,8 +28,8 @@
op.planets(
true_size=True,
style__marker__color="#ffe785",
- style__label__offset_x=14,
- style__label__offset_y=-14,
+ style__label__offset_x=6,
+ style__label__offset_y=-6,
)
op.stars(mag=12)
diff --git a/examples/optic_orion_nebula.py b/examples/optic_orion_nebula.py
index e999b413..ecd3d3b4 100644
--- a/examples/optic_orion_nebula.py
+++ b/examples/optic_orion_nebula.py
@@ -26,7 +26,8 @@
),
dt=dt,
style=style,
- resolution=1800,
+ resolution=2048,
+ autoscale=True,
)
p.stars(mag=15, color_fn=color_by_bv, bayer_labels=True)
p.dsos(mag=4.1, labels=None)
diff --git a/examples/star_chart_basic.py b/examples/star_chart_basic.py
index 56b6b7d3..403dfb83 100755
--- a/examples/star_chart_basic.py
+++ b/examples/star_chart_basic.py
@@ -1,6 +1,6 @@
from datetime import datetime
from pytz import timezone
-from starplot import MapPlot, Projection
+from starplot import MapPlot, Projection, Star
from starplot.styles import PlotStyle, extensions
tz = timezone("America/Los_Angeles")
@@ -15,9 +15,10 @@
extensions.BLUE_MEDIUM,
),
resolution=3600,
+ autoscale=True,
)
p.constellations()
-p.stars(mag=4.4)
+p.stars(mag=4.4, where_labels=[Star.magnitude < 2.4])
p.horizon()
p.export("star_chart_basic.png", transparent=True, padding=0.1)
diff --git a/examples/star_chart_detail.py b/examples/star_chart_detail.py
index 13f77988..26cb9086 100644
--- a/examples/star_chart_detail.py
+++ b/examples/star_chart_detail.py
@@ -15,6 +15,7 @@
extensions.NORD,
),
resolution=3600,
+ autoscale=True,
)
p.constellations()
p.stars(mag=4.6, where_labels=[Star.magnitude < 2.1])
@@ -32,7 +33,7 @@
dec=25.85,
style={
"marker": {
- "size": 28,
+ "size": 60,
"symbol": "circle",
"fill": "full",
"color": "#ed7eed",
@@ -42,7 +43,7 @@
},
"label": {
"zorder": 200,
- "font_size": 12,
+ "font_size": 30,
"font_weight": "bold",
"font_color": "ed7eed",
"font_alpha": 0.8,
diff --git a/hash_checks/hashlock.yml b/hash_checks/hashlock.yml
index fcb2f33a..d9f096ca 100644
--- a/hash_checks/hashlock.yml
+++ b/hash_checks/hashlock.yml
@@ -1,116 +1,116 @@
map_coma_berenices_dso_size:
- dhash: c2809292c88084a080829292c8828280868a92928a828282
+ dhash: 82848cece098c0c28a848cece098c482a28a92e2ec92c282
filename: /starplot/hash_checks/data/map-coma-berenices-dso-size.png
- phash: c16916963e696d96
+ phash: 969669694f9212cf
map_custom_stars:
- dhash: 5cb129ceae2e84285cb129ceae2e84285cb129ceae2e8428
+ dhash: 5c21a9ceac2c84485c21a9ceac2c84485c21a9ceac2c8448
filename: /starplot/hash_checks/data/map-custom-stars.png
- phash: a4cbd93487a53cc6
+ phash: e65a5bb405a535d8
map_gridlines:
- dhash: 34318517b2c0902034318517b2c0902034318517b2c09020
+ dhash: 0c219595224949210c219595224949210c21959522494921
filename: /starplot/hash_checks/data/map-gridlines.png
- phash: f82acf9c3c616392
+ phash: d2b45b172d3262ad
map_label_callables:
- dhash: 80988c0c9c8c88a080988c0c9c8c88a0809a9d1d0d9d9aa0
+ dhash: 80908c8c9c8c889080988c0c9c8c98a0809a9d0d8d9e98a0
filename: /starplot/hash_checks/data/map-m45-label-callables.png
- phash: b238cdc92ce63699
+ phash: b330cdcd3ce6268c
map_milky_way_multi_polygon:
- dhash: 351b89e4243a2604711b88e434322604751b88e424362624
+ dhash: 711f0b0c80761c2d711f0b0c80761c2d71530b0c80669c0d
filename: /starplot/hash_checks/data/map-milky-way-multi-polygon.png
- phash: bc8bf89e90ce5a44
+ phash: be89be8856524e69
map_mollweide:
- dhash: 0b2933632b3317070b2933632b3317070f295b6323130707
+ dhash: 0b0929733b31170f0b0929733b31170f0b0d29732b311707
filename: /starplot/hash_checks/data/map-mollweide.png
- phash: fa9af698c0259566
+ phash: facbe09885599563
map_moon_phase_waxing_crescent:
- dhash: 8884484d4d4884888884484d4d4884888884484d4d488488
+ dhash: 0804084d4d4804080804084d4d4804080804084d4d480408
filename: /starplot/hash_checks/data/map-moon-phase-waxing-crescent.png
phash: b3cccc3333cccc31
map_orion_base:
- dhash: 1c3b3b6e6e2f64681c3b3b4e6e2f64681c3b1bce6e2f646c
+ dhash: 1c39393e2e2f64681c39396e2e2f64685c3939a62e4f2468
filename: /starplot/hash_checks/data/map-orion-base.png
- phash: be79d9260785709c
+ phash: be785b2435a4b598
map_orion_extra:
- dhash: 1c1b3a6d2f656c171d1b1b6b2f646c871c1b1b6b2d646c07
+ dhash: 1c19393e2f656c4f1c39392e2f6568471c1b2b2e2f256c47
filename: /starplot/hash_checks/data/map-orion-extra.png
- phash: be71d11ee01fa4d0
+ phash: be71c11ee117a4d1
map_plot_custom_clip_path_virgo:
- dhash: 020103130f0c04040327252d0d0c08040327250f0d0c0804
+ dhash: 060529331d1c04040625252f0d1c0404060525270d1e0404
filename: /starplot/hash_checks/data/map-custom-clip-path-virgo.png
- phash: e66cc983b3919336
+ phash: e66e8d91b1191bd2
map_plot_limit_by_geometry:
- dhash: 22a886960c061204028a86960c0a1244122886864c0e0a04
+ dhash: 12a8809644041202122a82964404120232a880d645041242
filename: /starplot/hash_checks/data/map-limit-by-geometry.png
- phash: e54d9b926c6d1692
+ phash: a4899b366679669a
map_scope_bino_fov:
- dhash: 0bc0808c8c8080802bc6b22d4da9928c2bc6b22d4da9928c
+ dhash: 068080848cc088200686334d4d21924c0686334d4d21924c
filename: /starplot/hash_checks/data/map-scope-bino-fov.png
- phash: e46b296e6e293964
+ phash: e6659931666c9939
map_stereo_base:
- dhash: 627666f07c3c3436627666f0382c3436727666f2ecc89416
+ dhash: 777264f234381416777264f0303cd416777664f2b498d456
filename: /starplot/hash_checks/data/map-stereo-north-base.png
- phash: 9dce7973429062ba
+ phash: 9dce69624a9176b2
map_with_planets:
- dhash: 62930d63899b32b362930563899b32b362930563899b32b3
+ dhash: 629304668ddb3233629304668ddb3233629305668ddb3233
filename: /starplot/hash_checks/data/map-mercator-planets.png
- phash: e9c69d89861f5392
+ phash: eac29d81a65f5332
map_wrapping:
- dhash: 16391d2d7747211516391d2d7747211516391d2d77472115
+ dhash: 2f134c2d45cf23152f134c2d45cf23152f134c2d45cf2315
filename: /starplot/hash_checks/data/map-wrapping.png
- phash: ee30f1ed3464213d
+ phash: ba32c9c5968cb713
optic_camera_rotated:
- dhash: 25468b33269d9a6525468b33269d9a6525468b33269d9a65
+ dhash: 25468b13261c1a6525468b13261c1a6525468b13261c1a65
filename: /starplot/hash_checks/data/optic-camera-rotated-m45.png
- phash: ac6cc6941edb7285
+ phash: bc6cc6941ed97285
optic_clipping:
- dhash: 30c898f06ee4fc5d71c898f0eae4fc7931d898f0eae4f879
+ dhash: 70d898f066e4fc5d70d898f0eae4f85971f098eaf6f4fc71
filename: /starplot/hash_checks/data/optic-clipping.png
phash: 95696a872f9a7870
optic_m45_binoculars:
- dhash: 8e172b5133338e4d8e172b5133338e4d8e172b5133338e4d
+ dhash: 8e173b7133330e4d8e173b7133330e4d8e173b7133330e4d
filename: /starplot/hash_checks/data/optic-m45-binoculars.png
- phash: aa7b84f182d18ed6
+ phash: aa7b84f586d18ac6
optic_m45_camera:
- dhash: 4d717151716159754d717151716159754d71715171615975
+ dhash: 4d715151716559754d715151716559754d71515171655975
filename: /starplot/hash_checks/data/optic-m45-camera.png
- phash: fe7b846e843b8494
+ phash: ba7b846e847b84d1
optic_m45_reflector:
- dhash: 8e1f3b7123338e4d8e1f3b7123338e4d8e1f3b7123338e4d
+ dhash: 8e1f2b7123330e4d8e1f2b7123330e4d8e1f2b7123330e4d
filename: /starplot/hash_checks/data/optic-m45-reflector.png
- phash: ab7b80e4c6939b92
+ phash: ba7b80e4c6b19b92
optic_m45_scope:
- dhash: 8e173b5123238e4d8e173b5123238e4d8e173b5123238e4d
+ dhash: 8e173b5133230e4d8e173b5133230e4d8e173b5133230e4d
filename: /starplot/hash_checks/data/optic-m45-scope.png
- phash: ae7b81e48693ce92
+ phash: ba7b84e486b18e93
optic_moon_phase_full:
dhash: 0e2b553333552b0e0e2b553333552b0e0e2b553333552b0e
filename: /starplot/hash_checks/data/optic-moon-phase-full.png
- phash: fb2ba07395708725
+ phash: fb6ba06295718725
optic_moon_phase_new:
- dhash: 17498e96968e491717498e96968e491717498e96968e4917
+ dhash: 13498e96968e491313498e96968e491313498e96968e4913
filename: /starplot/hash_checks/data/optic-moon-phase-new.png
- phash: e1619e9369596636
+ phash: e1649e9669496766
optic_moon_phase_waxing_crescent:
dhash: 0e33454d59712b0e0e33454d59712b0e0e33454d59712b0e
filename: /starplot/hash_checks/data/optic-moon-phase-waxing-crescent.png
phash: bb26e4999166c699
optic_orion_nebula_refractor:
- dhash: 8e1f2d7163238e4d8e1f2d7163238e4d8e1f2d7163238e4d
+ dhash: 8e1f2d7163230e4d8e1f2d7163230e4d8e1f2d7163230e4d
filename: /starplot/hash_checks/data/optic-orion-nebula-refractor.png
- phash: ae3a9165c7919e92
+ phash: ae3ab165c3919e92
optic_polaris_binoculars:
- dhash: 8e172b512b338e4d8e172b512b338e4d8e172b512b338e4d
+ dhash: 8e172b5533338e4d8e172b5533338e4d8e172b5533338e4d
filename: /starplot/hash_checks/data/optic-binoculars-polaris.png
- phash: aa7b84f186d38e92
+ phash: aa7b84f182d18ed3
optic_solar_eclipse_binoculars:
- dhash: 0779e49a9ae469071779e09a9ae469170359c09e9ec06923
+ dhash: 1779609a9a64610f0759e09a9ae469070359c09c9cc06903
filename: /starplot/hash_checks/data/optic-binoculars-eclipse.png
- phash: b1cece3131cecc31
+ phash: b1ccce3131cece31
optic_wrapping:
- dhash: 71f0a4d0d2da652a71f0b4ecacc4792a71e0b4acacc47932
+ dhash: 71f0ac92d2d8650a71e0d4aca4c4782a71e8d4acacc4713a
filename: /starplot/hash_checks/data/optic-wrapping.png
- phash: d0c46f1b3d2ee078
+ phash: d1846e1b792ef138
zenith_base:
- dhash: 1779c4aedc5d338e1779c4acd65d338e1779c48cde5d330e
+ dhash: 1771c49e9e7d338e1779c49c9e5d338e1779c49c9e7d370e
filename: /starplot/hash_checks/data/zenith-base.png
- phash: be49aa3be12c8764
+ phash: bb41aa3be13c8764
diff --git a/hash_checks/map_checks.py b/hash_checks/map_checks.py
index cb1fc7f0..eb8862d2 100644
--- a/hash_checks/map_checks.py
+++ b/hash_checks/map_checks.py
@@ -31,6 +31,7 @@ def _mercator():
dec_max=23.6,
style=STYLE,
resolution=RESOLUTION,
+ autoscale=True,
)
p.stars(mag=7.6, bayer_labels=True)
p.dsos(
@@ -63,6 +64,7 @@ def _stereo_north():
dec_max=55,
style=STYLE,
resolution=RESOLUTION,
+ autoscale=True,
)
p.stars(mag=9, bayer_labels=True)
p.dsos(
@@ -132,6 +134,7 @@ def check_map_coma_berenices_dso_size():
styles.extensions.MAP,
),
resolution=RESOLUTION,
+ scale=1.5,
)
p.stars(mag=8, bayer_labels=True)
p.open_clusters(mag=8, true_size=True)
@@ -165,6 +168,7 @@ def check_map_with_planets():
hide_colliding_labels=False,
style=STYLE,
resolution=RESOLUTION,
+ autoscale=True,
)
p.stars(mag=3, labels=None)
p.planets()
@@ -192,8 +196,9 @@ def check_map_scope_bino_fov():
dec_max=28,
dt=dt,
style=style,
- resolution=1000,
+ resolution=2000,
star_catalog="big-sky-mag11",
+ scale=1,
)
p.stars(mag=12)
p.scope_fov(
@@ -227,6 +232,7 @@ def check_map_custom_stars():
dec_max=24,
style=style,
resolution=RESOLUTION,
+ autoscale=True,
)
p.stars(mag=6)
p.text(
@@ -255,6 +261,7 @@ def check_map_wrapping():
dec_max=64,
style=style,
resolution=RESOLUTION,
+ autoscale=True,
)
p.stars(mag=9, style={"marker": {"size": 40}})
p.dsos(
@@ -284,6 +291,7 @@ def check_map_mollweide():
projection=Projection.MOLLWEIDE,
style=style,
resolution=RESOLUTION,
+ autoscale=True,
)
p.stars(mag=4.2, mag_labels=1.8, style__marker__color="blue")
p.constellations()
@@ -318,6 +326,7 @@ def check_map_gridlines():
dec_max=67,
style=style,
resolution=RESOLUTION,
+ autoscale=True,
)
p.stars(mag=6, style__marker__size=45)
@@ -346,6 +355,7 @@ def check_map_moon_phase_waxing_crescent():
**POWAY,
dt=dt_dec_16,
style=STYLE,
+ autoscale=True,
)
p.moon(
true_size=True,
@@ -380,6 +390,7 @@ def check_map_plot_limit_by_geometry():
}
),
resolution=RESOLUTION,
+ autoscale=True,
)
lyra = Constellation.get(iau_id="lyr")
@@ -417,6 +428,7 @@ def check_map_plot_custom_clip_path_virgo():
}
),
resolution=RESOLUTION,
+ autoscale=True,
clip_path=virgo.boundary,
)
@@ -454,10 +466,10 @@ def check_map_label_callables():
{
"dso_open_cluster": {
"label": {
- "font_size": 28,
+ # "font_size": 28,
"font_weight": "bold",
- "offset_x": 310,
- "offset_y": 240,
+ # "offset_x": 310,
+ # "offset_y": 240,
}
},
}
@@ -470,6 +482,7 @@ def check_map_label_callables():
dec_max=26,
style=style,
resolution=2000,
+ autoscale=True,
)
m45 = DSO.get(m="45")
@@ -477,8 +490,8 @@ def check_map_label_callables():
geometry=m45.geometry,
style__color=None,
style__fill_color=style.dso_open_cluster.marker.color,
- style__edge_color="#000",
- style__edge_width=4,
+ style__edge_color="red",
+ style__edge_width=6,
style__line_style=(0, (1.2, 8)),
)
@@ -499,10 +512,11 @@ def check_map_milky_way_multi_polygon():
projection=Projection.MILLER,
ra_min=17.5,
ra_max=19.5,
- dec_min=-40,
+ dec_min=-30,
dec_max=0,
style=STYLE,
- resolution=2000,
+ resolution=3000,
+ autoscale=True,
)
p.stars(mag=6, bayer_labels=True)
p.constellations()
diff --git a/hash_checks/optic_checks.py b/hash_checks/optic_checks.py
index 9cf68307..7732d8db 100644
--- a/hash_checks/optic_checks.py
+++ b/hash_checks/optic_checks.py
@@ -26,6 +26,11 @@
POWAY = {"lat": 32.97, "lon": -117.038611}
+plot_kwargs = dict(
+ resolution=2048,
+ autoscale=True,
+)
+
def check_optic_polaris_binoculars():
optic_plot = OpticPlot(
@@ -41,7 +46,7 @@ def check_optic_polaris_binoculars():
),
dt=dt_dec_16,
style=style_dark,
- resolution=1600,
+ **plot_kwargs,
)
optic_plot.stars(mag=14)
optic_plot.info()
@@ -65,7 +70,7 @@ def check_optic_orion_nebula_refractor():
),
dt=dt_dec_16,
style=style_dark,
- resolution=1600,
+ **plot_kwargs,
)
optic_plot.stars(mag=12)
optic_plot.nebula()
@@ -88,7 +93,7 @@ def check_optic_wrapping():
),
dt=dt_dec_16,
style=style_blue,
- resolution=2000,
+ **plot_kwargs,
)
optic_plot.stars(
where=[Star.magnitude < 8],
@@ -122,7 +127,7 @@ def check_optic_clipping():
),
dt=dt_dec_16,
style=style_blue,
- resolution=1800,
+ **plot_kwargs,
)
optic_plot.stars(mag=12)
optic_plot.dsos(mag=8.1, labels=None)
@@ -147,7 +152,7 @@ def check_optic_m45_binoculars():
),
dt=dt_dec_16,
style=style_dark,
- resolution=1600,
+ **plot_kwargs,
)
optic_plot.stars(mag=12)
optic_plot.info()
@@ -170,7 +175,7 @@ def check_optic_m45_scope():
),
dt=dt_dec_16,
style=style_dark,
- resolution=1600,
+ **plot_kwargs,
)
optic_plot.stars(
mag=12,
@@ -197,7 +202,7 @@ def check_optic_m45_reflector():
),
dt=dt_dec_16,
style=style_dark,
- resolution=1600,
+ **plot_kwargs,
)
optic_plot.stars(mag=12)
optic_plot.info()
@@ -221,7 +226,7 @@ def check_optic_m45_camera():
),
dt=dt_dec_16,
style=style_dark,
- resolution=1600,
+ **plot_kwargs,
)
optic_plot.stars(mag=12, style__marker__symbol=styles.MarkerSymbolEnum.STAR)
optic_plot.info()
@@ -247,7 +252,7 @@ def check_optic_camera_rotated():
),
dt=dt_dec_16,
style=style_dark,
- resolution=1600,
+ **plot_kwargs,
)
optic_plot.stars(mag=12)
optic_plot.info()
@@ -268,7 +273,7 @@ def check_optic_solar_eclipse_binoculars():
style=styles.PlotStyle().extend(
styles.extensions.BLUE_MEDIUM,
),
- resolution=2000,
+ **plot_kwargs,
)
optic_plot.stars(mag=14)
optic_plot.moon(true_size=True, show_phase=True)
@@ -290,6 +295,7 @@ def check_optic_moon_phase_waxing_crescent():
dt=dt_dec_16,
style=style_dark,
raise_on_below_horizon=False,
+ **plot_kwargs,
)
optic_plot.moon(
true_size=True,
@@ -313,6 +319,7 @@ def check_optic_moon_phase_new():
dt=dt_april_8,
style=style_light,
raise_on_below_horizon=False,
+ **plot_kwargs,
)
optic_plot.moon(
true_size=True,
@@ -337,6 +344,7 @@ def check_optic_moon_phase_full():
dt=dt_full_moon,
style=style_dark,
raise_on_below_horizon=False,
+ **plot_kwargs,
)
optic_plot.moon(
true_size=True,
diff --git a/hash_checks/zenith_checks.py b/hash_checks/zenith_checks.py
index fc0f6a95..e3a83c89 100644
--- a/hash_checks/zenith_checks.py
+++ b/hash_checks/zenith_checks.py
@@ -3,7 +3,7 @@
from pytz import timezone
-from starplot import styles
+from starplot import styles, Star
from starplot.map import MapPlot, Projection
HERE = Path(__file__).resolve().parent
@@ -26,8 +26,9 @@ def _zenith():
dt=JUNE_2023,
style=STYLE,
resolution=RESOLUTION,
+ autoscale=True,
)
- p.stars(mag=4.6, style__marker__size=25)
+ p.stars(mag=4.6, where_labels=[Star.magnitude < 3])
p.constellations()
p.ecliptic(style__line__width=8)
p.celestial_equator(style__line__width=8)
diff --git a/requirements.txt b/requirements.txt
index 114cdf96..6132d548 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,10 +1,10 @@
-matplotlib==3.8.4
+matplotlib==3.9.2
numpy==1.26.2
pandas==2.0.3
pydantic==2.0.3
shapely==2.0.1
skyfield==1.48
-adjustText==1.0
+adjustText==1.2.0
cartopy==0.22.0
geopandas==0.14.4
pillow==10.0.0
@@ -12,5 +12,5 @@ PyYAML==6.0.1
pyarrow==17.0.0
fastparquet==2023.10.1
pyogrio==0.9.0
-rtree==1.2.0
+rtree==1.3.0
requests==2.31.0
diff --git a/scripts/geopack.py b/scripts/geopack.py
index b37ded78..5ff886f4 100644
--- a/scripts/geopack.py
+++ b/scripts/geopack.py
@@ -9,7 +9,10 @@
# constellation_borders = gpd.read_file(DataFiles.CONSTELLATION_BORDERS.value)
constellation_borders = gpd.read_file("raw/i.constellations.borders.json")
-constellation_borders.to_file("build/constellation_borders.gpkg", driver="GPKG")
+# constellation_borders.to_file("build/constellation_borders.gpkg", driver="GPKG")
+constellation_borders.to_file(
+ "src/starplot/data/library/constellation_borders_inv.gpkg", driver="GPKG"
+)
# constellation_lines = gpd.read_file(DataFiles.CONSTELLATION_LINES.value)
# print(constellation_lines.has_sindex)
diff --git a/scripts/starnames.py b/scripts/starnames.py
new file mode 100644
index 00000000..005276fe
--- /dev/null
+++ b/scripts/starnames.py
@@ -0,0 +1,52 @@
+import csv
+from pathlib import Path
+from pprint import pprint
+
+from starplot.data.stars import STAR_NAMES
+
+HERE = Path(__file__).resolve().parent
+DATA_PATH = HERE.parent / "raw"
+
+iau_names = {}
+
+with open(DATA_PATH / "star-names-iau-2024.csv") as namefile:
+ reader = csv.reader(namefile)
+ next(reader)
+
+ for row in reader:
+ hip = row[2]
+ name = row[0]
+
+ if hip and hip != "0":
+ hip = int(hip)
+ else:
+ continue
+
+ iau_names[hip] = name
+
+not_in_iau = []
+
+for hip, name in STAR_NAMES.items():
+ if hip in iau_names.keys() and name != iau_names[hip]:
+ print(f"{name} -> {iau_names[hip]}")
+
+ if hip not in iau_names.keys():
+ not_in_iau.append(name)
+
+print("Not in IAU :")
+pprint(not_in_iau)
+
+ctr = 0
+for hip, name in iau_names.items():
+ if hip not in STAR_NAMES.keys():
+ ctr += 1
+
+print(ctr)
+
+
+print(f"Count current = {len(STAR_NAMES)}")
+print(f"Count IAU = {len(iau_names)}")
+# print(iau_names)
+
+
+pprint(iau_names)
diff --git a/scripts/voronoi.py b/scripts/voronoi.py
new file mode 100644
index 00000000..0ece1a73
--- /dev/null
+++ b/scripts/voronoi.py
@@ -0,0 +1,163 @@
+import time
+from datetime import datetime
+
+from shapely import (
+ voronoi_polygons,
+ GeometryCollection,
+ MultiPoint,
+ intersection,
+ normalize,
+ delaunay_triangles,
+ distance,
+)
+from pytz import timezone
+from matplotlib import patches
+from starplot import Star, DSO, Constellation
+from starplot.styles import PlotStyle, extensions, PolygonStyle
+from starplot.map import Projection
+
+import starplot as sp
+
+start_time = time.time()
+
+p = sp.MapPlot(
+ projection=Projection.MILLER,
+ # projection=Projection.STEREO_NORTH,
+ ra_min=1,
+ ra_max=18,
+ dec_min=-40,
+ dec_max=60,
+ style=PlotStyle().extend(
+ extensions.GRAYSCALE,
+ # extensions.GRAYSCALE_DARK,
+ # extensions.BLUE_LIGHT,
+ # extensions.BLUE_MEDIUM,
+ # extensions.BLUE_DARK,
+ extensions.MAP,
+ {
+ "constellation": {
+ "label": {
+ "font_size": 5,
+ }
+ }
+ },
+ ),
+ resolution=4000,
+)
+
+p.stars(where=[Star.magnitude < 6], labels=None) # , size_fn=lambda s: 20)
+p.constellations()
+p.constellation_borders()
+
+print(len(p.objects.stars))
+
+p.export("temp/voronoi.png")
+exit()
+for constellation in p.objects.constellations:
+ # con = "gem"
+ # constellation = Constellation.get(iau_id=con)
+ print(constellation.name)
+ constellation_stars = [
+ s for s in p.objects.stars if s.constellation_id == constellation.iau_id
+ ]
+ print(len(constellation_stars))
+
+ points = MultiPoint([(s.ra, s.dec) for s in constellation_stars])
+
+ v = voronoi_polygons(
+ geometry=points,
+ # extend_to=orion.boundary.buffer(0.2),
+ tolerance=2,
+ )
+ triangles = delaunay_triangles(
+ geometry=points,
+ # tolerance=2,
+ )
+
+ # for polygon in polygons.geoms:
+ # # if not orion.boundary.contains(polygon.reverse()): continue
+ # inter = intersection(polygon, constellation.boundary)
+ # if inter.geom_type == "Polygon":
+ # p.polygon(
+ # geometry=inter,
+ # style__edge_color="red",
+ # )
+
+ polygons = []
+ for t in triangles.geoms:
+ # if not orion.boundary.contains(polygon.reverse()): continue
+ try:
+ inter = intersection(t, constellation.boundary)
+ except:
+ continue
+ if (
+ inter.geom_type == "Polygon"
+ and len(list(zip(*inter.exterior.coords.xy))) > 2
+ ):
+ # continue
+ # p.polygon(
+ # geometry=inter,
+ # style__edge_color="blue",
+ # style__alpha=0.13,
+ # )
+ polygons.append(inter)
+
+ p_by_area = {pg.area: pg for pg in polygons}
+ polygons_sorted = [p_by_area[k] for k in sorted(p_by_area.keys(), reverse=True)]
+
+ """
+ TODO: Turn this into a function on map plot class
+ """
+
+ constellation_centroid = constellation.boundary.centroid
+
+ def sorter(g):
+ d = distance(g.centroid, points.centroid)
+ # d = distance(g.centroid, constellation.boundary.centroid)
+ extent = abs(g.bounds[2] - g.bounds[0])
+ area = g.area / constellation.boundary.area
+ return (extent**2 + area) - (d**2)
+
+ # sort by combination of horizontal extent and area
+ polygons_sorted = sorted(polygons_sorted, key=sorter, reverse=True)
+
+ # p_sort_distance_to_center = sorted(polygons_sorted, key=lambda pg: distance(pg.centroid, points.centroid))
+
+ i = 0
+
+ p.polygon(
+ geometry=polygons_sorted[i],
+ style__fill_color="green",
+ style__alpha=0.23,
+ )
+ p.marker(
+ polygons_sorted[i].centroid.x,
+ polygons_sorted[i].centroid.y,
+ label=None,
+ style__marker__symbol="circle_cross",
+ style__marker__size=9,
+ style__marker__color="red",
+ style__marker__edge_color="red",
+ style__marker__edge_width=6,
+ )
+
+ # for big_p in polygons_sorted[:1]:
+ # # continue
+ # # if big_p.area < constellation.boundary.area/2:
+ # p.polygon(
+ # geometry=big_p,
+ # style__fill_color="green",
+ # style__alpha=0.23,
+ # )
+
+
+p.export("temp/voronoi.png")
+
+# print(Star.get(name="Sirius").hip)
+# print(DSO.all(2))
+
+# print(Star.all(1))
+# d = DSO.get(type="Open Cluster")
+# print(d)
+
+print(time.time() - start_time)
diff --git a/src/starplot/__init__.py b/src/starplot/__init__.py
index e7094584..76a5c8e7 100644
--- a/src/starplot/__init__.py
+++ b/src/starplot/__init__.py
@@ -1,6 +1,6 @@
"""Star charts and maps of the sky"""
-__version__ = "0.12.5"
+__version__ = "0.13.0"
from .base import BasePlot # noqa: F401
from .map import MapPlot, Projection # noqa: F401
diff --git a/src/starplot/base.py b/src/starplot/base.py
index 49b0828a..5fa76b81 100644
--- a/src/starplot/base.py
+++ b/src/starplot/base.py
@@ -2,6 +2,7 @@
from datetime import datetime
from typing import Dict, Union, Optional
import logging
+import warnings
import numpy as np
import rtree
@@ -17,6 +18,7 @@
from starplot.models.planet import PlanetName, PLANET_LABELS_DEFAULT
from starplot.models.moon import MoonPhase
from starplot.styles import (
+ AnchorPointEnum,
PlotStyle,
MarkerStyle,
ObjectStyle,
@@ -27,9 +29,16 @@
MarkerSymbolEnum,
PathStyle,
PolygonStyle,
+ fonts,
)
from starplot.styles.helpers import use_style
+# ignore noisy matplotlib warnings
+warnings.filterwarnings(
+ "ignore",
+ message="Setting the 'color' property will override the edgecolor or facecolor properties",
+)
+
LOGGER = logging.getLogger("starplot")
LOG_HANDLER = logging.StreamHandler()
LOG_FORMATTER = logging.Formatter(
@@ -46,6 +55,10 @@
DEFAULT_STYLE = PlotStyle()
+DEFAULT_RESOLUTION = 4096
+
+DPI = 100
+
class BasePlot(ABC):
_background_clip_path = None
@@ -55,26 +68,38 @@ def __init__(
dt: datetime = None,
ephemeris: str = "de421_2001.bsp",
style: PlotStyle = DEFAULT_STYLE,
- resolution: int = 2048,
+ resolution: int = 4096,
hide_colliding_labels: bool = True,
+ scale: float = 1.0,
+ autoscale: bool = False,
*args,
**kwargs,
):
- px = 1 / plt.rcParams["figure.dpi"] # pixel in inches
+ px = 1 / DPI # plt.rcParams["figure.dpi"] # pixel in inches
- self.pixels_per_point = plt.rcParams["figure.dpi"] / 72
+ self.pixels_per_point = DPI / 72
self.style = style
self.figure_size = resolution * px
self.resolution = resolution
self.hide_colliding_labels = hide_colliding_labels
+ self.scale = scale
+ self.autoscale = autoscale
+ if self.autoscale:
+ self.scale = self.resolution / DEFAULT_RESOLUTION
+
self.dt = dt or timezone("UTC").localize(datetime.now())
self._ephemeris_name = ephemeris
self.ephemeris = load(ephemeris)
self.labels = []
self._labels_rtree = rtree.index.Index()
+
+ # self.labels = []
+ self._constellations_rtree = rtree.index.Index()
+ self._stars_rtree = rtree.index.Index()
+
self._background_clip_path = None
self._legend = None
@@ -85,13 +110,14 @@ def __init__(
self.logger.setLevel(self.log_level)
self.text_border = patheffects.withStroke(
- linewidth=self.style.text_border_width,
+ linewidth=self.style.text_border_width * self.scale,
foreground=self.style.text_border_color.as_hex(),
)
- self._size_multiplier = self.resolution / 3000
self.timescale = load.timescale().from_datetime(self.dt)
self._objects = models.ObjectList()
+ self._labeled_stars = []
+ fonts.load()
def _plot_kwargs(self) -> dict:
return {}
@@ -107,36 +133,62 @@ def _is_label_collision(self, extent) -> bool:
)
return len(ix) > 0
+ def _is_object_collision(self, extent) -> bool:
+ ix = list(
+ self._constellations_rtree.intersection(
+ (extent.x0, extent.y0, extent.x1, extent.y1)
+ )
+ )
+ return len(ix) > 0
+
+ def _is_star_collision(self, extent) -> bool:
+ ix = list(
+ self._stars_rtree.intersection((extent.x0, extent.y0, extent.x1, extent.y1))
+ )
+ return len(ix) > 0
+
def _is_clipped(self, extent) -> bool:
return self._background_clip_path is not None and not all(
self._background_clip_path.contains_points(extent.get_points())
)
- def _maybe_remove_label(self, label) -> None:
+ def _add_label_to_rtree(self, label, extent=None):
+ extent = extent or label.get_window_extent(
+ renderer=self.fig.canvas.get_renderer()
+ )
+ self.labels.append(label)
+ self._labels_rtree.insert(
+ 0, np.array((extent.x0, extent.y0, extent.x1, extent.y1))
+ )
+
+ def _maybe_remove_label(
+ self, label, remove_on_collision=True, remove_on_clipped=True
+ ) -> bool:
+ """Returns true if the label is removed, else false"""
extent = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
if any([np.isnan(c) for c in (extent.x0, extent.y0, extent.x1, extent.y1)]):
label.remove()
- return
+ return True
- if any(
- [
- self._is_clipped(extent),
- self.hide_colliding_labels and self._is_label_collision(extent),
- ]
+ if remove_on_clipped and self._is_clipped(extent):
+ label.remove()
+ return True
+
+ if remove_on_collision and (
+ self._is_label_collision(extent)
+ or self._is_object_collision(extent)
+ # or self._is_star_collision(extent)
):
label.remove()
- return
+ return True
- self.labels.append(label)
- self._labels_rtree.insert(
- 0, np.array((extent.x0, extent.y0, extent.x1, extent.y1))
- )
+ return False
def _add_legend_handle_marker(self, label: str, style: MarkerStyle):
if label is not None and label not in self._legend_handles:
s = style.matplot_kwargs()
- s["markersize"] = self.style.legend.symbol_size * self._size_multiplier
+ s["markersize"] = self.style.legend.symbol_size * self.scale
self._legend_handles[label] = Line2D(
[],
[],
@@ -146,12 +198,176 @@ def _add_legend_handle_marker(self, label: str, style: MarkerStyle):
label=label,
)
+ def _collision_score(self, label) -> int:
+ config = {
+ "labels": 1.0, # always fail
+ "stars": 0.5,
+ "constellations": 0.8,
+ "anchors": [
+ ("bottom right", 0),
+ ("top right", 0.2),
+ ("top left", 0.5),
+ ],
+ "on_fail": "plot",
+ }
+ extent = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
+
+ if any(
+ [np.isnan(c) for c in (extent.x0, extent.y0, extent.x1, extent.y1)]
+ ) or self._is_clipped(extent):
+ return 1
+
+ x_labels = (
+ len(
+ list(
+ self._labels_rtree.intersection(
+ (extent.x0, extent.y0, extent.x1, extent.y1)
+ )
+ )
+ )
+ * config["labels"]
+ )
+
+ if x_labels >= 1:
+ return 1
+
+ x_constellations = (
+ len(
+ list(
+ self._constellations_rtree.intersection(
+ (extent.x0, extent.y0, extent.x1, extent.y1)
+ )
+ )
+ )
+ * config["constellations"]
+ )
+
+ if x_constellations >= 1:
+ return 1
+
+ x_stars = (
+ len(
+ list(
+ self._stars_rtree.intersection(
+ (extent.x0, extent.y0, extent.x1, extent.y1)
+ )
+ )
+ )
+ * config["stars"]
+ )
+ if x_stars >= 1:
+ return 1
+
+ return sum([x_labels, x_constellations, x_stars]) / 3
+
+ def _text_experimental(
+ self,
+ ra: float,
+ dec: float,
+ text: str,
+ hide_on_collision: bool = True,
+ auto_anchor: bool = True,
+ *args,
+ **kwargs,
+ ) -> None:
+ if not text:
+ return
+
+ x, y = self._prepare_coords(ra, dec)
+ kwargs["path_effects"] = kwargs.get("path_effects", [self.text_border])
+ clip_on = kwargs.get("clip_on") or True
+
+ def plot_text(**kwargs):
+ label = self.ax.annotate(
+ text,
+ (x, y),
+ *args,
+ **kwargs,
+ **self._plot_kwargs(),
+ )
+ if clip_on:
+ label.set_clip_on(True)
+ label.set_clip_path(self._background_clip_path)
+ return label
+
+ def add_label(label):
+ extent = label.get_window_extent(renderer=self.fig.canvas.get_renderer())
+ self.labels.append(label)
+ self._labels_rtree.insert(
+ 0, np.array((extent.x0, extent.y0, extent.x1, extent.y1))
+ )
+
+ label = plot_text(**kwargs)
+
+ if not clip_on:
+ add_label(label)
+ return
+
+ if not hide_on_collision and not auto_anchor:
+ add_label(label)
+ return
+
+ # removed = self._maybe_remove_label(label)
+ collision = self._collision_score(label)
+
+ if collision == 0:
+ add_label(label)
+ return
+
+ label.remove()
+
+ collision_scores = []
+ original_va = kwargs.pop("va", None)
+ original_ha = kwargs.pop("ha", None)
+ original_offset_x, original_offset_y = kwargs.pop("xytext", (0, 0))
+ anchor_fallbacks = self.style.text_anchor_fallbacks
+ for i, a in enumerate(anchor_fallbacks):
+ d = AnchorPointEnum.from_str(a).as_matplot()
+ va, ha = d["va"], d["ha"]
+ offset_x, offset_y = original_offset_x, original_offset_y
+ if original_ha != ha:
+ offset_x *= -1
+
+ if original_va != va:
+ offset_y *= -1
+
+ if ha == "center":
+ offset_x = 0
+ offset_y = 0
+
+ pt_kwargs = dict(**kwargs, va=va, ha=ha, xytext=(offset_x, offset_y))
+ label = plot_text(**pt_kwargs)
+
+ # if not hide_on_collision and i == len(anchor_fallbacks) - 1:
+ # break
+
+ collision = self._collision_score(label)
+ if collision == 0:
+ add_label(label)
+ return
+
+ if collision < 1:
+ collision_scores.append((collision, pt_kwargs))
+
+ label.remove()
+ # removed = self._maybe_remove_label(label)
+ # if not removed:
+ # break
+ if len(collision_scores) > 0:
+ best = sorted(collision_scores, key=lambda c: c[0])[0]
+ # return
+ if best[0] < 1:
+ label = plot_text(**best[1])
+ add_label(label)
+
def _text(
self,
ra: float,
dec: float,
text: str,
hide_on_collision: bool = True,
+ force: bool = False,
+ clip_on: bool = True,
*args,
**kwargs,
) -> None:
@@ -160,21 +376,59 @@ def _text(
x, y = self._prepare_coords(ra, dec)
kwargs["path_effects"] = kwargs.get("path_effects", [self.text_border])
- label = self.ax.annotate(
- text,
- (x, y),
- *args,
- **kwargs,
- **self._plot_kwargs(),
+
+ def plot_text(**kwargs):
+ label = self.ax.annotate(
+ text,
+ (x, y),
+ *args,
+ **kwargs,
+ **self._plot_kwargs(),
+ )
+ if clip_on:
+ label.set_clip_on(True)
+ label.set_clip_path(self._background_clip_path)
+ return label
+
+ label = plot_text(**kwargs)
+
+ if force:
+ return
+
+ removed = self._maybe_remove_label(
+ label, remove_on_collision=hide_on_collision, remove_on_clipped=clip_on
)
- if kwargs.get("clip_on") is False:
+
+ if not removed:
+ self._add_label_to_rtree(label)
return
- label.set_clip_on(True)
- label.set_clip_path(self._background_clip_path)
+ original_va = kwargs.pop("va", None)
+ original_ha = kwargs.pop("ha", None)
+ original_offset_x, original_offset_y = kwargs.pop("xytext", (0, 0))
+ anchor_fallbacks = self.style.text_anchor_fallbacks
+ for i, a in enumerate(anchor_fallbacks):
+ d = AnchorPointEnum.from_str(a).as_matplot()
+ va, ha = d["va"], d["ha"]
+ offset_x, offset_y = original_offset_x, original_offset_y
+ if original_ha != ha:
+ offset_x *= -1
+
+ if original_va != va:
+ offset_y *= -1
+
+ if ha == "center":
+ offset_x = 0
+ offset_y = 0
+
+ label = plot_text(**kwargs, va=va, ha=ha, xytext=(offset_x, offset_y))
+ removed = self._maybe_remove_label(
+ label, remove_on_collision=hide_on_collision, remove_on_clipped=clip_on
+ )
- if hide_on_collision:
- self._maybe_remove_label(label)
+ if not removed:
+ self._add_label_to_rtree(label)
+ break
@use_style(LabelStyle)
def text(
@@ -184,6 +438,8 @@ def text(
dec: float,
style: LabelStyle = None,
hide_on_collision: bool = True,
+ *args,
+ **kwargs,
):
"""
Plots text
@@ -195,15 +451,27 @@ def text(
style: Styling of the text
hide_on_collision: If True, then the text will not be plotted if it collides with another label
"""
+ if not self.in_bounds(ra, dec):
+ return
+
style = style or LabelStyle()
+
+ if style.offset_x == "auto":
+ style.offset_x = 0
+
+ if style.offset_y == "auto":
+ style.offset_y = 0
+
self._text(
ra,
dec,
text,
- **style.matplot_kwargs(self._size_multiplier),
+ **style.matplot_kwargs(self.scale),
hide_on_collision=hide_on_collision,
- xytext=(style.offset_x, style.offset_y),
- textcoords="offset pixels",
+ xycoords="data",
+ xytext=(style.offset_x * self.scale, style.offset_y * self.scale),
+ textcoords="offset points",
+ **kwargs,
)
@property
@@ -222,7 +490,7 @@ def title(self, text: str, style: LabelStyle = None):
text: Title text to plot
style: Styling of the title. If None, then the plot's style (specified when creating the plot) will be used
"""
- style_kwargs = style.matplot_kwargs(self._size_multiplier)
+ style_kwargs = style.matplot_kwargs(self.scale)
style_kwargs.pop("linespacing", None)
style_kwargs["pad"] = style.line_spacing
self.ax.set_title(text, **style_kwargs)
@@ -265,7 +533,7 @@ def legend(self, style: LegendStyle = None):
self._legend = self.ax.legend(
handles=self._legend_handles.values(),
- **style.matplot_kwargs(size_multiplier=self._size_multiplier),
+ **style.matplot_kwargs(self.scale),
**bbox_kwargs,
).set_zorder(
# zorder is not a valid kwarg to legend(), so we have to set it afterwards
@@ -317,6 +585,7 @@ def marker(
label: Optional[str] = None,
legend_label: str = None,
skip_bounds_check: bool = False,
+ **kwargs,
) -> None:
"""Plots a marker
@@ -335,18 +604,36 @@ def marker(
x, y = self._prepare_coords(ra, dec)
- self.ax.plot(
+ self.ax.scatter(
x,
y,
- **style.marker.matplot_kwargs(size_multiplier=self._size_multiplier),
+ **style.marker.matplot_scatter_kwargs(self.scale),
**self._plot_kwargs(),
- linestyle="None",
clip_on=True,
clip_path=self._background_clip_path,
+ gid=kwargs.get("gid_marker") or "marker",
)
if label:
- self.text(label, ra, dec, style.label)
+ label_style = style.label
+ if label_style.offset_x == "auto" or label_style.offset_y == "auto":
+ marker_size = ((style.marker.size / self.scale) ** 2) * (
+ self.scale**2
+ )
+
+ label_style = label_style.offset_from_marker(
+ marker_symbol=style.marker.symbol,
+ marker_size=marker_size,
+ scale=self.scale,
+ )
+ self.text(
+ label,
+ ra,
+ dec,
+ label_style,
+ hide_on_collision=self.hide_colliding_labels,
+ gid=kwargs.get("gid_label") or "marker-label",
+ )
if legend_label is not None:
self._add_legend_handle_marker(legend_label, style.marker)
@@ -386,11 +673,14 @@ def planets(
(p.ra, p.dec),
p.apparent_size,
polygon_style,
+ gid="planet-marker",
)
self._add_legend_handle_marker(legend_label, style.marker)
if label:
- self.text(label.upper(), p.ra, p.dec, style.label)
+ self.text(
+ label.upper(), p.ra, p.dec, style.label, gid="planet-label"
+ )
else:
self.marker(
ra=p.ra,
@@ -398,6 +688,8 @@ def planets(
style=style,
label=label.upper() if label else None,
legend_label=legend_label,
+ gid_marker="planet-marker",
+ gid_label="planet-label",
)
@use_style(ObjectStyle, "sun")
@@ -438,13 +730,14 @@ def sun(
(s.ra, s.dec),
s.apparent_size,
style=polygon_style,
+ gid="sun-marker",
)
style.marker.symbol = MarkerSymbolEnum.CIRCLE
self._add_legend_handle_marker(legend_label, style.marker)
if label:
- self.text(label, s.ra, s.dec, style.label)
+ self.text(label, s.ra, s.dec, style.label, gid="sun-label")
else:
self.marker(
@@ -453,6 +746,8 @@ def sun(
style=style,
label=label,
legend_label=legend_label,
+ gid_marker="sun-marker",
+ gid_label="sun-label",
)
@abstractmethod
@@ -491,7 +786,7 @@ def _polygon(self, points: list, style: PolygonStyle, **kwargs):
patch = patches.Polygon(
points,
# closed=False, # needs to be false for circles at poles?
- **style.matplot_kwargs(size_multiplier=self._size_multiplier),
+ **style.matplot_kwargs(self.scale),
**kwargs,
clip_on=True,
clip_path=self._background_clip_path,
@@ -504,6 +799,7 @@ def polygon(
style: PolygonStyle,
points: list = None,
geometry: Polygon = None,
+ **kwargs,
):
"""
Plots a polygon.
@@ -522,7 +818,7 @@ def polygon(
points = list(zip(*geometry.exterior.coords.xy))
_points = [(ra * 15, dec) for ra, dec in points]
- self._polygon(_points, style)
+ self._polygon(_points, style, gid=kwargs.get("gid") or "polygon")
@use_style(PolygonStyle)
def rectangle(
@@ -532,7 +828,6 @@ def rectangle(
width_degrees: float,
style: PolygonStyle,
angle: float = 0,
- *args,
**kwargs,
):
"""Plots a rectangle
@@ -550,7 +845,7 @@ def rectangle(
width_degrees,
angle,
)
- self._polygon(points, style)
+ self._polygon(points, style, gid=kwargs.get("gid") or "polygon")
@use_style(PolygonStyle)
def ellipse(
@@ -563,6 +858,7 @@ def ellipse(
num_pts: int = 100,
start_angle: int = 0,
end_angle: int = 360,
+ **kwargs,
):
"""Plots an ellipse
@@ -584,7 +880,7 @@ def ellipse(
start_angle,
end_angle,
)
- self._polygon(points, style)
+ self._polygon(points, style, gid=kwargs.get("gid") or "polygon")
@use_style(PolygonStyle)
def circle(
@@ -593,6 +889,7 @@ def circle(
radius_degrees: float,
style: PolygonStyle,
num_pts: int = 100,
+ **kwargs,
):
"""Plots a circle
@@ -609,10 +906,11 @@ def circle(
style=style,
angle=0,
num_pts=num_pts,
+ gid=kwargs.get("gid") or "polygon",
)
@use_style(LineStyle)
- def line(self, coordinates: list[tuple[float, float]], style: LineStyle):
+ def line(self, coordinates: list[tuple[float, float]], style: LineStyle, **kwargs):
"""Plots a line
Args:
@@ -626,7 +924,8 @@ def line(self, coordinates: list[tuple[float, float]], style: LineStyle):
y,
clip_on=True,
clip_path=self._background_clip_path,
- **style.matplot_kwargs(self._size_multiplier),
+ gid=kwargs.get("gid") or "line",
+ **style.matplot_kwargs(self.scale),
**self._plot_kwargs(),
)
@@ -680,13 +979,14 @@ def moon(
(m.ra, m.dec),
m.apparent_size,
style=polygon_style,
+ gid="moon-marker",
)
style.marker.symbol = MarkerSymbolEnum.CIRCLE
self._add_legend_handle_marker(legend_label, style.marker)
if label:
- self.text(label, m.ra, m.dec, style.label)
+ self.text(label, m.ra, m.dec, style.label, gid="moon-label")
else:
self.marker(
@@ -695,6 +995,8 @@ def moon(
style=style,
label=label,
legend_label=legend_label,
+ gid_marker="moon-marker",
+ gid_label="moon-label",
)
def _moon_with_phase(
@@ -761,6 +1063,7 @@ def _moon_with_phase(
num_pts=num_pts,
angle=0,
end_angle=180, # plot as a semicircle
+ gid="moon-marker",
)
# Plot right side
self.ellipse(
@@ -771,6 +1074,7 @@ def _moon_with_phase(
num_pts=num_pts,
angle=180,
end_angle=180, # plot as a semicircle
+ gid="moon-marker",
)
# Plot middle
self.ellipse(
@@ -778,6 +1082,7 @@ def _moon_with_phase(
radius_degrees * 2,
radius_degrees,
style=middle,
+ gid="moon-marker",
)
def _fov_circle(
@@ -860,17 +1165,16 @@ def ecliptic(self, style: PathStyle = None, label: str = "ECLIPTIC"):
y,
dash_capstyle=style.line.dash_capstyle,
clip_path=self._background_clip_path,
- **style.line.matplot_kwargs(self._size_multiplier),
+ gid="ecliptic-line",
+ **style.line.matplot_kwargs(self.scale),
**self._plot_kwargs(),
)
- if label:
- if len(inbounds) > 4:
- label_spacing = int(len(inbounds) / 3) or 1
+ if label and len(inbounds) > 4:
+ label_spacing = int(len(inbounds) / 4)
- for i in range(0, len(inbounds), label_spacing):
- ra, dec = inbounds[i]
- self.text(label, ra, dec, style.label)
+ for ra, dec in [inbounds[label_spacing], inbounds[label_spacing * 2]]:
+ self.text(label, ra, dec, style.label, gid="ecliptic-label")
@use_style(PathStyle, "celestial_equator")
def celestial_equator(
@@ -897,11 +1201,18 @@ def celestial_equator(
x,
y,
clip_path=self._background_clip_path,
- **style.line.matplot_kwargs(self._size_multiplier),
+ gid="celestial-equator-line",
+ **style.line.matplot_kwargs(self.scale),
**self._plot_kwargs(),
)
if label:
label_spacing = (self.ra_max - self.ra_min) / 3
for ra in np.arange(self.ra_min, self.ra_max, label_spacing):
- self.text(label, ra, 0.25, style.label)
+ self.text(
+ label,
+ ra,
+ 0.25,
+ style.label,
+ gid="celestial-equator-label",
+ )
diff --git a/src/starplot/callables.py b/src/starplot/callables.py
index bce37dfc..41395f4d 100644
--- a/src/starplot/callables.py
+++ b/src/starplot/callables.py
@@ -12,7 +12,7 @@ def size_by_magnitude_factory(
base: float = 20,
) -> Callable[[Star], float]:
"""
- Creates a new version of `size_by_magnitude` with a custom threshold and base multiplier:
+ Creates a new version of `size_by_magnitude_log` with a custom threshold and base multiplier:
```python
if magnitude >= threshold:
@@ -38,7 +38,7 @@ def size_fn(star: Star) -> float:
else:
size = base ** math.log(threshold - m)
- return size
+ return size * 9
return size_fn
@@ -46,7 +46,7 @@ def size_fn(star: Star) -> float:
_size_by_magnitude_default = size_by_magnitude_factory(7.6, 4)
-def size_by_magnitude(star: Star) -> float:
+def size_by_magnitude_log(star: Star) -> float:
"""
Calculates size by logarithmic scale of magnitude:
@@ -60,6 +60,60 @@ def size_by_magnitude(star: Star) -> float:
return _size_by_magnitude_default(star)
+def size_by_magnitude(star: Star) -> float:
+ """
+ Simple sizing by magnitude, using a step size of 1.
+
+ ```python
+ if mag <= 0:
+ size = 3800
+ elif mag <= 1: # 0..1
+ size = 2400
+ elif mag <= 2: # 1..2
+ size = 1600
+ elif mag <= 3: # 2..3
+ size = 1000
+ elif mag <= 4: # 3..4
+ size = 600
+ elif mag <= 5: # 4..5
+ size = 300
+ elif mag <= 6: # 5..6
+ size = 120
+ elif mag <= 7: # 6..7
+ size = 60
+ elif mag <= 8: # 7..8
+ size = 40
+ else: # > 8
+ size = 20
+
+ ```
+ """
+ mag = star.magnitude
+ size = 0
+ if mag <= 0:
+ size = 3800
+ elif mag <= 1: # 0..1
+ size = 2400
+ elif mag <= 2: # 1..2
+ size = 1600
+ elif mag <= 3: # 2..3
+ size = 1000
+ elif mag <= 4: # 3..4
+ size = 600
+ elif mag <= 5: # 4..5
+ size = 300
+ elif mag <= 6: # 5..6
+ size = 120
+ elif mag <= 7: # 6..7
+ size = 60
+ elif mag <= 8: # 7..8
+ size = 40
+ else: # > 8
+ size = 20
+
+ return size
+
+
def size_by_magnitude_simple(star: Star) -> float:
"""Very simple sizer by magnitude for map plots"""
m = star.magnitude
@@ -78,13 +132,13 @@ def size_by_magnitude_for_optic(star: Star) -> float:
m = star.magnitude
if m < 4.6:
- return (9 - m) ** 3.76
+ return (9 - m) ** 3.72 * 9
elif m < 5.85:
- return (9 - m) ** 3.72
+ return (9 - m) ** 3.72 * 9
elif m < 9:
- return (13 - m) ** 1.91
+ return (13 - m) ** 1.91 * 9
- return 4.93
+ return 4.93 * 9
def alpha_by_magnitude(star: Star) -> float:
diff --git a/src/starplot/data/bayer.py b/src/starplot/data/bayer.py
index 651e4476..da97943f 100644
--- a/src/starplot/data/bayer.py
+++ b/src/starplot/data/bayer.py
@@ -1,4 +1,4 @@
-hip = {
+hip_old = {
88: "Ο",
122: "ΞΈ",
183: "ΞΆ",
@@ -904,7 +904,7 @@
54879: "ΞΈ",
55016: "n",
55084: "Ο",
- 55203: "ΞΎ",
+ 55203: "ΞΎ", # missing?
55219: "Ξ½",
55282: "Ξ΄",
55425: "Ο",
@@ -1912,7 +1912,7 @@
26412: "Ξ½1",
27369: "Ξ»",
28957: "Ο1",
- 30422: "Ξ΅",
+ # 30422: "Ξ΅", # Monoceros binary B
33133: "Ο8",
40007: "Ο1",
41319: "d2",
@@ -1968,3 +1968,1532 @@
116737: "ΞΈ",
116904: "A2",
}
+
+# 1527
+hip = {
+ 8833: "ΞΎ",
+ 7007: "ΞΌ",
+ 7884: "Ξ½",
+ 9487: "Ξ±",
+ 12387: "Ξ΄",
+ 12706: "Ξ³",
+ 12093: "Ξ½",
+ 14135: "Ξ±",
+ 15457: "ΞΊ",
+ 18907: "Ξ½",
+ 23123: "ΟβΆ",
+ 22797: "Οβ΅",
+ 22549: "Οβ΄",
+ 22449: "ΟΒ³",
+ 24331: "Ο",
+ 25473: "Ο",
+ 26594: "Ο",
+ 30419: "Ξ΅",
+ 36265: "Ξ·",
+ 38373: "ΞΆ",
+ 36812: "δ³",
+ 36641: "δ¹",
+ 36723: "δ²",
+ 42799: "Ξ·",
+ 42402: "Ο",
+ 42313: "Ξ΄",
+ 43109: "Ξ΅",
+ 43234: "Ο",
+ 43813: "ΞΆ",
+ 45336: "ΞΈ",
+ 44659: "Ο",
+ 55945: "Ο",
+ 54182: "Ο",
+ 55434: "Ο",
+ 57757: "Ξ²",
+ 57380: "Ξ½",
+ 58590: "Ο",
+ 63090: "Ξ΄",
+ 64852: "Ο",
+ 68520: "Ο",
+ 77052: "Ο",
+ 77578: "Ο",
+ 77622: "Ξ΅",
+ 77070: "Ξ±",
+ 77257: "Ξ»",
+ 80179: "Ο",
+ 80883: "Ξ»",
+ 85355: "Ο",
+ 87108: "Ξ³",
+ 86742: "Ξ²",
+ 92946: "ΞΈΒΉ",
+ 92951: "ΞΈΒ²",
+ 95585: "Ξ½",
+ 95501: "Ξ΄",
+ 97804: "Ξ·",
+ 96665: "Ο",
+ 96229: "ΞΌ",
+ 98036: "Ξ²",
+ 98823: "Ο",
+ 103569: "Ξ΅",
+ 104987: "Ξ±",
+ 103813: "Ξ»",
+ 105570: "Ξ²",
+ 110672: "Ο",
+ 109068: "Ξ½",
+ 109427: "ΞΈ",
+ 115738: "ΞΊ",
+ 113889: "Ξ²",
+ 114971: "Ξ³",
+ 115830: "ΞΈ",
+ 116928: "Ξ»",
+ 116771: "ΞΉ",
+ 118268: "Ο",
+ 3786: "Ξ΄",
+ 4906: "Ξ΅",
+ 5743: "ΞΆ",
+ 5737: "ΞΆ",
+ 8198: "ΞΏ",
+ 7535: "Ο",
+ 10324: "ΞΎΒΉ",
+ 11484: "ΞΎΒ²",
+ 11249: "ΞΎ",
+ 13954: "Ξ»",
+ 12828: "ΞΌ",
+ 16083: "ΞΎ",
+ 15900: "ΞΏ",
+ 18724: "Ξ»",
+ 19860: "ΞΌ",
+ 20732: "Ο",
+ 21273: "Ο",
+ 22509: "ΟΒ²",
+ 22845: "ΟΒΉ",
+ 22667: "ΞΏΒΉ",
+ 22957: "ΞΏΒ²",
+ 26366: "ΟΒ²",
+ 26207: "Ξ»",
+ 26176: "ΟΒΉ",
+ 28614: "ΞΌ",
+ 29038: "Ξ½",
+ 29426: "ΞΎ",
+ 32362: "ΞΎ",
+ 36041: "Ξ΅",
+ 36284: "Ξ³",
+ 36188: "Ξ²",
+ 40526: "Ξ²",
+ 44066: "Ξ±",
+ 44798: "ΞΊ",
+ 46454: "Ο",
+ 47508: "ΞΏ",
+ 46771: "ΞΎ",
+ 45410: "Ο",
+ 49029: "Ο",
+ 48883: "Ξ½",
+ 47723: "Ο",
+ 51624: "Ο",
+ 56779: "Ο",
+ 55642: "ΞΉ",
+ 57328: "ΞΎ",
+ 58948: "ΞΏ",
+ 61960: "Ο",
+ 63608: "Ξ΅",
+ 71795: "ΞΆ",
+ 76276: "Ξ΄",
+ 76866: "Ο",
+ 77336: "Ο
",
+ 78132: "Ο",
+ 80463: "Ο",
+ 83000: "ΞΊ",
+ 82673: "ΞΉ",
+ 84345: "Ξ±ΒΉ",
+ 94834: "ΟΒΉ",
+ 95002: "ΟΒ²",
+ 93747: "ΞΆ",
+ 97229: "Ο
",
+ 97938: "ΞΎ",
+ 97278: "Ξ³",
+ 97675: "ΞΏ",
+ 96957: "Ο",
+ 97473: "Ο",
+ 97139: "Ο",
+ 98103: "Ο",
+ 101916: "ΞΊ",
+ 101483: "Ξ·",
+ 101800: "ΞΉ",
+ 101421: "Ξ΅",
+ 101589: "ΞΆ",
+ 101769: "Ξ²",
+ 101882: "ΞΈ",
+ 104521: "Ξ³",
+ 104858: "Ξ΄",
+ 107315: "Ξ΅",
+ 112935: "Ο",
+ 113186: "Ο",
+ 112447: "ΞΎ",
+ 112029: "ΞΆ",
+ 1067: "Ξ³",
+ 1168: "Ο",
+ 7097: "Ξ·",
+ 5454: "ΟΒ³",
+ 6706: "Ο",
+ 5131: "ΟΒΉ",
+ 5132: "ΟΒΉ",
+ 5571: "Ο",
+ 5310: "ΟΒ²",
+ 8832: "Ξ³ΒΉ",
+ 9110: "ΞΉ",
+ 8903: "Ξ²",
+ 10732: "ΞΈ",
+ 10306: "Ξ·",
+ 12332: "Ξ½",
+ 12803: "ΞΏ",
+ 13165: "Ο",
+ 13327: "Ο",
+ 13579: "ΟΒΉ",
+ 13702: "Ο",
+ 14838: "Ξ΄",
+ 12640: "ΞΌ",
+ 13914: "Ξ΅",
+ 15110: "ΞΆ",
+ 15627: "Ο",
+ 20205: "Ξ³",
+ 20885: "ΞΈΒΉ",
+ 20894: "ΞΈΒ²",
+ 21683: "ΟΒ²",
+ 21673: "ΟΒΉ",
+ 20455: "Ξ΄",
+ 19990: "Ο",
+ 20889: "Ξ΅",
+ 20635: "ΞΊ",
+ 23497: "ΞΉ",
+ 26451: "ΞΆ",
+ 27913: "ΟΒΉ",
+ 28716: "ΟΒ²",
+ 30883: "Ξ½",
+ 35350: "Ξ»",
+ 34088: "ΞΆ",
+ 35550: "Ξ΄",
+ 40167: "ΞΆΒΉ",
+ 41822: "ΞΈ",
+ 39780: "ΞΌ",
+ 44001: "ΞΏΒ²",
+ 43970: "ΞΏΒΉ",
+ 42556: "Ξ΅",
+ 42911: "Ξ΄",
+ 41909: "Ξ·",
+ 42806: "Ξ³",
+ 44946: "ΞΎ",
+ 49583: "Ξ·",
+ 54879: "ΞΈ",
+ 54872: "Ξ΄",
+ 64241: "Ξ±",
+ 67275: "Ο",
+ 67459: "Ο
",
+ 67927: "Ξ·",
+ 72125: "ΞΏ",
+ 71762: "ΟΒΉ",
+ 72659: "ΞΎ",
+ 75530: "ΟΒΉ",
+ 76423: "Οβ΄",
+ 76424: "Οβ΅",
+ 76069: "ΟΒ²",
+ 77111: "ΟβΈ",
+ 76810: "ΟβΆ",
+ 77233: "Ξ²",
+ 78072: "Ξ³",
+ 76337: "ΟΒ³",
+ 76852: "ΞΉ",
+ 76878: "Οβ·",
+ 77450: "ΞΊ",
+ 77661: "Ο",
+ 79045: "ΞΊ",
+ 79043: "ΞΊ",
+ 80170: "Ξ³",
+ 80816: "Ξ²",
+ 93244: "Ξ΅",
+ 96516: "Ξ΅",
+ 96757: "Ξ±",
+ 96837: "Ξ²",
+ 99742: "Ο",
+ 97365: "Ξ΄",
+ 97496: "ΞΆ",
+ 98337: "Ξ³",
+ 98920: "Ξ·",
+ 99352: "ΞΈ",
+ 101958: "Ξ±",
+ 102281: "Ξ΄",
+ 102532: "Ξ³Β²",
+ 102531: "Ξ³ΒΉ",
+ 113963: "Ξ±",
+ 117718: "Ο",
+ 3693: "ΞΆ",
+ 4463: "Ξ·",
+ 3031: "Ξ΅",
+ 5742: "Ο",
+ 6193: "Ο
",
+ 9153: "Ξ»",
+ 9884: "Ξ±",
+ 9836: "ΞΊ",
+ 9383: "Ο",
+ 8796: "Ξ±",
+ 17702: "Ξ·",
+ 20711: "Ο
",
+ 20430: "Ο",
+ 20250: "Ο",
+ 19205: "Ο",
+ 21881: "Ο",
+ 29655: "Ξ·",
+ 30343: "ΞΌ",
+ 29696: "ΞΊ",
+ 33927: "Ο",
+ 32246: "Ξ΅",
+ 37740: "ΞΊ",
+ 36046: "ΞΉ",
+ 36962: "Ο
",
+ 37629: "Ο",
+ 40881: "Ξ»",
+ 39191: "Ο",
+ 40007: "ΟΒΉ",
+ 40023: "Ο",
+ 38538: "Ο",
+ 39424: "Ο",
+ 40843: "Ο",
+ 41940: "Ο
Β²",
+ 41816: "Ο
ΒΉ",
+ 41404: "ΟΒ²",
+ 41377: "ΟΒΉ",
+ 43100: "ΞΉ",
+ 43103: "ΞΉ",
+ 43587: "ΟΒΉ",
+ 43834: "ΟΒ²",
+ 44405: "Ξ½",
+ 46146: "ΞΊ",
+ 44818: "Ο",
+ 46750: "Ξ»",
+ 47908: "Ξ΅",
+ 48455: "ΞΌ",
+ 50335: "ΞΆ",
+ 60742: "Ξ³",
+ 64394: "Ξ²",
+ 72105: "Ξ΅",
+ 73568: "Ο",
+ 71284: "Ο",
+ 73745: "Ο",
+ 74596: "Ο",
+ 75049: "ΞΏ",
+ 75695: "Ξ²",
+ 78554: "Ο",
+ 76952: "Ξ³",
+ 78159: "Ξ΅",
+ 77512: "Ξ΄",
+ 78493: "ΞΉ",
+ 79757: "Ο
",
+ 84379: "Ξ΄",
+ 85693: "Ξ»",
+ 86974: "ΞΌ",
+ 87933: "ΞΎ",
+ 88794: "ΞΏ",
+ 95771: "Ξ±",
+ 95951: "Ξ²Β²",
+ 95947: "Ξ²ΒΉ",
+ 107354: "ΞΊ",
+ 107310: "ΞΌΒΉ",
+ 109176: "ΞΉ",
+ 112440: "Ξ»",
+ 112748: "ΞΌ",
+ 112051: "ΞΏ",
+ 115250: "Ο",
+ 115623: "Ο
",
+ 113881: "Ξ²",
+ 118131: "Ο",
+ 1473: "Ο",
+ 3092: "Ξ΄",
+ 4889: "Ο",
+ 5586: "Ο",
+ 2912: "Ο",
+ 5447: "Ξ²",
+ 9570: "Ξ΅",
+ 10064: "Ξ²",
+ 10644: "Ξ΄",
+ 10670: "Ξ³",
+ 17448: "ΞΏ",
+ 18246: "ΞΆ",
+ 18614: "ΞΎ",
+ 23015: "ΞΉ",
+ 25984: "Ο",
+ 25541: "Ο",
+ 25292: "Ο",
+ 27639: "Ο
",
+ 28380: "ΞΈ",
+ 34693: "Ο",
+ 33018: "ΞΈ",
+ 36366: "Ο",
+ 38016: "Ο",
+ 37265: "ΞΏ",
+ 43584: "ΟΒΉ",
+ 44154: "ΟΒ³",
+ 43932: "ΟΒ²",
+ 45860: "Ξ±",
+ 51233: "Ξ²",
+ 55219: "Ξ½",
+ 71053: "Ο",
+ 75312: "Ξ·",
+ 76127: "ΞΈ",
+ 74666: "Ξ΄",
+ 75415: "ΞΌΒ²",
+ 75411: "ΞΌΒΉ",
+ 77048: "Ο",
+ 78459: "Ο",
+ 76669: "ΞΆΒ²",
+ 77655: "ΞΊ",
+ 79119: "Ο",
+ 80181: "ΞΎ",
+ 81693: "ΞΆ",
+ 79607: "Ο",
+ 80197: "Ξ½ΒΉ",
+ 80214: "Ξ½Β²",
+ 83207: "Ξ΅",
+ 84380: "Ο",
+ 85112: "Ο",
+ 87998: "Ξ½",
+ 87808: "ΞΈ",
+ 89826: "ΞΊ",
+ 92420: "Ξ²",
+ 92398: "Ξ½ΒΉ",
+ 92405: "Ξ½",
+ 93194: "Ξ³",
+ 93279: "Ξ»",
+ 92728: "δ¹",
+ 92791: "δ²",
+ 93903: "ΞΉ",
+ 96683: "Ο",
+ 97629: "Ο",
+ 98110: "Ξ·",
+ 102488: "Ξ΅",
+ 102589: "Ξ»",
+ 104732: "ΞΆ",
+ 105138: "Ο
",
+ 109410: "Ο",
+ 109352: "ΟΒΉ",
+ 112158: "Ξ·",
+ 1366: "ΞΈ",
+ 1686: "Ο",
+ 4436: "ΞΌ",
+ 3881: "Ξ½",
+ 7818: "Ο",
+ 7513: "Ο
",
+ 7719: "Ο",
+ 9640: "Ξ³ΒΉ",
+ 14354: "Ο",
+ 13879: "Ο",
+ 14817: "Ο",
+ 14668: "ΞΊ",
+ 18532: "Ξ΅",
+ 17529: "Ξ½",
+ 24340: "ΞΌ",
+ 23453: "ΞΆ",
+ 23767: "Ξ·",
+ 24813: "Ξ»",
+ 23416: "Ξ΅",
+ 27483: "Ο",
+ 27673: "Ξ½",
+ 25048: "Ο",
+ 31789: "ΟΒ³",
+ 31832: "ΟΒ²",
+ 33133: "ΟβΈ",
+ 32844: "Οβ·",
+ 32173: "Οβ΄",
+ 32480: "Οβ΅",
+ 50801: "ΞΌ",
+ 50372: "Ξ»",
+ 53295: "Ο",
+ 54539: "Ο",
+ 61317: "Ξ²",
+ 63125: "Ξ±Β²",
+ 71075: "Ξ³",
+ 73555: "Ξ²",
+ 76307: "ΞΌ",
+ 78012: "Ξ»",
+ 76041: "Ξ½Β²",
+ 75973: "Ξ½ΒΉ",
+ 76534: "Ο",
+ 77760: "Ο",
+ 81126: "Ο",
+ 79101: "Ο",
+ 81833: "Ξ·",
+ 90191: "ΞΌ",
+ 91971: "ΞΆΒΉ",
+ 91973: "ΞΆΒ²",
+ 94481: "Ξ·",
+ 94713: "ΞΈ",
+ 91919: "Ρ¹",
+ 91926: "Ρ²",
+ 100453: "Ξ³",
+ 104887: "Ο",
+ 103413: "Ξ½",
+ 105102: "Ο",
+ 104060: "ΞΎ",
+ 113726: "ΞΏ",
+ 116631: "ΞΉ",
+ 116805: "ΞΊ",
+ 3414: "Ο",
+ 3504: "ΞΏ",
+ 3300: "ΞΎ",
+ 6411: "ΞΎ",
+ 6813: "Ο",
+ 5434: "Ο",
+ 3801: "Ξ½",
+ 8068: "Ο",
+ 12777: "ΞΈ",
+ 16335: "Ο",
+ 16826: "Ο",
+ 17358: "Ξ΄",
+ 14632: "ΞΉ",
+ 19812: "ΞΌ",
+ 19167: "Ξ»",
+ 28404: "Ο",
+ 27196: "ΞΏ",
+ 30520: "ΟΒΉ",
+ 33377: "ΟβΉ",
+ 32562: "ΟβΆ",
+ 44127: "ΞΉ",
+ 44471: "ΞΊ",
+ 46853: "ΞΈ",
+ 57399: "Ο",
+ 69483: "ΞΊΒ²",
+ 69732: "Ξ»",
+ 70497: "ΞΈ",
+ 69713: "ΞΉ",
+ 78592: "Ο
",
+ 79992: "Ο",
+ 86414: "ΞΉ",
+ 85670: "Ξ²",
+ 87833: "Ξ³",
+ 95853: "ΞΉ",
+ 97165: "Ξ΄",
+ 99675: "ΞΏΒΉ",
+ 96441: "ΞΈ",
+ 98055: "Ο",
+ 99848: "ΞΏΒ²",
+ 101243: "ΟΒ²",
+ 101138: "ΟΒΉ",
+ 106481: "Ο",
+ 107136: "ΟΒΉ",
+ 107533: "ΟΒ²",
+ 110538: "Ξ²",
+ 111169: "Ξ±",
+ 116584: "Ξ»",
+ 117221: "Ο",
+ 2920: "ΞΆ",
+ 2505: "Ξ»",
+ 3179: "Ξ±",
+ 3821: "Ξ·",
+ 746: "Ξ²",
+ 5542: "ΞΈ",
+ 5336: "ΞΌ",
+ 4422: "Ο
Β²",
+ 4292: "Ο
ΒΉ",
+ 6242: "Ο",
+ 7294: "Ο",
+ 10729: "Ο",
+ 14328: "Ξ³",
+ 13531: "Ο",
+ 13268: "Ξ·",
+ 28358: "Ξ΄",
+ 27949: "ΞΎ",
+ 48402: "Ο",
+ 48319: "Ο
",
+ 58001: "Ξ³",
+ 59774: "Ξ΄",
+ 75458: "ΞΉ",
+ 78527: "ΞΈ",
+ 83608: "ΞΌ",
+ 85819: "Ξ½ΒΉ",
+ 85829: "Ξ½Β²",
+ 87585: "ΞΎ",
+ 94779: "ΞΊ",
+ 92512: "ΞΏ",
+ 102431: "Ο
ΒΉ",
+ 107259: "ΞΌ",
+ 109556: "Ξ»",
+ 109492: "ΞΆ",
+ 109857: "Ξ΅",
+ 110991: "Ξ΄",
+ 110988: "Ξ΄",
+ 118243: "Ο",
+ 117863: "Ο",
+ 117301: "Ο",
+ 2599: "ΞΊ",
+ 6686: "Ξ΄",
+ 8886: "Ξ΅",
+ 11569: "ΞΉ",
+ 23522: "Ξ²",
+ 22783: "Ξ±",
+ 41704: "ΞΏ",
+ 42527: "ΟΒ²",
+ 42438: "ΟΒΉ",
+ 44857: "ΟΒΉ",
+ 45075: "Ο",
+ 45038: "ΟΒ²",
+ 68756: "Ξ±",
+ 80331: "Ξ·",
+ 83895: "ΞΆ",
+ 95081: "Ο",
+ 101093: "ΞΈ",
+ 102422: "Ξ·",
+ 105199: "Ξ±",
+ 107418: "Ξ½",
+ 108917: "ΞΎ",
+ 112724: "ΞΉ",
+ 6692: "Ο",
+ 9009: "Ο",
+ 17959: "Ξ³",
+ 44390: "Ο",
+ 56211: "Ξ»",
+ 61281: "ΞΊ",
+ 75097: "Ξ³",
+ 72607: "Ξ²",
+ 86201: "Ο",
+ 86620: "Ο",
+ 86614: "Ο",
+ 89937: "Ο",
+ 89908: "Ο",
+ 92782: "Ο
",
+ 94376: "Ξ΄",
+ 98702: "Ο",
+ 96100: "Ο",
+ 97433: "Ξ΅",
+ 94648: "Ο",
+ 106032: "Ξ²",
+ 115088: "ΞΏ",
+ 4843: "Ο
",
+ 76008: "ΞΈ",
+ 76695: "ΟΒ²",
+ 77055: "ΞΆ",
+ 75829: "ΟΒΉ",
+ 75809: "ΟΒΉ",
+ 79822: "Ξ·",
+ 82080: "Ξ΅",
+ 99255: "ΞΊ",
+ 114222: "Ο",
+ 116727: "Ξ³",
+ 111056: "Ο",
+ 11767: "Ξ±",
+ 85822: "Ξ΄",
+ 84535: "Ξ»",
+ 10826: "ΞΏ",
+ 20507: "ΞΎ",
+ 19587: "ΞΏΒΉ",
+ 21444: "Ξ½",
+ 22109: "ΞΌ",
+ 22701: "Ο",
+ 25281: "Ξ·",
+ 23875: "Ξ²",
+ 23364: "Ο",
+ 24674: "Ο",
+ 26549: "Ο",
+ 26224: "ΞΈΒΉ",
+ 26221: "ΞΈΒΉ",
+ 26235: "ΞΈΒ²",
+ 26220: "ΞΈΒΉ",
+ 26241: "ΞΉ",
+ 25923: "Ο
",
+ 29651: "Ξ³",
+ 30867: "Ξ²",
+ 34769: "Ξ΄",
+ 39863: "ΞΆ",
+ 46509: "ΟΒΉ",
+ 46776: "ΟΒ²",
+ 47431: "ΞΉ",
+ 49641: "Ξ±",
+ 51362: "Ξ΄",
+ 51437: "Ξ²",
+ 55084: "Ο",
+ 56647: "Ο
",
+ 60129: "Ξ·",
+ 61941: "Ξ³",
+ 64238: "ΞΈ",
+ 66249: "ΞΆ",
+ 70012: "Ο
",
+ 70755: "Ο",
+ 69701: "ΞΉ",
+ 71957: "ΞΌ",
+ 77516: "ΞΌ",
+ 79593: "Ξ΄",
+ 79882: "Ξ΅",
+ 88175: "ΞΆ",
+ 89962: "Ξ·",
+ 92175: "Ξ²",
+ 93026: "Ξ·",
+ 93805: "Ξ»",
+ 96468: "ΞΉ",
+ 96483: "ΞΊ",
+ 99473: "ΞΈ",
+ 106278: "Ξ²",
+ 109074: "Ξ±",
+ 108874: "ΞΏ",
+ 110960: "ΞΆΒΉ",
+ 110395: "Ξ³",
+ 111497: "Ξ·",
+ 111710: "ΞΊ",
+ 114724: "Ο",
+ 1562: "ΞΉ",
+ 3455: "ΟΒΉ",
+ 3909: "ΟΒ²",
+ 4371: "ΟΒ³",
+ 4587: "Οβ΄",
+ 5364: "Ξ·",
+ 6537: "ΞΈ",
+ 8645: "ΞΆ",
+ 8497: "Ο",
+ 11345: "Ο",
+ 14060: "ΟΒΉ",
+ 14168: "ΟΒ²",
+ 13701: "Ξ·",
+ 12390: "Ξ΅",
+ 12770: "Ο",
+ 14293: "ΟΒ³",
+ 15197: "ΞΆ",
+ 16537: "Ξ΅",
+ 17378: "Ξ΄",
+ 17593: "Ο",
+ 18543: "Ξ³",
+ 19849: "ΞΏΒ²",
+ 23972: "Ξ»",
+ 24244: "ΞΉ",
+ 24327: "ΞΊ",
+ 24873: "Ξ½",
+ 24845: "Ξ»",
+ 27288: "ΞΆ",
+ 28103: "Ξ·",
+ 28910: "ΞΈ",
+ 33160: "ΞΈ",
+ 33345: "ΞΌ",
+ 37447: "Ξ±",
+ 46390: "Ξ±",
+ 47452: "ΞΊ",
+ 48437: "Ξ³",
+ 49402: "Ο
Β²",
+ 49841: "Ξ»",
+ 48356: "Ο
ΒΉ",
+ 50414: "Ξ΅",
+ 56633: "ΞΈ",
+ 55874: "ΞΊ",
+ 55687: "Ξ΅",
+ 55282: "Ξ΄",
+ 56802: "ΞΉ",
+ 61740: "Ο",
+ 62985: "Ο",
+ 69427: "ΞΊ",
+ 69974: "Ξ»",
+ 73473: "Ξ΄",
+ 74785: "Ξ²",
+ 72934: "ΞΎΒΉ",
+ 73133: "ΞΎΒ²",
+ 72489: "ΞΌ",
+ 75379: "Ξ΅",
+ 76333: "Ξ³",
+ 79375: "Ο",
+ 79540: "Ο",
+ 80628: "Ο
",
+ 81377: "ΞΆ",
+ 84880: "Ξ½",
+ 86284: "ΞΌ",
+ 86565: "ΞΏ",
+ 88404: "Ο",
+ 88048: "Ξ½",
+ 90135: "ΞΆ",
+ 91117: "Ξ±",
+ 91845: "Ξ΅",
+ 91726: "Ξ΄",
+ 90595: "Ξ³",
+ 99529: "ΞΎΒΉ",
+ 100027: "Ξ±ΒΉ",
+ 99572: "ΞΎ",
+ 100064: "Ξ±Β²",
+ 100310: "Ξ½",
+ 100345: "Ξ²",
+ 100325: "Ξ²Β²",
+ 103045: "ΞΌ",
+ 102618: "Ξ΅",
+ 101923: "Ο",
+ 104459: "Ξ½",
+ 106786: "ΞΎ",
+ 107517: "Ξ»",
+ 108036: "ΞΌ",
+ 110003: "ΞΈ",
+ 110273: "Ο",
+ 109139: "ΞΉ",
+ 112961: "Ξ»",
+ 111123: "Ο",
+ 112542: "ΟΒΉ",
+ 112716: "ΟΒ²",
+ 114939: "Ο",
+ 114855: "ΟΒΉ",
+ 115033: "ΟΒ²",
+ 115115: "ΟΒ³",
+ 116758: "ΟΒΉ",
+ 116971: "ΟΒ²",
+ 3419: "Ξ²",
+ 8102: "Ο",
+ 9347: "Ο
",
+ 11783: "Ο",
+ 12843: "ΟΒΉ",
+ 13288: "ΟΒ²",
+ 15474: "Οβ΄",
+ 16611: "Οβ΅",
+ 24305: "ΞΌ",
+ 23685: "Ξ΅",
+ 25985: "Ξ±",
+ 25606: "Ξ²",
+ 27654: "Ξ΄",
+ 27072: "Ξ³",
+ 31564: "Ξ½ΒΉ",
+ 31700: "Ξ½Β³",
+ 31592: "Ξ½Β²",
+ 33302: "Ο",
+ 34045: "Ξ³",
+ 33347: "ΞΉ",
+ 51069: "ΞΌ",
+ 51905: "ΟΒ²",
+ 51614: "ΟΒΉ",
+ 52085: "Ο",
+ 52943: "Ξ½",
+ 53740: "Ξ±",
+ 54742: "Ο",
+ 55705: "Ξ³",
+ 55598: "Ξ»",
+ 58188: "Ξ·",
+ 57283: "ΞΆ",
+ 59803: "Ξ³",
+ 61174: "Ξ·",
+ 60965: "Ξ΄",
+ 60189: "ΞΆ",
+ 72603: "Ξ±ΒΉ",
+ 72622: "Ξ±Β²",
+ 73945: "Ξ½",
+ 75118: "ΞΏ",
+ 74392: "ΞΉ",
+ 76126: "ΞΆ",
+ 77060: "Ξ·",
+ 77853: "ΞΈ",
+ 76880: "ΞΊ",
+ 77811: "Ξ»",
+ 80894: "Ο",
+ 80569: "Ο",
+ 78821: "Ξ²Β²",
+ 78820: "Ξ²ΒΉ",
+ 79374: "Ξ½",
+ 80343: "Ο",
+ 78933: "ΟΒΉ",
+ 78990: "ΟΒ²",
+ 80975: "Ο",
+ 84012: "Ξ·",
+ 84893: "ΞΎ",
+ 86263: "ΞΎ",
+ 89341: "ΞΌ",
+ 93057: "ΞΎΒΉ",
+ 93085: "ΞΎΒ²",
+ 94141: "Ο",
+ 93683: "ΞΏ",
+ 95176: "Ο
",
+ 95188: "ΟΒ²",
+ 95168: "ΟΒΉ",
+ 101751: "ΟΒΉ",
+ 101027: "Ο",
+ 100881: "Ο",
+ 101123: "ΞΏ",
+ 101120: "ΞΏ",
+ 101984: "Ο
",
+ 100195: "Ο",
+ 104139: "ΞΈ",
+ 104019: "Ξ·",
+ 104365: "Ο",
+ 104963: "Ο",
+ 105515: "ΞΉ",
+ 106985: "Ξ³",
+ 107556: "Ξ΄",
+ 107188: "ΞΊ",
+ 106723: "Ξ΅",
+ 105881: "ΞΆ",
+ 113136: "Ξ΄",
+ 111449: "Ο
",
+ 761: "ΞΊΒΉ",
+ 183: "ΞΆ",
+ 930: "ΞΊΒ²",
+ 1708: "ΞΉ",
+ 4577: "Ξ±",
+ 7463: "Ο",
+ 8209: "Ξ΅",
+ 9677: "Ξ½",
+ 11072: "ΞΊ",
+ 11918: "Ο",
+ 13197: "Ξ³ΒΉ",
+ 14146: "ΟΒ³",
+ 13942: "ΞΆ",
+ 13202: "Ξ³Β²",
+ 14086: "Ξ΅",
+ 14879: "Ξ±",
+ 17651: "ΟβΆ",
+ 17717: "Οβ·",
+ 18216: "ΟβΈ",
+ 17007: "Ο",
+ 17618: "Ο",
+ 18673: "ΟβΉ",
+ 21248: "Ο
ΒΉ",
+ 26412: "Ξ½ΒΉ",
+ 26460: "Ξ½Β²",
+ 31125: "ΞΎΒΉ",
+ 31416: "ΞΎΒ²",
+ 33152: "ΞΏΒΉ",
+ 33977: "ΞΏΒ²",
+ 33856: "Ο",
+ 34444: "Ξ΄",
+ 35037: "Ο",
+ 35415: "Ο",
+ 38070: "ΞΏ",
+ 37229: "ΞΊΒΉ",
+ 39757: "Ο",
+ 38170: "ΞΎ",
+ 42334: "Ξ·",
+ 43825: "Ξ΄",
+ 43409: "Ξ³",
+ 42483: "ΞΆ",
+ 44824: "ΞΊ",
+ 45902: "ΞΈ",
+ 46026: "Ξ»",
+ 47758: "ΞΈ",
+ 54255: "ΟΒ²",
+ 54204: "ΟΒΉ",
+ 54682: "Ξ²",
+ 59316: "Ξ΅",
+ 59199: "Ξ±",
+ 61359: "Ξ²",
+ 64166: "Ο",
+ 64962: "Ξ³",
+ 68895: "Ο",
+ 73714: "Ο",
+ 76470: "Ο
",
+ 78401: "Ξ΄",
+ 78265: "Ο",
+ 76600: "Ο",
+ 78104: "Ο",
+ 80473: "Ο",
+ 80079: "ΞΏ",
+ 80112: "Ο",
+ 81266: "Ο",
+ 84626: "ΞΏ",
+ 84625: "ΞΏ",
+ 84970: "ΞΈ",
+ 89931: "Ξ΄",
+ 92845: "Ξ½Β²",
+ 92761: "Ξ½ΒΉ",
+ 90496: "Ξ»",
+ 92041: "Ο",
+ 95503: "ΟΒ³",
+ 94643: "Ο",
+ 95486: "ΟΒ²",
+ 95477: "ΟΒΉ",
+ 93864: "Ο",
+ 93506: "ΞΆ",
+ 98066: "Ο",
+ 102485: "Ο",
+ 102978: "Ο",
+ 108661: "Ξ·",
+ 109789: "Ξ»",
+ 111138: "ΞΆ",
+ 111954: "Ξ΅",
+ 117452: "Ξ΄",
+ 2210: "Ξ·",
+ 950: "ΞΈ",
+ 4852: "Ο",
+ 7955: "Ο",
+ 9440: "Ο",
+ 10320: "ΞΌ",
+ 12122: "ΞΉΒΉ",
+ 12288: "ΞΉΒ²",
+ 13147: "Ξ²",
+ 11867: "λ¹",
+ 11477: "Ο",
+ 12186: "λ²",
+ 13040: "Ξ·ΒΉ",
+ 13265: "Ξ·Β³",
+ 13225: "Ξ·Β²",
+ 16112: "ΟΒ²",
+ 16156: "ΟΒ³",
+ 15987: "ΟΒΉ",
+ 17738: "Ο",
+ 17304: "Ξ΄",
+ 21393: "Ο
Β²",
+ 22280: "ΞΆ",
+ 20042: "Ο
β΄",
+ 21861: "Ξ²",
+ 24659: "ΞΏ",
+ 23595: "Ξ³",
+ 23596: "Ξ³",
+ 28098: "Ο",
+ 27204: "ΞΌ",
+ 25859: "Ξ΅",
+ 26634: "Ξ±",
+ 27810: "Ξ»",
+ 28199: "Ξ³",
+ 27628: "Ξ²",
+ 28010: "ΞΎ",
+ 30122: "ΞΆ",
+ 30277: "Ξ΄",
+ 30788: "Ξ»",
+ 29807: "ΞΊ",
+ 29034: "ΞΈ",
+ 32759: "ΞΊ",
+ 35264: "Ο",
+ 38901: "Ο",
+ 42828: "Ξ±",
+ 42515: "Ξ²",
+ 45001: "Ξ΅",
+ 46734: "ΞΆΒ²",
+ 46657: "ΞΆΒΉ",
+ 46515: "Ξ΅",
+ 48926: "Ξ·",
+ 51376: "Ξ΄",
+ 51172: "Ξ±",
+ 53502: "ΞΉ",
+ 56343: "ΞΎ",
+ 56922: "ΞΏ",
+ 57936: "Ξ²",
+ 65109: "ΞΉ",
+ 68933: "ΞΈ",
+ 75177: "ΟΒΉ",
+ 75304: "ΟΒ²",
+ 77634: "Ο",
+ 76705: "ΟΒΉ",
+ 76945: "ΟΒ²",
+ 78106: "ΞΎΒ²",
+ 78105: "ΞΎΒΉ",
+ 78918: "ΞΈ",
+ 82396: "Ξ΅",
+ 85696: "Ο
",
+ 88635: "Ξ³",
+ 89642: "Ξ·",
+ 93174: "Ξ΅",
+ 93825: "Ξ³",
+ 98421: "ΞΈΒ²",
+ 98412: "ΞΈΒΉ",
+ 102989: "Ξ²",
+ 102831: "Ξ±",
+ 104148: "Ξ΄",
+ 103738: "Ξ³",
+ 105140: "Ξ΅",
+ 107608: "ΞΈ",
+ 107380: "ΞΉ",
+ 109422: "Ο",
+ 109285: "ΞΌ",
+ 109289: "Ο
",
+ 108085: "Ξ³",
+ 111188: "Ξ²",
+ 113246: "Ξ΄",
+ 112948: "Ξ³",
+ 113860: "Ο",
+ 115102: "Ξ³",
+ 116820: "ΞΌ",
+ 2081: "Ξ±",
+ 2072: "ΞΊ",
+ 3456: "λ²",
+ 3356: "λ¹",
+ 4770: "ΞΎ",
+ 5300: "Ο
",
+ 8882: "Ο",
+ 6867: "Ξ³",
+ 9459: "Ο",
+ 12486: "ΞΉ",
+ 13473: "Ο",
+ 13847: "ΞΈΒΉ",
+ 19515: "Ξ΄",
+ 19747: "Ξ±",
+ 21060: "Ξ΄",
+ 21998: "Ξ»",
+ 22488: "Ξ½",
+ 21770: "Ξ±",
+ 28328: "Ξ·",
+ 29064: "ΟΒ²",
+ 28957: "ΟΒΉ",
+ 31685: "Ξ½",
+ 36377: "Ο",
+ 44816: "Ξ»",
+ 46651: "Ο",
+ 67472: "ΞΌ",
+ 67464: "Ξ½",
+ 68245: "Ο",
+ 68282: "Ο
ΒΉ",
+ 70090: "Ο",
+ 68862: "Ο",
+ 73334: "ΞΊ",
+ 73273: "Ξ²",
+ 72683: "ΞΏ",
+ 75439: "Ο
",
+ 75141: "Ξ΄",
+ 76297: "Ξ³",
+ 76552: "Ο",
+ 75264: "Ξ΅",
+ 78384: "Ξ·",
+ 79963: "Ξ»",
+ 81122: "ΞΌ",
+ 82545: "ΞΌΒ²",
+ 82514: "ΞΌΒΉ",
+ 82671: "ΞΆΒΉ",
+ 82729: "ΞΆΒ²",
+ 84143: "Ξ·",
+ 87073: "ΞΉΒΉ",
+ 87294: "ΞΉΒ²",
+ 90968: "ΞΊΒ²",
+ 90969: "ΞΊΒΉ",
+ 90982: "ΞΈ",
+ 91875: "Ξ»",
+ 94114: "Ξ±",
+ 94160: "Ξ²",
+ 92226: "ΞΌ",
+ 94005: "Ξ΄",
+ 93542: "ΞΆ",
+ 92308: "Ξ·ΒΉ",
+ 92382: "Ξ·Β²",
+ 95347: "Ξ±",
+ 98032: "ΞΉ",
+ 95241: "Ξ²ΒΉ",
+ 95294: "Ξ²Β²",
+ 100469: "ΞΊΒΉ",
+ 100591: "ΞΊΒ²",
+ 101477: "Ξ½",
+ 103882: "ΞΆ",
+ 104177: "Ξ·",
+ 102693: "ΞΉ",
+ 105696: "ΞΈΒ²",
+ 105382: "ΞΈΒΉ",
+ 106327: "ΞΎ",
+ 109111: "Ξ»",
+ 110936: "Ξ½",
+ 109908: "ΞΌΒΉ",
+ 109973: "ΞΌΒ²",
+ 111594: "ΟΒΉ",
+ 111643: "ΟΒ²",
+ 110997: "δ¹",
+ 111043: "δ²",
+ 114132: "Ο
",
+ 112203: "Ο",
+ 115054: "Ο",
+ 114131: "ΞΈ",
+ 116231: "Ξ²",
+ 116389: "ΞΉ",
+ 765: "Ξ΅",
+ 3245: "ΞΌ",
+ 88: "Ο",
+ 2802: "λ²",
+ 2472: "λ¹",
+ 5165: "Ξ²",
+ 5862: "Ξ½",
+ 3949: "Ο",
+ 8837: "Ο",
+ 7083: "Ξ΄",
+ 9007: "Ο",
+ 11407: "ΞΊ",
+ 10602: "Ο",
+ 12653: "ΞΉ",
+ 19893: "Ξ³",
+ 23649: "Ξ·Β²",
+ 23482: "Ξ·ΒΉ",
+ 21914: "Ξ»",
+ 24829: "ΞΆ",
+ 25303: "ΞΈ",
+ 27321: "Ξ²",
+ 32768: "Ο",
+ 52727: "ΞΌ",
+ 61622: "Ο",
+ 60823: "Ο",
+ 61932: "Ξ³",
+ 59196: "Ξ΄",
+ 59449: "Ο",
+ 63724: "ΞΎΒΉ",
+ 64004: "ΞΎΒ²",
+ 68523: "Ο
Β²",
+ 69996: "ΞΉ",
+ 70574: "ΟΒΉ",
+ 70576: "ΟΒ²",
+ 71121: "Ο",
+ 71536: "Ο",
+ 74117: "Ξ»",
+ 73807: "Ο",
+ 75206: "Ξ½ΒΉ",
+ 75181: "Ξ½Β²",
+ 74911: "ΞΌ",
+ 74376: "ΞΊΒΉ",
+ 74380: "ΞΊΒ²",
+ 74395: "ΞΆ",
+ 78914: "Ξ΄",
+ 79653: "ΞΈ",
+ 80582: "Ξ΅",
+ 78639: "Ξ·",
+ 79790: "Ξ³ΒΉ",
+ 80000: "Ξ³Β²",
+ 86092: "Ο",
+ 85079: "ΞΉ",
+ 85792: "Ξ±",
+ 86486: "Ξ»",
+ 85312: "ΞΊ",
+ 86796: "ΞΌ",
+ 89112: "Ξ΅",
+ 90853: "δ²",
+ 90830: "δ¹",
+ 90422: "Ξ±",
+ 88714: "ΞΈ",
+ 90568: "ΞΆ",
+ 92646: "ΞΊ",
+ 93815: "Ο",
+ 96341: "ΞΉ",
+ 102790: "ΞΆ",
+ 101772: "Ξ±",
+ 102333: "Ξ·",
+ 102950: "ΞΉ",
+ 110478: "ΟΒΉ",
+ 110506: "ΟΒ²",
+ 112122: "Ξ²",
+ 114421: "ΞΉ",
+ 113307: "ΟΒ³",
+ 113044: "ΟΒΉ",
+ 113190: "ΟΒ²",
+ 113191: "ΟΒ²",
+ 112623: "Ξ΅",
+ 116737: "ΞΈ",
+ 117315: "Ο",
+ 3277: "ΞΎ",
+ 3405: "Ξ·",
+ 4829: "Ο",
+ 5348: "ΞΆ",
+ 12225: "Ξ·",
+ 12484: "ΞΆ",
+ 14240: "ΞΌ",
+ 19921: "Ξ΅",
+ 22534: "ΞΉ",
+ 22531: "ΞΉ",
+ 21281: "Ξ±",
+ 23693: "ΞΆ",
+ 22040: "ΞΊ",
+ 25098: "ΞΊ",
+ 27530: "Ξ³",
+ 29276: "Ξ΄",
+ 25429: "Ξ»",
+ 30342: "Ξ½",
+ 31137: "ΞΌ",
+ 38827: "Ο",
+ 42536: "ΞΏ",
+ 45556: "ΞΉ",
+ 48774: "Ο",
+ 55425: "Ο",
+ 56243: "ΞΏΒΉ",
+ 56250: "ΞΏΒ²",
+ 59747: "Ξ΄",
+ 63005: "ΞΌΒ²",
+ 63003: "ΞΌΒΉ",
+ 63007: "Ξ»",
+ 74824: "Ξ²",
+ 75323: "Ξ³",
+ 79497: "ΞΆ",
+ 79509: "ΞΊ",
+ 79153: "ΞΉΒ²",
+ 78662: "ΞΉΒΉ",
+ 82363: "Ξ·",
+ 83153: "Ρ¹",
+ 83431: "Ρ²",
+ 83081: "ΞΆ",
+ 85258: "Ξ²",
+ 86305: "Ο",
+ 85267: "Ξ³",
+ 87379: "Ο
Β²",
+ 87314: "Ο
ΒΉ",
+ 93148: "Ξ»",
+ 95932: "ΞΌ",
+ 95261: "Ξ·",
+ 99120: "ΞΎ",
+ 97421: "Ξ½",
+ 105319: "ΞΈ",
+ 104085: "ΞΌ",
+ 103227: "Ξ²",
+ 105841: "Ξ³",
+ 108431: "Ξ΄",
+ 108281: "Ο",
+ 108870: "Ξ΅",
+ 108478: "ΞΊ",
+ 109081: "ΞΊ",
+ 112374: "Ξ·",
+ 113957: "ΞΊ",
+ 113638: "ΞΆ",
+ 115713: "ΞΏ",
+ 118234: "Ο",
+ 114996: "Ξ³",
+ 5268: "ΞΉ",
+ 1599: "ΞΆ",
+ 2484: "Ξ²ΒΉ",
+ 2487: "Ξ²Β²",
+ 2578: "Ξ²Β³",
+ 3330: "Ο",
+ 9236: "Ξ±",
+ 11258: "Ξ»",
+ 13141: "Ξ½",
+ 12871: "Ξ³",
+ 13884: "Ξ²",
+ 15330: "ΞΆΒΉ",
+ 15371: "ΞΆΒ²",
+ 18597: "Ξ΄",
+ 18744: "Ξ³",
+ 18772: "ΞΉ",
+ 19780: "Ξ±",
+ 16245: "ΞΊ",
+ 17440: "Ξ²",
+ 20020: "ΞΈ",
+ 20384: "Ξ·",
+ 26069: "Ξ²",
+ 24372: "ΞΈ",
+ 32607: "Ξ±",
+ 29353: "Ξ·Β²",
+ 27100: "Ξ΄",
+ 27534: "Ξ΅",
+ 28909: "Ξ·ΒΉ",
+ 41312: "Ξ²",
+ 44382: "Ξ±",
+ 48002: "Ο
",
+ 52419: "ΞΈ",
+ 60260: "Ξ΅",
+ 56561: "Ξ»",
+ 58867: "ΞΈΒ²",
+ 58758: "ΞΈΒΉ",
+ 59072: "Ξ·",
+ 60009: "ΞΆ",
+ 57363: "Ξ»",
+ 57581: "ΞΌ",
+ 62268: "ΞΉ",
+ 62931: "ΞΊ",
+ 64094: "ΞΈ",
+ 71908: "Ξ±",
+ 74778: "Ξ΄",
+ 73129: "ΞΈ",
+ 74837: "Ξ΅",
+ 77952: "Ξ²",
+ 73776: "Ξ·",
+ 72965: "ΞΆ",
+ 76440: "Ξ΅",
+ 79664: "Ξ΄",
+ 80645: "ΞΉ",
+ 81252: "ΞΈ",
+ 85727: "Ξ΄",
+ 89042: "ΞΉ",
+ 88866: "Ο",
+ 86929: "Ξ·",
+ 90098: "ΞΎ",
+ 93163: "Ο",
+ 90797: "Ξ½",
+ 92609: "Ξ»",
+ 92294: "ΞΈ",
+ 93015: "ΞΊ",
+ 98624: "ΞΌΒ²",
+ 98478: "ΞΌΒΉ",
+ 99240: "Ξ΄",
+ 101983: "ΟΒ²",
+ 101773: "Ο",
+ 101612: "ΟΒΉ",
+ 105858: "Ξ³",
+ 102157: "Ο
",
+ 102395: "Ξ²",
+ 110130: "Ξ±",
+ 111310: "Ξ½",
+ 110838: "Ξ΄",
+ 118121: "Ξ·",
+ 118322: "Ξ΅",
+ 1647: "Ο",
+ 4084: "λ¹",
+ 4293: "λ²",
+ 5896: "ΞΊ",
+ 2629: "ΞΈ",
+ 3781: "Ξ»",
+ 8928: "Ξ·Β²",
+ 8751: "Ξ·ΒΉ",
+ 10513: "ΟΒ²",
+ 10418: "ΟΒΉ",
+ 12394: "Ξ΅",
+ 11001: "Ξ΄",
+ 12876: "ΞΆ",
+ 14131: "ΞΈ",
+ 11095: "ΞΊ",
+ 21949: "ΞΌ",
+ 17678: "Ξ³",
+ 29134: "Ξ½",
+ 30321: "ΟΒΉ",
+ 23467: "Ξ²",
+ 27369: "Ξ»",
+ 22871: "Ξ·",
+ 29271: "Ξ±",
+ 35228: "Ξ΄",
+ 30565: "ΟΒ²",
+ 34473: "Ξ³ΒΉ",
+ 34481: "Ξ³Β²",
+ 32912: "ΞΉ",
+ 37504: "ΞΆ",
+ 39794: "Ξ΅",
+ 42425: "ΞΈ",
+ 40834: "ΞΊΒ²",
+ 40817: "ΞΊΒΉ",
+ 41003: "Ξ·",
+ 50099: "Ο",
+ 60320: "ΞΆΒ²",
+ 60329: "ΞΆΒΉ",
+ 59929: "Ξ΅",
+ 62322: "Ξ²",
+ 61585: "Ξ±",
+ 61199: "Ξ³",
+ 64661: "Ξ·",
+ 63613: "Ξ΄",
+ 65628: "ΞΉΒ²",
+ 65468: "ΞΉΒΉ",
+ 74946: "Ξ³",
+ 77982: "ΞΊ",
+ 76013: "ΞΊΒΉ",
+ 76750: "ΞΊΒ²",
+ 81710: "Ξ·ΒΉ",
+ 82273: "Ξ±",
+ 84969: "ΞΆ",
+ 80686: "ΞΆ",
+ 84979: "ΞΉ",
+ 91792: "ΞΆ",
+ 94724: "Ο",
+ 98495: "Ξ΅",
+ 102773: "Ο",
+ 104755: "ΞΏ",
+ 107835: "ΞΏ",
+ 113137: "Ο",
+ 110618: "Ξ½",
+ 122: "ΞΈ",
+ 2021: "Ξ²",
+ 8991: "Ο",
+ 8366: "ΟΒ²",
+ 7879: "ΟΒΉ",
+ 11757: "ΞΌ",
+ 814: "Ξ³Β³",
+ 13244: "Ξ½",
+ 15201: "ΞΉ",
+ 20049: "Ξ΄",
+ 20297: "Ξ½",
+ 23148: "ΞΎ",
+ 25918: "Ξ³",
+ 26264: "ΞΉ",
+ 27566: "ΞΊ",
+ 26394: "Ο",
+ 33384: "ΞΈ",
+ 36039: "Ξ΅",
+ 31897: "ΞΆ",
+ 47956: "Ξ½",
+ 40702: "Ξ±",
+ 40888: "ΞΈ",
+ 51839: "Ξ³",
+ 42637: "Ξ·",
+ 46107: "ΞΉ",
+ 46928: "ΞΆ",
+ 49326: "ΞΌΒ²",
+ 49065: "ΞΌ",
+ 56675: "Ο",
+ 58905: "ΞΊ",
+ 58484: "Ξ΅",
+ 52595: "δ¹",
+ 52633: "δ²",
+ 60000: "Ξ²",
+ 68815: "ΞΈ",
+ 70248: "Ξ΅",
+ 72370: "Ξ±",
+ 69896: "Ξ·",
+ 90133: "Ο",
+ 80057: "δ²",
+ 80047: "δ¹",
+ 81852: "Ξ²",
+ 81065: "Ξ³",
+ 102125: "ΞΌΒ²",
+ 102162: "ΞΌΒΉ",
+ 104043: "Ξ±",
+ 107089: "Ξ½",
+ 110078: "Ο",
+ 110256: "Ξ΅",
+ 112781: "ΞΎ",
+ 112405: "Ξ²",
+ 118114: "Ξ³Β²",
+ 117689: "Ξ³ΒΉ",
+ 73540: "ΟΒΉ",
+ 73771: "ΟΒ²",
+ 70638: "Ξ΄",
+ 43908: "ΞΆ",
+ 53702: "Ξ·",
+ 66753: "ΞΊ",
+ 63031: "ΞΉ",
+ 76996: "Ο",
+ 74296: "Ο",
+ 107843: "Ξ»",
+ 111196: "Ο
",
+ 92824: "Ο",
+ 115836: "Ο",
+ 104382: "Ο",
+ 25336: "Ξ³",
+ 27989: "Ξ±",
+ 37279: "Ξ±",
+ 49669: "Ξ±",
+ 57632: "Ξ²",
+ 86032: "Ξ±",
+ 97649: "Ξ±",
+ 21421: "Ξ±",
+ 31681: "Ξ³",
+ 50583: "Ξ³ΒΉ",
+ 69673: "Ξ±",
+ 677: "Ξ±",
+ 25428: "Ξ²",
+ 37826: "Ξ²",
+ 76267: "Ξ±",
+ 36850: "Ξ±",
+ 14576: "Ξ²",
+ 28360: "Ξ²",
+ 63121: "Ξ±ΒΉ",
+ 91262: "Ξ±",
+ 15863: "Ξ±",
+ 24608: "Ξ±",
+ 67301: "Ξ·",
+ 69481: "ΞΊΒΉ",
+ 102098: "Ξ±",
+ 53910: "Ξ²",
+ 62956: "Ξ΅",
+ 65378: "ΞΆ",
+ 4427: "Ξ³",
+ 54061: "Ξ±",
+ 25930: "Ξ΄",
+ 26311: "Ξ΅",
+ 26727: "ΞΆ",
+ 24436: "Ξ²",
+ 27366: "ΞΊ",
+ 65474: "Ξ±",
+ 30324: "Ξ²",
+ 32349: "Ξ±",
+ 33579: "Ξ΅",
+ 35904: "Ξ·",
+ 80763: "Ξ±",
+ 92855: "Ο",
+ 113368: "Ξ±",
+ 85927: "Ξ»",
+ 90185: "Ξ΅",
+ 39429: "ΞΆ",
+ 71352: "Ξ·",
+ 86670: "ΞΊ",
+ 86228: "ΞΈ",
+ 39953: "Ξ³Β²",
+ 68002: "ΞΆ",
+ 71860: "Ξ±",
+ 109268: "Ξ±",
+ 7588: "Ξ±",
+ 30438: "Ξ±",
+ 42913: "Ξ΄",
+ 41037: "Ξ΅",
+ 45941: "ΞΊ",
+ 61084: "Ξ³",
+ 62434: "Ξ²",
+ 66657: "Ξ΅",
+ 100751: "Ξ±",
+ 60718: "Ξ±ΒΉ",
+ 68702: "Ξ²",
+ 71681: "Ξ±",
+ 71683: "Ξ±",
+ 45238: "Ξ²",
+}
diff --git a/src/starplot/data/flamsteed.py b/src/starplot/data/flamsteed.py
new file mode 100644
index 00000000..bc78e84e
--- /dev/null
+++ b/src/starplot/data/flamsteed.py
@@ -0,0 +1,2682 @@
+hip = {
+ 2006: 44,
+ 2548: 51,
+ 3697: 60,
+ 3760: 62,
+ 5315: 29,
+ 4979: 26,
+ 5510: 33,
+ 5646: 35,
+ 5141: 77,
+ 5144: 77,
+ 6061: 89,
+ 5074: 73,
+ 5346: 80,
+ 5824: 88,
+ 6815: 95,
+ 8833: 111,
+ 7007: 98,
+ 7884: 106,
+ 9589: 60,
+ 11021: 69,
+ 9353: 112,
+ 9487: 113,
+ 12387: 82,
+ 12706: 86,
+ 12093: 78,
+ 14143: 93,
+ 14135: 92,
+ 15457: 96,
+ 15619: 97,
+ 16852: 10,
+ 17103: 12,
+ 17563: 29,
+ 18089: 31,
+ 20884: 44,
+ 18957: 40,
+ 18907: 38,
+ 19554: 45,
+ 21515: 49,
+ 23123: 10,
+ 22730: 5,
+ 22797: 8,
+ 22549: 3,
+ 22449: 1,
+ 25302: 25,
+ 24331: 17,
+ 24817: 21,
+ 25145: 23,
+ 25142: 23,
+ 25473: 30,
+ 26885: 51,
+ 27750: 56,
+ 28271: 59,
+ 28296: 60,
+ 25861: 33,
+ 26126: 38,
+ 26594: 47,
+ 25813: 32,
+ 27386: 52,
+ 30717: 77,
+ 28812: 63,
+ 28814: 66,
+ 30419: 8,
+ 32578: 18,
+ 31159: 12,
+ 31216: 13,
+ 36265: 5,
+ 38373: 13,
+ 36812: 9,
+ 36641: 7,
+ 36723: 8,
+ 38962: 14,
+ 42799: 7,
+ 42402: 5,
+ 42313: 4,
+ 42931: 10,
+ 43109: 11,
+ 43234: 13,
+ 43813: 16,
+ 45336: 22,
+ 44659: 18,
+ 48414: 7,
+ 47310: 2,
+ 48552: 9,
+ 48273: 4,
+ 48990: 12,
+ 47205: 10,
+ 50684: 23,
+ 49329: 13,
+ 50027: 19,
+ 49530: 14,
+ 50851: 43,
+ 51451: 31,
+ 52584: 36,
+ 53423: 55,
+ 52452: 35,
+ 52401: 34,
+ 51775: 48,
+ 52660: 37,
+ 53449: 56,
+ 54336: 65,
+ 55137: 75,
+ 55249: 76,
+ 55650: 79,
+ 53807: 58,
+ 55791: 80,
+ 55846: 83,
+ 55945: 84,
+ 53824: 59,
+ 54182: 63,
+ 55434: 77,
+ 57757: 5,
+ 56445: 89,
+ 58510: 7,
+ 57380: 3,
+ 59285: 10,
+ 60172: 16,
+ 58590: 8,
+ 59309: 11,
+ 60353: 17,
+ 62443: 35,
+ 63090: 43,
+ 62757: 37,
+ 61968: 31,
+ 64852: 60,
+ 65241: 64,
+ 68092: 92,
+ 66200: 78,
+ 66936: 84,
+ 68520: 93,
+ 72220: 109,
+ 72154: 108,
+ 74975: 5,
+ 75119: 6,
+ 74689: 4,
+ 75761: 10,
+ 73620: 110,
+ 74649: 3,
+ 77052: 23,
+ 77578: 34,
+ 77622: 37,
+ 77070: 24,
+ 77257: 27,
+ 80179: 50,
+ 78685: 43,
+ 79488: 9,
+ 80351: 21,
+ 81734: 14,
+ 82480: 21,
+ 82037: 16,
+ 80883: 10,
+ 82162: 19,
+ 81007: 28,
+ 81641: 37,
+ 81634: 36,
+ 82216: 45,
+ 81991: 41,
+ 82402: 47,
+ 84500: 38,
+ 85355: 49,
+ 86831: 61,
+ 87108: 62,
+ 86742: 60,
+ 88290: 68,
+ 90441: 59,
+ 88192: 67,
+ 88601: 70,
+ 89918: 74,
+ 88149: 66,
+ 88964: 73,
+ 91975: 4,
+ 93051: 64,
+ 92946: 63,
+ 92951: 63,
+ 92872: 62,
+ 94913: 24,
+ 94885: 23,
+ 95585: 32,
+ 94477: 21,
+ 95501: 30,
+ 95793: 35,
+ 94727: 22,
+ 94068: 19,
+ 97804: 55,
+ 97980: 58,
+ 96665: 44,
+ 96229: 38,
+ 98036: 60,
+ 98823: 63,
+ 101936: 1,
+ 103569: 1,
+ 102633: 13,
+ 104031: 3,
+ 104987: 8,
+ 104101: 4,
+ 103813: 2,
+ 105413: 9,
+ 105570: 10,
+ 107144: 26,
+ 106944: 25,
+ 107575: 11,
+ 106783: 3,
+ 106856: 4,
+ 107151: 7,
+ 108691: 28,
+ 110672: 52,
+ 111062: 37,
+ 110785: 34,
+ 110882: 35,
+ 108612: 18,
+ 109068: 22,
+ 109427: 26,
+ 110298: 30,
+ 113167: 1,
+ 113521: 2,
+ 114273: 5,
+ 113610: 3,
+ 115768: 9,
+ 115738: 8,
+ 113889: 4,
+ 114971: 6,
+ 115830: 10,
+ 115227: 7,
+ 116422: 15,
+ 116495: 16,
+ 117491: 21,
+ 116928: 18,
+ 117774: 25,
+ 117245: 19,
+ 117683: 22,
+ 116771: 17,
+ 117927: 26,
+ 118268: 28,
+ 194: 32,
+ 186: 31,
+ 1196: 35,
+ 1319: 36,
+ 1392: 38,
+ 1645: 41,
+ 2025: 45,
+ 813: 34,
+ 476: 86,
+ 1772: 42,
+ 3786: 63,
+ 3675: 58,
+ 4906: 71,
+ 5743: 86,
+ 5737: 86,
+ 5081: 72,
+ 5204: 75,
+ 8198: 110,
+ 7535: 102,
+ 8588: 54,
+ 7436: 101,
+ 7710: 104,
+ 7364: 100,
+ 10212: 64,
+ 10324: 65,
+ 11484: 73,
+ 11249: 24,
+ 11427: 25,
+ 13954: 91,
+ 12153: 31,
+ 12647: 85,
+ 12832: 38,
+ 12828: 87,
+ 16083: 2,
+ 16511: 6,
+ 15900: 1,
+ 16322: 4,
+ 16369: 5,
+ 17771: 30,
+ 18724: 35,
+ 19719: 46,
+ 19860: 49,
+ 19740: 47,
+ 20522: 66,
+ 21402: 88,
+ 20901: 79,
+ 20219: 57,
+ 20732: 73,
+ 20400: 60,
+ 21273: 86,
+ 20873: 76,
+ 21036: 83,
+ 22509: 2,
+ 22845: 7,
+ 21589: 90,
+ 21735: 93,
+ 22833: 6,
+ 22667: 4,
+ 22957: 9,
+ 23879: 14,
+ 26366: 40,
+ 23983: 16,
+ 23852: 13,
+ 26207: 39,
+ 26176: 37,
+ 24555: 18,
+ 26093: 35,
+ 28614: 61,
+ 27511: 134,
+ 27316: 131,
+ 27581: 135,
+ 27364: 133,
+ 27743: 137,
+ 29038: 67,
+ 31385: 14,
+ 29850: 75,
+ 29736: 73,
+ 29800: 74,
+ 29426: 70,
+ 32463: 16,
+ 32533: 17,
+ 31978: 15,
+ 32404: 32,
+ 32362: 31,
+ 32249: 30,
+ 32814: 35,
+ 33202: 38,
+ 36041: 2,
+ 36284: 4,
+ 36188: 3,
+ 36425: 6,
+ 35987: 1,
+ 37921: 11,
+ 39567: 8,
+ 39874: 12,
+ 40526: 17,
+ 41163: 21,
+ 41904: 34,
+ 42353: 37,
+ 42265: 36,
+ 41400: 27,
+ 41578: 29,
+ 43121: 50,
+ 42917: 49,
+ 43851: 60,
+ 44066: 65,
+ 44798: 76,
+ 42795: 45,
+ 44838: 74,
+ 46454: 2,
+ 46457: 3,
+ 47508: 14,
+ 46774: 6,
+ 46771: 5,
+ 45170: 81,
+ 45410: 82,
+ 47266: 11,
+ 47096: 7,
+ 49029: 29,
+ 49637: 31,
+ 47959: 18,
+ 48029: 19,
+ 48883: 27,
+ 48324: 23,
+ 47723: 16,
+ 49929: 34,
+ 51213: 45,
+ 51624: 47,
+ 51008: 44,
+ 51802: 49,
+ 50333: 37,
+ 50755: 42,
+ 51585: 46,
+ 52911: 53,
+ 52689: 52,
+ 56779: 1,
+ 55642: 78,
+ 55016: 73,
+ 56242: 88,
+ 57328: 2,
+ 57562: 4,
+ 58110: 6,
+ 58948: 9,
+ 59608: 12,
+ 62267: 32,
+ 61246: 20,
+ 61937: 27,
+ 61960: 30,
+ 59819: 6,
+ 62325: 33,
+ 62933: 41,
+ 62394: 34,
+ 63608: 47,
+ 62541: 29,
+ 62478: 28,
+ 64792: 59,
+ 65790: 71,
+ 65721: 70,
+ 69612: 15,
+ 69536: 14,
+ 71832: 31,
+ 71837: 32,
+ 69989: 18,
+ 71795: 30,
+ 76276: 13,
+ 76425: 16,
+ 75230: 7,
+ 76866: 20,
+ 77336: 31,
+ 77910: 40,
+ 79072: 47,
+ 79047: 46,
+ 79007: 45,
+ 79634: 13,
+ 77801: 39,
+ 79492: 49,
+ 82073: 43,
+ 81008: 29,
+ 80463: 24,
+ 83000: 27,
+ 82673: 25,
+ 84177: 37,
+ 83613: 60,
+ 82526: 49,
+ 83430: 32,
+ 84345: 64,
+ 85998: 53,
+ 88765: 71,
+ 88771: 72,
+ 93867: 18,
+ 94834: 25,
+ 95002: 29,
+ 94982: 28,
+ 93179: 10,
+ 93203: 11,
+ 93747: 17,
+ 97229: 49,
+ 97938: 59,
+ 97278: 50,
+ 97675: 54,
+ 95447: 31,
+ 96931: 46,
+ 96957: 47,
+ 97473: 52,
+ 97139: 48,
+ 98103: 61,
+ 102819: 14,
+ 101160: 1,
+ 101916: 7,
+ 101483: 3,
+ 101800: 5,
+ 101421: 2,
+ 103298: 16,
+ 102805: 15,
+ 101589: 4,
+ 101769: 6,
+ 101882: 8,
+ 102080: 10,
+ 103294: 17,
+ 103527: 18,
+ 104521: 5,
+ 104538: 6,
+ 104858: 7,
+ 107315: 8,
+ 108339: 17,
+ 108699: 19,
+ 108875: 21,
+ 108693: 20,
+ 110386: 31,
+ 110986: 36,
+ 112935: 49,
+ 113186: 50,
+ 112447: 46,
+ 112029: 42,
+ 114144: 55,
+ 114389: 58,
+ 114520: 59,
+ 114347: 57,
+ 113503: 52,
+ 115444: 66,
+ 115919: 70,
+ 117628: 80,
+ 117020: 77,
+ 117730: 82,
+ 1595: 40,
+ 1067: 88,
+ 2224: 48,
+ 729: 87,
+ 2213: 46,
+ 2219: 47,
+ 1168: 89,
+ 2903: 53,
+ 3810: 64,
+ 3632: 57,
+ 3685: 59,
+ 4267: 66,
+ 3138: 55,
+ 2568: 52,
+ 3093: 54,
+ 3730: 61,
+ 5778: 87,
+ 7097: 99,
+ 5454: 81,
+ 6732: 94,
+ 6706: 93,
+ 6981: 97,
+ 5131: 74,
+ 5132: 74,
+ 5571: 84,
+ 5310: 79,
+ 7740: 105,
+ 8271: 3,
+ 8387: 4,
+ 8832: 5,
+ 9110: 8,
+ 8159: 109,
+ 7981: 107,
+ 8544: 1,
+ 8903: 6,
+ 10328: 19,
+ 11843: 29,
+ 10732: 22,
+ 10155: 15,
+ 11678: 26,
+ 11698: 27,
+ 10306: 17,
+ 12332: 32,
+ 12803: 37,
+ 13165: 42,
+ 13327: 43,
+ 13108: 40,
+ 12784: 36,
+ 13579: 44,
+ 13654: 45,
+ 13702: 46,
+ 14586: 54,
+ 14514: 53,
+ 14838: 57,
+ 12640: 34,
+ 13914: 48,
+ 13834: 47,
+ 17408: 14,
+ 17309: 13,
+ 15110: 58,
+ 15870: 65,
+ 15627: 61,
+ 15737: 63,
+ 19877: 48,
+ 19388: 43,
+ 18471: 32,
+ 19038: 37,
+ 19076: 39,
+ 20215: 55,
+ 20484: 63,
+ 20205: 54,
+ 20261: 58,
+ 20877: 75,
+ 20885: 77,
+ 20661: 70,
+ 20894: 78,
+ 21137: 85,
+ 21039: 81,
+ 20995: 80,
+ 20713: 71,
+ 21082: 84,
+ 21588: 89,
+ 21683: 92,
+ 21673: 91,
+ 20455: 61,
+ 20542: 64,
+ 20648: 68,
+ 19990: 50,
+ 20889: 74,
+ 20186: 56,
+ 20087: 51,
+ 20171: 53,
+ 20635: 65,
+ 20641: 67,
+ 22441: 96,
+ 23214: 101,
+ 24010: 15,
+ 23607: 11,
+ 23835: 104,
+ 22565: 97,
+ 23871: 106,
+ 23497: 102,
+ 23883: 105,
+ 24512: 108,
+ 24822: 109,
+ 25216: 110,
+ 25410: 113,
+ 25555: 116,
+ 26777: 126,
+ 27265: 129,
+ 25499: 115,
+ 25278: 111,
+ 25945: 119,
+ 26064: 120,
+ 25583: 117,
+ 26382: 122,
+ 27338: 130,
+ 25539: 114,
+ 26451: 123,
+ 29704: 72,
+ 29434: 69,
+ 27965: 57,
+ 27913: 54,
+ 28716: 62,
+ 28691: 64,
+ 29433: 68,
+ 29650: 71,
+ 28561: 141,
+ 31105: 19,
+ 31525: 23,
+ 32753: 33,
+ 31158: 20,
+ 32104: 26,
+ 30769: 16,
+ 30883: 18,
+ 30757: 15,
+ 30756: 15,
+ 32921: 36,
+ 33715: 41,
+ 34440: 45,
+ 35350: 54,
+ 34909: 51,
+ 34088: 43,
+ 36156: 61,
+ 35699: 56,
+ 35550: 55,
+ 36238: 63,
+ 36760: 68,
+ 38848: 1,
+ 37300: 74,
+ 37908: 81,
+ 37811: 79,
+ 38722: 85,
+ 39236: 5,
+ 39177: 3,
+ 40167: 16,
+ 41117: 20,
+ 41319: 25,
+ 41822: 31,
+ 39780: 10,
+ 43454: 54,
+ 43463: 52,
+ 44001: 63,
+ 43970: 62,
+ 42523: 40,
+ 42133: 35,
+ 42485: 38,
+ 42578: 42,
+ 42556: 41,
+ 42911: 47,
+ 42516: 39,
+ 41909: 33,
+ 42806: 43,
+ 45153: 80,
+ 45699: 83,
+ 45033: 79,
+ 44946: 77,
+ 47189: 8,
+ 49583: 30,
+ 48218: 20,
+ 50564: 40,
+ 52686: 51,
+ 53954: 60,
+ 55533: 71,
+ 55765: 81,
+ 54879: 70,
+ 56146: 86,
+ 56473: 90,
+ 56080: 85,
+ 54872: 68,
+ 56975: 92,
+ 58159: 95,
+ 59352: 3,
+ 57565: 93,
+ 58661: 1,
+ 58858: 2,
+ 59501: 5,
+ 60202: 11,
+ 61415: 24,
+ 61418: 24,
+ 61571: 25,
+ 62356: 27,
+ 60957: 20,
+ 61724: 26,
+ 62807: 32,
+ 63533: 38,
+ 63355: 36,
+ 64241: 42,
+ 62886: 35,
+ 63948: 39,
+ 67275: 4,
+ 67459: 5,
+ 66727: 1,
+ 66763: 2,
+ 67480: 6,
+ 70027: 20,
+ 69829: 101,
+ 67787: 7,
+ 67927: 8,
+ 68276: 10,
+ 72125: 35,
+ 71762: 29,
+ 70602: 22,
+ 72659: 37,
+ 71115: 26,
+ 75530: 9,
+ 76423: 17,
+ 76424: 18,
+ 76069: 12,
+ 77111: 26,
+ 76810: 19,
+ 77233: 28,
+ 78072: 41,
+ 76337: 15,
+ 76852: 21,
+ 76878: 22,
+ 77450: 35,
+ 77661: 38,
+ 78481: 5,
+ 79102: 8,
+ 79045: 7,
+ 79043: 7,
+ 79666: 16,
+ 80170: 20,
+ 80816: 27,
+ 82802: 54,
+ 88128: 93,
+ 88657: 98,
+ 88267: 95,
+ 88331: 96,
+ 88899: 101,
+ 88886: 102,
+ 89861: 106,
+ 90139: 109,
+ 93244: 13,
+ 92161: 111,
+ 92043: 110,
+ 92614: 112,
+ 96516: 4,
+ 95435: 3,
+ 95398: 2,
+ 96757: 5,
+ 96837: 6,
+ 95560: 5,
+ 95498: 4,
+ 95818: 7,
+ 96275: 9,
+ 94703: 1,
+ 94620: 1,
+ 98085: 10,
+ 98234: 11,
+ 98754: 14,
+ 99742: 67,
+ 97365: 7,
+ 97796: 9,
+ 98819: 15,
+ 98438: 13,
+ 97496: 8,
+ 98337: 12,
+ 98920: 16,
+ 99352: 17,
+ 101958: 9,
+ 102281: 11,
+ 102532: 12,
+ 102531: 12,
+ 99913: 18,
+ 101867: 29,
+ 103511: 33,
+ 107348: 9,
+ 107788: 13,
+ 105502: 1,
+ 106787: 5,
+ 109240: 25,
+ 109458: 28,
+ 110548: 33,
+ 111884: 41,
+ 111278: 39,
+ 111810: 40,
+ 112358: 45,
+ 113963: 54,
+ 113357: 51,
+ 115407: 65,
+ 116592: 74,
+ 116972: 76,
+ 116611: 75,
+ 117718: 81,
+ 116264: 71,
+ 171: 85,
+ 2355: 28,
+ 3693: 34,
+ 4288: 36,
+ 4463: 38,
+ 4366: 67,
+ 3031: 30,
+ 3885: 65,
+ 4510: 68,
+ 5742: 85,
+ 6193: 90,
+ 6315: 91,
+ 9153: 9,
+ 8993: 7,
+ 9884: 13,
+ 9836: 12,
+ 10053: 14,
+ 10203: 16,
+ 9621: 10,
+ 9859: 11,
+ 8796: 2,
+ 10540: 20,
+ 10535: 21,
+ 12184: 30,
+ 12189: 30,
+ 12719: 35,
+ 12489: 33,
+ 10793: 10,
+ 11486: 12,
+ 11548: 13,
+ 14376: 52,
+ 15557: 60,
+ 13209: 41,
+ 14150: 51,
+ 14109: 49,
+ 14893: 56,
+ 15514: 59,
+ 13061: 39,
+ 14677: 55,
+ 16181: 66,
+ 16859: 9,
+ 17573: 20,
+ 17489: 16,
+ 17499: 17,
+ 17851: 28,
+ 17702: 25,
+ 17847: 27,
+ 17608: 23,
+ 17832: 26,
+ 15861: 64,
+ 16664: 7,
+ 17181: 11,
+ 17527: 18,
+ 17579: 21,
+ 17531: 19,
+ 17588: 22,
+ 15696: 62,
+ 18485: 33,
+ 19009: 36,
+ 20533: 62,
+ 20789: 72,
+ 20711: 69,
+ 20430: 59,
+ 19171: 41,
+ 19513: 44,
+ 20250: 52,
+ 19205: 42,
+ 21961: 95,
+ 21881: 94,
+ 23068: 99,
+ 23088: 98,
+ 23900: 103,
+ 25695: 118,
+ 25192: 22,
+ 26248: 121,
+ 28969: 2,
+ 28734: 1,
+ 26640: 125,
+ 27468: 132,
+ 28237: 139,
+ 27830: 136,
+ 29288: 4,
+ 29655: 7,
+ 29789: 8,
+ 29225: 3,
+ 29450: 6,
+ 30046: 11,
+ 30049: 12,
+ 30343: 13,
+ 29840: 9,
+ 30003: 10,
+ 29379: 5,
+ 31434: 49,
+ 29696: 44,
+ 31737: 53,
+ 31852: 54,
+ 32019: 25,
+ 34182: 44,
+ 33927: 42,
+ 34819: 48,
+ 32246: 27,
+ 33277: 37,
+ 33650: 40,
+ 33595: 39,
+ 34861: 49,
+ 35025: 52,
+ 34722: 47,
+ 32311: 28,
+ 35842: 58,
+ 38106: 82,
+ 35846: 57,
+ 37704: 76,
+ 37740: 77,
+ 35152: 53,
+ 36393: 64,
+ 36429: 65,
+ 36046: 60,
+ 35941: 59,
+ 36962: 69,
+ 37629: 75,
+ 39659: 9,
+ 40881: 19,
+ 39263: 4,
+ 39191: 2,
+ 40007: 13,
+ 40023: 14,
+ 38538: 83,
+ 40843: 18,
+ 40240: 15,
+ 41389: 24,
+ 41940: 32,
+ 41574: 28,
+ 41816: 30,
+ 41404: 23,
+ 41377: 22,
+ 43100: 48,
+ 43103: 48,
+ 43587: 55,
+ 43575: 53,
+ 43834: 58,
+ 44405: 69,
+ 44892: 75,
+ 46146: 1,
+ 44342: 67,
+ 44512: 70,
+ 44818: 72,
+ 47247: 9,
+ 46750: 4,
+ 47908: 17,
+ 48390: 22,
+ 47550: 13,
+ 48455: 24,
+ 47701: 15,
+ 50319: 35,
+ 50335: 36,
+ 50384: 39,
+ 50316: 24,
+ 50303: 23,
+ 52457: 41,
+ 53355: 48,
+ 53492: 50,
+ 54347: 52,
+ 54196: 51,
+ 54388: 64,
+ 53417: 54,
+ 54487: 67,
+ 52882: 43,
+ 52959: 44,
+ 52422: 40,
+ 54951: 72,
+ 59468: 4,
+ 59847: 7,
+ 60514: 13,
+ 60904: 17,
+ 60891: 17,
+ 60351: 12,
+ 61071: 21,
+ 60941: 18,
+ 60087: 8,
+ 61295: 22,
+ 61394: 23,
+ 60123: 10,
+ 60742: 15,
+ 60098: 9,
+ 60697: 14,
+ 60746: 16,
+ 62576: 30,
+ 63950: 40,
+ 62763: 31,
+ 64394: 43,
+ 64022: 41,
+ 67239: 3,
+ 68103: 9,
+ 68478: 11,
+ 69226: 12,
+ 72105: 36,
+ 71995: 34,
+ 73568: 41,
+ 71284: 28,
+ 73996: 45,
+ 73745: 43,
+ 74087: 46,
+ 74596: 48,
+ 75049: 1,
+ 75695: 3,
+ 78554: 44,
+ 76952: 8,
+ 78159: 13,
+ 77512: 10,
+ 78493: 14,
+ 79349: 10,
+ 79889: 19,
+ 81729: 39,
+ 79757: 18,
+ 84054: 63,
+ 82504: 51,
+ 82987: 57,
+ 82780: 56,
+ 84379: 65,
+ 82324: 48,
+ 82422: 50,
+ 85157: 73,
+ 86254: 79,
+ 86731: 84,
+ 84887: 70,
+ 85693: 76,
+ 86667: 83,
+ 87194: 87,
+ 86974: 86,
+ 85790: 78,
+ 88346: 97,
+ 87747: 89,
+ 88818: 100,
+ 88817: 100,
+ 89773: 105,
+ 87933: 92,
+ 88794: 103,
+ 89925: 108,
+ 89935: 107,
+ 92818: 113,
+ 94827: 2,
+ 95785: 8,
+ 95771: 6,
+ 95260: 3,
+ 95951: 6,
+ 95947: 6,
+ 95372: 2,
+ 97886: 13,
+ 97679: 12,
+ 98375: 14,
+ 97077: 10,
+ 98636: 16,
+ 98543: 15,
+ 96302: 9,
+ 99080: 17,
+ 99853: 22,
+ 101868: 28,
+ 99951: 24,
+ 100435: 25,
+ 101641: 26,
+ 99404: 18,
+ 99518: 19,
+ 99531: 20,
+ 99874: 23,
+ 101716: 27,
+ 99738: 21,
+ 102388: 30,
+ 103200: 32,
+ 103004: 31,
+ 105411: 34,
+ 106140: 2,
+ 107354: 10,
+ 105966: 35,
+ 107310: 78,
+ 107472: 12,
+ 108022: 16,
+ 109176: 24,
+ 107975: 15,
+ 109056: 23,
+ 112440: 47,
+ 112748: 48,
+ 110371: 32,
+ 112051: 43,
+ 115250: 62,
+ 115623: 68,
+ 114155: 56,
+ 113881: 53,
+ 114526: 60,
+ 114844: 61,
+ 115806: 69,
+ 118131: 84,
+ 117073: 78,
+ 117500: 79,
+ 1473: 25,
+ 3092: 31,
+ 4889: 69,
+ 5544: 82,
+ 5586: 83,
+ 2912: 29,
+ 5175: 76,
+ 5319: 78,
+ 5447: 43,
+ 10220: 5,
+ 10280: 6,
+ 9570: 3,
+ 10559: 7,
+ 10064: 4,
+ 10644: 8,
+ 10670: 9,
+ 9021: 56,
+ 11432: 11,
+ 13775: 21,
+ 12086: 15,
+ 13905: 24,
+ 13328: 17,
+ 11784: 14,
+ 17448: 38,
+ 17886: 42,
+ 18246: 44,
+ 17313: 40,
+ 18614: 46,
+ 20252: 54,
+ 20579: 55,
+ 20591: 56,
+ 23015: 3,
+ 24727: 16,
+ 24504: 14,
+ 24832: 18,
+ 24740: 17,
+ 24879: 19,
+ 22678: 2,
+ 22453: 1,
+ 26536: 26,
+ 25984: 25,
+ 25541: 24,
+ 25292: 21,
+ 27639: 31,
+ 28380: 37,
+ 30827: 48,
+ 34693: 46,
+ 33018: 34,
+ 36366: 62,
+ 38016: 80,
+ 37204: 70,
+ 37265: 71,
+ 35710: 65,
+ 42954: 46,
+ 43584: 51,
+ 44154: 64,
+ 43721: 57,
+ 44031: 61,
+ 43932: 59,
+ 41975: 32,
+ 42090: 33,
+ 44307: 66,
+ 45860: 40,
+ 46652: 7,
+ 45688: 38,
+ 46904: 9,
+ 46952: 10,
+ 47080: 11,
+ 46735: 8,
+ 48742: 18,
+ 49081: 20,
+ 47631: 13,
+ 49593: 21,
+ 50218: 22,
+ 51556: 33,
+ 52098: 37,
+ 52638: 42,
+ 50860: 27,
+ 50935: 28,
+ 51056: 30,
+ 51685: 34,
+ 52032: 36,
+ 51233: 31,
+ 51047: 29,
+ 51914: 35,
+ 53426: 46,
+ 55219: 54,
+ 53229: 46,
+ 53377: 47,
+ 57029: 62,
+ 56997: 61,
+ 63462: 37,
+ 63901: 14,
+ 66458: 25,
+ 71053: 25,
+ 75312: 2,
+ 76127: 4,
+ 74666: 49,
+ 75178: 50,
+ 75415: 51,
+ 75411: 51,
+ 77048: 9,
+ 78459: 15,
+ 76669: 7,
+ 77655: 11,
+ 79119: 16,
+ 80247: 23,
+ 80181: 19,
+ 81066: 32,
+ 81693: 40,
+ 79607: 17,
+ 80197: 20,
+ 80214: 21,
+ 80927: 31,
+ 80460: 25,
+ 82587: 53,
+ 83207: 58,
+ 83313: 59,
+ 84862: 72,
+ 84573: 68,
+ 83462: 61,
+ 84380: 67,
+ 84606: 69,
+ 85112: 75,
+ 87998: 94,
+ 87808: 91,
+ 88745: 99,
+ 89172: 104,
+ 89826: 1,
+ 94311: 19,
+ 92420: 10,
+ 92398: 8,
+ 92405: 9,
+ 93194: 14,
+ 93279: 15,
+ 93917: 17,
+ 92728: 11,
+ 92791: 12,
+ 93903: 18,
+ 96683: 12,
+ 97295: 17,
+ 96052: 8,
+ 95556: 4,
+ 96387: 11,
+ 97118: 15,
+ 100587: 39,
+ 98110: 21,
+ 100122: 35,
+ 98425: 25,
+ 99303: 28,
+ 99770: 29,
+ 99031: 27,
+ 100108: 36,
+ 101076: 41,
+ 101765: 48,
+ 102453: 52,
+ 102066: 49,
+ 101474: 47,
+ 102488: 53,
+ 101214: 44,
+ 101067: 42,
+ 102589: 54,
+ 104732: 64,
+ 105138: 66,
+ 105811: 69,
+ 105942: 70,
+ 107763: 14,
+ 109410: 29,
+ 109352: 27,
+ 112158: 44,
+ 111068: 38,
+ 115355: 64,
+ 115271: 63,
+ 116310: 72,
+ 115591: 67,
+ 116355: 73,
+ 1366: 24,
+ 1686: 27,
+ 1086: 23,
+ 1501: 26,
+ 4436: 37,
+ 5550: 45,
+ 3881: 35,
+ 3231: 32,
+ 4903: 39,
+ 5493: 44,
+ 5317: 41,
+ 6514: 47,
+ 7818: 53,
+ 8814: 55,
+ 7513: 50,
+ 7719: 52,
+ 9977: 58,
+ 10180: 59,
+ 10176: 59,
+ 9640: 57,
+ 10340: 60,
+ 13490: 20,
+ 13254: 16,
+ 14354: 25,
+ 12623: 12,
+ 13879: 22,
+ 14817: 28,
+ 12768: 14,
+ 14668: 27,
+ 15338: 30,
+ 18532: 45,
+ 17529: 41,
+ 15648: 32,
+ 19302: 49,
+ 19335: 50,
+ 19811: 52,
+ 21476: 58,
+ 21242: 57,
+ 23179: 4,
+ 24340: 11,
+ 23261: 5,
+ 23268: 6,
+ 23453: 8,
+ 23767: 10,
+ 24813: 15,
+ 21928: 59,
+ 23416: 7,
+ 27483: 29,
+ 27673: 32,
+ 25048: 20,
+ 28946: 40,
+ 31771: 51,
+ 31789: 52,
+ 28823: 39,
+ 28677: 38,
+ 31832: 50,
+ 33133: 61,
+ 33041: 59,
+ 33064: 60,
+ 33614: 62,
+ 34752: 63,
+ 35341: 64,
+ 32844: 58,
+ 32173: 55,
+ 32480: 56,
+ 35907: 66,
+ 39722: 28,
+ 41075: 31,
+ 44248: 10,
+ 43531: 35,
+ 45290: 36,
+ 47570: 43,
+ 48256: 16,
+ 47300: 42,
+ 48833: 19,
+ 51420: 32,
+ 52139: 38,
+ 50801: 34,
+ 50372: 33,
+ 53721: 47,
+ 53838: 49,
+ 54136: 51,
+ 55266: 55,
+ 53295: 45,
+ 54539: 52,
+ 56034: 57,
+ 56148: 58,
+ 56770: 59,
+ 55560: 56,
+ 59831: 2,
+ 61692: 9,
+ 60646: 6,
+ 58684: 67,
+ 60467: 4,
+ 61317: 8,
+ 62207: 10,
+ 63125: 12,
+ 64692: 19,
+ 64844: 20,
+ 64217: 15,
+ 64246: 17,
+ 65072: 23,
+ 71075: 27,
+ 71618: 33,
+ 73369: 40,
+ 73555: 42,
+ 76307: 6,
+ 78012: 12,
+ 76041: 53,
+ 75973: 52,
+ 76534: 54,
+ 77760: 1,
+ 77907: 2,
+ 77986: 4,
+ 81126: 35,
+ 80704: 30,
+ 79248: 14,
+ 79101: 11,
+ 81833: 44,
+ 87563: 90,
+ 90191: 2,
+ 91971: 6,
+ 91973: 7,
+ 94481: 20,
+ 94713: 21,
+ 91919: 4,
+ 91926: 5,
+ 92862: 13,
+ 97630: 19,
+ 98068: 22,
+ 96693: 14,
+ 100044: 34,
+ 100907: 40,
+ 100453: 37,
+ 104217: 61,
+ 104214: 61,
+ 104887: 65,
+ 103413: 58,
+ 105102: 67,
+ 102843: 56,
+ 103089: 57,
+ 104060: 62,
+ 105186: 68,
+ 106551: 72,
+ 107253: 79,
+ 106711: 74,
+ 107097: 76,
+ 107162: 77,
+ 106999: 75,
+ 109937: 1,
+ 111841: 10,
+ 111546: 8,
+ 111544: 8,
+ 111104: 6,
+ 112031: 12,
+ 112242: 13,
+ 112778: 14,
+ 113281: 16,
+ 113788: 2,
+ 113726: 1,
+ 115191: 10,
+ 115065: 9,
+ 111944: 11,
+ 112917: 15,
+ 114430: 6,
+ 115280: 12,
+ 116076: 14,
+ 116354: 15,
+ 115755: 13,
+ 116631: 17,
+ 116805: 19,
+ 841: 22,
+ 3478: 68,
+ 3414: 20,
+ 3504: 22,
+ 3300: 19,
+ 6411: 46,
+ 6813: 48,
+ 5434: 42,
+ 3801: 25,
+ 7607: 51,
+ 6999: 49,
+ 9222: 3,
+ 8714: 2,
+ 10366: 6,
+ 10819: 62,
+ 10944: 63,
+ 11313: 65,
+ 11220: 64,
+ 11465: 66,
+ 12777: 13,
+ 16499: 36,
+ 16335: 35,
+ 16826: 37,
+ 17358: 39,
+ 15404: 29,
+ 15444: 31,
+ 16244: 34,
+ 20354: 53,
+ 19812: 51,
+ 19343: 48,
+ 19167: 47,
+ 18453: 43,
+ 23783: 9,
+ 28404: 35,
+ 27196: 27,
+ 29949: 43,
+ 29884: 42,
+ 30972: 47,
+ 28499: 36,
+ 29388: 41,
+ 30520: 46,
+ 28765: 35,
+ 33485: 16,
+ 32562: 57,
+ 35726: 20,
+ 36439: 22,
+ 36145: 21,
+ 38623: 25,
+ 38639: 26,
+ 39847: 27,
+ 42604: 34,
+ 44127: 9,
+ 44471: 12,
+ 46471: 41,
+ 44901: 15,
+ 45836: 37,
+ 47006: 26,
+ 46853: 25,
+ 47973: 14,
+ 48113: 15,
+ 48682: 31,
+ 58117: 65,
+ 56789: 60,
+ 57399: 63,
+ 58112: 65,
+ 60122: 3,
+ 60485: 5,
+ 60988: 7,
+ 62516: 11,
+ 64906: 21,
+ 66234: 24,
+ 69068: 13,
+ 69483: 17,
+ 69732: 19,
+ 72487: 38,
+ 70791: 24,
+ 72524: 39,
+ 70497: 23,
+ 69713: 21,
+ 73841: 47,
+ 73695: 44,
+ 78592: 6,
+ 79992: 22,
+ 82321: 52,
+ 80809: 34,
+ 81497: 42,
+ 84835: 74,
+ 86414: 85,
+ 86182: 82,
+ 85379: 77,
+ 87280: 88,
+ 85670: 23,
+ 87833: 33,
+ 87212: 30,
+ 93408: 16,
+ 92204: 205,
+ 95656: 7,
+ 95853: 10,
+ 97165: 18,
+ 99639: 30,
+ 99675: 31,
+ 96441: 13,
+ 96895: 16,
+ 96901: 16,
+ 98571: 26,
+ 98055: 24,
+ 102724: 55,
+ 99848: 32,
+ 103632: 59,
+ 100859: 43,
+ 101243: 46,
+ 101138: 45,
+ 102177: 51,
+ 103732: 60,
+ 106093: 71,
+ 106481: 73,
+ 104194: 63,
+ 107136: 80,
+ 110351: 2,
+ 111022: 5,
+ 107533: 81,
+ 110609: 4,
+ 110538: 3,
+ 114200: 4,
+ 111169: 7,
+ 114210: 5,
+ 113919: 3,
+ 114570: 7,
+ 111674: 9,
+ 116584: 16,
+ 117221: 20,
+ 115152: 11,
+ 115022: 8,
+ 116709: 18,
+ 2920: 17,
+ 2505: 14,
+ 3179: 18,
+ 3821: 24,
+ 746: 11,
+ 5542: 33,
+ 5336: 30,
+ 4422: 28,
+ 4292: 26,
+ 6242: 34,
+ 7294: 39,
+ 8704: 1,
+ 9505: 4,
+ 11060: 9,
+ 10227: 5,
+ 11279: 10,
+ 10729: 7,
+ 10718: 8,
+ 14328: 23,
+ 13531: 18,
+ 13268: 15,
+ 12692: 11,
+ 21148: 1,
+ 21727: 3,
+ 21730: 2,
+ 23040: 7,
+ 23216: 8,
+ 22854: 5,
+ 22287: 4,
+ 24836: 15,
+ 23743: 12,
+ 23734: 11,
+ 28358: 33,
+ 27249: 26,
+ 27949: 30,
+ 25197: 16,
+ 25973: 18,
+ 26587: 22,
+ 26942: 24,
+ 27283: 28,
+ 27592: 29,
+ 27971: 31,
+ 27731: 30,
+ 29246: 37,
+ 30247: 45,
+ 31359: 7,
+ 31665: 11,
+ 32489: 13,
+ 30272: 4,
+ 30060: 2,
+ 29730: 40,
+ 30679: 5,
+ 31039: 6,
+ 33048: 14,
+ 32438: 12,
+ 33449: 15,
+ 35783: 19,
+ 35785: 19,
+ 37406: 23,
+ 35146: 18,
+ 35735: 47,
+ 37609: 24,
+ 39348: 54,
+ 40646: 29,
+ 40875: 30,
+ 45493: 18,
+ 45455: 17,
+ 47965: 44,
+ 48402: 30,
+ 51814: 37,
+ 51459: 36,
+ 48319: 29,
+ 53261: 44,
+ 53043: 43,
+ 52650: 40,
+ 52685: 41,
+ 52478: 39,
+ 53064: 42,
+ 58001: 64,
+ 59708: 1,
+ 58181: 66,
+ 60712: 72,
+ 59458: 68,
+ 59774: 69,
+ 60584: 71,
+ 60212: 70,
+ 60795: 73,
+ 63503: 78,
+ 60992: 75,
+ 60978: 74,
+ 65477: 80,
+ 66634: 82,
+ 66738: 83,
+ 67231: 84,
+ 67848: 86,
+ 66198: 81,
+ 75458: 12,
+ 81292: 17,
+ 81290: 16,
+ 78527: 13,
+ 83608: 21,
+ 85819: 24,
+ 85829: 25,
+ 87585: 32,
+ 90905: 45,
+ 90156: 39,
+ 93713: 51,
+ 94779: 1,
+ 91755: 46,
+ 93340: 49,
+ 92997: 48,
+ 94302: 53,
+ 94490: 54,
+ 92512: 47,
+ 97635: 20,
+ 97870: 23,
+ 99655: 33,
+ 108165: 13,
+ 108772: 14,
+ 108925: 15,
+ 109556: 22,
+ 109492: 21,
+ 109857: 23,
+ 110991: 27,
+ 110988: 27,
+ 114104: 1,
+ 118243: 8,
+ 117863: 7,
+ 114365: 2,
+ 117301: 5,
+ 1960: 12,
+ 330: 9,
+ 2599: 15,
+ 531: 10,
+ 2474: 13,
+ 2707: 16,
+ 6686: 37,
+ 8046: 44,
+ 8886: 45,
+ 6312: 35,
+ 5589: 32,
+ 9564: 52,
+ 9573: 53,
+ 10438: 55,
+ 23522: 10,
+ 24348: 14,
+ 25769: 17,
+ 26408: 19,
+ 22783: 9,
+ 27046: 23,
+ 30278: 3,
+ 29919: 1,
+ 31676: 8,
+ 29490: 36,
+ 34572: 17,
+ 37934: 49,
+ 37949: 51,
+ 40474: 56,
+ 39261: 53,
+ 41704: 1,
+ 43644: 5,
+ 40772: 57,
+ 42527: 4,
+ 43903: 6,
+ 42080: 2,
+ 42438: 3,
+ 44857: 11,
+ 45333: 16,
+ 45075: 14,
+ 47911: 28,
+ 46733: 23,
+ 45038: 13,
+ 51401: 35,
+ 50448: 32,
+ 52353: 38,
+ 57111: 3,
+ 61936: 76,
+ 62423: 7,
+ 63076: 8,
+ 63432: 9,
+ 67627: 10,
+ 68756: 11,
+ 80331: 14,
+ 81660: 18,
+ 82898: 20,
+ 82860: 19,
+ 86036: 26,
+ 89348: 36,
+ 83895: 22,
+ 90344: 42,
+ 94140: 55,
+ 95081: 58,
+ 99500: 68,
+ 98962: 66,
+ 101093: 2,
+ 100221: 71,
+ 98658: 65,
+ 98583: 64,
+ 102422: 3,
+ 105199: 5,
+ 106801: 9,
+ 105268: 6,
+ 102253: 4,
+ 105972: 7,
+ 107418: 10,
+ 107586: 12,
+ 108924: 18,
+ 109005: 20,
+ 109017: 19,
+ 110103: 25,
+ 111797: 30,
+ 108917: 17,
+ 110817: 26,
+ 112724: 32,
+ 115590: 4,
+ 117447: 6,
+ 5518: 31,
+ 6692: 36,
+ 7078: 38,
+ 3721: 23,
+ 3572: 21,
+ 9009: 46,
+ 7965: 43,
+ 8016: 42,
+ 9480: 48,
+ 7650: 40,
+ 10031: 54,
+ 9598: 50,
+ 33104: 43,
+ 32864: 42,
+ 40215: 55,
+ 44390: 8,
+ 46977: 24,
+ 47013: 22,
+ 47654: 27,
+ 56583: 2,
+ 56211: 1,
+ 61281: 5,
+ 60998: 4,
+ 61384: 6,
+ 68956: 3,
+ 73440: 9,
+ 75097: 13,
+ 74793: 11,
+ 72607: 7,
+ 73136: 8,
+ 75152: 14,
+ 80650: 15,
+ 85805: 27,
+ 85852: 29,
+ 86201: 28,
+ 89594: 38,
+ 89448: 37,
+ 86620: 31,
+ 86614: 31,
+ 87728: 34,
+ 89937: 44,
+ 89908: 43,
+ 92782: 52,
+ 94376: 57,
+ 98702: 67,
+ 96100: 61,
+ 97433: 63,
+ 94648: 60,
+ 101260: 73,
+ 106032: 8,
+ 107230: 78, # maybe wrong?
+ 107119: 11,
+ 109400: 24,
+ 108535: 16,
+ 115088: 34,
+ 111532: 31,
+ 9763: 49,
+ 9727: 47,
+ 70692: 5,
+ 76008: 15,
+ 69112: 4,
+ 77055: 16,
+ 78661: 18,
+ 79280: 19,
+ 79420: 20,
+ 79822: 21,
+ 87234: 35,
+ 88136: 41,
+ 88127: 40,
+ 82080: 22,
+ 94083: 59,
+ 92112: 50,
+ 98401: 69,
+ 99255: 1,
+ 104105: 77,
+ 100965: 75,
+ 101082: 74,
+ 114222: 33,
+ 116727: 35,
+ 110787: 28,
+ 111056: 29,
+ 5372: 2,
+ 11767: 1,
+ 102208: 76,
+ 85699: 24,
+ 85822: 23,
+ 664: 5,
+ 2100: 10,
+ 635: 4,
+ 145: 29,
+ 443: 33,
+ 154: 30,
+ 2994: 15,
+ 2787: 14,
+ 4147: 20,
+ 2353: 12,
+ 2762: 13,
+ 5833: 38,
+ 5594: 34,
+ 5951: 39,
+ 6429: 43,
+ 6226: 42,
+ 5985: 40,
+ 4914: 25,
+ 9155: 58,
+ 9165: 58,
+ 9631: 61,
+ 10234: 63,
+ 10305: 66,
+ 11046: 70,
+ 10826: 68,
+ 11261: 71,
+ 10642: 67,
+ 11791: 75,
+ 12530: 84,
+ 13951: 5,
+ 12247: 81,
+ 12048: 79,
+ 15383: 95,
+ 14954: 94,
+ 14040: 7,
+ 17506: 25,
+ 17457: 24,
+ 18255: 32,
+ 16341: 17,
+ 17027: 21,
+ 17167: 22,
+ 18141: 30,
+ 18788: 35,
+ 20507: 42,
+ 19587: 38,
+ 19483: 37,
+ 21139: 45,
+ 21444: 48,
+ 21547: 51,
+ 22109: 57,
+ 22958: 62,
+ 22701: 61,
+ 21278: 46,
+ 25044: 22,
+ 25282: 27,
+ 25737: 31,
+ 25281: 28,
+ 23794: 66,
+ 23941: 68,
+ 23875: 67,
+ 23364: 65,
+ 24674: 20,
+ 26549: 48,
+ 26237: 42,
+ 26268: 45,
+ 26224: 41,
+ 26221: 41,
+ 26235: 43,
+ 26220: 41,
+ 26241: 44,
+ 25923: 36,
+ 26563: 49,
+ 30720: 78,
+ 30700: 9,
+ 30772: 10,
+ 29651: 5,
+ 30867: 11,
+ 34724: 21,
+ 34769: 22,
+ 35080: 24,
+ 33971: 19,
+ 34622: 20,
+ 39079: 27,
+ 37088: 25,
+ 39211: 28,
+ 39863: 29,
+ 41211: 1,
+ 41375: 2,
+ 43305: 14,
+ 43496: 15,
+ 46509: 31,
+ 46221: 28,
+ 45184: 21,
+ 45527: 23,
+ 46776: 32,
+ 47431: 35,
+ 48341: 6,
+ 46982: 33,
+ 49641: 15,
+ 51117: 26,
+ 51135: 27,
+ 51362: 29,
+ 50885: 25,
+ 50100: 20,
+ 51437: 30,
+ 52316: 33,
+ 52913: 40,
+ 54049: 62,
+ 53907: 61,
+ 54849: 69,
+ 55084: 74,
+ 56647: 91,
+ 56127: 87,
+ 60129: 15,
+ 60030: 13,
+ 61941: 29,
+ 62875: 38,
+ 63414: 44,
+ 61558: 25,
+ 63494: 46,
+ 63750: 48,
+ 65323: 65,
+ 64238: 51,
+ 65420: 66,
+ 66249: 79,
+ 67929: 90,
+ 66320: 80,
+ 66006: 74,
+ 65892: 72,
+ 70012: 102,
+ 70755: 105,
+ 69701: 99,
+ 70680: 104,
+ 70794: 106,
+ 73193: 1,
+ 72631: 11,
+ 73165: 16,
+ 71957: 107,
+ 73536: 2,
+ 75342: 8,
+ 76427: 14,
+ 76133: 11,
+ 77227: 25,
+ 77660: 36,
+ 77516: 32,
+ 77464: 30,
+ 79593: 1,
+ 79882: 2,
+ 81300: 12,
+ 82730: 23,
+ 84514: 41,
+ 83262: 30,
+ 85365: 47,
+ 88175: 57,
+ 89962: 58,
+ 90642: 60,
+ 90844: 61,
+ 92117: 5,
+ 92524: 8,
+ 95073: 27,
+ 93526: 14,
+ 93717: 15,
+ 93805: 16,
+ 95066: 26,
+ 93429: 12,
+ 96468: 41,
+ 96807: 45,
+ 95937: 36,
+ 96556: 42,
+ 96483: 39,
+ 98844: 62,
+ 99171: 64,
+ 99473: 65,
+ 99631: 66,
+ 101101: 69,
+ 100977: 68,
+ 101847: 71,
+ 101692: 70,
+ 102624: 3,
+ 103005: 5,
+ 102945: 4,
+ 105729: 20,
+ 105767: 21,
+ 103682: 11,
+ 105164: 15,
+ 105412: 16,
+ 103981: 12,
+ 106942: 24,
+ 106278: 22,
+ 109074: 34,
+ 108991: 32,
+ 108874: 31,
+ 110960: 55,
+ 110395: 48,
+ 110578: 51,
+ 108868: 30,
+ 110023: 44,
+ 111394: 60,
+ 111497: 62,
+ 111710: 63,
+ 112179: 67,
+ 113127: 78,
+ 113781: 82,
+ 113674: 81,
+ 115142: 96,
+ 114724: 90,
+ 116146: 13,
+ 116323: 14,
+ 117375: 20,
+ 117761: 24,
+ 118209: 27,
+ 1562: 8,
+ 355: 3,
+ 1803: 9,
+ 3455: 17,
+ 4257: 21,
+ 3909: 19,
+ 3559: 18,
+ 4371: 22,
+ 4587: 23,
+ 5121: 27,
+ 5799: 37,
+ 5485: 32,
+ 5296: 30,
+ 5164: 28,
+ 5364: 31,
+ 6539: 44,
+ 6537: 45,
+ 6748: 47,
+ 6670: 46,
+ 8645: 55,
+ 8497: 53,
+ 11345: 72,
+ 12107: 80,
+ 12002: 77,
+ 14060: 8,
+ 14168: 9,
+ 13701: 3,
+ 12390: 83,
+ 12770: 89,
+ 14293: 10,
+ 15244: 14,
+ 15197: 13,
+ 16537: 18,
+ 17378: 23,
+ 17593: 26,
+ 18543: 34,
+ 19849: 40,
+ 21296: 47,
+ 19777: 39,
+ 22024: 56,
+ 21986: 55,
+ 23221: 63,
+ 21594: 53,
+ 23231: 64,
+ 23972: 69,
+ 25247: 29,
+ 24244: 3,
+ 24327: 4,
+ 24873: 7,
+ 24845: 6,
+ 25202: 8,
+ 27658: 55,
+ 28321: 1,
+ 28325: 2,
+ 28574: 3,
+ 27288: 14,
+ 28103: 16,
+ 28910: 18,
+ 30073: 7,
+ 29885: 6,
+ 33160: 14,
+ 32492: 11,
+ 33345: 18,
+ 37447: 26,
+ 38048: 5,
+ 38372: 8,
+ 37891: 4,
+ 37842: 2,
+ 37843: 2,
+ 38427: 10,
+ 38382: 9,
+ 42146: 3,
+ 40084: 19,
+ 41067: 22,
+ 42509: 6,
+ 40035: 18,
+ 43822: 17,
+ 44883: 19,
+ 44961: 20,
+ 43067: 12,
+ 45526: 24,
+ 46390: 30,
+ 46365: 29,
+ 45811: 27,
+ 47249: 34,
+ 47427: 37,
+ 45588: 25,
+ 45751: 26,
+ 47452: 38,
+ 48437: 8,
+ 49812: 17,
+ 49865: 18,
+ 49402: 40,
+ 49841: 41,
+ 48356: 39,
+ 50140: 21,
+ 50414: 22,
+ 52980: 41,
+ 56633: 21,
+ 55874: 16,
+ 55687: 14,
+ 55282: 12,
+ 56802: 24,
+ 61318: 21,
+ 61969: 28,
+ 61740: 26,
+ 62985: 40,
+ 64625: 56,
+ 64224: 50,
+ 64078: 49,
+ 66131: 77,
+ 66803: 82,
+ 65074: 62,
+ 66098: 76,
+ 67172: 86,
+ 65581: 68,
+ 68888: 94,
+ 68940: 95,
+ 69127: 96,
+ 69427: 98,
+ 70336: 2,
+ 69974: 100,
+ 73473: 19,
+ 73310: 18,
+ 73249: 17,
+ 74785: 27,
+ 72934: 13,
+ 73133: 15,
+ 72489: 7,
+ 75379: 31,
+ 76219: 37,
+ 76333: 38,
+ 78436: 50,
+ 79387: 16,
+ 79672: 18,
+ 79375: 15,
+ 79005: 11,
+ 79540: 17,
+ 78207: 48,
+ 80628: 3,
+ 81377: 13,
+ 82369: 20,
+ 84880: 53,
+ 86284: 57,
+ 86565: 56,
+ 88404: 69,
+ 88048: 64,
+ 94385: 20,
+ 97928: 56,
+ 97966: 57,
+ 97967: 57,
+ 96327: 37,
+ 97650: 51,
+ 98953: 65,
+ 99529: 1,
+ 99918: 3,
+ 100027: 5,
+ 99572: 2,
+ 100064: 6,
+ 100310: 8,
+ 98633: 63,
+ 100345: 9,
+ 103045: 6,
+ 102618: 2,
+ 101923: 14,
+ 105574: 17,
+ 103401: 7,
+ 105761: 19,
+ 104459: 13,
+ 105668: 18,
+ 106786: 23,
+ 107382: 46,
+ 107487: 47,
+ 107517: 48,
+ 107095: 42,
+ 107232: 44,
+ 107302: 45,
+ 108036: 51,
+ 110003: 43,
+ 110273: 46,
+ 109460: 37,
+ 109472: 38,
+ 109139: 33,
+ 109624: 39,
+ 110000: 42,
+ 110602: 50,
+ 110179: 45,
+ 112961: 73,
+ 111123: 57,
+ 111200: 58,
+ 112615: 70,
+ 113031: 74,
+ 111086: 56,
+ 112542: 69,
+ 112716: 71,
+ 113996: 83,
+ 114939: 92,
+ 114855: 91,
+ 115033: 93,
+ 115115: 95,
+ 115126: 94,
+ 116758: 102,
+ 116971: 105,
+ 301: 2,
+ 910: 6,
+ 1170: 7,
+ 3419: 16,
+ 7345: 49,
+ 7450: 50,
+ 6960: 48,
+ 8102: 52,
+ 9326: 57,
+ 9347: 59,
+ 11783: 76,
+ 12843: 1,
+ 13288: 2,
+ 16803: 20,
+ 15474: 16,
+ 16611: 19,
+ 22263: 58,
+ 21763: 54,
+ 22479: 60,
+ 22325: 59,
+ 24305: 5,
+ 23685: 2,
+ 25985: 11,
+ 25606: 9,
+ 25853: 10,
+ 26865: 12,
+ 27654: 15,
+ 27072: 13,
+ 28816: 17,
+ 29048: 19,
+ 31564: 6,
+ 31700: 8,
+ 31592: 7,
+ 33302: 19,
+ 33092: 15,
+ 33248: 17,
+ 32504: 12,
+ 34045: 23,
+ 33347: 20,
+ 37379: 140,
+ 38211: 6,
+ 40259: 20,
+ 40604: 21,
+ 39524: 14,
+ 39906: 16,
+ 42662: 9,
+ 51069: 42,
+ 53740: 7,
+ 55705: 15,
+ 55598: 13,
+ 58188: 30,
+ 57283: 27,
+ 58587: 31,
+ 59803: 4,
+ 61174: 8,
+ 60965: 7,
+ 60189: 5,
+ 64407: 53,
+ 64520: 54,
+ 64725: 57,
+ 64577: 55,
+ 64924: 61,
+ 65639: 69,
+ 66091: 75,
+ 67139: 85,
+ 67057: 83,
+ 65301: 63,
+ 66015: 73,
+ 67288: 87,
+ 67494: 89,
+ 72194: 5,
+ 72603: 8,
+ 72622: 9,
+ 73945: 21,
+ 73953: 22,
+ 75294: 30,
+ 75118: 29,
+ 74600: 26,
+ 75110: 28,
+ 74493: 25,
+ 74392: 24,
+ 75730: 32,
+ 75944: 34,
+ 76126: 35,
+ 77060: 44,
+ 77853: 46,
+ 75848: 33,
+ 76880: 43,
+ 76628: 41,
+ 77939: 47,
+ 77811: 45,
+ 78400: 49,
+ 80894: 8,
+ 80569: 7,
+ 78821: 8,
+ 78820: 8,
+ 79374: 14,
+ 80343: 4,
+ 78933: 9,
+ 78990: 10,
+ 81724: 24,
+ 83331: 29,
+ 80975: 9,
+ 84012: 35,
+ 84893: 40,
+ 86060: 52,
+ 86263: 55,
+ 88258: 6,
+ 86736: 58,
+ 89440: 16,
+ 89567: 17,
+ 90289: 21,
+ 89439: 15,
+ 89341: 13,
+ 89369: 14,
+ 92390: 29,
+ 92111: 28,
+ 92480: 30,
+ 92747: 33,
+ 93057: 36,
+ 93085: 37,
+ 94141: 41,
+ 93683: 39,
+ 95176: 46,
+ 96950: 55,
+ 96808: 54,
+ 95188: 45,
+ 95168: 44,
+ 94820: 43,
+ 95564: 50,
+ 98258: 61,
+ 97290: 56,
+ 97783: 57,
+ 101751: 13,
+ 101027: 11,
+ 100881: 10,
+ 101123: 12,
+ 101120: 12,
+ 101984: 15,
+ 100195: 7,
+ 100062: 4,
+ 102487: 17,
+ 104974: 29,
+ 103226: 19,
+ 104139: 23,
+ 105168: 31,
+ 105143: 30,
+ 103616: 20,
+ 104019: 22,
+ 104452: 27,
+ 104365: 25,
+ 104963: 28,
+ 105515: 32,
+ 106985: 40,
+ 107556: 49,
+ 106559: 37,
+ 107188: 43,
+ 106723: 39,
+ 105665: 33,
+ 105928: 35,
+ 105881: 34,
+ 106039: 36,
+ 108797: 29,
+ 109332: 35,
+ 109786: 41,
+ 110391: 47,
+ 110778: 53,
+ 113136: 76,
+ 113148: 77,
+ 112211: 66,
+ 112529: 68,
+ 111449: 59,
+ 115404: 97,
+ 114341: 88,
+ 114375: 89,
+ 115438: 98,
+ 115669: 99,
+ 118178: 1,
+ 116904: 104,
+ 116901: 104,
+ 116889: 103,
+ 117089: 106,
+ 117218: 107,
+ 117629: 108,
+ 116247: 101,
+ 116118: 100,
+ 9061: 56,
+ 13835: 6,
+ 13782: 4,
+ 14146: 11,
+ 15382: 15,
+ 17651: 27,
+ 17717: 28,
+ 18216: 33,
+ 18673: 36,
+ 21248: 50,
+ 23474: 1,
+ 31125: 4,
+ 31416: 5,
+ 33152: 16,
+ 33977: 24,
+ 34798: 26,
+ 33856: 22,
+ 34981: 27,
+ 34444: 25,
+ 35037: 28,
+ 35210: 145,
+ 35412: 29,
+ 35415: 30,
+ 37648: 1,
+ 37677: 3,
+ 38835: 11,
+ 39023: 12,
+ 39757: 15,
+ 38170: 7,
+ 38146: 188,
+ 51718: 44,
+ 54682: 11,
+ 56280: 17,
+ 59316: 2,
+ 59394: 3,
+ 59199: 1,
+ 61359: 9,
+ 60425: 6,
+ 64166: 45,
+ 64962: 46,
+ 68269: 47,
+ 68390: 48,
+ 69415: 50,
+ 68895: 49,
+ 70306: 51,
+ 70753: 52,
+ 71974: 4,
+ 72197: 54,
+ 72323: 55,
+ 72357: 56,
+ 72929: 12,
+ 73714: 20,
+ 72378: 57,
+ 72571: 58,
+ 73284: 59,
+ 73566: 60,
+ 74500: 23,
+ 76259: 36,
+ 76470: 39,
+ 76742: 42,
+ 78401: 7,
+ 77840: 2,
+ 77635: 1,
+ 77909: 3,
+ 78265: 6,
+ 77984: 4,
+ 76600: 40,
+ 78104: 5,
+ 80473: 5,
+ 80079: 19,
+ 80112: 20,
+ 80815: 22,
+ 79404: 13,
+ 79399: 12,
+ 81266: 23,
+ 82925: 24,
+ 84626: 39,
+ 84625: 39,
+ 82140: 25,
+ 83196: 26,
+ 84405: 36,
+ 85340: 44,
+ 85755: 51,
+ 84970: 42,
+ 87072: 3,
+ 85084: 43,
+ 85423: 45,
+ 88116: 4,
+ 88469: 9,
+ 88380: 7,
+ 89153: 11,
+ 87706: 63,
+ 89931: 19,
+ 91004: 24,
+ 91066: 25,
+ 91689: 26,
+ 92845: 35,
+ 92761: 32,
+ 90496: 22,
+ 92041: 27,
+ 95503: 49,
+ 94643: 42,
+ 95486: 48,
+ 95477: 47,
+ 93864: 40,
+ 93506: 38,
+ 96729: 53,
+ 96406: 51,
+ 96465: 52,
+ 98353: 60,
+ 98066: 58,
+ 98162: 59,
+ 98688: 62,
+ 102485: 16,
+ 104234: 24,
+ 102978: 18,
+ 107128: 41,
+ 106654: 8,
+ 104750: 3,
+ 108952: 13,
+ 108661: 12,
+ 109789: 16,
+ 110529: 49,
+ 111954: 18,
+ 112862: 21,
+ 112102: 19,
+ 114119: 86,
+ 21393: 52,
+ 20042: 41,
+ 20535: 43,
+ 30122: 1,
+ 28756: 72,
+ 32292: 10,
+ 32759: 13,
+ 37853: 171,
+ 38423: 212,
+ 67153: 1,
+ 67786: 4,
+ 67669: 3,
+ 67457: 2,
+ 68933: 5,
+ 74857: 2,
+ 74604: 1,
+ 77634: 5,
+ 76705: 3,
+ 76945: 4,
+ 82960: 27,
+ 82396: 26,
+ 85696: 34,
+ 88635: 10,
+ 90260: 18,
+ 106067: 5,
+ 104174: 2,
+ 106340: 6,
+ 107608: 10,
+ 107380: 9,
+ 106703: 7,
+ 109422: 15,
+ 109285: 14,
+ 111188: 17,
+ 113246: 23,
+ 112948: 22,
+ 15510: 82,
+ 58799: 89,
+ 8240: 120,
+ 33779: 23,
+ 40282: 16,
+ 30932: 61,
+ 61136: 35,
+ 61966: 39,
+ 79490: 39,
+ 26001: 28,
+ 27890: 36,
+ 42794: 9,
+ 25776: 31,
+ 25336: 24,
+ 27989: 58,
+ # 30422: 8, # Monoceros binary B
+ 37279: 10,
+ 49669: 32,
+ 57632: 94,
+ 86032: 55,
+ 97649: 53,
+ 21421: 87,
+ 31681: 24,
+ 50583: 41,
+ 69673: 16,
+ 677: 21,
+ 25428: 112,
+ 37826: 78,
+ 76267: 5,
+ 36850: 66,
+ 14576: 26,
+ 28360: 34,
+ 63121: 12,
+ 91262: 3,
+ 15863: 33,
+ 24608: 13,
+ 67301: 85,
+ 69481: 17,
+ 102098: 50,
+ 53910: 48,
+ 62956: 77,
+ 65378: 79,
+ 4427: 27,
+ 54061: 50,
+ 25930: 34,
+ 26311: 46,
+ 26727: 50,
+ 24436: 19,
+ 27366: 53,
+ 65474: 67,
+ 30324: 2,
+ 32349: 9,
+ 33579: 21,
+ 35904: 31,
+ 80763: 21,
+ 92855: 34,
+ 113368: 24,
+ 85927: 35,
+ 90185: 20,
+}
diff --git a/src/starplot/data/library/constellation_borders_inv.gpkg b/src/starplot/data/library/constellation_borders_inv.gpkg
index f6fa98af6807377bf8a9dbf615b2b020ca7e77a9..989bf709709f43cb6f3a257f7d3e19ea5b3fbb7d 100644
GIT binary patch
delta 1412
zcmah|TTC2P7(VCB?9OE`r<)YEaamw#xs-G%v)AcTIv`k0V{03sq=~q!WZN_XU0@-x
zDS@G&F+6N=V#Wtki7{<_X-cON8`IDxR#P8LYpkY?X=wUnj19&YS+w!Y8F$$T#+irz
z%>U2#{oj8%Q&Ym!lyD`F?NtB(?76zlzKxv&c+T)ttJpOP9g1IZwIYtZ8qAlAr-#N*
zrc>Fmk+gPfwcM8^-=XGEBq&M2=6IJBOEgOfDby4W$F-mL`q?J{KQTND4=`K7D1yo0
zA_$S>f_Uqgi^f&}UNJn)KVVxWWE7ej6Ci~D54{8M9Rgj(Y!q^ec5m`I)>NteGU;O<
z0sM%>zQ?uztCUea9uhm1J~Q}V#0-gFU0~Z74>+uGD+k5tE)VDH4)QaScKLCwG5=Qs
zG(LIU>kc{mz>P9M_>Ldv8g1XQ(@Y)8;LGl7?lIRF&Oe>)j
>WzW4*&{P&(Vkk}MGG+IRy%o?fP3fF6QbH##d-Vtq56t;Z!KW_+UPpXY<#+Y9bSWEueKPfNw)N?
z3i3zgB)}ix1MUIWL+9tt9>;C*8&MXPgj!zXRIb|oy6r<-3;U3jnID-E%Mo-8we`4g7Na-S)m4J#D?uI`Y5X=vuly0S|}R
z9KboU3A30#1MNy#a5zwCxPE0B9yjcnudzL3BN4hKrcN`k{j<9e_IVv=0S_(#;Xd!M
zf6RWsG$IHtxi8MEIdQtDj`O{x@H0>!me4+H_x_owzAT~VsYiz))L@;?DX2pq2%$uo
zxu*5ITdH3;(m7pM%lY~h{(Mv)l+jL_{y_)@EXbma_E1-C7)31Ci7+}uT}NWb?=l(Q
z+{O9QL`Ule!_zRT`4>Y=1Vt@ZCW0m@!_x?A5@)!b6#n+u2K=SyCiwf&5_psTM-=rG
zR2-v+3o$e-oUE~=gg3Yn@l9Os#F1i2@$ExF{Xzs;6lO(wpNv@H#Q^1*Ezy*dH}U)=
zm+<5YDl+2vnrhn9*&vm%ac52sjiyJk#rYSzJD(_Y_jUq}7m3InWcDno4GfueePPyL
Y9@t{mJM=~cHCi=E;gW$*I7(*yH{zmvH#45cxyoNp+_Vf1vxvy*7CR~tL>Ai1!QeQfA;AI*YPV%pF
z9{3~0Gh29R4t-zH-iP8yy9fI_Lqg6=vrj9FH}5IZCgB#4Kp41tan4@H|IKBQ0&at_
z+v8g0{L@)49&t>FUfqSDAE4EiW1p%*?Z8k^@BXx5#5{$$^~JyD+67*^44&q-$3$zW
zz`m|7sHqlMC^wX8gET-e|=EbTd*`MiH$1)Z6Hjefll
zmNo{kFO?Fs?aL@{VAPP57MqG4iDC|gP0QrHm
zO8ec@u3ubz&OgLyvBNRphzqB29!Km4ZRc#;`TzJ1ZkGEPJw!Rg!Bg-JFbOhwt#N$M
zAl&7`k{0Aau8Lhh3Y(ytJvj=C<5!Nsx8;?U5(c1MEejp0HWFfE*Wicz6d+Ujx=j!+
zY*X3fEm&^czxvZ8gdeSTrGW$X0q0q9wXhdUwg7hnoritWm4cSiN7uFi`wywYXg@1N
zP_wCtqv%bRilRF9MHC(L*4DRFz4cD}$SXD2->s8DcDEU=W3r5@SWf~quoE(hv8yuL
z#_q;Zz-_Qy>#={gD&%S`wmXKZEp{x16!uRH4YL6Sg~ZX!Iukt6X@S2gOJGIZ0>j@n
zqcFRsqIyGp2XnTdj5zY%T1($=mbuLNem1zTXDBV37R-Ousi>8GqM|RC|1}z`HtUY>
zw%Tn$RGUN>!%e<7FmnEm8l$Pt%8oR(kA8>fZB7bi}M7TA~vBy1Sva
zb$P4knfY4!`+O6XN-=$vP-j7V7wNgzF6BPf`aF00W "HorizonPlot":
+ super().__init__(
+ dt,
+ ephemeris,
+ style,
+ resolution,
+ hide_colliding_labels,
+ scale=scale,
+ autoscale=autoscale,
+ *args,
+ **kwargs,
+ )
+ self.logger.debug("Creating HorizonPlot...")
+ self.alt = altitude
+ self.az = azimuth
+ self.center_az = sum(azimuth) / 2
+ self.lat = lat
+ self.lon = lon
+
+ self._crs = ccrs.CRS(
+ proj4_params=[
+ ("proj", "latlong"),
+ ("a", "6378137"),
+ ],
+ globe=ccrs.Globe(ellipse="sphere", flattening=0),
+ )
+
+ self._calc_position()
+ self._init_plot()
+ self._adjust_radec_minmax()
+
+ def _prepare_coords(self, ra, dec) -> (float, float):
+ """Converts RA/DEC to AZ/ALT"""
+ point = SkyfieldStar(ra_hours=ra, dec_degrees=dec)
+ position = self.observe(point)
+ pos_apparent = position.apparent()
+ pos_alt, pos_az, _ = pos_apparent.altaz()
+ return pos_az.degrees, pos_alt.degrees
+
+ def _plot_kwargs(self) -> dict:
+ return dict(transform=self._crs)
+
+ def in_bounds(self, ra, dec) -> bool:
+ """Determine if a coordinate is within the bounds of the plot.
+
+ Args:
+ ra: Right ascension, in hours (0...24)
+ dec: Declination, in degrees (-90...90)
+
+ Returns:
+ True if the coordinate is in bounds, otherwise False
+ """
+ az, alt = self._prepare_coords(ra, dec)
+ return (
+ az < self.az[1]
+ and az > self.az[0]
+ and alt < self.alt[1]
+ and alt > self.alt[0]
+ )
+
+ def in_bounds_altaz(self, alt, az, scale: float = 1) -> bool:
+ """Determine if a coordinate is within the bounds of the plot.
+
+ Args:
+ alt: Altitude angle in degrees (0...90)
+ az: Azimuth angle in degrees (0...360)
+
+ Returns:
+ True if the coordinate is in bounds, otherwise False
+ """
+ # x, y = self._proj.transform_point(az, alt, self._crs)
+ return (
+ az < self.az[1]
+ and az > self.az[0]
+ and alt < self.alt[1]
+ and alt > self.alt[0]
+ )
+
+ def _polygon(self, points, style, **kwargs):
+ super()._polygon(points, style, transform=self._crs, **kwargs)
+
+ def _calc_position(self):
+ earth = self.ephemeris["earth"]
+
+ self.location = earth + wgs84.latlon(self.lat, self.lon)
+ self.observe = self.location.at(self.timescale).observe
+
+ # get radec at center horizon
+ center = self.location.at(self.timescale).from_altaz(
+ alt_degrees=0, az_degrees=self.center_az
+ )
+ print(self.center_az)
+ print(center.radec())
+ locations = [
+ self.location.at(self.timescale).from_altaz(
+ alt_degrees=self.alt[0], az_degrees=self.az[0]
+ ), # lower left
+ self.location.at(self.timescale).from_altaz(
+ alt_degrees=self.alt[0], az_degrees=self.az[1]
+ ), # lower right
+ self.location.at(self.timescale).from_altaz(
+ alt_degrees=self.alt[1], az_degrees=self.center_az
+ ), # top center
+ # self.location.at(self.timescale).from_altaz(alt_degrees=self.alt[1], az_degrees=self.az[0]), # upper left
+ # self.location.at(self.timescale).from_altaz(alt_degrees=self.alt[1], az_degrees=self.az[1]), # upper right
+ ]
+
+ self.ra_min = None
+ self.ra_max = None
+ self.dec_max = None
+ self.dec_min = None
+
+ for location in locations:
+ ra, dec, _ = location.radec()
+ ra = ra.hours
+ dec = dec.degrees
+ if self.ra_min is None or ra < self.ra_min:
+ self.ra_min = ra
+
+ if self.ra_max is None or ra > self.ra_max:
+ self.ra_max = ra
+
+ if self.dec_min is None or dec < self.dec_min:
+ self.dec_min = dec
+
+ if self.dec_max is None or dec > self.dec_max:
+ self.dec_max = dec
+
+ # self.star = SkyfieldStar(ra_hours=self.ra, dec_degrees=self.dec)
+ # self.position = self.observe(self.star)
+ # self.pos_apparent = self.position.apparent()
+ # self.pos_alt, self.pos_az, _ = self.pos_apparent.altaz()
+
+ # if self.pos_alt.degrees < 0 and self.raise_on_below_horizon:
+ # raise ValueError("Target is below horizon at specified time/location.")
+
+ def _adjust_radec_minmax(self):
+ # self.ra_min = self.ra - self.optic.true_fov / 15 * 1.08
+ # self.ra_max = self.ra + self.optic.true_fov / 15 * 1.08
+ # self.dec_max = self.dec + self.optic.true_fov / 2 * 1.03
+ # self.dec_min = self.dec - self.optic.true_fov / 2 * 1.03
+
+ if self.dec_max > 70 or self.dec_min < -70:
+ # naive method of getting all the stars near the poles
+ self.ra_min = 0
+ self.ra_max = 24
+
+ # TODO : below are in ra/dec - need to convert to alt/az
+ # adjust declination to match extent
+ extent = self.ax.get_extent(crs=ccrs.PlateCarree())
+ self.dec_min = extent[2]
+ self.dec_max = extent[3]
+
+ # adjust right ascension to match extent
+ if self.ra_max < 24:
+ ra_min = (-1 * extent[1]) / 15
+ ra_max = (-1 * extent[0]) / 15
+
+ if ra_min < 0 or ra_max < 0:
+ ra_min += 24
+ ra_max += 24
+
+ self.ra_min = ra_min
+ self.ra_max = ra_max
+
+ self.logger.debug(
+ f"Extent = RA ({self.ra_min:.2f}, {self.ra_max:.2f}) DEC ({self.dec_min:.2f}, {self.dec_max:.2f})"
+ )
+
+ def _in_bounds_xy(self, x: float, y: float) -> bool:
+ return self.in_bounds_altaz(y, x) # alt = y, az = x
+
+ def _prepare_star_coords(self, df):
+ stars_apparent = self.observe(SkyfieldStar.from_dataframe(df)).apparent()
+ nearby_stars_alt, nearby_stars_az, _ = stars_apparent.altaz()
+ df["x"], df["y"] = (
+ nearby_stars_az.degrees,
+ nearby_stars_alt.degrees,
+ )
+ return df
+
+ def _scatter_stars(self, ras, decs, sizes, alphas, colors, style=None, **kwargs):
+ plotted = super()._scatter_stars(
+ ras, decs, sizes, alphas, colors, style, **kwargs
+ )
+
+ if type(self._background_clip_path) == patches.Rectangle:
+ # convert to generic path to handle possible rotation angle:
+ clip_path = path.Path(self._background_clip_path.get_corners())
+ plotted.set_clip_path(clip_path, transform=self.ax.transData)
+ else:
+ plotted.set_clip_path(self._background_clip_path)
+
+ @use_style(ObjectStyle, "star")
+ def stars(
+ self,
+ mag: float = 6.0,
+ catalog: StarCatalog = StarCatalog.HIPPARCOS,
+ style: ObjectStyle = None,
+ rasterize: bool = False,
+ size_fn: Callable[[Star], float] = callables.size_by_magnitude,
+ alpha_fn: Callable[[Star], float] = callables.alpha_by_magnitude,
+ color_fn: Callable[[Star], str] = None,
+ where: list = None,
+ where_labels: list = None,
+ labels: Mapping[int, str] = STAR_NAMES,
+ legend_label: str = "Star",
+ bayer_labels: bool = False,
+ *args,
+ **kwargs,
+ ):
+ """
+ Plots stars
+
+ Args:
+ mag: Limiting magnitude of stars to plot
+ catalog: The catalog of stars to use
+ style: If `None`, then the plot's style for stars will be used
+ rasterize: If True, then the stars will be rasterized when plotted, which can speed up exporting to SVG and reduce the file size but with a loss of image quality
+ size_fn: Callable for calculating the marker size of each star. If `None`, then the marker style's size will be used.
+ alpha_fn: Callable for calculating the alpha value (aka "opacity") of each star. If `None`, then the marker style's alpha will be used.
+ color_fn: Callable for calculating the color of each star. If `None`, then the marker style's color will be used.
+ where: A list of expressions that determine which stars to plot. See [Selecting Objects](/reference-selecting-objects/) for details.
+ where_labels: A list of expressions that determine which stars are labeled on the plot. See [Selecting Objects](/reference-selecting-objects/) for details.
+ labels: A dictionary that maps a star's HIP id to the label that'll be plotted for that star. If you want to hide name labels, then set this arg to `None`.
+ legend_label: Label for stars in the legend. If `None`, then they will not be in the legend.
+ bayer_labels: If True, then Bayer labels for stars will be plotted. Set this to False if you want to hide Bayer labels.
+ """
+ # optic_star_multiplier = 0.57 * (self.FIELD_OF_VIEW_MAX / self.optic.true_fov)
+
+ # def size_fn_mx(st: Star) -> float:
+ # return size_fn(st) * optic_star_multiplier
+
+ super().stars(
+ mag=mag,
+ catalog=catalog,
+ style=style,
+ rasterize=rasterize,
+ size_fn=size_fn,
+ alpha_fn=alpha_fn,
+ color_fn=color_fn,
+ where=where,
+ where_labels=where_labels,
+ labels=labels,
+ legend_label=legend_label,
+ bayer_labels=bayer_labels,
+ *args,
+ **kwargs,
+ )
+
+ def _plot_border(self):
+ # since we're using AzimuthalEquidistant projection, the center will always be (0, 0)
+ x = 0
+ y = 0
+
+ # Background of Viewable Area
+ self._background_clip_path = self.optic.patch(
+ x,
+ y,
+ facecolor=self.style.background_color.as_hex(),
+ linewidth=0,
+ fill=True,
+ zorder=ZOrderEnum.LAYER_1,
+ )
+ self.ax.add_patch(self._background_clip_path)
+
+ # Inner Border
+ inner_border = self.optic.patch(
+ x,
+ y,
+ linewidth=2 * self.scale,
+ edgecolor=self.style.border_line_color.as_hex(),
+ fill=False,
+ zorder=ZOrderEnum.LAYER_5 + 100,
+ )
+ self.ax.add_patch(inner_border)
+
+ # Outer border
+ outer_border = self.optic.patch(
+ x,
+ y,
+ padding=0.05,
+ linewidth=20 * self.scale,
+ edgecolor=self.style.border_bg_color.as_hex(),
+ fill=False,
+ zorder=ZOrderEnum.LAYER_5,
+ )
+ self.ax.add_patch(outer_border)
+
+ def _fit_to_ax(self) -> None:
+ bbox = self.ax.get_window_extent().transformed(
+ self.fig.dpi_scale_trans.inverted()
+ )
+ width, height = bbox.width, bbox.height
+ self.fig.set_size_inches(width, height)
+
+ def _init_plot(self):
+ self._proj = ccrs.LambertAzimuthalEqualArea(
+ central_longitude=sum(self.az) / 2,
+ central_latitude=0,
+ )
+ self._proj.threshold = 100
+ self.fig = plt.figure(
+ figsize=(self.figure_size, self.figure_size),
+ facecolor=self.style.figure_background_color.as_hex(),
+ layout="constrained",
+ dpi=DPI,
+ )
+ self.ax = plt.axes(projection=self._proj)
+ self.ax.xaxis.set_visible(False)
+ self.ax.yaxis.set_visible(False)
+ self.ax.axis("off")
+
+ bounds = [
+ self.az[0],
+ self.az[1] * 1.2,
+ self.alt[0],
+ self.alt[1],
+ ]
+ print(bounds)
+
+ self.ax.set_extent(bounds, crs=ccrs.PlateCarree())
+ self.ax.gridlines()
+
+ # self._plot_border()
+ self._fit_to_ax()
+
+ # self.ax.set_xlim(-1.06 * self.optic.xlim, 1.06 * self.optic.xlim)
+ # self.ax.set_ylim(-1.06 * self.optic.ylim, 1.06 * self.optic.ylim)
+ # self.optic.transform(self.ax)
diff --git a/src/starplot/map.py b/src/starplot/map.py
index 63cab3f3..87da5104 100644
--- a/src/starplot/map.py
+++ b/src/starplot/map.py
@@ -14,7 +14,7 @@
import numpy as np
from starplot import geod
-from starplot.base import BasePlot
+from starplot.base import BasePlot, DPI
from starplot.data import DataFiles, constellations as condata, stars
from starplot.data.constellations import CONSTELLATIONS_FULL_NAMES
from starplot.mixins import ExtentMaskMixin
@@ -28,7 +28,6 @@
PlotStyle,
PolygonStyle,
PathStyle,
- extensions,
)
from starplot.styles.helpers import use_style
from starplot.utils import lon_to_ra, ra_to_lon
@@ -37,7 +36,25 @@
warnings.filterwarnings("ignore", module="cartopy")
warnings.filterwarnings("ignore", module="shapely")
-DEFAULT_MAP_STYLE = PlotStyle().extend(extensions.MAP)
+DEFAULT_MAP_STYLE = PlotStyle() # .extend(extensions.MAP)
+
+
+def points(start, end, num_points=100):
+ """Generates points along a line segment.
+
+ Args:
+ start (tuple): (x, y) coordinates of the starting point.
+ end (tuple): (x, y) coordinates of the ending point.
+ num_points (int): Number of points to generate.
+
+ Returns:
+ list: List of (x, y) coordinates of the generated points.
+ """
+
+ x_coords = np.linspace(start[0], end[0], num_points)
+ y_coords = np.linspace(start[1], end[1], num_points)
+
+ return list(zip(x_coords, y_coords))
class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
@@ -60,6 +77,8 @@ class MapPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
resolution: Size (in pixels) of largest dimension of the map
hide_colliding_labels: If True, then labels will not be plotted if they collide with another existing label
clip_path: An optional Shapely Polygon that specifies the clip path of the plot -- only objects inside the polygon will be plotted. If `None` (the default), then the clip path will be the extent of the map you specified with the RA/DEC parameters.
+ scale: Scaling factor that will be applied to all sizes in styles (e.g. font size, marker size, line widths, etc). For example, if you want to make everything 2x bigger, then set the scale to 2. At `scale=1` and `resolution=4096` (the default), all sizes are optimized visually for a map that covers 1-3 constellations. So, if you're creating a plot of a _larger_ extent, then it'd probably be good to decrease the scale (i.e. make everything smaller) -- and _increase_ the scale if you're plotting a very small area.
+ autoscale: If True, then the scale will be set automatically based on resolution.
Returns:
MapPlot: A new instance of a MapPlot
@@ -78,9 +97,11 @@ def __init__(
dt: datetime = None,
ephemeris: str = "de421_2001.bsp",
style: PlotStyle = DEFAULT_MAP_STYLE,
- resolution: int = 2048,
+ resolution: int = 4096,
hide_colliding_labels: bool = True,
clip_path: Polygon = None,
+ scale: float = 1.0,
+ autoscale: bool = False,
*args,
**kwargs,
) -> "MapPlot":
@@ -90,6 +111,8 @@ def __init__(
style,
resolution,
hide_colliding_labels,
+ scale=scale,
+ autoscale=autoscale,
*args,
**kwargs,
)
@@ -249,7 +272,7 @@ def constellation_borders(self, style: LineStyle = None):
if constellation_borders.empty:
return
- style_kwargs = style.matplot_kwargs(self._size_multiplier)
+ style_kwargs = style.matplot_kwargs(self.scale)
geometries = []
@@ -265,6 +288,7 @@ def constellation_borders(self, style: LineStyle = None):
transform=self._plate_carree,
clip_on=True,
clip_path=self._background_clip_path,
+ gid="constellations-border",
**style_kwargs,
)
@@ -325,9 +349,7 @@ def _plot_constellation_borders(self):
mls = MultiLineString(geometries)
geometries = unary_union(mls)
- style_kwargs = self.style.constellation_borders.matplot_kwargs(
- size_multiplier=self._size_multiplier
- )
+ style_kwargs = self.style.constellation_borders.matplot_kwargs(self.scale)
for ls in list(geometries.geoms):
# print(ls)
@@ -350,7 +372,9 @@ def constellations(
labels: dict[str, str] = CONSTELLATIONS_FULL_NAMES,
where: list = None,
):
- """Plots the constellation lines and/or labels
+ """Plots the constellation lines and/or labels.
+
+ **Important:** If you're plotting the constellation lines, then it's good to plot them _first_, because Starplot will use the constellation lines to determine where to place labels that are plotted afterwards (labels will look better if they're not crossing a constellation line).
Args:
style: Styling of the constellations. If None, then the plot's style (specified when creating the plot) will be used
@@ -379,7 +403,7 @@ def constellations(
transform = self._geodetic
conline_hips = condata.lines()
- style_kwargs = style.line.matplot_kwargs(size_multiplier=self._size_multiplier)
+ style_kwargs = style.line.matplot_kwargs(self.scale)
for c in constellations_gdf.itertuples():
obj = constellation_from_tuple(c)
@@ -416,19 +440,45 @@ def constellations(
# s1_ra, s1_dec = self._proj.transform_point(s1_ra, s1.dec_degrees, self._geodetic)
# s2_ra, s2_dec = self._proj.transform_point(s2_ra, s2.dec_degrees, self._geodetic)
- self.ax.plot(
+ constellation_line = self.ax.plot(
[s1_ra, s2_ra],
[s1_dec, s2_dec],
transform=transform,
**style_kwargs,
clip_on=True,
clip_path=self._background_clip_path,
+ gid="constellations-line",
+ )[0]
+
+ extent = constellation_line.get_window_extent(
+ renderer=self.fig.canvas.get_renderer()
)
+ if extent.xmin < 0:
+ continue
+
+ start = self._proj.transform_point(s1_ra, s1_dec, self._geodetic)
+ end = self._proj.transform_point(s2_ra, s2_dec, self._geodetic)
+ radius = style_kwargs.get("linewidth") or 1
+
+ if any([np.isnan(n) for n in start + end]):
+ continue
+
+ for x, y in points(start, end, 25):
+ x0, y0 = self.ax.transData.transform((x, y))
+ if x0 < 0 or y0 < 0:
+ continue
+ self._constellations_rtree.insert(
+ 0,
+ np.array((x0 - radius, y0 - radius, x0 + radius, y0 + radius)),
+ obj=obj.name,
+ )
+
if inbounds:
self._objects.constellations.append(obj)
self._plot_constellation_labels(style.label, labels)
+ # self._plot_constellation_labels_experimental(style.label, labels)
def _plot_constellation_labels(
self,
@@ -440,6 +490,75 @@ def _plot_constellation_labels(
for con in condata.iterator():
_, ra, dec = condata.get(con)
text = labels.get(con.lower())
+ self.text(
+ text,
+ ra,
+ dec,
+ style,
+ hide_on_collision=False,
+ gid="constellations-label-name",
+ )
+
+ def _plot_constellation_labels_experimental(
+ self,
+ style: PathStyle = None,
+ labels: dict[str, str] = CONSTELLATIONS_FULL_NAMES,
+ ):
+ from shapely import (
+ MultiPoint,
+ intersection,
+ delaunay_triangles,
+ distance,
+ )
+
+ def sorter(g):
+ d = distance(g.centroid, points.centroid)
+ # d = distance(g.centroid, constellation.boundary.centroid)
+ extent = abs(g.bounds[2] - g.bounds[0])
+ area = g.area / constellation.boundary.area
+ return (extent**2 + area) - (d**2)
+
+ for constellation in self.objects.constellations:
+ constellation_stars = [
+ s
+ for s in self.objects.stars
+ if s.constellation_id == constellation.iau_id
+ ]
+ points = MultiPoint([(s.ra, s.dec) for s in constellation_stars])
+
+ triangles = delaunay_triangles(
+ geometry=points,
+ # tolerance=2,
+ )
+
+ polygons = []
+ for t in triangles.geoms:
+ try:
+ inter = intersection(t, constellation.boundary)
+ except Exception:
+ continue
+ if (
+ inter.geom_type == "Polygon"
+ and len(list(zip(*inter.exterior.coords.xy))) > 2
+ ):
+ polygons.append(inter)
+
+ p_by_area = {pg.area: pg for pg in polygons}
+ polygons_sorted = [
+ p_by_area[k] for k in sorted(p_by_area.keys(), reverse=True)
+ ]
+
+ # sort by combination of horizontal extent and area
+ polygons_sorted = sorted(polygons_sorted, key=sorter, reverse=True)
+
+ if len(polygons_sorted) > 0:
+ i = 0
+ ra, dec = polygons_sorted[i].centroid.x, polygons_sorted[i].centroid.y
+ else:
+ ra, dec = constellation.ra, constellation.dec
+
+ text = labels.get(constellation.iau_id)
+ style = style or self.style.constellation.label
self.text(text, ra, dec, style)
@use_style(PolygonStyle, "milky_way")
@@ -554,7 +673,7 @@ def horizon(
TODO : investigate why line is extra thick on bottom when plotting line
"""
- style_kwargs = style.line.matplot_kwargs(self._size_multiplier)
+ style_kwargs = style.line.matplot_kwargs(self.scale)
style_kwargs["clip_on"] = False
style_kwargs["edgecolor"] = style_kwargs.pop("color")
@@ -574,7 +693,7 @@ def horizon(
x,
y,
dash_capstyle=style.line.dash_capstyle,
- **style.line.matplot_kwargs(self._size_multiplier),
+ **style.line.matplot_kwargs(self.scale),
**style_kwargs,
**self._plot_kwargs(),
)
@@ -597,10 +716,13 @@ def horizon(
cardinal_directions = [north, east, south, west]
text_kwargs = dict(
- **style.label.matplot_kwargs(self._size_multiplier),
+ **style.label.matplot_kwargs(self.scale),
hide_on_collision=False,
- xytext=(style.label.offset_x, style.label.offset_y),
- textcoords="offset pixels",
+ xytext=(
+ style.label.offset_x * self.scale,
+ style.label.offset_y * self.scale,
+ ),
+ textcoords="offset points",
path_effects=[],
)
@@ -609,7 +731,7 @@ def horizon(
for i, position in enumerate(cardinal_directions):
ra, dec, _ = position.radec()
- self._text(ra.hours, dec.degrees, labels[i], **text_kwargs)
+ self._text(ra.hours, dec.degrees, labels[i], force=True, **text_kwargs)
@use_style(PathStyle, "gridlines")
def gridlines(
@@ -664,6 +786,7 @@ def dec_formatter(x, pos) -> str:
ypadding=12,
clip_on=True,
clip_path=self._background_clip_path,
+ gid="gridlines",
**line_style_kwargs,
)
@@ -681,6 +804,7 @@ def dec_formatter(x, pos) -> str:
self.ax.plot(
(ra * 15, ra * 15),
(-90, 90),
+ gid="gridlines",
**line_style_kwargs,
**self._plot_kwargs(),
)
@@ -732,6 +856,7 @@ def _init_plot(self):
figsize=(self.figure_size, self.figure_size),
facecolor=self.style.figure_background_color.as_hex(),
layout="constrained",
+ dpi=DPI,
)
bounds = self._latlon_bounds()
center_lat = (bounds[2] + bounds[3]) / 2
@@ -800,7 +925,7 @@ def info(self, style: LabelStyle = None):
0.05,
info,
transform=self.ax.transAxes,
- **style.matplot_kwargs(self._size_multiplier * 1.36),
+ **style.matplot_kwargs(self.scale),
)
def _plot_background_clip_path(self):
@@ -830,7 +955,7 @@ def to_axes(points):
fill=True,
facecolor=self.style.background_color.as_hex(),
# edgecolor=self.style.border_line_color.as_hex(),
- linewidth=0, # 4 * self._size_multiplier,
+ linewidth=0,
zorder=-2_000,
transform=self.ax.transAxes,
)
diff --git a/src/starplot/optic.py b/src/starplot/optic.py
index 7d1627eb..181b3272 100644
--- a/src/starplot/optic.py
+++ b/src/starplot/optic.py
@@ -8,7 +8,7 @@
from skyfield.api import wgs84, Star as SkyfieldStar
from starplot import callables
-from starplot.base import BasePlot
+from starplot.base import BasePlot, DPI
from starplot.data.stars import StarCatalog, STAR_NAMES
from starplot.mixins import ExtentMaskMixin
from starplot.models import Star
@@ -44,6 +44,8 @@ class OpticPlot(BasePlot, ExtentMaskMixin, StarPlotterMixin, DsoPlotterMixin):
resolution: Size (in pixels) of largest dimension of the map
hide_colliding_labels: If True, then labels will not be plotted if they collide with another existing label
raise_on_below_horizon: If True, then a ValueError will be raised if the target is below the horizon at the observing time/location
+ scale: Scaling factor that will be applied to all sizes in styles (e.g. font size, marker size, line widths, etc). For example, if you want to make everything 2x bigger, then set the scale to 2. At `scale=1` and `resolution=4096` (the default), all sizes are optimized visually for a map that covers 1-3 constellations. So, if you're creating a plot of a _larger_ extent, then it'd probably be good to decrease the scale (i.e. make everything smaller) -- and _increase_ the scale if you're plotting a very small area.
+ autoscale: If True, then the scale will be set automatically based on resolution.
Returns:
OpticPlot: A new instance of an OpticPlot
@@ -62,9 +64,11 @@ def __init__(
dt: datetime = None,
ephemeris: str = "de421_2001.bsp",
style: PlotStyle = DEFAULT_OPTIC_STYLE,
- resolution: int = 2048,
+ resolution: int = 4096,
hide_colliding_labels: bool = True,
raise_on_below_horizon: bool = True,
+ scale: float = 1.0,
+ autoscale: bool = False,
*args,
**kwargs,
) -> "OpticPlot":
@@ -74,6 +78,8 @@ def __init__(
style,
resolution,
hide_colliding_labels,
+ scale=scale,
+ autoscale=autoscale,
*args,
**kwargs,
)
@@ -193,6 +199,7 @@ def _scatter_stars(self, ras, decs, sizes, alphas, colors, style=None, **kwargs)
else:
plotted.set_clip_path(self._background_clip_path)
+ @use_style(ObjectStyle, "star")
def stars(
self,
mag: float = 6.0,
@@ -207,6 +214,7 @@ def stars(
labels: Mapping[int, str] = STAR_NAMES,
legend_label: str = "Star",
bayer_labels: bool = False,
+ flamsteed_labels: bool = False,
*args,
**kwargs,
):
@@ -225,12 +233,16 @@ def stars(
where_labels: A list of expressions that determine which stars are labeled on the plot. See [Selecting Objects](/reference-selecting-objects/) for details.
labels: A dictionary that maps a star's HIP id to the label that'll be plotted for that star. If you want to hide name labels, then set this arg to `None`.
legend_label: Label for stars in the legend. If `None`, then they will not be in the legend.
- bayer_labels: If True, then Bayer labels for stars will be plotted. Set this to False if you want to hide Bayer labels.
+ bayer_labels: If True, then Bayer labels for stars will be plotted.
+ flamsteed_labels: If True, then Flamsteed number labels for stars will be plotted.
"""
- optic_star_multiplier = 0.4 * (self.FIELD_OF_VIEW_MAX / self.optic.true_fov)
+ optic_star_multiplier = self.FIELD_OF_VIEW_MAX / self.optic.true_fov
+ size_fn_mx = None
- def size_fn_mx(st: Star) -> float:
- return size_fn(st) * optic_star_multiplier
+ if size_fn is not None:
+
+ def size_fn_mx(s):
+ return size_fn(s) * optic_star_multiplier
super().stars(
mag=mag,
@@ -245,6 +257,7 @@ def size_fn_mx(st: Star) -> float:
labels=labels,
legend_label=legend_label,
bayer_labels=bayer_labels,
+ flamsteed_labels=flamsteed_labels,
*args,
**kwargs,
)
@@ -268,7 +281,7 @@ def info(self, style: LabelStyle = None):
) # apply transform again because new xy limits will undo the transform
dt_str = self.dt.strftime("%m/%d/%Y @ %H:%M:%S") + " " + self.dt.tzname()
- font_size = style.font_size * self._size_multiplier * 2
+ font_size = style.font_size * self.scale
column_labels = [
"Target (Alt/Az)",
@@ -296,19 +309,17 @@ def info(self, style: LabelStyle = None):
edges="vertical",
)
table.auto_set_font_size(False)
- table.set_fontsize(font_size)
+ table.set_fontsize(style.font_size)
table.scale(1, 3.1)
# Apply style to all cells
for row in [0, 1]:
for col in range(len(values)):
- table[row, col].set_text_props(
- **style.matplot_kwargs(self._size_multiplier)
- )
+ table[row, col].set_text_props(**style.matplot_kwargs(self.scale))
# Apply some styles only to the header row
for col in range(len(values)):
- table[0, col].set_text_props(fontweight="heavy", fontsize=font_size * 1.15)
+ table[0, col].set_text_props(fontweight="heavy", fontsize=font_size * 1.2)
def _plot_border(self):
# since we're using AzimuthalEquidistant projection, the center will always be (0, 0)
@@ -330,7 +341,7 @@ def _plot_border(self):
inner_border = self.optic.patch(
x,
y,
- linewidth=2 * self._size_multiplier,
+ linewidth=2 * self.scale,
edgecolor=self.style.border_line_color.as_hex(),
fill=False,
zorder=ZOrderEnum.LAYER_5 + 100,
@@ -342,7 +353,7 @@ def _plot_border(self):
x,
y,
padding=0.05,
- linewidth=20 * self._size_multiplier,
+ linewidth=20 * self.scale,
edgecolor=self.style.border_bg_color.as_hex(),
fill=False,
zorder=ZOrderEnum.LAYER_5,
@@ -366,6 +377,7 @@ def _init_plot(self):
figsize=(self.figure_size, self.figure_size),
facecolor=self.style.figure_background_color.as_hex(),
layout="constrained",
+ dpi=DPI,
)
self.ax = plt.axes(projection=self._proj)
self.ax.xaxis.set_visible(False)
diff --git a/src/starplot/plotters/dsos.py b/src/starplot/plotters/dsos.py
index a991520f..d2b5309c 100644
--- a/src/starplot/plotters/dsos.py
+++ b/src/starplot/plotters/dsos.py
@@ -222,7 +222,9 @@ def dsos(
)
if label:
- self.text(label, ra / 15, dec, style.label)
+ self.text(
+ label, ra / 15, dec, style.label, gid=f"dso-{d.type}-label"
+ )
else:
# if no major axis, then just plot as a marker
@@ -232,6 +234,8 @@ def dsos(
style=style,
label=label,
skip_bounds_check=True,
+ gid_marker=f"dso-{d.type}-marker",
+ gid_label=f"dso-{d.type}-label",
)
self._objects.dsos.append(_dso)
diff --git a/src/starplot/plotters/stars.py b/src/starplot/plotters/stars.py
index f5012873..d3cca6ac 100644
--- a/src/starplot/plotters/stars.py
+++ b/src/starplot/plotters/stars.py
@@ -3,11 +3,13 @@
from skyfield.api import Star as SkyfieldStar
+# import numpy as np
+
from starplot import callables
-from starplot.data import bayer, stars
+from starplot.data import bayer, stars, flamsteed
from starplot.data.stars import StarCatalog, STAR_NAMES
from starplot.models.star import Star, from_tuple
-from starplot.styles import ObjectStyle, LabelStyle, use_style
+from starplot.styles import ObjectStyle, use_style
class StarPlotterMixin:
@@ -64,6 +66,7 @@ def _scatter_stars(self, ras, decs, sizes, alphas, colors, style=None, **kwargs)
zorder=kwargs.pop("zorder", None) or style.marker.zorder,
edgecolors=edge_colors,
alpha=alphas,
+ gid="stars",
**self._plot_kwargs(),
**kwargs,
)
@@ -77,24 +80,90 @@ def _scatter_stars(self, ras, decs, sizes, alphas, colors, style=None, **kwargs)
def _star_labels(
self,
star_objects: list[Star],
+ star_sizes: list[float],
where_labels: list,
- style: LabelStyle,
+ style: ObjectStyle,
labels: Mapping[str, str],
bayer_labels: bool,
+ flamsteed_labels: bool,
label_fn: Callable[[Star], str],
):
- for s in star_objects:
+ _bayer = []
+ _flamsteed = []
+
+ # Plot all star common names first
+ for i, s in enumerate(star_objects):
if where_labels and not all([e.evaluate(s) for e in where_labels]):
continue
+ if (
+ s.hip
+ and s.hip in self._labeled_stars
+ or s.tyc
+ and s.tyc in self._labeled_stars
+ ):
+ continue
+ elif s.hip:
+ self._labeled_stars.append(s.hip)
+ elif s.tyc:
+ self._labeled_stars.append(s.tyc)
+
label = labels.get(s.hip) if label_fn is None else label_fn(s)
bayer_desig = bayer.hip.get(s.hip)
+ flamsteed_num = flamsteed.hip.get(s.hip)
if label:
- self.text(label, s.ra, s.dec, style)
+ self.text(
+ label,
+ s.ra,
+ s.dec,
+ # style,
+ # _offset(style, star_sizes[i]),
+ style=style.label.offset_from_marker(
+ marker_symbol=style.marker.symbol,
+ marker_size=star_sizes[i],
+ scale=self.scale,
+ ),
+ hide_on_collision=self.hide_colliding_labels,
+ gid="stars-label-name",
+ )
if bayer_labels and bayer_desig:
- self.text(bayer_desig, s.ra, s.dec, self.style.bayer_labels)
+ _bayer.append((bayer_desig, s.ra, s.dec, star_sizes[i], s.hip))
+
+ if flamsteed_labels and flamsteed_num:
+ _flamsteed.append((flamsteed_num, s.ra, s.dec, star_sizes[i], s.hip))
+
+ # Plot bayer/flamsteed
+ for bayer_desig, ra, dec, star_size, _ in _bayer:
+ self.text(
+ bayer_desig,
+ ra,
+ dec,
+ style=self.style.bayer_labels.offset_from_marker(
+ marker_symbol=style.marker.symbol,
+ marker_size=star_size,
+ scale=self.scale,
+ ),
+ hide_on_collision=self.hide_colliding_labels,
+ gid="stars-label-bayer",
+ )
+
+ for flamsteed_num, ra, dec, star_size, hip in _flamsteed:
+ if hip in bayer.hip:
+ continue
+ self.text(
+ flamsteed_num,
+ ra,
+ dec,
+ style=self.style.flamsteed_labels.offset_from_marker(
+ marker_symbol=style.marker.symbol,
+ marker_size=star_size,
+ scale=self.scale,
+ ),
+ hide_on_collision=self.hide_colliding_labels,
+ gid="stars-label-flamsteed",
+ )
def _prepare_star_coords(self, df):
df["x"], df["y"] = (
@@ -119,6 +188,7 @@ def stars(
labels: Mapping[int, str] = STAR_NAMES,
legend_label: str = "Star",
bayer_labels: bool = False,
+ flamsteed_labels: bool = False,
*args,
**kwargs,
):
@@ -138,7 +208,8 @@ def stars(
where_labels: A list of expressions that determine which stars are labeled on the plot. See [Selecting Objects](/reference-selecting-objects/) for details.
labels: A dictionary that maps a star's HIP id to the label that'll be plotted for that star. If you want to hide name labels, then set this arg to `None`.
legend_label: Label for stars in the legend. If `None`, then they will not be in the legend.
- bayer_labels: If True, then Bayer labels for stars will be plotted. Set this to False if you want to hide Bayer labels.
+ bayer_labels: If True, then Bayer labels for stars will be plotted.
+ flamsteed_labels: If True, then Flamsteed number labels for stars will be plotted.
"""
self.logger.debug("Plotting stars...")
@@ -160,8 +231,6 @@ def stars(
else:
labels = {**STAR_NAMES, **labels}
- star_size_multiplier = self._size_multiplier * style.marker.size / 5
-
nearby_stars_df = self._load_stars(catalog, mag)
nearby_stars = SkyfieldStar.from_dataframe(nearby_stars_df)
astrometric = self.ephemeris["earth"].at(self.timescale).observe(nearby_stars)
@@ -180,12 +249,36 @@ def stars(
if not all([e.evaluate(obj) for e in where]):
continue
- size = size_fn(obj) * star_size_multiplier
+ size = size_fn(obj) * self.scale**2
alpha = alpha_fn(obj)
color = color_fn(obj) or style.marker.color.as_hex()
starz.append((star.x, star.y, size, alpha, color, obj))
+ # Experimental code for keeping spatial index of plotted stars (for better label placement)
+ # if getattr(self, "_geodetic", None):
+ # # TODO : clean up!
+ # x, y = self._proj.transform_point(
+ # star.ra * -1, star.dec, self._geodetic
+ # )
+ # x0, y0 = self.ax.transData.transform((x, y))
+
+ # if (
+ # x0 < 0
+ # or y0 < 0
+ # or obj.magnitude > 5
+ # or np.isnan(x0)
+ # or np.isnan(y0)
+ # ):
+ # continue
+ # radius = 1 + (5 - obj.magnitude)
+ # # radius = max(((size**0.5 / 2) / self.scale)/1.44 - 6, 0) #size / self.scale**2 / 200
+ # self._stars_rtree.insert(
+ # 0,
+ # np.array((x0 - radius, y0 - radius, x0 + radius, y0 + radius)),
+ # obj=star.x,
+ # )
+
starz.sort(key=lambda s: s[2], reverse=True) # sort by descending size
if not starz:
@@ -215,6 +308,14 @@ def stars(
self._add_legend_handle_marker(legend_label, style.marker)
- self._star_labels(
- star_objects, where_labels, style.label, labels, bayer_labels, label_fn
- )
+ if labels:
+ self._star_labels(
+ star_objects,
+ sizes,
+ where_labels,
+ style,
+ labels,
+ bayer_labels,
+ flamsteed_labels,
+ label_fn,
+ )
diff --git a/src/starplot/styles/base.py b/src/starplot/styles/base.py
index 82f97bc6..94e6525c 100644
--- a/src/starplot/styles/base.py
+++ b/src/starplot/styles/base.py
@@ -1,8 +1,8 @@
import json
+
from enum import Enum
from pathlib import Path
-from typing import Optional, Union
-from functools import cache
+from typing import Optional, Union, List
import yaml
@@ -14,7 +14,14 @@
from starplot.data.dsos import DsoType
from starplot.styles.helpers import merge_dict
-from starplot.styles.markers import ellipse, circle_cross, circle_line
+from starplot.styles.markers import (
+ ellipse,
+ circle_cross,
+ circle_crosshair,
+ circle_line,
+ circle_dot,
+ circle_dotted_rings,
+)
ColorStr = Annotated[
@@ -26,10 +33,11 @@
]
-FONT_SCALE = 2
-
HERE = Path(__file__).resolve().parent
+PI = 3.141592653589793
+SQR_2 = 1.41421356237
+
class BaseStyle(BaseModel):
__hash__ = object.__hash__
@@ -97,9 +105,6 @@ class MarkerSymbolEnum(str, Enum):
SQUARE_STRIPES_DIAGONAL = "square_stripes_diagonal"
"""\u25A8"""
- # SQUARE_CROSSHAIR = "square_crosshair"
- # """\u2BD0"""
-
STAR = "star"
"""\u2605"""
@@ -118,9 +123,17 @@ class MarkerSymbolEnum(str, Enum):
CIRCLE_CROSS = "circle_cross"
"""\u1AA0"""
+ CIRCLE_CROSSHAIR = "circle_crosshair"
+ """No preview available, but this is the standard symbol for planetary nebulae"""
+
+ CIRCLE_DOT = "circle_dot"
+ """\u29BF"""
+
CIRCLE_DOTTED_EDGE = "circle_dotted_edge"
"""\u25CC"""
+ CIRCLE_DOTTED_RINGS = "circle_dotted_rings"
+
CIRCLE_LINE = "circle_line"
"""\u29B5"""
@@ -150,7 +163,10 @@ def as_matplot(self) -> str:
MarkerSymbolEnum.TRIANGLE: "^",
MarkerSymbolEnum.CIRCLE_PLUS: "$\u2295$",
MarkerSymbolEnum.CIRCLE_CROSS: circle_cross(),
+ MarkerSymbolEnum.CIRCLE_CROSSHAIR: circle_crosshair(),
+ MarkerSymbolEnum.CIRCLE_DOT: circle_dot(),
MarkerSymbolEnum.CIRCLE_DOTTED_EDGE: "$\u25CC$",
+ MarkerSymbolEnum.CIRCLE_DOTTED_RINGS: circle_dotted_rings(),
MarkerSymbolEnum.CIRCLE_LINE: circle_line(),
MarkerSymbolEnum.COMET: "$\u2604$",
MarkerSymbolEnum.STAR_8: "$\u2734$",
@@ -222,6 +238,11 @@ def as_matplot(self) -> dict:
return style
+ @staticmethod
+ def from_str(value: str) -> "AnchorPointEnum":
+ options = {ap.value: ap for ap in AnchorPointEnum}
+ return options.get(value)
+
class ZOrderEnum(int, Enum):
"""
@@ -245,19 +266,6 @@ class ZOrderEnum(int, Enum):
class MarkerStyle(BaseStyle):
"""
Styling properties for markers.
-
- ???- tip "Example Usage"
- Creates a style for a red triangle marker:
- ```python
- m = MarkerStyle(
- color="#b13737",
- symbol="triangle",
- size=8,
- fill="full",
- alpha=1.0,
- zorder=100,
- )
- ```
"""
color: Optional[ColorStr] = ColorStr("#000")
@@ -266,14 +274,20 @@ class MarkerStyle(BaseStyle):
edge_color: Optional[ColorStr] = ColorStr("#000")
"""Edge color of marker. Can be a hex, rgb, hsl, or word string."""
- edge_width: int = 1
- """Edge width of marker. Not available for all marker symbols."""
+ edge_width: float = 1
+ """Edge width of marker, in points. Not available for all marker symbols."""
+
+ line_style: Union[LineStyleEnum, tuple] = LineStyleEnum.SOLID
+ """Edge line style. Can be a predefined value in `LineStyleEnum` or a [Matplotlib linestyle tuple](https://matplotlib.org/stable/gallery/lines_bars_and_markers/linestyles.html)."""
+
+ dash_capstyle: DashCapStyleEnum = DashCapStyleEnum.PROJECTING
+ """Style of dash endpoints"""
symbol: MarkerSymbolEnum = MarkerSymbolEnum.POINT
"""Symbol for marker"""
- size: int = 4
- """Relative size of marker"""
+ size: float = 22
+ """Size of marker in points"""
fill: FillStyleEnum = FillStyleEnum.NONE
"""Fill style of marker"""
@@ -281,53 +295,59 @@ class MarkerStyle(BaseStyle):
alpha: float = 1.0
"""Alpha value (controls transparency)"""
- zorder: int = -1
+ zorder: int = ZOrderEnum.LAYER_2
"""Zorder of marker"""
@property
def symbol_matplot(self) -> str:
return MarkerSymbolEnum(self.symbol).as_matplot()
- @cache
- def matplot_kwargs(self, size_multiplier: float = 1.0) -> dict:
+ def matplot_kwargs(self, scale: float = 1.0) -> dict:
return dict(
color=self.color.as_hex() if self.color else "none",
markeredgecolor=self.edge_color.as_hex() if self.edge_color else "none",
marker=MarkerSymbolEnum(self.symbol).as_matplot(),
- markersize=self.size * size_multiplier * FONT_SCALE,
+ markersize=self.size * scale,
fillstyle=self.fill,
alpha=self.alpha,
zorder=self.zorder,
)
+ def matplot_scatter_kwargs(self, scale: float = 1.0) -> dict:
+ plot_kwargs = self.matplot_kwargs(scale)
+ plot_kwargs["edgecolors"] = plot_kwargs.pop("markeredgecolor")
+
+ # matplotlib's plot() function takes the marker size in points diameter
+ # and the scatter() function takes it in points squared
+ plot_kwargs["s"] = ((plot_kwargs.pop("markersize") / scale) ** 2) * (scale**2)
+
+ plot_kwargs["c"] = plot_kwargs.pop("color")
+ plot_kwargs["linewidths"] = self.edge_width * scale
+ plot_kwargs["linestyle"] = self.line_style
+ plot_kwargs["capstyle"] = self.dash_capstyle
+
+ plot_kwargs.pop("fillstyle")
+
+ return plot_kwargs
+
def to_polygon_style(self):
return PolygonStyle(
fill_color=self.color.as_hex() if self.color else None,
edge_color=self.edge_color.as_hex() if self.edge_color else None,
+ edge_width=self.edge_width,
alpha=self.alpha,
zorder=self.zorder,
+ line_style=self.line_style,
)
class LineStyle(BaseStyle):
"""
Styling properties for lines.
-
- ???- tip "Example Usage"
- Creates a style for a dashed green line:
- ```python
- ls = LineStyle(
- width=2,
- color="#6ba832",
- style="dashed",
- alpha=0.2,
- zorder=-10,
- )
- ```
"""
- width: float = 2
- """Width of line"""
+ width: float = 4
+ """Width of line in points"""
color: ColorStr = ColorStr("#000")
"""Color of the line. Can be a hex, rgb, hsl, or word string."""
@@ -341,17 +361,17 @@ class LineStyle(BaseStyle):
alpha: float = 1.0
"""Alpha value (controls transparency)"""
- zorder: int = -1
+ zorder: int = ZOrderEnum.LAYER_2
"""Zorder of the line"""
edge_width: int = 0
- """Width of the line's edge. _If the width or color is falsey then the line will NOT be drawn with an edge._"""
+ """Width of the line's edge in points. _If the width or color is falsey then the line will NOT be drawn with an edge._"""
edge_color: Optional[ColorStr] = None
"""Edge color of the line. _If the width or color is falsey then the line will NOT be drawn with an edge._"""
- def matplot_kwargs(self, size_multiplier: float = 1.0) -> dict:
- line_width = self.width * size_multiplier
+ def matplot_kwargs(self, scale: float = 1.0) -> dict:
+ line_width = self.width * scale
result = dict(
color=self.color.as_hex(),
@@ -365,7 +385,7 @@ def matplot_kwargs(self, size_multiplier: float = 1.0) -> dict:
if self.edge_width and self.edge_color:
result["path_effects"] = [
patheffects.withStroke(
- linewidth=line_width + 2 * self.edge_width * size_multiplier,
+ linewidth=line_width + 2 * self.edge_width * scale,
foreground=self.edge_color.as_hex(),
)
]
@@ -376,21 +396,10 @@ def matplot_kwargs(self, size_multiplier: float = 1.0) -> dict:
class PolygonStyle(BaseStyle):
"""
Styling properties for polygons.
-
- ???- tip "Example Usage"
- Creates a style for a partially transparent blue polygon:
- ```python
- ps = PolygonStyle(
- color="#d9d9d9",
- alpha=0.36,
- edge_width=0,
- zorder=-10000,
- )
- ```
"""
- edge_width: int = 1
- """Width of the polygon's edge"""
+ edge_width: float = 1
+ """Width of the polygon's edge in points"""
color: Optional[ColorStr] = None
"""If specified, this will be the fill color AND edge color of the polygon"""
@@ -410,12 +419,12 @@ class PolygonStyle(BaseStyle):
zorder: int = -1
"""Zorder of the polygon"""
- def matplot_kwargs(self, size_multiplier: float = 1.0) -> dict:
+ def matplot_kwargs(self, scale: float = 1.0) -> dict:
styles = dict(
edgecolor=self.edge_color.as_hex() if self.edge_color else "none",
facecolor=self.fill_color.as_hex() if self.fill_color else "none",
fill=True if self.fill_color else False,
- linewidth=self.edge_width * size_multiplier,
+ linewidth=self.edge_width * scale,
linestyle=self.line_style,
alpha=self.alpha,
zorder=self.zorder,
@@ -430,23 +439,10 @@ def matplot_kwargs(self, size_multiplier: float = 1.0) -> dict:
class LabelStyle(BaseStyle):
"""
Styling properties for a label.
-
- ???- tip "Example Usage"
- Creates a style for a bold blue label:
- ```python
- ls = LabelStyle(
- font_color="blue",
- font_weight="bold",
- zorder=1,
- )
- ```
"""
- anchor_point: AnchorPointEnum = AnchorPointEnum.BOTTOM_RIGHT
- """Anchor point of label"""
-
- font_size: int = 8
- """Relative font size of the label"""
+ font_size: float = 15
+ """Font size of the label, in points"""
font_weight: FontWeightEnum = FontWeightEnum.NORMAL
"""Font weight (e.g. normal, bold, ultra bold, etc)"""
@@ -460,28 +456,50 @@ class LabelStyle(BaseStyle):
font_style: FontStyleEnum = FontStyleEnum.NORMAL
"""Style of the label (e.g. normal, italic, etc)"""
- font_name: Optional[str] = None
+ font_name: Optional[str] = "Inter"
"""Name of the font to use"""
font_family: Optional[str] = None
"""Font family (e.g. 'monospace', 'sans-serif', 'serif', etc)"""
- line_spacing: Optional[int] = None
+ line_spacing: Optional[float] = None
"""Spacing between lines of text"""
- offset_x: int = 0
- """Horizontal offset of the label, in pixels. Negative values supported."""
+ anchor_point: AnchorPointEnum = AnchorPointEnum.BOTTOM_RIGHT
+ """Anchor point of label"""
- offset_y: int = 0
- """Vertical offset of the label, in pixels. Negative values supported."""
+ border_width: float = 0
+ """Width of border (also known as 'halos') around the text, in points"""
+
+ border_color: Optional[ColorStr] = None
+ """Color of border (also known as 'halos') around the text"""
+
+ offset_x: Union[float, int, str] = 0
+ """
+ Horizontal offset of the label, in points. Negative values supported.
+
+
+ **Auto Mode** (_experimental_): If the label is plotted as part of a marker (e.g. stars, via `marker()`, etc), then you can also
+ specify the offset as `"auto"` which will calculate the offset automatically based on the marker's size and place
+ the label just outside the marker (avoiding overlapping). To enable "auto" mode you have to specify BOTH offsets (x and y) as "auto."
+ """
- zorder: int = 101
+ offset_y: Union[float, int, str] = 0
+ """
+ Vertical offset of the label, in points. Negative values supported.
+
+ **Auto Mode** (_experimental_): If the label is plotted as part of a marker (e.g. stars, via `marker()`, etc), then you can also
+ specify the offset as `"auto"` which will calculate the offset automatically based on the marker's size and place
+ the label just outside the marker (avoiding overlapping). To enable "auto" mode you have to specify BOTH offsets (x and y) as "auto."
+ """
+
+ zorder: int = ZOrderEnum.LAYER_4
"""Zorder of the label"""
- def matplot_kwargs(self, size_multiplier: float = 1.0) -> dict:
+ def matplot_kwargs(self, scale: float = 1.0) -> dict:
style = dict(
color=self.font_color.as_hex(),
- fontsize=self.font_size * size_multiplier * FONT_SCALE,
+ fontsize=self.font_size * scale,
fontstyle=self.font_style,
fontname=self.font_name,
weight=self.font_weight,
@@ -494,10 +512,45 @@ def matplot_kwargs(self, size_multiplier: float = 1.0) -> dict:
if self.line_spacing:
style["linespacing"] = self.line_spacing
+ if self.border_width != 0 and self.border_color is not None:
+ style["path_effects"] = [
+ patheffects.withStroke(
+ linewidth=self.border_width * scale,
+ foreground=self.border_color.as_hex(),
+ )
+ ]
+
style.update(AnchorPointEnum(self.anchor_point).as_matplot())
return style
+ def offset_from_marker(self, marker_symbol, marker_size, scale: float = 1.0):
+ if self.offset_x != "auto" or self.offset_y != "auto":
+ return self
+
+ new_style = self.model_copy()
+
+ x_direction = -1 if new_style.anchor_point.endswith("left") else 1
+ y_direction = -1 if new_style.anchor_point.startswith("bottom") else 1
+
+ offset = (marker_size**0.5 / 2) / scale
+
+ # matplotlib seems to use marker size differently depending on symbol (for scatter)
+ # it is NOT strictly the area of the bounding box of the marker
+ if marker_symbol in [MarkerSymbolEnum.POINT]:
+ offset /= PI
+
+ elif marker_symbol != MarkerSymbolEnum.SQUARE:
+ offset /= SQR_2
+ offset *= scale
+
+ offset += 1.1
+
+ new_style.offset_x = offset * float(x_direction)
+ new_style.offset_y = offset * float(y_direction)
+
+ return new_style
+
class ObjectStyle(BaseStyle):
"""Defines the style for a sky object (e.g. star, DSO)"""
@@ -540,8 +593,8 @@ class LegendStyle(BaseStyle):
label_padding: float = 1.6
"""Padding between legend labels"""
- symbol_size: int = 16
- """Relative size of symbols in the legend"""
+ symbol_size: int = 34
+ """Size of symbols in the legend, in points"""
symbol_padding: float = 0.2
"""Padding between each symbol and its label"""
@@ -549,8 +602,8 @@ class LegendStyle(BaseStyle):
border_padding: float = 1.28
"""Padding around legend border"""
- font_size: int = 9
- """Relative font size of the legend labels"""
+ font_size: int = 23
+ """Font size of the legend labels, in points"""
font_color: ColorStr = ColorStr("#000")
"""Font color for legend labels"""
@@ -558,12 +611,12 @@ class LegendStyle(BaseStyle):
zorder: int = ZOrderEnum.LAYER_5
"""Zorder of the legend"""
- def matplot_kwargs(self, size_multiplier: float = 1.0) -> dict:
+ def matplot_kwargs(self, scale: float = 1.0) -> dict:
return dict(
loc=self.location,
ncols=self.num_columns,
framealpha=self.background_alpha,
- fontsize=self.font_size * size_multiplier * FONT_SCALE,
+ fontsize=self.font_size * scale,
labelcolor=self.font_color.as_hex(),
borderpad=self.border_padding,
labelspacing=self.label_padding,
@@ -578,16 +631,25 @@ class PlotStyle(BaseStyle):
Defines the styling for a plot
"""
- # Base
background_color: ColorStr = ColorStr("#fff")
"""Background color of the map region"""
figure_background_color: ColorStr = ColorStr("#fff")
- text_border_width: int = 3
+ text_border_width: int = 2
+ """Text border (aka halos) width. This will apply to _all_ text labels on the plot. If you'd like to control these borders by object type, then set this global width to `0` and refer to the label style's `border_width` and `border_color` properties."""
+
text_border_color: ColorStr = ColorStr("#fff")
- text_offset_x: float = 0.005
- text_offset_y: float = 0.005
+
+ text_anchor_fallbacks: List[AnchorPointEnum] = [
+ AnchorPointEnum.BOTTOM_RIGHT,
+ AnchorPointEnum.TOP_LEFT,
+ AnchorPointEnum.TOP_RIGHT,
+ AnchorPointEnum.BOTTOM_LEFT,
+ AnchorPointEnum.BOTTOM_CENTER,
+ AnchorPointEnum.TOP_CENTER,
+ ]
+ """If a label's preferred anchor point results in a collision, then these fallbacks will be tried in sequence until a collision-free position is found."""
# Borders
border_font_size: int = 18
@@ -608,10 +670,10 @@ class PlotStyle(BaseStyle):
# Info text
info_text: LabelStyle = LabelStyle(
- font_size=10,
+ font_size=30,
zorder=ZOrderEnum.LAYER_5,
- font_family="monospace",
- line_spacing=2,
+ font_family="Inter",
+ line_spacing=1.2,
anchor_point=AnchorPointEnum.BOTTOM_CENTER,
)
"""Styling for info text (only applies to zenith and optic plots)"""
@@ -619,31 +681,53 @@ class PlotStyle(BaseStyle):
# Stars
star: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
- fill=FillStyleEnum.FULL, zorder=ZOrderEnum.LAYER_3, size=36, edge_color=None
+ fill=FillStyleEnum.FULL,
+ zorder=ZOrderEnum.LAYER_3 + 1,
+ size=40,
+ edge_color=None,
),
label=LabelStyle(
- font_size=9, font_weight=FontWeightEnum.BOLD, zorder=ZOrderEnum.LAYER_4
+ font_size=24,
+ font_weight=FontWeightEnum.BOLD,
+ zorder=ZOrderEnum.LAYER_3 + 2,
+ offset_x="auto",
+ offset_y="auto",
),
)
"""Styling for stars *(see [`ObjectStyle`][starplot.styles.ObjectStyle])*"""
bayer_labels: LabelStyle = LabelStyle(
- font_size=7,
+ font_size=21,
font_weight=FontWeightEnum.LIGHT,
+ font_name="GFS Didot",
zorder=ZOrderEnum.LAYER_4,
anchor_point=AnchorPointEnum.TOP_LEFT,
+ offset_x="auto",
+ offset_y="auto",
)
"""Styling for Bayer labels of stars"""
+ flamsteed_labels: LabelStyle = LabelStyle(
+ font_size=13,
+ font_weight=FontWeightEnum.NORMAL,
+ zorder=ZOrderEnum.LAYER_4,
+ anchor_point=AnchorPointEnum.BOTTOM_LEFT,
+ offset_x="auto",
+ offset_y="auto",
+ )
+ """Styling for Flamsteed number labels of stars"""
+
planets: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
symbol=MarkerSymbolEnum.CIRCLE,
- size=4,
+ size=50,
fill=FillStyleEnum.LEFT,
),
label=LabelStyle(
- font_size=8,
+ font_size=28,
font_weight=FontWeightEnum.BOLD,
+ offset_x="auto",
+ offset_y="auto",
),
)
"""Styling for planets"""
@@ -651,15 +735,17 @@ class PlotStyle(BaseStyle):
moon: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
symbol=MarkerSymbolEnum.CIRCLE,
- size=14,
+ size=50,
fill=FillStyleEnum.FULL,
color="#c8c8c8",
alpha=1,
zorder=ZOrderEnum.LAYER_4,
),
label=LabelStyle(
- font_size=8,
+ font_size=28,
font_weight=FontWeightEnum.BOLD,
+ offset_x="auto",
+ offset_y="auto",
),
)
"""Styling for the moon"""
@@ -667,13 +753,13 @@ class PlotStyle(BaseStyle):
sun: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
symbol=MarkerSymbolEnum.SUN,
- size=14,
+ size=80,
fill=FillStyleEnum.FULL,
color="#000",
zorder=ZOrderEnum.LAYER_4 - 100,
),
label=LabelStyle(
- font_size=8,
+ font_size=28,
font_weight=FontWeightEnum.BOLD,
),
)
@@ -683,14 +769,17 @@ class PlotStyle(BaseStyle):
dso_open_cluster: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
symbol=MarkerSymbolEnum.CIRCLE,
- size=7,
fill=FillStyleEnum.FULL,
+ line_style=(0, (1, 2)),
+ edge_width=1.3,
+ zorder=ZOrderEnum.LAYER_3 - 1,
),
label=LabelStyle(
- font_size=7,
- font_weight=FontWeightEnum.LIGHT,
- offset_x=10,
- offset_y=-10,
+ # font_weight=FontWeightEnum.LIGHT,
+ # offset_x=7,
+ # offset_y=-6,
+ offset_x="auto",
+ offset_y="auto",
),
)
"""Styling for open star clusters"""
@@ -698,14 +787,15 @@ class PlotStyle(BaseStyle):
dso_association_stars: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
symbol=MarkerSymbolEnum.CIRCLE,
- size=7,
fill=FillStyleEnum.FULL,
+ line_style=(0, (1, 2)),
+ edge_width=1.3,
+ zorder=ZOrderEnum.LAYER_3 - 1,
),
label=LabelStyle(
- font_size=7,
font_weight=FontWeightEnum.LIGHT,
- offset_x=10,
- offset_y=-10,
+ offset_x=7,
+ offset_y=-6,
),
)
"""Styling for associations of stars"""
@@ -713,113 +803,132 @@ class PlotStyle(BaseStyle):
dso_globular_cluster: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
symbol=MarkerSymbolEnum.CIRCLE_CROSS,
- size=7,
fill=FillStyleEnum.FULL,
color="#555",
alpha=0.8,
+ edge_width=1.2,
+ zorder=ZOrderEnum.LAYER_3 - 1,
),
- label=LabelStyle(font_size=7, offset_x=10, offset_y=-10),
+ label=LabelStyle(offset_x=7, offset_y=-6),
)
"""Styling for globular star clusters"""
dso_galaxy: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
- symbol=MarkerSymbolEnum.ELLIPSE, size=7, fill=FillStyleEnum.FULL
+ symbol=MarkerSymbolEnum.ELLIPSE,
+ fill=FillStyleEnum.FULL,
+ zorder=ZOrderEnum.LAYER_3 - 1,
),
- label=LabelStyle(font_size=7, offset_x=10, offset_y=-10),
+ label=LabelStyle(offset_x=1, offset_y=-1),
)
"""Styling for galaxies"""
dso_nebula: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
- symbol=MarkerSymbolEnum.SQUARE, size=7, fill=FillStyleEnum.FULL
+ symbol=MarkerSymbolEnum.SQUARE,
+ fill=FillStyleEnum.FULL,
+ zorder=ZOrderEnum.LAYER_3 - 1,
),
- label=LabelStyle(font_size=7, offset_x=10, offset_y=-10),
+ label=LabelStyle(offset_x=1, offset_y=-1),
)
"""Styling for nebulas"""
+ dso_planetary_nebula: ObjectStyle = ObjectStyle(
+ marker=MarkerStyle(
+ symbol=MarkerSymbolEnum.CIRCLE_CROSSHAIR,
+ fill=FillStyleEnum.FULL,
+ edge_width=1.6,
+ size=26,
+ zorder=ZOrderEnum.LAYER_3 - 1,
+ ),
+ label=LabelStyle(offset_x=1, offset_y=-1),
+ )
+ """Styling for planetary nebulas"""
+
dso_double_star: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
- symbol=MarkerSymbolEnum.CIRCLE, size=7, fill=FillStyleEnum.TOP
+ symbol=MarkerSymbolEnum.CIRCLE_LINE,
+ fill=FillStyleEnum.TOP,
+ zorder=ZOrderEnum.LAYER_3 - 1,
),
- label=LabelStyle(font_size=7),
+ label=LabelStyle(offset_x=1, offset_y=-1),
)
"""Styling for double stars"""
dso_dark_nebula: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
symbol=MarkerSymbolEnum.SQUARE,
- size=7,
fill=FillStyleEnum.TOP,
color="#000",
+ zorder=ZOrderEnum.LAYER_3 - 1,
),
- label=LabelStyle(font_size=7),
+ label=LabelStyle(),
)
"""Styling for dark nebulas"""
dso_hii_ionized_region: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
symbol=MarkerSymbolEnum.SQUARE,
- size=7,
fill=FillStyleEnum.TOP,
color="#000",
+ zorder=ZOrderEnum.LAYER_3 - 1,
),
- label=LabelStyle(font_size=7),
+ label=LabelStyle(),
)
"""Styling for HII Ionized regions"""
dso_supernova_remnant: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
symbol=MarkerSymbolEnum.SQUARE,
- size=7,
fill=FillStyleEnum.TOP,
color="#000",
+ zorder=ZOrderEnum.LAYER_3 - 1,
),
- label=LabelStyle(font_size=7),
+ label=LabelStyle(),
)
"""Styling for supernova remnants"""
dso_nova_star: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
symbol=MarkerSymbolEnum.SQUARE,
- size=7,
fill=FillStyleEnum.TOP,
color="#000",
+ zorder=ZOrderEnum.LAYER_3 - 1,
),
- label=LabelStyle(font_size=7),
+ label=LabelStyle(),
)
"""Styling for nova stars"""
dso_nonexistant: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
symbol=MarkerSymbolEnum.SQUARE,
- size=7,
fill=FillStyleEnum.TOP,
color="#000",
+ zorder=ZOrderEnum.LAYER_3 - 1,
),
- label=LabelStyle(font_size=7),
+ label=LabelStyle(),
)
"""Styling for 'nonexistent' (as designated by OpenNGC) deep sky objects"""
dso_unknown: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
symbol=MarkerSymbolEnum.SQUARE,
- size=7,
fill=FillStyleEnum.TOP,
color="#000",
+ zorder=ZOrderEnum.LAYER_3 - 1,
),
- label=LabelStyle(font_size=7),
+ label=LabelStyle(),
)
"""Styling for 'unknown' (as designated by OpenNGC) types of deep sky objects"""
dso_duplicate: ObjectStyle = ObjectStyle(
marker=MarkerStyle(
symbol=MarkerSymbolEnum.SQUARE,
- size=7,
fill=FillStyleEnum.TOP,
color="#000",
+ zorder=ZOrderEnum.LAYER_3 - 1,
),
- label=LabelStyle(font_size=7),
+ label=LabelStyle(),
)
"""Styling for 'duplicate record' (as designated by OpenNGC) types of deep sky objects"""
@@ -827,8 +936,8 @@ class PlotStyle(BaseStyle):
constellation: PathStyle = PathStyle(
line=LineStyle(color="#c8c8c8"),
label=LabelStyle(
- font_size=7,
- font_weight=FontWeightEnum.LIGHT,
+ font_size=21,
+ font_weight=FontWeightEnum.NORMAL,
zorder=ZOrderEnum.LAYER_3,
anchor_point=AnchorPointEnum.TOP_RIGHT,
),
@@ -837,9 +946,9 @@ class PlotStyle(BaseStyle):
constellation_borders: LineStyle = LineStyle(
color="#000",
- width=2,
+ width=1.5,
style=LineStyleEnum.DASHED,
- alpha=0.2,
+ alpha=0.4,
zorder=ZOrderEnum.LAYER_3,
)
"""Styling for constellation borders (only applies to map plots)"""
@@ -849,7 +958,7 @@ class PlotStyle(BaseStyle):
fill_color="#d9d9d9",
alpha=0.36,
edge_width=0,
- zorder=ZOrderEnum.LAYER_2,
+ zorder=ZOrderEnum.LAYER_1,
)
"""Styling for the Milky Way (only applies to map plots)"""
@@ -867,9 +976,8 @@ class PlotStyle(BaseStyle):
zorder=ZOrderEnum.LAYER_2,
),
label=LabelStyle(
- font_size=9,
+ font_size=18,
font_color="#000",
- font_weight=FontWeightEnum.LIGHT,
font_alpha=1,
anchor_point=AnchorPointEnum.BOTTOM_CENTER,
),
@@ -880,16 +988,15 @@ class PlotStyle(BaseStyle):
ecliptic: PathStyle = PathStyle(
line=LineStyle(
color="#777",
- width=2,
+ width=3,
style=LineStyleEnum.DOTTED,
dash_capstyle=DashCapStyleEnum.ROUND,
- alpha=0.8,
+ alpha=1,
zorder=ZOrderEnum.LAYER_3,
),
label=LabelStyle(
- font_size=6,
+ font_size=22,
font_color="#777",
- font_weight=FontWeightEnum.LIGHT,
font_alpha=1,
zorder=ZOrderEnum.LAYER_3,
),
@@ -900,13 +1007,13 @@ class PlotStyle(BaseStyle):
celestial_equator: PathStyle = PathStyle(
line=LineStyle(
color="#999",
- width=2,
+ width=3,
style=LineStyleEnum.DASHED_DOTS,
alpha=0.65,
zorder=ZOrderEnum.LAYER_3,
),
label=LabelStyle(
- font_size=6,
+ font_size=22,
font_color="#999",
font_weight=FontWeightEnum.LIGHT,
font_alpha=0.65,
@@ -918,7 +1025,7 @@ class PlotStyle(BaseStyle):
horizon: PathStyle = PathStyle(
line=LineStyle(
color="#fff",
- width=64,
+ width=80,
edge_width=4,
edge_color="#000",
style=LineStyleEnum.SOLID,
@@ -929,7 +1036,7 @@ class PlotStyle(BaseStyle):
label=LabelStyle(
anchor_point=AnchorPointEnum.CENTER,
font_color="#000",
- font_size=23,
+ font_size=64,
font_weight=FontWeightEnum.BOLD,
zorder=ZOrderEnum.LAYER_5,
),
@@ -961,7 +1068,7 @@ def get_dso_style(self, dso_type: DsoType):
DsoType.GROUP_OF_GALAXIES: self.dso_galaxy,
# Nebulas ----------
DsoType.NEBULA: self.dso_nebula,
- DsoType.PLANETARY_NEBULA: self.dso_nebula,
+ DsoType.PLANETARY_NEBULA: self.dso_planetary_nebula,
DsoType.EMISSION_NEBULA: self.dso_nebula,
DsoType.STAR_CLUSTER_NEBULA: self.dso_nebula,
DsoType.REFLECTION_NEBULA: self.dso_nebula,
diff --git a/src/starplot/styles/ext/antique.yml b/src/starplot/styles/ext/antique.yml
index c395c92f..3f4351a2 100644
--- a/src/starplot/styles/ext/antique.yml
+++ b/src/starplot/styles/ext/antique.yml
@@ -30,9 +30,16 @@ zenith:
title:
font_color: hsl(60, 20%, 93%)
+
bayer_labels:
- font_alpha: 0.74
- font_color: '#000'
+ font_alpha: 0.9
+ font_color: hsl(60, 3%, 17%)
+
+flamsteed_labels:
+ font_alpha: 0.9
+ font_color: hsl(60, 3%, 17%)
+
+
celestial_equator:
label:
font_color: hsl(188, 35%, 56%)
@@ -41,30 +48,28 @@ celestial_equator:
alpha: 0.62
constellation:
label:
- font_size: 10
font_weight: light
font_color: hsl(60, 3%, 52%)
- font_alpha: 0.36
+ font_alpha: 0.46
font_name: "serif"
line:
- alpha: 0.2
+ alpha: 0.3
color: hsl(48, 80%, 14%)
- width: 1.46
constellation_borders:
color: hsl(37, 33%, 40%)
- alpha: 0.44
- width: 1.2
+ alpha: 0.8
zorder: -500
ecliptic:
label:
- font_color: hsl(26, 63%, 52%)
+ font_color: hsl(26, 63%, 50%)
line:
- color: hsl(26, 93%, 82%)
- alpha: 0.6
+ color: hsl(26, 90%, 62%)
+ alpha: 1
+
milky_way:
- alpha: 0.14
- fill_color: hsl(48, 40%, 70%)
- edge_width: 0
+ alpha: 0.2
+ fill_color: hsl(48, 40%, 75%)
+
gridlines:
label:
font_alpha: 0.8
@@ -78,20 +83,18 @@ gridlines:
width: 1
star:
marker:
- color: hsl(60, 12%, 32%)
+ color: hsl(60, 12%, 24%)
edge_color: hsl(48, 80%, 96%)
label:
- font_color: hsl(60, 3%, 42%)
+ font_color: hsl(60, 3%, 24%)
planets:
marker:
- size: 8
symbol: circle
alpha: 0.68
fill: full
color: hsl(26, 93%, 82%)
edge_color: hsl(26, 93%, 52%)
label:
- font_size: 7
font_color: hsl(60, 3%, 42%)
sun:
label:
@@ -109,47 +112,50 @@ moon:
# DSOs
dso_double_star:
marker:
- alpha: 0.6
+ alpha: 0.9
dso_galaxy:
marker:
- alpha: 0.42
+ alpha: 1
color: hsl(26, 93%, 82%)
edge_color: hsl(26, 93%, 32%)
label:
font_color: hsl(26, 93%, 32%)
dso_nebula:
marker:
- alpha: 0.52
- color: hsl(71, 58%, 76%)
- edge_color: hsl(71, 58%, 36%)
+ alpha: 1
+ color: hsl(71, 58%, 80%)
+ edge_color: hsl(71, 58%, 30%)
label:
- font_color: hsl(71, 58%, 36%)
-dso_open_cluster:
+ font_color: hsl(71, 58%, 30%)
+dso_planetary_nebula:
marker:
- alpha: 0.52
- color: hsl(49, 92%, 77%)
- edge_color: hsl(49, 92%, 27%)
+ alpha: 1
+ color: hsl(71, 58%, 80%)
+ edge_color: hsl(71, 58%, 30%)
label:
- font_color: hsl(49, 92%, 27%)
-dso_association_stars:
+ font_color: hsl(71, 58%, 30%)
+
+dso_open_cluster: &DSO-OC
marker:
- alpha: 0.52
- color: hsl(49, 92%, 77%)
- edge_color: hsl(49, 92%, 27%)
+ alpha: 1
+ color: hsl(49, 96%, 76%)
+ edge_color: hsl(49, 92%, 17%)
label:
- font_color: hsl(49, 92%, 27%)
+ font_color: hsl(49, 92%, 17%)
+dso_association_stars: *DSO-OC
+
dso_globular_cluster:
marker:
- alpha: 0.5
- color: hsl(60, 53%, 76%)
- edge_color: hsl(60, 3%, 32%)
+ alpha: 1
+ color: hsl(49, 96%, 76%)
+ edge_color: hsl(49, 92%, 17%)
label:
- font_color: hsl(60, 3%, 32%)
+ font_color: hsl(49, 92%, 17%)
# other DSOs
dso_unknown: &DSO
marker:
- alpha: 0.52
+ alpha: 0.8
color: hsl(71, 58%, 76%)
edge_color: hsl(71, 58%, 36%)
label:
diff --git a/src/starplot/styles/ext/blue_dark.yml b/src/starplot/styles/ext/blue_dark.yml
index 60c762ea..6a2a59cc 100644
--- a/src/starplot/styles/ext/blue_dark.yml
+++ b/src/starplot/styles/ext/blue_dark.yml
@@ -30,12 +30,14 @@ celestial_equator:
font_color: hsl(209, 30%, 80%)
line:
color: hsl(209, 30%, 80%)
+
ecliptic:
label:
- font_color: hsl(209, 30%, 80%)
+ font_color: '#e33b3b'
line:
- width: 1.6
- color: hsl(209, 30%, 80%)
+ color: '#e33b3b'
+ alpha: 1
+
constellation:
label:
font_alpha: 0.37
@@ -56,48 +58,43 @@ dso_double_star:
alpha: 0.8
color: '#88c0d0'
edge_color: '#88c0d0'
+
+
dso_galaxy:
- label:
- font_color: hsl(209, 23%, 72%)
marker:
- alpha: 0.16
- color: hsl(209, 20%, 75%)
- edge_color: hsl(209, 20%, 90%)
- zorder: -600
-dso_nebula:
+ alpha: 1
+ color: hsl(330, 70%, 66%)
+ edge_color: hsl(330, 54%, 30%)
label:
- font_color: hsl(209, 23%, 72%)
+ font_color: hsl(330, 34%, 73%)
+
+dso_nebula: &DSO-NEB
marker:
- alpha: 0.46
- color: hsl(209, 50%, 78%)
- edge_color: hsl(209, 50%, 20%)
- zorder: -500
-dso_open_cluster:
+ alpha: 1
+ # color: hsl(98, 63%, 66%)
+ color: hsl(110, 52%, 62%)
+ edge_color: hsl(91, 53%, 10%)
label:
- font_color: hsl(209, 23%, 72%)
+ font_color: hsl(91, 63%, 89%)
+dso_planetary_nebula: *DSO-NEB
+
+dso_open_cluster: &DSO-OC
marker:
- size: 10
- alpha: 0.52
- fill: none
- edge_color: hsl(209, 50%, 92%)
- zorder: -500
-dso_association_stars:
+ alpha: 1
+ color: hsl(58, 64%, 50%)
+ edge_color: "#000"
label:
- font_color: hsl(209, 23%, 72%)
- marker:
- alpha: 0.52
- fill: none
- edge_color: hsl(209, 50%, 92%)
- zorder: -500
+ font_color: hsl(58, 98%, 20%)
+dso_association_stars: *DSO-OC
+
dso_globular_cluster:
+ marker:
+ alpha: 1
+ color: hsl(58, 64%, 50%)
+ edge_color: "#000"
label:
font_color: hsl(209, 23%, 72%)
- marker:
- alpha: 0.68
- size: 10
- color: hsl(209, 50%, 92%)
- edge_color: null
- zorder: 100
+
# other DSOs
dso_unknown: &DSO
@@ -161,7 +158,6 @@ sun:
marker:
color: hsl(209, 50%, 94%)
edge_color: hsl(209, 50%, 94%)
- size: 25
legend:
background_color: hsl(209, 50%, 26%)
diff --git a/src/starplot/styles/ext/blue_light.yml b/src/starplot/styles/ext/blue_light.yml
index 1df3f667..8d823ef8 100644
--- a/src/starplot/styles/ext/blue_light.yml
+++ b/src/starplot/styles/ext/blue_light.yml
@@ -31,48 +31,63 @@ celestial_equator:
color: '#2d5ec2'
constellation:
label:
- font_color: '#c5c5c5'
+ font_alpha: 0.27
+ font_color: '#000'
line:
- alpha: 0.3
+ alpha: 0.52
color: '#6ba832'
width: 3
dso_double_star:
marker:
- alpha: 0.6
+ alpha: 1
+
dso_galaxy:
marker:
- alpha: 0.5
- color: hsl(18, 68%, 75%)
- edge_color: hsl(18, 68%, 40%)
+ alpha: 1
+ color: hsl(330, 80%, 85%)
+ edge_color: hsl(330, 34%, 43%)
+ # edge_color: hsl(18, 68%, 40%)
label:
- font_color: hsl(18, 68%, 40%)
-dso_nebula:
+ font_color: hsl(330, 34%, 43%)
+ # font_color: hsl(18, 68%, 40%)
+
+dso_nebula: &DSO-NEB
marker:
- alpha: 0.5
- color: hsl(91, 53%, 75%)
- edge_color: hsl(91, 53%, 40%)
+ alpha: 1
+ color: hsl(98, 68%, 82%)
+ edge_color: hsl(91, 63%, 24%)
label:
- font_color: hsl(91, 53%, 40%)
-dso_open_cluster:
+ font_color: hsl(91, 63%, 29%)
+dso_planetary_nebula:
marker:
- alpha: 0.3
- color: '#fffb68'
- edge_color: '#989400'
+ alpha: 1
+ color: hsl(96, 76%, 82%)
+ edge_color: hsl(91, 63%, 24%)
label:
- font_color: '#989400'
-dso_association_stars:
+ font_color: hsl(91, 63%, 29%)
+
+dso_open_cluster: &DSO-OC
+ marker:
+ alpha: 1
+ color: hsl(58, 98%, 77%)
+ edge_color: "#000"
+ label:
+ font_color: hsl(58, 98%, 20%)
+dso_association_stars: *DSO-OC
+
+dso_globular_cluster:
marker:
- alpha: 0.3
- color: '#fffb68'
- edge_color: '#989400'
+ alpha: 1
+ color: hsl(58, 98%, 77%)
+ edge_color: "#000"
label:
font_color: '#989400'
# other DSOs
dso_unknown: &DSO
marker:
- alpha: 0.5
+ alpha: 0.76
color: hsl(91, 53%, 75%)
edge_color: hsl(91, 53%, 40%)
label:
@@ -89,10 +104,13 @@ ecliptic:
label:
font_color: '#e33b3b'
line:
- color: '#e33b3b'
+ # color: hsl(360, 100%, 50%)
+ color: hsl(359, 98%, 49%)
+ alpha: 1
+ style: [0, [0.14, 2]]
milky_way:
- alpha: 0.16
- fill_color: '#94c5e3'
+ alpha: 0.2
+ fill_color: hsl(203, 70%, 83%)
edge_width: 0
planets:
marker:
diff --git a/src/starplot/styles/ext/blue_medium.yml b/src/starplot/styles/ext/blue_medium.yml
index b4126607..cbb087c0 100644
--- a/src/starplot/styles/ext/blue_medium.yml
+++ b/src/starplot/styles/ext/blue_medium.yml
@@ -1,4 +1,6 @@
-background_color: '#f1f6ff'
+# blue_medium
+
+background_color: hsl(218, 85%, 97%)
figure_background_color: '#fff'
text_border_color: '#f1f6ff'
@@ -13,28 +15,30 @@ star:
marker:
edge_color: '#f1f6ff'
bayer_labels:
- font_alpha: 0.8
+ font_alpha: 1
font_color: '#000'
-celestial_equator:
- label:
- font_color: '#2d5ec2'
- line:
- color: '#2d5ec2'
- alpha: 0.6
+
constellation:
label:
- font_size: 7
- font_weight: light
+ font_alpha: 0.8
+ font_color: hsl(212, 20%, 10%)
line:
- alpha: 0.23
+ alpha: 0.48
color: '#6ba832'
- width: 3
+
+celestial_equator:
+ label:
+ font_color: '#2d5ec2'
+ line:
+ color: hsl(220, 62%, 47%)
+ alpha: 0.8
ecliptic:
label:
font_color: '#e33b3b'
line:
- color: '#e33b3b'
- alpha: 0.6
+ color: hsl(360, 85%, 56%)
+ alpha: 0.9
+
planets:
marker:
alpha: 0.4
@@ -46,15 +50,13 @@ milky_way:
edge_width: 0
gridlines:
label:
- font_alpha: 0.8
+ font_alpha: 1
font_color: '#2f4358'
- font_size: 8
font_weight: light
line:
alpha: 0.6
color: '#888'
style: solid
- width: 1
sun:
marker:
color: '#ffd22e'
@@ -74,50 +76,52 @@ zenith:
font_color: '#b979b7'
# DSOs
-dso_double_star:
- marker:
- alpha: 0.6
+
dso_galaxy:
marker:
- alpha: 0.45
- color: '#D99CBA'
- edge_color: '#b15d87'
+ alpha: 1
+ color: hsl(330, 80%, 85%)
+ edge_color: hsl(330, 34%, 33%)
label:
- font_color: '#b15d87'
-dso_nebula:
+ font_color: hsl(330, 34%, 33%)
+
+dso_nebula: &DSO-NEB
marker:
- alpha: 0.56
- color: hsl(91, 62%, 82%)
- edge_color: hsl(91, 53%, 40%)
+ alpha: 1
+ color: hsl(98, 68%, 82%)
+ edge_color: hsl(91, 63%, 24%)
label:
- font_color: hsl(91, 53%, 40%)
-dso_open_cluster:
+ font_color: hsl(91, 63%, 29%)
+dso_planetary_nebula:
marker:
- alpha: 0.4
- color: '#fffb68'
- edge_color: '#989400'
+ alpha: 1
+ color: hsl(96, 76%, 82%)
+ edge_color: hsl(91, 63%, 24%)
label:
- font_color: '#989400'
-dso_association_stars:
+ font_color: hsl(91, 63%, 29%)
+
+dso_open_cluster: &DSO-OC
marker:
- alpha: 0.4
- color: '#fffb68'
- edge_color: '#989400'
+ alpha: 1
+ color: hsl(58, 98%, 77%)
+ edge_color: "#000"
label:
- font_color: '#989400'
+ font_color: hsl(58, 98%, 20%)
+dso_association_stars: *DSO-OC
+
dso_globular_cluster:
marker:
- alpha: 0.8
- color: '#c7c7c7'
- edge_color: '#444'
+ alpha: 1
+ color: hsl(58, 98%, 77%)
+ edge_color: "#000"
label:
- font_color: '#444'
+ font_color: '#989400'
# other DSOs
dso_unknown: &DSO
marker:
- alpha: 0.56
+ alpha: 1
color: hsl(91, 62%, 82%)
edge_color: hsl(91, 53%, 40%)
label:
diff --git a/src/starplot/styles/ext/cb_wong.yml b/src/starplot/styles/ext/cb_wong.yml
index b80a362e..f53a25bc 100644
--- a/src/starplot/styles/ext/cb_wong.yml
+++ b/src/starplot/styles/ext/cb_wong.yml
@@ -58,6 +58,13 @@ dso_nebula:
alpha: 0.7
color: hsl(163, 99%, 31%)
edge_color: hsl(163, 99%, 21%)
+dso_planetary_nebula:
+ label:
+ font_color: hsl(163, 99%, 31%)
+ marker:
+ alpha: 0.7
+ color: hsl(163, 99%, 31%)
+ edge_color: hsl(163, 99%, 21%)
dso_open_cluster:
label:
font_color: hsl(56, 85%, 30%)
diff --git a/src/starplot/styles/ext/grayscale.yml b/src/starplot/styles/ext/grayscale.yml
index 29707907..59b309f4 100644
--- a/src/starplot/styles/ext/grayscale.yml
+++ b/src/starplot/styles/ext/grayscale.yml
@@ -24,20 +24,26 @@ dso_double_star:
color: '#000'
dso_galaxy:
marker:
- color: '#000'
- alpha: 0.6
- symbol: diamond
- fill: top
+ color: null
+ fill: none
+ alpha: 1
+ symbol: ellipse
dso_nebula:
marker:
- color: '#000'
- alpha: 0.28
+ color: '#c8c8c8'
+ fill: full
+ alpha: 1
+dso_planetary_nebula:
+ marker:
+ color: null
+ fill: none
+ alpha: 1
dso_open_cluster:
marker:
color: null
edge_color: '#000'
fill: none
- alpha: 0.8
+ alpha: 1
# other DSOs
diff --git a/src/starplot/styles/ext/grayscale_dark.yml b/src/starplot/styles/ext/grayscale_dark.yml
index d9d4964b..1e951267 100644
--- a/src/starplot/styles/ext/grayscale_dark.yml
+++ b/src/starplot/styles/ext/grayscale_dark.yml
@@ -26,9 +26,9 @@ info_text:
font_color: hsl(136, 0%, 0%)
star:
label:
- font_color: hsl(136, 0%, 90%)
+ font_color: hsl(136, 0%, 78%)
marker:
- color: hsl(136, 0%, 97%)
+ color: hsl(136, 0%, 92%)
edge_color: hsl(136, 0%, 10%)
sun:
label:
@@ -37,7 +37,9 @@ sun:
color: hsl(136, 0%, 97%)
edge_color: hsl(136, 0%, 97%)
bayer_labels:
- font_color: hsl(136, 0%, 77%)
+ font_color: hsl(136, 0%, 80%)
+flamsteed_labels:
+ font_color: hsl(136, 0%, 80%)
celestial_equator:
label:
font_color: '#999'
@@ -47,7 +49,7 @@ constellation:
label:
font_color: hsl(136, 0%, 77%)
line:
- color: hsl(136, 0%, 42%)
+ color: hsl(136, 0%, 30%)
constellation_borders:
color: hsl(136, 0%, 42%)
alpha: 0.5
@@ -70,6 +72,12 @@ dso_nebula:
marker:
color: 'hsl(136, 0%, 97%)'
alpha: 0.28
+dso_planetary_nebula:
+ label:
+ font_color: hsl(136, 0%, 97%)
+ marker:
+ color: 'hsl(136, 0%, 97%)'
+ alpha: 0.28
dso_open_cluster:
label:
font_color: hsl(136, 0%, 97%)
diff --git a/src/starplot/styles/ext/map.yml b/src/starplot/styles/ext/map.yml
index 6731126c..11730cbf 100644
--- a/src/starplot/styles/ext/map.yml
+++ b/src/starplot/styles/ext/map.yml
@@ -1,9 +1,9 @@
constellation:
label:
- font_alpha: 0.82
- font_size: 10
+ font_size: 38
+ font_weight: heavy
line:
- width: 2.36
+ width: 3.4
star:
label:
- font_size: 11
+ font_size: 22
diff --git a/src/starplot/styles/ext/nord.yml b/src/starplot/styles/ext/nord.yml
index 8e9fac2a..7e703df9 100644
--- a/src/starplot/styles/ext/nord.yml
+++ b/src/starplot/styles/ext/nord.yml
@@ -26,55 +26,59 @@ celestial_equator:
font_color: '#77A67F'
line:
color: '#77A67F'
+
constellation:
label:
- font_alpha: 0.6
+ font_alpha: 0.7
font_color: rgb(230, 204, 147)
- font_size: 7
- font_weight: light
line:
alpha: 0.36
color: rgb(230, 204, 147)
- width: 2
+constellation_borders:
+ color: hsl(220, 16%, 12%)
+ alpha: 0.8
dso_double_star:
marker:
- alpha: 0.8
+ alpha: 0.9
color: '#88c0d0'
edge_color: '#88c0d0'
label:
font_color: '#88c0d0'
dso_galaxy:
marker:
- alpha: 0.6
- color: '#D99CBA'
- edge_color: '#bd5187'
+ alpha: 0.7
+ color: hsl(330, 45%, 74%)
+ edge_color: hsl(330, 45%, 63%)
label:
- font_color: '#bd5187'
+ font_color: hsl(330, 45%, 63%)
dso_nebula:
marker:
- alpha: 0.32
- color: '#9CD9BB'
- edge_color: '#52896e'
+ alpha: 0.7
+ color: hsl(172, 15%, 56%)
+ edge_color: hsl(172, 15%, 50%)
label:
- font_color: '#52896e'
-dso_open_cluster:
+ font_color: hsl(172, 15%, 56%)
+dso_planetary_nebula:
marker:
- alpha: 0.32
- color: '#d8d99c'
- edge_color: '#9d9f3c'
+ alpha: 0.7
+ color: hsl(172, 15%, 56%)
+ edge_color: hsl(172, 15%, 50%)
label:
- font_color: '#9d9f3c'
-dso_association_stars:
+ font_color: hsl(172, 15%, 56%)
+
+dso_open_cluster: &DSO-OC
marker:
- alpha: 0.32
- color: '#d8d99c'
- edge_color: '#9d9f3c'
+ alpha: 0.7
+ color: hsl(61, 45%, 73%)
+ edge_color: hsl(61, 45%, 5%)
label:
font_color: '#9d9f3c'
+dso_association_stars: *DSO-OC
+
dso_globular_cluster:
marker:
- alpha: 0.5
+ alpha: 0.7
color: '#c7c7c7'
edge_color: '#444'
label:
@@ -84,7 +88,7 @@ dso_globular_cluster:
# other DSOs
dso_unknown: &DSO
marker:
- alpha: 0.32
+ alpha: 0.7
color: '#9CD9BB'
edge_color: '#52896e'
label:
@@ -106,17 +110,16 @@ gridlines:
label:
font_alpha: 0.8
font_color: '#c2d2f3'
- font_size: 8
font_weight: light
line:
alpha: 0.8
color: '#888'
style: solid
- width: 1
+
milky_way:
- alpha: 0.14
+ alpha: 0.16
fill_color: '#95a3bf'
- edge_width: 0
+
planets:
label:
font_color: '#D99CCF'
@@ -128,7 +131,6 @@ planets:
star:
label:
font_color: '#88c0d0'
- font_size: 9
font_weight: bold
marker:
color: '#88c0d0'
@@ -136,7 +138,6 @@ star:
sun:
label:
font_color: '#88c0d0'
- font_size: 9
font_weight: bold
marker:
color: '#88c0d0'
@@ -144,13 +145,12 @@ sun:
moon:
label:
font_color: '#88c0d0'
- font_size: 9
font_weight: bold
marker:
color: '#88c0d0'
bayer_labels:
- font_alpha: 0.8
+ font_alpha: 0.9
font_color: '#85c9de'
legend:
diff --git a/src/starplot/styles/ext/optic.yml b/src/starplot/styles/ext/optic.yml
index 55641595..b56b2811 100644
--- a/src/starplot/styles/ext/optic.yml
+++ b/src/starplot/styles/ext/optic.yml
@@ -1,18 +1,20 @@
star:
label:
- font_size: 15
+ font_size: 40
marker:
size: 30
bayer_labels:
- font_size: 15
+ font_size: 40
+flamsteed_labels:
+ font_size: 40
planets:
marker:
- size: 11
+ size: 30
legend:
location: "lower center"
ecliptic:
label:
- font_size: 15
+ font_size: 27
celestial_equator:
label:
- font_size: 15
+ font_size: 27
diff --git a/src/starplot/styles/fonts-library/gfs-didot/DESCRIPTION.en_us.html b/src/starplot/styles/fonts-library/gfs-didot/DESCRIPTION.en_us.html
new file mode 100644
index 00000000..c6716b67
--- /dev/null
+++ b/src/starplot/styles/fonts-library/gfs-didot/DESCRIPTION.en_us.html
@@ -0,0 +1,9 @@
+
+Under the influence of the neoclassical ideals of the late 18th century, the famous French typecutter Firmin Didot in Paris designed a new Greek typeface (1805) which was immediately used in the publishing programme of Adamantios Korais, the prominent intellectual figure of the Greek diaspora and leading scholar of the Greek Enligntment.
+The typeface eventually arrived in Greece, with the field press which came with Didotβs grandson Ambroise Firmin Didot, during the Greek Revolution in 1821.
+
+
+Since then the typeface enjoyed an unrivaled success as the type of choice for almost every kind of publication, until the last decades of the 20th century.
+Didotβs type was the base for a new font, GFS Didot (1994), which was designed by Takis Katsoulidis, and digitised by George Matthiopoulos, of the Greek Font Society.
+The typeface is accompanied by a matching Latin design, inspired by Hermann Zapfβs Palatino.
+
diff --git a/src/starplot/styles/fonts-library/gfs-didot/GFSDidot-Regular.ttf b/src/starplot/styles/fonts-library/gfs-didot/GFSDidot-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..4e771c995cc09c2a994c77b93568a2d1f4c1ded7
GIT binary patch
literal 191168
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