astrolabium 0.1.0__tar.gz

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 (33) hide show
  1. astrolabium-0.1.0/LICENSE +21 -0
  2. astrolabium-0.1.0/PKG-INFO +18 -0
  3. astrolabium-0.1.0/astrolabium/__init__.py +3 -0
  4. astrolabium-0.1.0/astrolabium/config.py +5 -0
  5. astrolabium-0.1.0/astrolabium/converters/__init__.py +2 -0
  6. astrolabium-0.1.0/astrolabium/converters/iau.py +186 -0
  7. astrolabium-0.1.0/astrolabium/creator/__init__.py +5 -0
  8. astrolabium-0.1.0/astrolabium/creator/catalogue_creator.py +173 -0
  9. astrolabium-0.1.0/astrolabium/creator/galaxy.py +32 -0
  10. astrolabium-0.1.0/astrolabium/creator/star.py +129 -0
  11. astrolabium-0.1.0/astrolabium/creator/system.py +64 -0
  12. astrolabium-0.1.0/astrolabium/creator/wds_analyser.py +292 -0
  13. astrolabium-0.1.0/astrolabium/fileIO.py +89 -0
  14. astrolabium-0.1.0/astrolabium/parsers/__init__.py +6 -0
  15. astrolabium-0.1.0/astrolabium/parsers/data/__init__.py +7 -0
  16. astrolabium-0.1.0/astrolabium/parsers/data/entry_base.py +138 -0
  17. astrolabium-0.1.0/astrolabium/parsers/data/hip_entry.py +76 -0
  18. astrolabium-0.1.0/astrolabium/parsers/data/orb6_entry.py +120 -0
  19. astrolabium-0.1.0/astrolabium/parsers/data/text.py +307 -0
  20. astrolabium-0.1.0/astrolabium/parsers/data/wds_entry.py +88 -0
  21. astrolabium-0.1.0/astrolabium/parsers/data/wikidata_star.py +240 -0
  22. astrolabium-0.1.0/astrolabium/parsers/gliese_parser.py +47 -0
  23. astrolabium-0.1.0/astrolabium/parsers/hip_parser.py +85 -0
  24. astrolabium-0.1.0/astrolabium/parsers/orb6_parser.py +51 -0
  25. astrolabium-0.1.0/astrolabium/parsers/parser_base.py +132 -0
  26. astrolabium-0.1.0/astrolabium/parsers/wds_parser.py +55 -0
  27. astrolabium-0.1.0/astrolabium/queries/__init__.py +2 -0
  28. astrolabium-0.1.0/astrolabium/queries/gaia.py +119 -0
  29. astrolabium-0.1.0/astrolabium/queries/simbad.py +132 -0
  30. astrolabium-0.1.0/astrolabium/queries/wiki_entities.py +143 -0
  31. astrolabium-0.1.0/astrolabium/queries/wikidata.py +549 -0
  32. astrolabium-0.1.0/astrolabium/table.py +19 -0
  33. astrolabium-0.1.0/pyproject.toml +32 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Vindemiatrix Collective
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.3
2
+ Name: astrolabium
3
+ Version: 0.1.0
4
+ Summary: Stellar catalogue parser and creator
5
+ License: MIT
6
+ Keywords: astronomy,cosmology,space,science,stars,star,double,binary
7
+ Author: Vindemiatrix Collective
8
+ Author-email: vindemiatrixcollective@gmail.com
9
+ Requires-Python: >=3.12
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Requires-Dist: SPARQLWrapper
15
+ Requires-Dist: anytree
16
+ Requires-Dist: astropy
17
+ Requires-Dist: astroquery
18
+ Requires-Dist: tqdm
@@ -0,0 +1,3 @@
1
+ import astrolabium.config
2
+ import astrolabium.fileIO
3
+ import astrolabium.table as Table
@@ -0,0 +1,5 @@
1
+ path_outdir = "out"
2
+ path_temp = "temp"
3
+ path_cataloguedir = "catalogues"
4
+ path_datadir = "data"
5
+ path_entitiesdir = "entities"
@@ -0,0 +1,2 @@
1
+ from .hip import *
2
+ from ..creator.star import Star
@@ -0,0 +1,186 @@
1
+ import astrolabium.config as config
2
+ import astrolabium.queries.wikidata as wiki
3
+ import astrolabium.queries.simbad as simbad
4
+ import astrolabium.fileIO as io
5
+ import time
6
+ import json
7
+ import unicodedata
8
+ from tqdm import tqdm
9
+
10
+ __missing_ids = {"TrES-3": "Q1107722"}
11
+ __missing_names = {""}
12
+ # __iau_data = io.read_dict_json(f"{config.path_cataloguedir}/IAU-CSN")
13
+
14
+
15
+ def find_catalogue_ids(n=0):
16
+ starDict = dict()
17
+ jsonData = io.read_dict_json(f"{config.path_cataloguedir}/IAU-CSN")
18
+ if n == 0:
19
+ n = len(jsonData)
20
+ for i, entry in enumerate(jsonData[:n]):
21
+ name = entry["Name"]
22
+ hip = entry["HIP"]
23
+ if hip != "_":
24
+ print(f"[{i + 1}/{n}] Parsing {name}")
25
+ starDict[name] = hip
26
+ else:
27
+ print(f"[{i + 1}/{n}] No HIP for {name}")
28
+
29
+ return starDict
30
+
31
+
32
+ def find_entries_without_catalogue_ids(catalog="HIP"):
33
+ starDict = dict()
34
+ n = len(__iau_data)
35
+ counter = 1
36
+ for i, entry in enumerate(__iau_data[:n]):
37
+ hip = entry[catalog]
38
+ if hip != "_":
39
+ continue
40
+ label = entry["Designation"]
41
+ print(f"[{counter}/{i + 1}]: {label}")
42
+ counter += 1
43
+ starDict[label] = entry
44
+ qid_list = []
45
+
46
+ for key, value in tqdm(starDict.items(), colour="GREEN"):
47
+ name = value["Name"]
48
+ qid = __parse_entities(key, name)
49
+ if qid is not None:
50
+ qid_list.append(qid)
51
+ else:
52
+ if key in __missing_ids:
53
+ qid = __missing_ids[key]
54
+ else:
55
+ qid = __parse_entities(key, name, False)
56
+
57
+ if qid is not None:
58
+ qid_list.append(qid)
59
+ else:
60
+ tqdm.write(f">>> Cannot find {key}: {name}")
61
+ time.sleep(0.250)
62
+ return qid_list
63
+
64
+
65
+ def __parse_entities(key: str, name: str, use_key=True):
66
+ starFound = False
67
+ try:
68
+ for entity in wiki.get_entity_from_name(key if use_key else name):
69
+ qid = entity["id"]
70
+ starFound = __is_entity_star(entity, key, name, qid)
71
+ if starFound:
72
+ return qid
73
+ if not starFound:
74
+ return None
75
+ except TypeError as e:
76
+ return None
77
+
78
+
79
+ def __is_entity_star(entity, key, name, qid):
80
+ if "description" not in entity:
81
+ tqdm.write(f"Looking for <{key}/{name}> cannot parse qid:{qid}")
82
+ return False
83
+ desc = entity["description"].lower()
84
+ tokens = ["star", "gamma ray", "pulsar"]
85
+ if any(x in desc for x in tokens):
86
+ tqdm.write(f"Looking for <{key}/{name}>, found qid: {qid} is a star")
87
+ return True
88
+ else:
89
+ return False
90
+
91
+
92
+ def iau_to_wiki(out_filename=f"{config.path_outdir}/IAU-CSN"):
93
+ iau_ids = find_catalogue_ids()
94
+ print("\nRequesting qids from Wikidata\n...")
95
+ iau_in_cat = wiki.get_qids_from_catalogue_entries("Q537199", "HIP", iau_ids.values())
96
+ iau_not_cat = find_entries_without_catalogue_ids()
97
+ iau_qids = [item["qid"] for item in iau_in_cat] + iau_not_cat
98
+ n = len(iau_qids)
99
+ print(f" > Found {len(iau_in_cat) + len(iau_not_cat)} / {n} matching entities")
100
+
101
+ iau_entries = wiki.get_entities_batch(iau_qids, batch_size=50)
102
+
103
+ io.write_dict_json(iau_entries, out_filename)
104
+ print(f"\n{n} Wikidata entities written to {config.path_outdir}/{out_filename}.json")
105
+
106
+
107
+ def normalise_names(filename=f"{config.path_outdir}/IAU-CSN"):
108
+ print("Normalising names in IAU entities")
109
+ name_key = "Name/Diacritics"
110
+ iau_entities = io.read_dict_json(filename)
111
+ for i, (key, value) in tqdm(enumerate(iau_entities.items())):
112
+ if "name" in value:
113
+ name = value["name"]
114
+ using_name = True
115
+ else:
116
+ name = value["id"]
117
+ using_name = False
118
+
119
+ catalogue_id = value["cat"]["hip"] if "hip" in value["cat"] else list(value["cat"])[0]
120
+ catalogue_id = (
121
+ catalogue_id.split(" ")[1] if catalogue_id.startswith("HIP") or catalogue_id.startswith("HD") else None
122
+ )
123
+
124
+ iau_match = [star for star in __iau_data if star[name_key] == name]
125
+ if len(iau_match) > 0:
126
+ value["name"] = name
127
+ tqdm.write(f"{key}: {name} matches")
128
+ continue
129
+ else:
130
+ if using_name:
131
+ iau_match = [star for star in __iau_data if star[name_key] == value["id"]]
132
+ if len(iau_match) > 0:
133
+ value["name"] = value["id"]
134
+ tqdm.write(f"{key}: {value['id']} matches")
135
+ continue
136
+
137
+ if catalogue_id is not None:
138
+ iau_match = [star for star in __iau_data if star["HIP"] == catalogue_id or star["HD"] == catalogue_id]
139
+ if len(iau_match) > 0:
140
+ iau_name = iau_match[0][name_key]
141
+ tqdm.write(f">>> {key}: {name} does not match with {iau_name}, using IAU name")
142
+ value["name"] = iau_name
143
+ continue
144
+
145
+ iau_match = [star for star in __iau_data if star["Designation"] == name]
146
+ if len(iau_match) > 0:
147
+ iau_name = iau_match[0][name_key]
148
+ tqdm.write(f">>> {key}: {name} does not match with {iau_name}, using IAU name")
149
+ value["name"] = iau_name
150
+ continue
151
+
152
+ tqdm.write(f">>> {key}: {name} no name found")
153
+ raise
154
+ io.write_dict_json(iau_entities, filename)
155
+ print("All stars matched!")
156
+
157
+
158
+ def add_simbad_otypes(filename=f"{config.path_outdir}/IAU-CSN"):
159
+ print("Adding otypes from Simbad")
160
+ iau_entities = io.read_dict_json(filename)
161
+
162
+ iau_ids = []
163
+ iau_map = {}
164
+ valid_cat = ["hip", "tyc", "gaia_dr3", "PSR"]
165
+ for i, (key, entity) in enumerate(iau_entities.items()):
166
+ catalog = entity["cat"]
167
+ iau_id = None
168
+ for cat_type in list(catalog.items()):
169
+ if cat_type[0] in valid_cat:
170
+ iau_id = f"{cat_type[0].upper().replace('_', ' ')} {cat_type[1]}"
171
+ iau_map[key] = iau_id
172
+ iau_ids.append(iau_id)
173
+ break
174
+
175
+ if iau_id is None:
176
+ raise ValueError(f"Catalog ids for {key}: {catalog}")
177
+
178
+ results = simbad.get_object_ids(iau_ids)
179
+ for result in tqdm(results):
180
+ iau_map[result["id"]] = result["otypes"]
181
+ tqdm.write(json.dumps(result))
182
+
183
+ res_ids = [x["id"] for x in results]
184
+ diff = set(iau_ids) - set(res_ids)
185
+ print(diff)
186
+ io.write_list_json(diff, filename + "_missing_simbad_otype")
@@ -0,0 +1,5 @@
1
+ from .star import Star
2
+ from .system import System
3
+ from .galaxy import Galaxy
4
+ from .wds_analyser import WDSAnalyser
5
+ from .catalogue_creator import CatalogueCreator
@@ -0,0 +1,173 @@
1
+ from astrolabium import fileIO as io, config
2
+ from astrolabium.parsers import HipparcosParser, WDSParser, Orb6Parser
3
+ from astrolabium.parsers.data import WikidataStar, Text
4
+ from astrolabium.catalogues import Hipparcos, WDS, Crossref, filters
5
+ from astrolabium.queries import WikiEntities, gaia
6
+ from astrolabium.creator import WDSAnalyser, Star, System, Galaxy
7
+ import logging
8
+ from typing import Callable
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class CatalogueCreator:
13
+ def __init__(
14
+ self,
15
+ lyr=100,
16
+ catalogue_path=f"{config.path_datadir}/hipparcos2007",
17
+ entities_path=f"{config.path_entitiesdir}/hipparcos2007_entities",
18
+ verbose=False,
19
+ ):
20
+ self.verbose = verbose
21
+ self.lyr = lyr
22
+ self.catalogue_path = catalogue_path
23
+ self.entities_path = entities_path
24
+ self.iau_entities_path = f"{config.path_entitiesdir}/IAU_entities"
25
+ self.output_catalogue_path = f"{config.path_outdir}/catalogue_{self.lyr}_ly"
26
+ self._use_entities = True
27
+ logging.basicConfig(format="%(message)s")
28
+ logging.root.setLevel(logging.INFO)
29
+ logger.info("Astrolabium v0.1.0 by Vindemiatrix Collective (https://vindemiatrixcollective.com)\n")
30
+ io.create_directory(config.path_entitiesdir)
31
+ io.create_directory(config.path_datadir)
32
+ io.create_directory(config.path_temp)
33
+ io.create_directory(config.path_outdir)
34
+ if (not io.file_exists(entities_path)):
35
+ self._use_entities = False
36
+ pass
37
+
38
+ @classmethod
39
+ def download_and_parse_catalogues(cls):
40
+ io.create_directory(config.path_cataloguedir)
41
+ stars = HipparcosParser.run()
42
+ WDSParser.run()
43
+ Orb6Parser.run()
44
+ hip = Hipparcos(catalogue_data=stars)
45
+ Crossref.build_table("HIP", [str(x.HIP) for x in hip.entries], Crossref.catalogue_labels, save=True)
46
+
47
+ def find_star_systems(self, crossref_filters: list[Callable] = None, rebuild=False) -> list[System]:
48
+ hip_stars_path = f"{config.path_temp}/catalogue_stars_{self.lyr}_ly"
49
+ systems_within_distance = io.read_list_json(hip_stars_path)
50
+ if systems_within_distance is None or len(systems_within_distance) == 0 or rebuild:
51
+ logger.info(f"Filtering star systems within {self.lyr} ly")
52
+
53
+ if crossref_filters is None:
54
+ crossref_filters = [
55
+ lambda star: filters.distance(star, self.lyr),
56
+ lambda star: filters.any_catalogues(star, ["b", "fl", "Name"]),
57
+ ]
58
+ crossref = Crossref()
59
+ hipparcos = Hipparcos()
60
+ systems_within_distance = crossref.query_filters(hipparcos.entries, "HIP", crossref_filters, hip_stars_path)
61
+
62
+ if systems_within_distance is None or len(systems_within_distance) == 0:
63
+ raise ValueError("No single stars found: are the filters too stringent?")
64
+
65
+ logger.info(f"Found {len(systems_within_distance)} / {Hipparcos.num_entries()} stars within {self.lyr} lyr")
66
+
67
+ systems = []
68
+ for entry in systems_within_distance:
69
+ if "WDS" in entry:
70
+ continue
71
+ star = Star(catalogue_entry=entry, crossref=entry)
72
+ name = Text.classic_system_name(entry)
73
+ systems.append(System(name, star))
74
+
75
+ logger.info(f" of which {len(systems)} are single star systems")
76
+ logger.info(f" and {len(systems_within_distance) - len(systems)} are potential multiple star systems")
77
+
78
+ return systems
79
+
80
+ def find_multiple_systems(self, rebuild=False):
81
+ wds_stars_path = f"{config.path_temp}/wds_stars_{self.lyr}_ly"
82
+ mult_systems = io.read_list_json(wds_stars_path)
83
+
84
+ if mult_systems is None or len(mult_systems) == 0 or rebuild:
85
+ hipparcos = Hipparcos()
86
+ wds = WDS()
87
+ query_filters = [
88
+ lambda star: filters.distance(star, self.lyr),
89
+ lambda star: filters.any_catalogues(star, ["WDS"]),
90
+ lambda star: filters.any_catalogues(star, ["b", "fl", "Name"]),
91
+ lambda star: filters.wds_is_physical(star, wds),
92
+ ]
93
+ crossref = Crossref()
94
+ mult_systems = crossref.query_filters(hipparcos.entries, "HIP", query_filters, wds_stars_path)
95
+
96
+ if mult_systems is None or len(mult_systems) == 0:
97
+ raise ValueError("No multiple stars found: are the filters too stringent?")
98
+ logger.info("Analysing multiple star systems")
99
+ analysis = WDSAnalyser(crossref_data=mult_systems, verbose=self.verbose)
100
+
101
+ systems = analysis.analyse()
102
+ return systems
103
+
104
+ def __match_systems_to_entities(self, systems: list[System], save=True):
105
+ entities_path = f"{config.path_temp}/catalogue_{self.lyr}_ly_entities"
106
+ wiki_entities = io.read_dict_json(entities_path)
107
+
108
+ catalogue_ids = []
109
+ for system in systems:
110
+ catalogue_ids += system.orbiters_catalogue_ids
111
+
112
+ if not wiki_entities:
113
+ wiki_entities = io.read_dict_json(self.entities_path)
114
+ if not wiki_entities:
115
+ logger.warning("Wikidata entity file not available, retrieving from wikidata")
116
+ wiki_entities = WikiEntities.retrieve_entities_from_file(catalogue_ids, self.entities_path)
117
+ wikiCatalog = WikiEntities(wiki_entities)
118
+ wikiCatalog.filter(lambda e: f"HIP {e.cat['hip']}" in catalogue_ids, "> Selecting stars in Hipparcos 2007 catalogue")
119
+ if save:
120
+ wikiCatalog.save(entities_path)
121
+ else:
122
+ wikiCatalog = WikiEntities(wiki_entities)
123
+
124
+ wikiCatalog.add(self.get_stars_from_IAU())
125
+ logger.info(f"> HIP: found {wikiCatalog.count} wikidata entities within {self.lyr} lyr distance")
126
+ wikiCatalog.build_index("HIP")
127
+
128
+ for system in systems:
129
+ for star in system.preorder_visit():
130
+ entity = wikiCatalog.query(star.id)
131
+ if entity is not None:
132
+ star.add_properties(entity)
133
+
134
+ def update_names_from_IAU(self, table, namekey="Name"):
135
+ # TODO: ensure IAU names overwrite names we might get from other sources
136
+ raise NotImplementedError("Todo")
137
+ iau_data = io.read_list_json(f"{config.path_datadir}/IAU-CSN.json")
138
+
139
+ def get_stars_from_IAU(self) -> list[WikidataStar]:
140
+ iau = WikiEntities(io.read_dict_json(self.iau_entities_path))
141
+
142
+ iau.filter(
143
+ lambda e: "hip" not in e.cat,
144
+ "> Adding missing star from IAU",
145
+ )
146
+ iau.filter(lambda e: WikiEntities.filter_distance(e, self.lyr), f"> Removing stars farther than {self.lyr} ly")
147
+ logger.warning(f"> Found {iau.count} stars missing from HIP catalogue")
148
+ return iau.values
149
+
150
+ def update_from_gaia(self, stars: dict[str, "Star"]):
151
+ stars_dr3 = {star.dr3: star for key, star in stars.items() if hasattr(star, "dr3")}
152
+ source_ids = list(stars_dr3.keys())
153
+ if len(source_ids) == 0:
154
+ source_ids = [2067518817314952576, 3211922645854328832, 66529975427235712]
155
+ data = gaia.retrieve_data(source_ids)
156
+ gaia.update_data(stars_dr3, data)
157
+
158
+ def create_galaxy(self, rebuild=False, match_to_wikidata_entities=True, save=True):
159
+ single_stars = self.find_star_systems(rebuild=rebuild)
160
+ multiple_stars = self.find_multiple_systems(rebuild=rebuild)
161
+ total_systems = single_stars + multiple_stars
162
+
163
+ if not self._use_entities and match_to_wikidata_entities:
164
+ logger.warning("No Wikidata entities file found. Download it from the github and place it in your entities/ folder.")
165
+ elif match_to_wikidata_entities:
166
+ self.__match_systems_to_entities(total_systems)
167
+
168
+ logger.info("Catalogue creation complete")
169
+ galaxy = Galaxy(total_systems)
170
+ if save:
171
+ galaxy.save(self.output_catalogue_path)
172
+ logger.info(f"Saved to {self.output_catalogue_path}")
173
+ return galaxy
@@ -0,0 +1,32 @@
1
+ from astrolabium.creator import System, Star
2
+ from astrolabium import fileIO as io
3
+ import json
4
+ from typing import Any
5
+
6
+
7
+ class Galaxy:
8
+ def __init__(self, systems: list[System]):
9
+ self.Name = "Milky Way"
10
+ self.systems: dict[str, System] = {}
11
+
12
+ for system in systems:
13
+ self.systems[system.Name] = system
14
+ pass
15
+
16
+ @property
17
+ def count(self) -> int:
18
+ return len(self.systems)
19
+
20
+ def save(self, out_filename: str):
21
+ json_galaxy = {}
22
+
23
+ json_galaxy["Name"] = self.Name
24
+ json_galaxy["Systems"] = {name: system.to_dict() for name, system in self.systems.items()}
25
+
26
+ io.write_text_json(json.dumps(json_galaxy, indent=2), out_filename)
27
+
28
+ def add_systems(self, systems: list[System]):
29
+ self.systems = self.systems | {system.Name: system for system in systems}
30
+
31
+ def select(self, system_name: str) -> System | None:
32
+ return self.systems.get(system_name)
@@ -0,0 +1,129 @@
1
+ from astrolabium.parsers.data import EntryBase, HipparcosEntry, Orb6Entry, WikidataStar
2
+ from astropy import units as u
3
+ from astropy.coordinates import SkyCoord, ICRS, Galactic
4
+ from tqdm import tqdm
5
+ from typing import Any, Tuple
6
+ import math
7
+
8
+
9
+ class Star(EntryBase):
10
+ key_settings = [
11
+ ["id", [], None, None, None],
12
+ ["Name", [], None, None, None],
13
+ ["ra", [], lambda v: float(v), u.rad, 6],
14
+ ["dec", [], lambda v: float(v), u.rad, 6],
15
+ ["sc", [], None, None, None],
16
+ ["d", [], lambda v: float(v), u.pc, 6],
17
+ ["a", [], lambda v: float(v), u.AU, 6],
18
+ ["e", [], lambda v: float(v), None, 3],
19
+ ["P", [], lambda v: float(v), u.yr, 3],
20
+ ["i", [], lambda v: float(v), u.deg, 3],
21
+ ["lan", [], lambda v: float(v), u.deg, 3],
22
+ ["argp", [], lambda v: float(v), u.deg, 3],
23
+ ]
24
+
25
+ def __init__(self, catalogue_entry: EntryBase | None = None, orbit: Orb6Entry | None = None, crossref: Any | None = None):
26
+ self.id: str | None = None
27
+ self.Name: str | None = None
28
+ self.a: u.Quantity | None = None
29
+ self.d: u.Quantity | None = None
30
+ self.sc: str | None = None
31
+ self.Orbiters: dict[str, Star | None] = {}
32
+
33
+ data = {}
34
+ if orbit is not None and isinstance(orbit, Orb6Entry):
35
+ data = orbit.to_dict()
36
+ data["a"] = orbit.calculate_sma_AU(catalogue_entry.d)
37
+ # assuming lpa (Longitude of the Periastron ϖ) refers to the argument of the periastron instead (ω)
38
+ data["argp"] = data["lpa"]
39
+ del data["lpa"]
40
+
41
+ if crossref is not None:
42
+ data = data | crossref
43
+ if "st" in crossref and crossref["st"] != "":
44
+ data["sc"] = crossref["st"]
45
+ if catalogue_entry is None and "Name" in crossref:
46
+ del data["Name"]
47
+
48
+ if catalogue_entry is not None:
49
+ if not isinstance(catalogue_entry, HipparcosEntry):
50
+ catalogue_entry = HipparcosEntry(catalogue_entry)
51
+ data = data | catalogue_entry.to_dict()
52
+ data["id"] = catalogue_entry.id
53
+ # renaming de_clination to dec_lination
54
+ data["dec"] = data["de"]
55
+ data["d"] = catalogue_entry.d
56
+
57
+ self._parse_values(self.key_settings, data)
58
+
59
+ def to_dict(self):
60
+ json = {}
61
+ json["Id"] = self.extract_value("id", None, None)
62
+ json["Name"] = self.extract_value("Name", None, None)
63
+ json["SC"] = self.extract_value("sc", None, None)
64
+
65
+ physicalData = {
66
+ "l": self.extract_value("l", 6, u.L_sun),
67
+ "m": self.extract_value("m", 6, u.M_sun),
68
+ "t": self.extract_value("t", 6, u.K),
69
+ "g": self.extract_value("g", 6, u.Unit("cm/s**2")),
70
+ "age": self.extract_value("age", 6, u.Gyr),
71
+ }
72
+ json["PhysicalData"] = {k: v for k, v in physicalData.items() if v}
73
+
74
+ orbitalData = {
75
+ "a": self.extract_value("a", 6, u.AU),
76
+ "P": self.extract_value("P", 6, u.yr),
77
+ "e": self.extract_value("e", 6, None),
78
+ "i": self.extract_value("i", 3, u.deg),
79
+ "lan": self.extract_value("lan", 3, u.deg),
80
+ "argp": self.extract_value("argp", 3, u.deg),
81
+ }
82
+ json["OrbitalData"] = {k: v for k, v in orbitalData.items() if v}
83
+
84
+ orbiters = {}
85
+ for key, star in self.Orbiters.items():
86
+ if star is not None:
87
+ orbiters[key] = star.to_dict()
88
+
89
+ json["Orbiters"] = orbiters
90
+
91
+ return {k: v for k, v in json.items() if v}
92
+
93
+ def add_properties(self, data: WikidataStar, properties: list[str] = ["l", "m", "t", "g", "age"]):
94
+ for prop in properties:
95
+ value = getattr(data, prop, None)
96
+ if value is not None:
97
+ setattr(self, prop, value)
98
+
99
+ def to_string(self, indent_spaces=3):
100
+ (x, y, z) = self.xyz
101
+ string = super().to_string(indent_spaces)
102
+ string += f"{self._get_indent(indent_spaces)}x, y, z: {x}, {y}, {z}"
103
+ return string
104
+
105
+ @property
106
+ def gc(self):
107
+ assert hasattr(self, "ra"), "Missing ra"
108
+ assert hasattr(self, "dec"), "Missing dec"
109
+ gc = SkyCoord(ra=self.ra, dec=self.dec, distance=self.d if self.d > 0 else None, frame=ICRS)
110
+ return gc.transform_to(Galactic)
111
+
112
+ @property
113
+ def xyz(self) -> tuple[float, float, float]:
114
+ assert self.d is not None, f"{self.id}: missing distance"
115
+ gc = self.gc
116
+ l = gc.l.to(u.rad)
117
+ b = gc.b.to(u.rad)
118
+ d = self.d.to(u.pc)
119
+ x = d.value * math.cos(b.value) * math.cos(l.value)
120
+ y = d.value * math.cos(b.value) * math.sin(l.value)
121
+ z = d.value * math.sin(b.value)
122
+ return (x, y, z)
123
+
124
+ @classmethod
125
+ def preorder_visit(cls, star: "Star"):
126
+ yield star
127
+ for orbiter in star.Orbiters.values():
128
+ if orbiter is not None:
129
+ yield from Star.preorder_visit(orbiter)
@@ -0,0 +1,64 @@
1
+ from typing import Any
2
+ from astrolabium.creator import Star
3
+ import astropy.units as u
4
+
5
+
6
+ class System:
7
+ def __init__(self, name: str, primary: Star = None):
8
+ self.Name = name
9
+ self.Orbiters: dict[str, Star] = {}
10
+ if primary is not None:
11
+ self.Orbiters["A"] = primary
12
+ self.gc: str | None = None
13
+
14
+ @property
15
+ def primary(self) -> Star:
16
+ return self.Orbiters["A"]
17
+
18
+ @property
19
+ def coordinates(self) -> str:
20
+ return self.__coord
21
+
22
+ @coordinates.setter
23
+ def coordinates(self, value: str):
24
+ self.__coord = value
25
+
26
+ def to_dict(self) -> dict:
27
+ json: dict[str, Any] = {}
28
+ json["Name"] = self.Name
29
+
30
+ x, y, z = self.primary.xyz
31
+ self.gc = f"{round(float(x), 6)}, {round(float(y), 6)}, {round(float(z), 6)}"
32
+
33
+ orbiters = {}
34
+
35
+ for key, star in self.Orbiters.items():
36
+ orbiters[key] = star.to_dict()
37
+
38
+ json["Orbiters"] = orbiters
39
+ json["c"] = self.gc
40
+
41
+ return json
42
+
43
+ def __iter__(self):
44
+ return self
45
+
46
+ def preorder_visit(self):
47
+ for star in self.Orbiters.values():
48
+ if star is not None:
49
+ yield from Star.preorder_visit(star)
50
+
51
+ @property
52
+ def orbiters_catalogue_ids(self) -> list[str]:
53
+ ids = []
54
+ for star in self.preorder_visit():
55
+ if star.id is not None:
56
+ ids.append(star.id)
57
+ return ids
58
+
59
+ @property
60
+ def stars(self) -> list[Star]:
61
+ stars = []
62
+ for star in self.preorder_visit():
63
+ stars.append(star)
64
+ return stars