starplot 0.20.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- starplot/__init__.py +59 -0
- starplot/callables.py +176 -0
- starplot/cli.py +29 -0
- starplot/config.py +80 -0
- starplot/coordinates.py +7 -0
- starplot/data/__init__.py +22 -0
- starplot/data/catalogs.py +356 -0
- starplot/data/constellations.py +71 -0
- starplot/data/db.py +33 -0
- starplot/data/dsos.py +85 -0
- starplot/data/ecliptic.py +201 -0
- starplot/data/library/constellation_names.parquet +0 -0
- starplot/data/library/dso_names.parquet +0 -0
- starplot/data/library/readme.md +1 -0
- starplot/data/library/star_designations.parquet +0 -0
- starplot/data/stars.py +79 -0
- starplot/data/translations.py +408 -0
- starplot/data/utils.py +35 -0
- starplot/geometry.py +319 -0
- starplot/mixins.py +360 -0
- starplot/models/__init__.py +21 -0
- starplot/models/base.py +77 -0
- starplot/models/comet.py +302 -0
- starplot/models/constellation.py +151 -0
- starplot/models/dso.py +317 -0
- starplot/models/milky_way.py +30 -0
- starplot/models/moon.py +130 -0
- starplot/models/objects.py +29 -0
- starplot/models/observer.py +125 -0
- starplot/models/optics.py +342 -0
- starplot/models/planet.py +137 -0
- starplot/models/satellite.py +138 -0
- starplot/models/star.py +253 -0
- starplot/models/sun.py +62 -0
- starplot/plots/__init__.py +7 -0
- starplot/plots/base.py +1023 -0
- starplot/plots/galaxy.py +372 -0
- starplot/plots/horizon.py +543 -0
- starplot/plots/map.py +515 -0
- starplot/plots/optic.py +469 -0
- starplot/plots/zenith.py +217 -0
- starplot/plotters/__init__.py +9 -0
- starplot/plotters/arrow.py +174 -0
- starplot/plotters/constellations.py +298 -0
- starplot/plotters/debug.py +21 -0
- starplot/plotters/dsos.py +294 -0
- starplot/plotters/experimental.py +722 -0
- starplot/plotters/gradients.py +153 -0
- starplot/plotters/legend.py +253 -0
- starplot/plotters/milkyway.py +51 -0
- starplot/plotters/stars.py +319 -0
- starplot/plotters/text.py +802 -0
- starplot/profile.py +16 -0
- starplot/projections.py +184 -0
- starplot/styles/__init__.py +6 -0
- starplot/styles/base.py +1344 -0
- starplot/styles/ext/antique.yml +175 -0
- starplot/styles/ext/blue_dark.yml +163 -0
- starplot/styles/ext/blue_gold.yml +147 -0
- starplot/styles/ext/blue_light.yml +123 -0
- starplot/styles/ext/blue_medium.yml +142 -0
- starplot/styles/ext/blue_night.yml +185 -0
- starplot/styles/ext/cb_wong.yml +124 -0
- starplot/styles/ext/color_print.yml +111 -0
- starplot/styles/ext/gradient_presets.yml +158 -0
- starplot/styles/ext/grayscale.yml +94 -0
- starplot/styles/ext/grayscale_dark.yml +136 -0
- starplot/styles/ext/map.yml +12 -0
- starplot/styles/ext/nord.yml +158 -0
- starplot/styles/ext/optic.yml +20 -0
- starplot/styles/ext/publication.yml +8 -0
- starplot/styles/extensions.py +129 -0
- starplot/styles/fonts-library/gfs-didot/DESCRIPTION.en_us.html +9 -0
- starplot/styles/fonts-library/gfs-didot/GFSDidot-Regular.ttf +0 -0
- starplot/styles/fonts-library/gfs-didot/METADATA.pb +16 -0
- starplot/styles/fonts-library/gfs-didot/OFL.txt +94 -0
- starplot/styles/fonts-library/inter/Inter-Bold.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-BoldItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ExtraBold.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ExtraLight.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-ExtraLightItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Italic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Light.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-LightItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-Regular.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-SemiBold.ttf +0 -0
- starplot/styles/fonts-library/inter/Inter-SemiBoldItalic.ttf +0 -0
- starplot/styles/fonts-library/inter/LICENSE.txt +92 -0
- starplot/styles/fonts.py +15 -0
- starplot/styles/helpers.py +93 -0
- starplot/styles/markers.py +308 -0
- starplot/utils.py +169 -0
- starplot/warnings.py +21 -0
- starplot-0.20.5.dist-info/METADATA +146 -0
- starplot-0.20.5.dist-info/RECORD +98 -0
- starplot-0.20.5.dist-info/WHEEL +4 -0
- starplot-0.20.5.dist-info/entry_points.txt +3 -0
- starplot-0.20.5.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from astropy import units as u
|
|
9
|
+
from astropy_healpix import HEALPix
|
|
10
|
+
from ibis import Table
|
|
11
|
+
import pyarrow as pa
|
|
12
|
+
from shapely import Geometry, Polygon, MultiPolygon
|
|
13
|
+
|
|
14
|
+
from starplot.config import settings
|
|
15
|
+
from starplot.models.base import SkyObject
|
|
16
|
+
from starplot.data.utils import download
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def merge_schemas(df, explicit_schema: pa.Schema) -> pa.Schema:
|
|
20
|
+
"""Merge explicit schema with inferred schema for remaining columns."""
|
|
21
|
+
df_columns = df.columns.tolist()
|
|
22
|
+
|
|
23
|
+
explicit_cols = {field.name for field in explicit_schema}
|
|
24
|
+
|
|
25
|
+
# infer schema for ALL columns
|
|
26
|
+
inferred_table = pa.Table.from_pandas(df)
|
|
27
|
+
inferred_schema = inferred_table.schema
|
|
28
|
+
|
|
29
|
+
# add explicit fields first
|
|
30
|
+
fields = [f for f in explicit_schema if f.name in df_columns]
|
|
31
|
+
|
|
32
|
+
# add inferred fields for remaining columns
|
|
33
|
+
for field in inferred_schema:
|
|
34
|
+
if field.name not in explicit_cols:
|
|
35
|
+
fields.append(field)
|
|
36
|
+
|
|
37
|
+
return pa.schema(fields)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def to_parquet(
|
|
41
|
+
rows: list[dict],
|
|
42
|
+
path: Path,
|
|
43
|
+
columns: list[str] = None,
|
|
44
|
+
schema: pa.Schema = None,
|
|
45
|
+
partition_columns: list[str] = None,
|
|
46
|
+
sorting_columns: list[str] = None,
|
|
47
|
+
compression: str = "snappy",
|
|
48
|
+
row_group_size: int = 100_000,
|
|
49
|
+
chunk_id: int = 0,
|
|
50
|
+
) -> None:
|
|
51
|
+
import pandas as pd
|
|
52
|
+
import pyarrow.parquet as pq
|
|
53
|
+
|
|
54
|
+
df = pd.DataFrame(rows)
|
|
55
|
+
df = df.sort_values(sorting_columns)
|
|
56
|
+
|
|
57
|
+
merged_schema = merge_schemas(df, schema)
|
|
58
|
+
table = pa.Table.from_pandas(df, schema=merged_schema)
|
|
59
|
+
|
|
60
|
+
if "__index_level_0__" in table.column_names:
|
|
61
|
+
table = table.drop_columns("__index_level_0__")
|
|
62
|
+
|
|
63
|
+
sort_columns = [pq.SortingColumn(columns.index(c)) for c in sorting_columns]
|
|
64
|
+
|
|
65
|
+
if partition_columns:
|
|
66
|
+
pq.write_to_dataset(
|
|
67
|
+
table,
|
|
68
|
+
root_path=path,
|
|
69
|
+
partition_cols=partition_columns,
|
|
70
|
+
compression=compression,
|
|
71
|
+
row_group_size=row_group_size,
|
|
72
|
+
sorting_columns=sort_columns,
|
|
73
|
+
)
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
pq.write_table(
|
|
77
|
+
table,
|
|
78
|
+
path if chunk_id is None else path.with_stem(f"{path.stem}_{chunk_id}"),
|
|
79
|
+
compression=compression,
|
|
80
|
+
row_group_size=row_group_size,
|
|
81
|
+
sorting_columns=sort_columns,
|
|
82
|
+
)
|
|
83
|
+
# schema = pq.read_schema(filename)
|
|
84
|
+
# print("\nColumn names:", schema.names)
|
|
85
|
+
# print("Column types:", schema.types)
|
|
86
|
+
# print(schema.to_string())
|
|
87
|
+
|
|
88
|
+
# parquet_file = pq.ParquetFile(filename)
|
|
89
|
+
# print(f"Parquet file: {filename} Schema")
|
|
90
|
+
# print(parquet_file.schema)
|
|
91
|
+
|
|
92
|
+
# print("\nFull Metadata:")
|
|
93
|
+
# print(parquet_file.metadata)
|
|
94
|
+
|
|
95
|
+
# df = pd.read_parquet(path)
|
|
96
|
+
# df["geometry"] = df["geometry"].apply(wkb.loads)
|
|
97
|
+
# gdf = gpd.GeoDataFrame(df, geometry="geometry")
|
|
98
|
+
# print(gdf)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class SpatialQueryMethod(Enum):
|
|
102
|
+
"""Options for spatial querying method"""
|
|
103
|
+
|
|
104
|
+
GEOMETRY = "geometry"
|
|
105
|
+
"""Use the `geometry` field"""
|
|
106
|
+
|
|
107
|
+
HEALPIX = "healpix"
|
|
108
|
+
"""Use the `healpix_index` field"""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass
|
|
112
|
+
class Catalog:
|
|
113
|
+
"""Catalog of objects"""
|
|
114
|
+
|
|
115
|
+
path: Path | str
|
|
116
|
+
"""Path of the catalog. If using Hive partitions, this should be a glob (e.g. `/data/**/*.parquet`)."""
|
|
117
|
+
|
|
118
|
+
url: str = None
|
|
119
|
+
"""Remote URL of the catalog. If the catalog doesn't exist at the `path` then it'll be downloaded from this URL."""
|
|
120
|
+
|
|
121
|
+
hive_partitioning: bool = False
|
|
122
|
+
"""If the catalog uses hive partitioning, then set this to True"""
|
|
123
|
+
|
|
124
|
+
healpix_nside: int = None
|
|
125
|
+
"""HEALPix resolution (NSIDE)"""
|
|
126
|
+
|
|
127
|
+
spatial_query_method: SpatialQueryMethod = SpatialQueryMethod.GEOMETRY
|
|
128
|
+
"""
|
|
129
|
+
Method to use for spatial querying on this catalog.
|
|
130
|
+
|
|
131
|
+
For relatively small catalogs (less than 1 million objects), the geometry method should have good performance.
|
|
132
|
+
|
|
133
|
+
For larger catalogs, you can improve querying performance tremendously by defining a `healpix_nside` on the catalog,
|
|
134
|
+
and setting this query method to `SpatialQueryMethod.HEALPIX`
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
_healpix: HEALPix = None
|
|
138
|
+
|
|
139
|
+
def __post_init__(self):
|
|
140
|
+
if not isinstance(self.path, Path):
|
|
141
|
+
self.path = Path(self.path)
|
|
142
|
+
if self.healpix_nside is not None:
|
|
143
|
+
self._healpix = HEALPix(nside=self.healpix_nside, order="nested")
|
|
144
|
+
|
|
145
|
+
def __eq__(self, other):
|
|
146
|
+
if isinstance(other, Catalog):
|
|
147
|
+
return str(self.path) == str(other.path)
|
|
148
|
+
return NotImplemented
|
|
149
|
+
|
|
150
|
+
def __hash__(self):
|
|
151
|
+
return hash(str(self.path))
|
|
152
|
+
|
|
153
|
+
def exists(self) -> bool:
|
|
154
|
+
"""Returns true if the catalog path exists, else False."""
|
|
155
|
+
return any(glob.iglob(str(self.path)))
|
|
156
|
+
|
|
157
|
+
def download(self, silent: bool = False):
|
|
158
|
+
"""Downloads the catalog from its URL to its path"""
|
|
159
|
+
download(
|
|
160
|
+
url=self.url,
|
|
161
|
+
download_path=self.path,
|
|
162
|
+
description=f"Catalog: {self.url}",
|
|
163
|
+
silent=silent,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def download_if_not_exists(self, silent: bool = False):
|
|
167
|
+
"""Downloads the catalog only if it doesn't already exist at its path"""
|
|
168
|
+
if not self.exists() and self.url:
|
|
169
|
+
self.download(silent=silent)
|
|
170
|
+
|
|
171
|
+
def healpix_ids_from_extent(self, extent: Polygon | MultiPolygon) -> list[int]:
|
|
172
|
+
"""
|
|
173
|
+
Returns HEALPix ids from a given polygon or multipolygon
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
extent: Polygon or multipolygon to get the HEALPix ids for
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
List of integer HEALPix ids that are in the geometry (inclusive)
|
|
180
|
+
"""
|
|
181
|
+
healpix_ids = set()
|
|
182
|
+
polygons = extent.geoms if isinstance(extent, MultiPolygon) else [extent]
|
|
183
|
+
|
|
184
|
+
for p in polygons:
|
|
185
|
+
minx, miny, maxx, maxy = p.bounds
|
|
186
|
+
radius = max(abs(maxx - minx), abs(maxy - miny))
|
|
187
|
+
healpix_ids.update(
|
|
188
|
+
[
|
|
189
|
+
int(i)
|
|
190
|
+
for i in self._healpix.cone_search_lonlat(
|
|
191
|
+
lon=p.centroid.x * u.deg,
|
|
192
|
+
lat=p.centroid.y * u.deg,
|
|
193
|
+
radius=radius * u.deg,
|
|
194
|
+
)
|
|
195
|
+
]
|
|
196
|
+
)
|
|
197
|
+
return healpix_ids
|
|
198
|
+
|
|
199
|
+
def _load(self, connection, table_name) -> Table:
|
|
200
|
+
self.download_if_not_exists()
|
|
201
|
+
return connection.read_parquet(
|
|
202
|
+
str(self.path),
|
|
203
|
+
table_name=table_name,
|
|
204
|
+
hive_partitioning=self.hive_partitioning,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def build(
|
|
208
|
+
self,
|
|
209
|
+
objects: Iterable[SkyObject],
|
|
210
|
+
chunk_size: int = 1_000_000,
|
|
211
|
+
columns: list[str] = None,
|
|
212
|
+
partition_columns: list[str] = None,
|
|
213
|
+
sorting_columns: list[str] = None,
|
|
214
|
+
compression: str = "snappy",
|
|
215
|
+
row_group_size: int = 200_000,
|
|
216
|
+
) -> None:
|
|
217
|
+
"""
|
|
218
|
+
Creates the catalog from an iterable of sky objects. Output is one or more Parquet files.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
objects: Iterable that contains the sky objects for the catalog
|
|
222
|
+
chunk_size: Max number of objects to write per file
|
|
223
|
+
columns: List of columns to include in the catalog. Only the columns in this list will be written to the Parquet files.
|
|
224
|
+
partition_columns: List of columns to create Hive partitions for
|
|
225
|
+
sorting_columns: List of columns to sort by
|
|
226
|
+
compression: Type of compression to use -- this is passed directly to PyArrow's Parquet writer.
|
|
227
|
+
row_group_size: Row group size for the Parquet files
|
|
228
|
+
"""
|
|
229
|
+
path = self.path
|
|
230
|
+
if partition_columns:
|
|
231
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
232
|
+
|
|
233
|
+
chunk_ctr = 0
|
|
234
|
+
schema = None
|
|
235
|
+
columns = columns or []
|
|
236
|
+
partition_columns = partition_columns or []
|
|
237
|
+
sorting_columns = sorting_columns or []
|
|
238
|
+
rows = []
|
|
239
|
+
|
|
240
|
+
hpix = None
|
|
241
|
+
if self.healpix_nside is not None:
|
|
242
|
+
hpix = HEALPix(nside=self.healpix_nside, order="nested")
|
|
243
|
+
columns.append("healpix_index")
|
|
244
|
+
|
|
245
|
+
def serialize(obj):
|
|
246
|
+
"""Converts geometry types to WKB"""
|
|
247
|
+
return obj.wkb if isinstance(obj, Geometry) else obj
|
|
248
|
+
|
|
249
|
+
for row in objects:
|
|
250
|
+
if schema is None:
|
|
251
|
+
schema = row._pyarrow_schema()
|
|
252
|
+
|
|
253
|
+
if hpix:
|
|
254
|
+
idx = hpix.lonlat_to_healpix([row.ra] * u.deg, [row.dec] * u.deg)
|
|
255
|
+
row.healpix_index = idx[0]
|
|
256
|
+
|
|
257
|
+
rows.append({column: serialize(getattr(row, column)) for column in columns})
|
|
258
|
+
|
|
259
|
+
if len(rows) == chunk_size:
|
|
260
|
+
to_parquet(
|
|
261
|
+
rows=rows,
|
|
262
|
+
path=path,
|
|
263
|
+
columns=columns,
|
|
264
|
+
schema=schema,
|
|
265
|
+
partition_columns=partition_columns,
|
|
266
|
+
sorting_columns=sorting_columns,
|
|
267
|
+
compression=compression,
|
|
268
|
+
row_group_size=row_group_size,
|
|
269
|
+
chunk_id=chunk_ctr,
|
|
270
|
+
)
|
|
271
|
+
rows = []
|
|
272
|
+
chunk_ctr += 1
|
|
273
|
+
|
|
274
|
+
if rows:
|
|
275
|
+
to_parquet(
|
|
276
|
+
rows=rows,
|
|
277
|
+
path=path,
|
|
278
|
+
columns=columns,
|
|
279
|
+
schema=schema,
|
|
280
|
+
partition_columns=partition_columns,
|
|
281
|
+
sorting_columns=sorting_columns,
|
|
282
|
+
compression=compression,
|
|
283
|
+
row_group_size=row_group_size,
|
|
284
|
+
chunk_id=chunk_ctr if chunk_ctr else None,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# --------------------------------------------------------
|
|
289
|
+
# Catalog definitions
|
|
290
|
+
# --------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
BIG_SKY = Catalog(
|
|
293
|
+
path=settings.data_path / "stars.bigksy.0.1.3.mag16.parquet",
|
|
294
|
+
url="https://github.com/steveberardi/starplot-bigsky/releases/download/v0.1.3/stars.bigksy.0.1.3.mag16.parquet",
|
|
295
|
+
)
|
|
296
|
+
"""
|
|
297
|
+
[Big Sky Catalog](https://github.com/steveberardi/bigsky) ~ 2.5M stars
|
|
298
|
+
|
|
299
|
+
This is the full version of the Big Sky Catalog, which includes 2,557,501 stars from Hipparcos, Tycho-1, and Tycho-2.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
BIG_SKY_MAG11 = Catalog(
|
|
303
|
+
path=settings.data_path / "stars.bigksy.0.1.3.mag11.parquet",
|
|
304
|
+
url="https://github.com/steveberardi/starplot-bigsky/releases/download/v0.1.3/stars.bigksy.0.1.3.mag11.parquet",
|
|
305
|
+
)
|
|
306
|
+
"""
|
|
307
|
+
[Big Sky Catalog](https://github.com/steveberardi/bigsky) ~ 983,823 stars with limiting magnitude 11
|
|
308
|
+
|
|
309
|
+
This is an _abridged_ version of the Big Sky Catalog.
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
BIG_SKY_MAG9 = Catalog(
|
|
313
|
+
path=settings.data_path / "stars.bigksy.0.1.3.mag9.parquet",
|
|
314
|
+
url="https://github.com/steveberardi/starplot-bigsky/releases/download/v0.1.3/stars.bigksy.0.1.3.mag9.parquet",
|
|
315
|
+
)
|
|
316
|
+
"""
|
|
317
|
+
[Big Sky Catalog](https://github.com/steveberardi/bigsky) ~ 136,126 stars with limiting magnitude 9
|
|
318
|
+
|
|
319
|
+
This is an _abridged_ version of the Big Sky Catalog.
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
OPEN_NGC = Catalog(
|
|
323
|
+
path=settings.data_path / "ongc.0.1.2.parquet",
|
|
324
|
+
url="https://github.com/steveberardi/starplot-ongc/releases/download/v0.1.2/ongc.0.1.2.parquet",
|
|
325
|
+
)
|
|
326
|
+
"""
|
|
327
|
+
[OpenNGC](https://github.com/mattiaverga/OpenNGC) catalog, including nebulae outlines.
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
CONSTELLATIONS_IAU = Catalog(
|
|
331
|
+
path=settings.data_path / "constellations.0.3.3.parquet",
|
|
332
|
+
url="https://github.com/steveberardi/starplot-constellations/releases/download/v0.3.3/constellations.0.3.3.parquet",
|
|
333
|
+
)
|
|
334
|
+
"""
|
|
335
|
+
Constellations recognized by IAU, with lines by Sky & Telescope.
|
|
336
|
+
"""
|
|
337
|
+
|
|
338
|
+
CONSTELLATION_BORDERS = Catalog(
|
|
339
|
+
path=settings.data_path / "constellations-borders-0.3.1.parquet",
|
|
340
|
+
url="https://github.com/steveberardi/starplot-constellations/releases/download/v0.3.1/constellations-borders.0.3.1.parquet",
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
MILKY_WAY = Catalog(
|
|
344
|
+
path=settings.data_path / "milky_way-0.1.0.parquet",
|
|
345
|
+
url="https://github.com/steveberardi/starplot-milkyway/releases/download/v0.1.0/milky_way.parquet",
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def download_all_catalogs(silent=False):
|
|
350
|
+
BIG_SKY.download_if_not_exists(silent=silent)
|
|
351
|
+
BIG_SKY_MAG9.download_if_not_exists(silent=silent)
|
|
352
|
+
BIG_SKY_MAG11.download_if_not_exists(silent=silent)
|
|
353
|
+
OPEN_NGC.download_if_not_exists(silent=silent)
|
|
354
|
+
CONSTELLATIONS_IAU.download_if_not_exists(silent=silent)
|
|
355
|
+
CONSTELLATION_BORDERS.download_if_not_exists(silent=silent)
|
|
356
|
+
MILKY_WAY.download_if_not_exists(silent=silent)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from functools import cache
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from ibis import _
|
|
5
|
+
|
|
6
|
+
from starplot.config import settings
|
|
7
|
+
from starplot.data import db
|
|
8
|
+
from starplot.data.catalogs import Catalog
|
|
9
|
+
from starplot.data.translations import language_name_column, LANGUAGE_NAME_COLUMNS
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@cache
|
|
13
|
+
def table(
|
|
14
|
+
catalog: Catalog | Path | str,
|
|
15
|
+
language: str,
|
|
16
|
+
):
|
|
17
|
+
con = db.connect()
|
|
18
|
+
table_name = "constellations"
|
|
19
|
+
|
|
20
|
+
if isinstance(catalog, Catalog):
|
|
21
|
+
c = catalog._load(connection=con, table_name=table_name)
|
|
22
|
+
else:
|
|
23
|
+
c = con.read_parquet(str(catalog), table_name=table_name)
|
|
24
|
+
|
|
25
|
+
name_column = language_name_column(language)
|
|
26
|
+
name_columns = ["name"] + LANGUAGE_NAME_COLUMNS
|
|
27
|
+
name_columns_missing = {col for col in name_columns if col not in c.columns}
|
|
28
|
+
|
|
29
|
+
if name_columns_missing and "iau_id" in c.columns:
|
|
30
|
+
constellation_names = con.table("constellation_names")
|
|
31
|
+
constellation_names = constellation_names.mutate(
|
|
32
|
+
name=getattr(constellation_names, language_name_column(language))
|
|
33
|
+
)
|
|
34
|
+
constellations_joined = c.join(
|
|
35
|
+
constellation_names,
|
|
36
|
+
c.iau_id == constellation_names.iau_id,
|
|
37
|
+
how="left",
|
|
38
|
+
)
|
|
39
|
+
c = constellations_joined.select(*c.columns, *name_columns_missing)
|
|
40
|
+
|
|
41
|
+
if name_column not in c.columns:
|
|
42
|
+
name_column = "name"
|
|
43
|
+
|
|
44
|
+
return c.mutate(
|
|
45
|
+
boundary=_.boundary.cast("geometry"), # cast WKB to geometry type
|
|
46
|
+
border=_.border.cast("geometry"), # cast WKB to geometry type
|
|
47
|
+
name=getattr(c, name_column),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def load(
|
|
52
|
+
catalog: Catalog | Path | str,
|
|
53
|
+
extent=None,
|
|
54
|
+
filters=None,
|
|
55
|
+
sql=None,
|
|
56
|
+
):
|
|
57
|
+
filters = filters or []
|
|
58
|
+
c = table(catalog=catalog, language=settings.language)
|
|
59
|
+
|
|
60
|
+
if extent:
|
|
61
|
+
filters.append(_.boundary.intersects(extent))
|
|
62
|
+
|
|
63
|
+
if filters:
|
|
64
|
+
c = c.filter(*filters)
|
|
65
|
+
|
|
66
|
+
if sql:
|
|
67
|
+
result = c.alias("_").sql(sql).select("pk").execute()
|
|
68
|
+
pks = result["pk"].to_list()
|
|
69
|
+
c = c.filter(_.pk.isin(pks))
|
|
70
|
+
|
|
71
|
+
return c
|
starplot/data/db.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from functools import cache
|
|
2
|
+
|
|
3
|
+
from ibis import duckdb
|
|
4
|
+
|
|
5
|
+
from starplot.config import settings
|
|
6
|
+
from starplot.data import DataFiles
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
NAME_TABLES = {
|
|
10
|
+
"star_designations": DataFiles.STAR_DESIGNATIONS,
|
|
11
|
+
"constellation_names": DataFiles.CONSTELLATION_NAMES,
|
|
12
|
+
"dso_names": DataFiles.DSO_NAMES,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@cache
|
|
17
|
+
def _connect(extensions_path):
|
|
18
|
+
connection = duckdb.connect()
|
|
19
|
+
connection.raw_sql(f"SET extension_directory = '{str(extensions_path)}';")
|
|
20
|
+
connection.raw_sql("INSTALL spatial;")
|
|
21
|
+
connection.load_extension("spatial")
|
|
22
|
+
|
|
23
|
+
missing_name_tables = set(NAME_TABLES.keys()) - set(connection.list_tables())
|
|
24
|
+
|
|
25
|
+
for table_name in missing_name_tables:
|
|
26
|
+
connection.read_parquet(NAME_TABLES[table_name], table_name=table_name)
|
|
27
|
+
|
|
28
|
+
return connection
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def connect():
|
|
32
|
+
path = settings.data_path / "duckdb-extensions"
|
|
33
|
+
return _connect(extensions_path=path)
|
starplot/data/dsos.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from functools import cache
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from ibis import _
|
|
5
|
+
|
|
6
|
+
from starplot.config import settings
|
|
7
|
+
from starplot.data import db
|
|
8
|
+
from starplot.data.catalogs import Catalog, SpatialQueryMethod
|
|
9
|
+
from starplot.data.translations import (
|
|
10
|
+
language_name_column,
|
|
11
|
+
LANGUAGES,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@cache
|
|
16
|
+
def table(
|
|
17
|
+
con,
|
|
18
|
+
catalog: Catalog | Path | str,
|
|
19
|
+
language: str,
|
|
20
|
+
):
|
|
21
|
+
table_name = "deep_sky_objects"
|
|
22
|
+
|
|
23
|
+
if isinstance(catalog, Catalog):
|
|
24
|
+
dsos = catalog._load(connection=con, table_name=table_name)
|
|
25
|
+
else:
|
|
26
|
+
dsos = con.read_parquet(str(catalog), table_name=table_name)
|
|
27
|
+
|
|
28
|
+
name_column = language_name_column(language, column_prefix="common_names")
|
|
29
|
+
name_columns = [
|
|
30
|
+
language_name_column(lang, column_prefix="common_names") for lang in LANGUAGES
|
|
31
|
+
]
|
|
32
|
+
name_columns_missing = {col for col in name_columns if col not in dsos.columns}
|
|
33
|
+
|
|
34
|
+
if name_columns_missing and "name" in dsos.columns:
|
|
35
|
+
dso_names = con.table("dso_names")
|
|
36
|
+
dsos_joined = dsos.join(
|
|
37
|
+
dso_names,
|
|
38
|
+
dsos.name == dso_names.open_ngc_name,
|
|
39
|
+
how="left",
|
|
40
|
+
)
|
|
41
|
+
dsos = dsos_joined.select(*dsos.columns, *name_columns_missing)
|
|
42
|
+
|
|
43
|
+
if name_column not in dsos.columns:
|
|
44
|
+
name_column = "name"
|
|
45
|
+
|
|
46
|
+
return dsos.mutate(common_names=getattr(dsos, name_column))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def load(
|
|
50
|
+
catalog: Catalog | Path | str,
|
|
51
|
+
extent=None,
|
|
52
|
+
filters=None,
|
|
53
|
+
sql=None,
|
|
54
|
+
):
|
|
55
|
+
filters = filters or []
|
|
56
|
+
con = db.connect()
|
|
57
|
+
dsos = table(con=con, catalog=catalog, language=settings.language)
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
catalog.spatial_query_method == SpatialQueryMethod.HEALPIX.value
|
|
61
|
+
and catalog.healpix_nside
|
|
62
|
+
and extent is not None
|
|
63
|
+
):
|
|
64
|
+
healpix_indices = catalog.healpix_ids_from_extent(extent)
|
|
65
|
+
dsos = dsos.filter(dsos.healpix_index.isin(healpix_indices))
|
|
66
|
+
dsos = con.create_table("dsos_temp", obj=dsos, temp=True, overwrite=True)
|
|
67
|
+
|
|
68
|
+
dsos = dsos.mutate(
|
|
69
|
+
geometry=_.geometry.cast("geometry"), # cast WKB to geometry type
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if extent:
|
|
73
|
+
dsos = dsos.filter(_.geometry.intersects(extent))
|
|
74
|
+
|
|
75
|
+
filters.extend([_.ra.notnull() & _.dec.notnull()])
|
|
76
|
+
|
|
77
|
+
if filters:
|
|
78
|
+
dsos = dsos.filter(*filters)
|
|
79
|
+
|
|
80
|
+
if sql:
|
|
81
|
+
result = dsos.alias("_").sql(sql).select("pk").execute()
|
|
82
|
+
pks = result["pk"].to_list()
|
|
83
|
+
dsos = dsos.filter(_.pk.isin(pks))
|
|
84
|
+
|
|
85
|
+
return dsos
|