starplot 0.15.8__py2.py3-none-any.whl → 0.16.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- starplot/__init__.py +7 -2
- starplot/base.py +57 -60
- starplot/cli.py +3 -3
- starplot/config.py +56 -0
- starplot/data/__init__.py +5 -5
- starplot/data/bigsky.py +3 -3
- starplot/data/db.py +2 -2
- starplot/data/library/sky.db +0 -0
- starplot/geometry.py +48 -0
- starplot/horizon.py +194 -90
- starplot/map.py +71 -168
- starplot/mixins.py +0 -55
- starplot/models/dso.py +10 -2
- starplot/observer.py +71 -0
- starplot/optic.py +61 -26
- starplot/plotters/__init__.py +2 -0
- starplot/plotters/constellations.py +4 -6
- starplot/plotters/dsos.py +3 -2
- starplot/plotters/gradients.py +153 -0
- starplot/plotters/legend.py +247 -0
- starplot/plotters/milkyway.py +8 -5
- starplot/plotters/stars.py +5 -3
- starplot/projections.py +155 -55
- starplot/styles/base.py +98 -22
- starplot/styles/ext/antique.yml +0 -1
- starplot/styles/ext/blue_dark.yml +0 -1
- starplot/styles/ext/blue_gold.yml +60 -52
- starplot/styles/ext/blue_light.yml +0 -1
- starplot/styles/ext/blue_medium.yml +7 -7
- starplot/styles/ext/blue_night.yml +178 -0
- starplot/styles/ext/cb_wong.yml +0 -1
- starplot/styles/ext/gradient_presets.yml +158 -0
- starplot/styles/ext/grayscale.yml +0 -1
- starplot/styles/ext/grayscale_dark.yml +0 -1
- starplot/styles/ext/nord.yml +0 -1
- starplot/styles/extensions.py +90 -0
- starplot/zenith.py +174 -0
- {starplot-0.15.8.dist-info → starplot-0.16.1.dist-info}/METADATA +18 -11
- {starplot-0.15.8.dist-info → starplot-0.16.1.dist-info}/RECORD +42 -36
- starplot/settings.py +0 -26
- {starplot-0.15.8.dist-info → starplot-0.16.1.dist-info}/WHEEL +0 -0
- {starplot-0.15.8.dist-info → starplot-0.16.1.dist-info}/entry_points.txt +0 -0
- {starplot-0.15.8.dist-info → starplot-0.16.1.dist-info}/licenses/LICENSE +0 -0
starplot/__init__.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"""Star charts and maps of the sky"""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.
|
|
3
|
+
__version__ = "0.16.1"
|
|
4
4
|
|
|
5
5
|
from .base import BasePlot # noqa: F401
|
|
6
|
-
from .map import MapPlot
|
|
6
|
+
from .map import MapPlot # noqa: F401
|
|
7
7
|
from .horizon import HorizonPlot # noqa: F401
|
|
8
8
|
from .optic import OpticPlot # noqa: F401
|
|
9
|
+
from .zenith import ZenithPlot # noqa: F401
|
|
9
10
|
from .models import (
|
|
10
11
|
DSO, # noqa: F401
|
|
11
12
|
DsoType, # noqa: F401
|
|
@@ -17,4 +18,8 @@ from .models import (
|
|
|
17
18
|
ObjectList, # noqa: F401
|
|
18
19
|
)
|
|
19
20
|
from .styles import * # noqa: F401 F403
|
|
21
|
+
from .observer import Observer # noqa: F401
|
|
22
|
+
from .projections import * # noqa: F401 F403
|
|
23
|
+
from .config import settings # noqa: F401
|
|
24
|
+
|
|
20
25
|
from ibis import _ # noqa: F401 F403
|
starplot/base.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from datetime import datetime
|
|
3
2
|
from typing import Dict, Union, Optional
|
|
4
3
|
from random import randrange
|
|
5
4
|
import logging
|
|
@@ -8,27 +7,29 @@ import numpy as np
|
|
|
8
7
|
import rtree
|
|
9
8
|
from matplotlib import patches
|
|
10
9
|
from matplotlib import pyplot as plt, patheffects
|
|
10
|
+
from matplotlib.axes import Axes
|
|
11
|
+
from matplotlib.figure import Figure
|
|
11
12
|
from matplotlib.lines import Line2D
|
|
12
|
-
from pytz import timezone
|
|
13
13
|
from shapely import Polygon, Point
|
|
14
14
|
|
|
15
15
|
from starplot.coordinates import CoordinateSystem
|
|
16
16
|
from starplot import geod, models, warnings
|
|
17
|
+
from starplot.config import settings, SvgTextType
|
|
17
18
|
from starplot.data import load, ecliptic
|
|
18
19
|
from starplot.models.planet import PlanetName, PLANET_LABELS_DEFAULT
|
|
19
20
|
from starplot.models.moon import MoonPhase
|
|
21
|
+
from starplot.observer import Observer
|
|
20
22
|
from starplot.styles import (
|
|
21
23
|
AnchorPointEnum,
|
|
22
24
|
PlotStyle,
|
|
23
25
|
MarkerStyle,
|
|
24
26
|
ObjectStyle,
|
|
25
27
|
LabelStyle,
|
|
26
|
-
LegendLocationEnum,
|
|
27
|
-
LegendStyle,
|
|
28
28
|
LineStyle,
|
|
29
29
|
MarkerSymbolEnum,
|
|
30
30
|
PathStyle,
|
|
31
31
|
PolygonStyle,
|
|
32
|
+
GradientDirection,
|
|
32
33
|
fonts,
|
|
33
34
|
)
|
|
34
35
|
from starplot.styles.helpers import use_style
|
|
@@ -67,10 +68,28 @@ class BasePlot(ABC):
|
|
|
67
68
|
_background_clip_path = None
|
|
68
69
|
_clip_path_polygon: Polygon = None # clip path in display coordinates
|
|
69
70
|
_coordinate_system = CoordinateSystem.RA_DEC
|
|
71
|
+
_gradient_direction: GradientDirection = GradientDirection.LINEAR
|
|
72
|
+
|
|
73
|
+
ax: Axes
|
|
74
|
+
"""
|
|
75
|
+
The underlying [Matplotlib axes](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html#matplotlib.axes.Axes) that everything is plotted on.
|
|
76
|
+
|
|
77
|
+
**Important**: Most Starplot plotting functions also specify a transform based on the plot's projection when plotting things on the Matplotlib Axes instance, so use this property at your own risk!
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
fig: Figure
|
|
81
|
+
"""
|
|
82
|
+
The underlying [Matplotlib figure](https://matplotlib.org/stable/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure) that the axes is drawn on.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
style: PlotStyle
|
|
86
|
+
"""
|
|
87
|
+
The plot's style.
|
|
88
|
+
"""
|
|
70
89
|
|
|
71
90
|
def __init__(
|
|
72
91
|
self,
|
|
73
|
-
|
|
92
|
+
observer: Observer = Observer(),
|
|
74
93
|
ephemeris: str = "de421_2001.bsp",
|
|
75
94
|
style: PlotStyle = DEFAULT_STYLE,
|
|
76
95
|
resolution: int = 4096,
|
|
@@ -81,6 +100,11 @@ class BasePlot(ABC):
|
|
|
81
100
|
*args,
|
|
82
101
|
**kwargs,
|
|
83
102
|
):
|
|
103
|
+
if settings.svg_text_type == SvgTextType.PATH:
|
|
104
|
+
plt.rcParams["svg.fonttype"] = "path"
|
|
105
|
+
else:
|
|
106
|
+
plt.rcParams["svg.fonttype"] = "none"
|
|
107
|
+
|
|
84
108
|
px = 1 / DPI # plt.rcParams["figure.dpi"] # pixel in inches
|
|
85
109
|
self.pixels_per_point = DPI / 72
|
|
86
110
|
|
|
@@ -97,7 +121,7 @@ class BasePlot(ABC):
|
|
|
97
121
|
if suppress_warnings:
|
|
98
122
|
warnings.suppress()
|
|
99
123
|
|
|
100
|
-
self.
|
|
124
|
+
self.observer = observer
|
|
101
125
|
self._ephemeris_name = ephemeris
|
|
102
126
|
self.ephemeris = load(ephemeris)
|
|
103
127
|
self.earth = self.ephemeris["earth"]
|
|
@@ -121,7 +145,6 @@ class BasePlot(ABC):
|
|
|
121
145
|
linewidth=self.style.text_border_width * self.scale,
|
|
122
146
|
foreground=self.style.text_border_color.as_hex(),
|
|
123
147
|
)
|
|
124
|
-
self.timescale = load.timescale().from_datetime(self.dt)
|
|
125
148
|
|
|
126
149
|
self._objects = models.ObjectList()
|
|
127
150
|
self._labeled_stars = []
|
|
@@ -359,7 +382,10 @@ class BasePlot(ABC):
|
|
|
359
382
|
return None
|
|
360
383
|
|
|
361
384
|
x, y = self._prepare_coords(ra, dec)
|
|
362
|
-
|
|
385
|
+
|
|
386
|
+
if settings.svg_text_type == SvgTextType.PATH:
|
|
387
|
+
kwargs["path_effects"] = kwargs.get("path_effects", [self.text_border])
|
|
388
|
+
|
|
363
389
|
remove_on_constellation_collision = kwargs.pop(
|
|
364
390
|
"remove_on_constellation_collision", True
|
|
365
391
|
)
|
|
@@ -495,6 +521,14 @@ class BasePlot(ABC):
|
|
|
495
521
|
elif label is not None:
|
|
496
522
|
label.remove()
|
|
497
523
|
|
|
524
|
+
@property
|
|
525
|
+
def magnitude_range(self) -> tuple[float, float]:
|
|
526
|
+
"""
|
|
527
|
+
Range of magnitude for all plotted stars, as a tuple (min, max)
|
|
528
|
+
"""
|
|
529
|
+
mags = [s.magnitude for s in self.objects.stars]
|
|
530
|
+
return (min(mags), max(mags))
|
|
531
|
+
|
|
498
532
|
@use_style(LabelStyle)
|
|
499
533
|
def text(
|
|
500
534
|
self,
|
|
@@ -510,7 +544,7 @@ class BasePlot(ABC):
|
|
|
510
544
|
|
|
511
545
|
Args:
|
|
512
546
|
text: Text to plot
|
|
513
|
-
ra: Right ascension of text (0...
|
|
547
|
+
ra: Right ascension of text (0...360)
|
|
514
548
|
dec: Declination of text (-90...90)
|
|
515
549
|
style: Styling of the text
|
|
516
550
|
hide_on_collision: If True, then the text will not be plotted if it collides with another label
|
|
@@ -574,51 +608,6 @@ class BasePlot(ABC):
|
|
|
574
608
|
style_kwargs["pad"] = style.line_spacing
|
|
575
609
|
self.ax.set_title(text, **style_kwargs)
|
|
576
610
|
|
|
577
|
-
@use_style(LegendStyle, "legend")
|
|
578
|
-
def legend(self, style: LegendStyle = None):
|
|
579
|
-
"""
|
|
580
|
-
Plots the legend.
|
|
581
|
-
|
|
582
|
-
If the legend is already plotted, then it'll be removed first and then plotted again. So, it's safe to call this function multiple times if you need to 'refresh' the legend.
|
|
583
|
-
|
|
584
|
-
Args:
|
|
585
|
-
style: Styling of the legend. If None, then the plot's style (specified when creating the plot) will be used
|
|
586
|
-
"""
|
|
587
|
-
if self._legend is not None:
|
|
588
|
-
self._legend.remove()
|
|
589
|
-
|
|
590
|
-
if not self._legend_handles:
|
|
591
|
-
return
|
|
592
|
-
|
|
593
|
-
bbox_kwargs = {}
|
|
594
|
-
|
|
595
|
-
if style.location == LegendLocationEnum.OUTSIDE_BOTTOM:
|
|
596
|
-
style.location = "lower center"
|
|
597
|
-
offset_y = -0.08
|
|
598
|
-
if getattr(self, "_axis_labels", False):
|
|
599
|
-
offset_y -= 0.05
|
|
600
|
-
bbox_kwargs = dict(
|
|
601
|
-
bbox_to_anchor=(0.5, offset_y),
|
|
602
|
-
)
|
|
603
|
-
|
|
604
|
-
elif style.location == LegendLocationEnum.OUTSIDE_TOP:
|
|
605
|
-
style.location = "upper center"
|
|
606
|
-
offset_y = 1.08
|
|
607
|
-
if getattr(self, "_axis_labels", False):
|
|
608
|
-
offset_y += 0.05
|
|
609
|
-
bbox_kwargs = dict(
|
|
610
|
-
bbox_to_anchor=(0.5, offset_y),
|
|
611
|
-
)
|
|
612
|
-
|
|
613
|
-
self._legend = self.ax.legend(
|
|
614
|
-
handles=self._legend_handles.values(),
|
|
615
|
-
**style.matplot_kwargs(self.scale),
|
|
616
|
-
**bbox_kwargs,
|
|
617
|
-
).set_zorder(
|
|
618
|
-
# zorder is not a valid kwarg to legend(), so we have to set it afterwards
|
|
619
|
-
style.zorder
|
|
620
|
-
)
|
|
621
|
-
|
|
622
611
|
def close_fig(self) -> None:
|
|
623
612
|
"""Closes the underlying matplotlib figure."""
|
|
624
613
|
if self.fig:
|
|
@@ -674,7 +663,7 @@ class BasePlot(ABC):
|
|
|
674
663
|
# Plot marker
|
|
675
664
|
x, y = self._prepare_coords(ra, dec)
|
|
676
665
|
style_kwargs = style.marker.matplot_scatter_kwargs(self.scale)
|
|
677
|
-
self.ax.scatter(
|
|
666
|
+
result = self.ax.scatter(
|
|
678
667
|
x,
|
|
679
668
|
y,
|
|
680
669
|
**style_kwargs,
|
|
@@ -722,7 +711,7 @@ class BasePlot(ABC):
|
|
|
722
711
|
)
|
|
723
712
|
|
|
724
713
|
if legend_label is not None:
|
|
725
|
-
self.
|
|
714
|
+
self._legend_handles[legend_label] = result
|
|
726
715
|
|
|
727
716
|
@use_style(ObjectStyle, "planets")
|
|
728
717
|
def planets(
|
|
@@ -744,7 +733,9 @@ class BasePlot(ABC):
|
|
|
744
733
|
legend_label: How to label the planets in the legend. If `None`, then the planets will not be added to the legend
|
|
745
734
|
"""
|
|
746
735
|
labels = labels or {}
|
|
747
|
-
planets = models.Planet.all(
|
|
736
|
+
planets = models.Planet.all(
|
|
737
|
+
self.observer.dt, self.observer.lat, self.observer.lon, self._ephemeris_name
|
|
738
|
+
)
|
|
748
739
|
|
|
749
740
|
for p in planets:
|
|
750
741
|
label = labels.get(p.name)
|
|
@@ -798,7 +789,10 @@ class BasePlot(ABC):
|
|
|
798
789
|
legend_label: How the sun will be labeled in the legend
|
|
799
790
|
"""
|
|
800
791
|
s = models.Sun.get(
|
|
801
|
-
dt=self.dt,
|
|
792
|
+
dt=self.observer.dt,
|
|
793
|
+
lat=self.observer.lat,
|
|
794
|
+
lon=self.observer.lon,
|
|
795
|
+
ephemeris=self._ephemeris_name,
|
|
802
796
|
)
|
|
803
797
|
s.name = label or s.name
|
|
804
798
|
|
|
@@ -909,7 +903,7 @@ class BasePlot(ABC):
|
|
|
909
903
|
if geometry is not None:
|
|
910
904
|
points = list(zip(*geometry.exterior.coords.xy))
|
|
911
905
|
|
|
912
|
-
self._polygon(points, style, gid=kwargs.get("gid") or "polygon")
|
|
906
|
+
self._polygon(points, style, gid=kwargs.get("gid") or "polygon", **kwargs)
|
|
913
907
|
|
|
914
908
|
if legend_label is not None:
|
|
915
909
|
self._add_legend_handle_marker(
|
|
@@ -1073,7 +1067,10 @@ class BasePlot(ABC):
|
|
|
1073
1067
|
label: How the Moon will be labeled on the plot and legend
|
|
1074
1068
|
"""
|
|
1075
1069
|
m = models.Moon.get(
|
|
1076
|
-
dt=self.dt,
|
|
1070
|
+
dt=self.observer.dt,
|
|
1071
|
+
lat=self.observer.lat,
|
|
1072
|
+
lon=self.observer.lon,
|
|
1073
|
+
ephemeris=self._ephemeris_name,
|
|
1077
1074
|
)
|
|
1078
1075
|
m.name = label or m.name
|
|
1079
1076
|
|
starplot/cli.py
CHANGED
|
@@ -6,7 +6,7 @@ COMMANDS = ["setup"]
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def setup(options):
|
|
9
|
-
from starplot import settings
|
|
9
|
+
from starplot.config import settings
|
|
10
10
|
from starplot.data import db, bigsky
|
|
11
11
|
|
|
12
12
|
print("Installing DuckDB spatial extension...")
|
|
@@ -16,11 +16,11 @@ def setup(options):
|
|
|
16
16
|
|
|
17
17
|
fonts.load()
|
|
18
18
|
|
|
19
|
-
print(f"Installed to: {settings.
|
|
19
|
+
print(f"Installed to: {settings.duckdb_extension_path}")
|
|
20
20
|
|
|
21
21
|
if "--install-big-sky" in options:
|
|
22
22
|
bigsky.download_if_not_exists()
|
|
23
|
-
print(f"Big Sky Catalog downloaded and installed to: {settings.
|
|
23
|
+
print(f"Big Sky Catalog downloaded and installed to: {settings.download_path}")
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def main():
|
starplot/config.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
STARPLOT_PATH = Path(__file__).resolve().parent
|
|
8
|
+
"""Path of starplot source"""
|
|
9
|
+
|
|
10
|
+
DATA_PATH = STARPLOT_PATH / "data" / "library"
|
|
11
|
+
"""Path of starplot data"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
RAW_DATA_PATH = STARPLOT_PATH.parent.parent / "raw"
|
|
15
|
+
BUILD_PATH = STARPLOT_PATH.parent.parent / "build"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SvgTextType(str, Enum):
|
|
19
|
+
PATH = "path"
|
|
20
|
+
ELEMENT = "element"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Settings(BaseSettings):
|
|
24
|
+
model_config = SettingsConfigDict(env_prefix="STARPLOT_")
|
|
25
|
+
"""Configuration for the Pydantic settings model. Do not change."""
|
|
26
|
+
|
|
27
|
+
download_path: Path = DATA_PATH / "downloads"
|
|
28
|
+
"""
|
|
29
|
+
Path for downloaded data, including the Big Sky catalog, ephemeris files, etc.
|
|
30
|
+
|
|
31
|
+
Default = `<starplot_source_path>/data/library/downloads/`
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
duckdb_extension_path: Path = DATA_PATH / "duckdb-extensions"
|
|
35
|
+
"""
|
|
36
|
+
Path for the DuckDB spatial extension, which is required for the data backend.
|
|
37
|
+
|
|
38
|
+
Default = `<starplot_source_path>/data/library/duckdb-extensions/`
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
svg_text_type: SvgTextType = SvgTextType.PATH
|
|
42
|
+
"""
|
|
43
|
+
Method for rendering text in SVG exports:
|
|
44
|
+
|
|
45
|
+
- `"path"` (default) will render all text as paths. This will increase the filesize,
|
|
46
|
+
but allow all viewers to see the font correctly (even if they don't have the font
|
|
47
|
+
installed on their system).
|
|
48
|
+
|
|
49
|
+
- `"element"` will render all text as an [SVG `<text>` element](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/text),
|
|
50
|
+
which means the text will be editable in graphic design applications but the text may render in a system default font if the original
|
|
51
|
+
font isn't available. **Important: when using the "element" method, text borders will be turned OFF.**
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
settings = Settings()
|
starplot/data/__init__.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from skyfield.api import Loader
|
|
2
2
|
|
|
3
|
-
from starplot import settings
|
|
3
|
+
from starplot.config import settings, DATA_PATH
|
|
4
4
|
|
|
5
|
-
load = Loader(
|
|
5
|
+
load = Loader(DATA_PATH)
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class DataFiles:
|
|
9
|
-
BIG_SKY = settings.
|
|
9
|
+
BIG_SKY = settings.download_path / "bigsky.0.4.0.stars.parquet"
|
|
10
10
|
|
|
11
|
-
BIG_SKY_MAG11 =
|
|
11
|
+
BIG_SKY_MAG11 = DATA_PATH / "bigsky.0.4.0.stars.mag11.parquet"
|
|
12
12
|
|
|
13
|
-
DATABASE =
|
|
13
|
+
DATABASE = DATA_PATH / "sky.db"
|
starplot/data/bigsky.py
CHANGED
|
@@ -3,7 +3,7 @@ from pathlib import Path
|
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
|
|
6
|
-
from starplot import settings
|
|
6
|
+
from starplot.config import settings
|
|
7
7
|
from starplot.data import DataFiles, utils
|
|
8
8
|
|
|
9
9
|
|
|
@@ -21,7 +21,7 @@ def get_url(version: str = BIG_SKY_VERSION, filename: str = BIG_SKY_FILENAME):
|
|
|
21
21
|
|
|
22
22
|
def download(
|
|
23
23
|
url: str = None,
|
|
24
|
-
download_path: str = settings.
|
|
24
|
+
download_path: str = settings.download_path,
|
|
25
25
|
download_filename: str = BIG_SKY_FILENAME,
|
|
26
26
|
build_file: str = DataFiles.BIG_SKY,
|
|
27
27
|
):
|
|
@@ -102,7 +102,7 @@ def exists(path) -> bool:
|
|
|
102
102
|
def download_if_not_exists(
|
|
103
103
|
filename: str = DataFiles.BIG_SKY,
|
|
104
104
|
url: str = None,
|
|
105
|
-
download_path: str = settings.
|
|
105
|
+
download_path: str = settings.download_path,
|
|
106
106
|
download_filename: str = BIG_SKY_FILENAME,
|
|
107
107
|
build_file: str = DataFiles.BIG_SKY,
|
|
108
108
|
):
|
starplot/data/db.py
CHANGED
|
@@ -2,7 +2,7 @@ from functools import cache
|
|
|
2
2
|
|
|
3
3
|
import ibis
|
|
4
4
|
|
|
5
|
-
from starplot import settings
|
|
5
|
+
from starplot.config import settings
|
|
6
6
|
from starplot.data import DataFiles
|
|
7
7
|
|
|
8
8
|
|
|
@@ -12,6 +12,6 @@ def connect():
|
|
|
12
12
|
DataFiles.DATABASE, read_only=True
|
|
13
13
|
) # , threads=2, memory_limit="1GB"
|
|
14
14
|
connection.raw_sql(
|
|
15
|
-
f"SET extension_directory = '{str(settings.
|
|
15
|
+
f"SET extension_directory = '{str(settings.duckdb_extension_path)}';"
|
|
16
16
|
)
|
|
17
17
|
return connection
|
starplot/data/library/sky.db
CHANGED
|
Binary file
|
starplot/geometry.py
CHANGED
|
@@ -95,6 +95,54 @@ def unwrap_polygon_360(polygon: Polygon) -> Polygon:
|
|
|
95
95
|
return polygon
|
|
96
96
|
|
|
97
97
|
|
|
98
|
+
def split_polygon_at_zero(polygon: Polygon) -> list[Polygon]:
|
|
99
|
+
"""
|
|
100
|
+
Splits a polygon at the first point of Aries (RA=0)
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
polygon: Polygon that possibly needs splitting
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
List of polygons
|
|
107
|
+
"""
|
|
108
|
+
ra, dec = [p for p in polygon.exterior.coords.xy]
|
|
109
|
+
|
|
110
|
+
if min(ra) < 180 and max(ra) > 300:
|
|
111
|
+
new_ra = [r + 360 if r < 180 else r for r in ra]
|
|
112
|
+
new_polygon = Polygon(list(zip(new_ra, dec)))
|
|
113
|
+
|
|
114
|
+
polygon_1 = new_polygon.intersection(
|
|
115
|
+
Polygon(
|
|
116
|
+
[
|
|
117
|
+
[0, -90],
|
|
118
|
+
[360, -90],
|
|
119
|
+
[360, 90],
|
|
120
|
+
[0, 90],
|
|
121
|
+
[0, -90],
|
|
122
|
+
]
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
polygon_2 = new_polygon.intersection(
|
|
127
|
+
Polygon(
|
|
128
|
+
[
|
|
129
|
+
[360, -90],
|
|
130
|
+
[720, -90],
|
|
131
|
+
[720, 90],
|
|
132
|
+
[360, 90],
|
|
133
|
+
[360, -90],
|
|
134
|
+
]
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
p2_ra, p2_dec = [p for p in polygon_2.exterior.coords.xy]
|
|
139
|
+
p2_new_ra = [ra - 360 for ra in p2_ra]
|
|
140
|
+
|
|
141
|
+
return [polygon_1, Polygon(list(zip(p2_new_ra, p2_dec)))]
|
|
142
|
+
|
|
143
|
+
return [polygon]
|
|
144
|
+
|
|
145
|
+
|
|
98
146
|
def random_point_in_polygon(
|
|
99
147
|
polygon: Polygon, max_iterations: int = 100, seed: int = None
|
|
100
148
|
) -> Point:
|