starplot 0.20.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. starplot/__init__.py +59 -0
  2. starplot/callables.py +176 -0
  3. starplot/cli.py +29 -0
  4. starplot/config.py +80 -0
  5. starplot/coordinates.py +7 -0
  6. starplot/data/__init__.py +22 -0
  7. starplot/data/catalogs.py +356 -0
  8. starplot/data/constellations.py +71 -0
  9. starplot/data/db.py +33 -0
  10. starplot/data/dsos.py +85 -0
  11. starplot/data/ecliptic.py +201 -0
  12. starplot/data/library/constellation_names.parquet +0 -0
  13. starplot/data/library/dso_names.parquet +0 -0
  14. starplot/data/library/readme.md +1 -0
  15. starplot/data/library/star_designations.parquet +0 -0
  16. starplot/data/stars.py +79 -0
  17. starplot/data/translations.py +408 -0
  18. starplot/data/utils.py +35 -0
  19. starplot/geometry.py +319 -0
  20. starplot/mixins.py +360 -0
  21. starplot/models/__init__.py +21 -0
  22. starplot/models/base.py +77 -0
  23. starplot/models/comet.py +302 -0
  24. starplot/models/constellation.py +151 -0
  25. starplot/models/dso.py +317 -0
  26. starplot/models/milky_way.py +30 -0
  27. starplot/models/moon.py +130 -0
  28. starplot/models/objects.py +29 -0
  29. starplot/models/observer.py +125 -0
  30. starplot/models/optics.py +342 -0
  31. starplot/models/planet.py +137 -0
  32. starplot/models/satellite.py +138 -0
  33. starplot/models/star.py +253 -0
  34. starplot/models/sun.py +62 -0
  35. starplot/plots/__init__.py +7 -0
  36. starplot/plots/base.py +1023 -0
  37. starplot/plots/galaxy.py +372 -0
  38. starplot/plots/horizon.py +543 -0
  39. starplot/plots/map.py +515 -0
  40. starplot/plots/optic.py +469 -0
  41. starplot/plots/zenith.py +217 -0
  42. starplot/plotters/__init__.py +9 -0
  43. starplot/plotters/arrow.py +174 -0
  44. starplot/plotters/constellations.py +298 -0
  45. starplot/plotters/debug.py +21 -0
  46. starplot/plotters/dsos.py +294 -0
  47. starplot/plotters/experimental.py +722 -0
  48. starplot/plotters/gradients.py +153 -0
  49. starplot/plotters/legend.py +253 -0
  50. starplot/plotters/milkyway.py +51 -0
  51. starplot/plotters/stars.py +319 -0
  52. starplot/plotters/text.py +802 -0
  53. starplot/profile.py +16 -0
  54. starplot/projections.py +184 -0
  55. starplot/styles/__init__.py +6 -0
  56. starplot/styles/base.py +1344 -0
  57. starplot/styles/ext/antique.yml +175 -0
  58. starplot/styles/ext/blue_dark.yml +163 -0
  59. starplot/styles/ext/blue_gold.yml +147 -0
  60. starplot/styles/ext/blue_light.yml +123 -0
  61. starplot/styles/ext/blue_medium.yml +142 -0
  62. starplot/styles/ext/blue_night.yml +185 -0
  63. starplot/styles/ext/cb_wong.yml +124 -0
  64. starplot/styles/ext/color_print.yml +111 -0
  65. starplot/styles/ext/gradient_presets.yml +158 -0
  66. starplot/styles/ext/grayscale.yml +94 -0
  67. starplot/styles/ext/grayscale_dark.yml +136 -0
  68. starplot/styles/ext/map.yml +12 -0
  69. starplot/styles/ext/nord.yml +158 -0
  70. starplot/styles/ext/optic.yml +20 -0
  71. starplot/styles/ext/publication.yml +8 -0
  72. starplot/styles/extensions.py +129 -0
  73. starplot/styles/fonts-library/gfs-didot/DESCRIPTION.en_us.html +9 -0
  74. starplot/styles/fonts-library/gfs-didot/GFSDidot-Regular.ttf +0 -0
  75. starplot/styles/fonts-library/gfs-didot/METADATA.pb +16 -0
  76. starplot/styles/fonts-library/gfs-didot/OFL.txt +94 -0
  77. starplot/styles/fonts-library/inter/Inter-Bold.ttf +0 -0
  78. starplot/styles/fonts-library/inter/Inter-BoldItalic.ttf +0 -0
  79. starplot/styles/fonts-library/inter/Inter-ExtraBold.ttf +0 -0
  80. starplot/styles/fonts-library/inter/Inter-ExtraLight.ttf +0 -0
  81. starplot/styles/fonts-library/inter/Inter-ExtraLightItalic.ttf +0 -0
  82. starplot/styles/fonts-library/inter/Inter-Italic.ttf +0 -0
  83. starplot/styles/fonts-library/inter/Inter-Light.ttf +0 -0
  84. starplot/styles/fonts-library/inter/Inter-LightItalic.ttf +0 -0
  85. starplot/styles/fonts-library/inter/Inter-Regular.ttf +0 -0
  86. starplot/styles/fonts-library/inter/Inter-SemiBold.ttf +0 -0
  87. starplot/styles/fonts-library/inter/Inter-SemiBoldItalic.ttf +0 -0
  88. starplot/styles/fonts-library/inter/LICENSE.txt +92 -0
  89. starplot/styles/fonts.py +15 -0
  90. starplot/styles/helpers.py +93 -0
  91. starplot/styles/markers.py +308 -0
  92. starplot/utils.py +169 -0
  93. starplot/warnings.py +21 -0
  94. starplot-0.20.5.dist-info/METADATA +146 -0
  95. starplot-0.20.5.dist-info/RECORD +98 -0
  96. starplot-0.20.5.dist-info/WHEEL +4 -0
  97. starplot-0.20.5.dist-info/entry_points.txt +3 -0
  98. starplot-0.20.5.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,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