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.
- astrolabium-0.1.0/LICENSE +21 -0
- astrolabium-0.1.0/PKG-INFO +18 -0
- astrolabium-0.1.0/astrolabium/__init__.py +3 -0
- astrolabium-0.1.0/astrolabium/config.py +5 -0
- astrolabium-0.1.0/astrolabium/converters/__init__.py +2 -0
- astrolabium-0.1.0/astrolabium/converters/iau.py +186 -0
- astrolabium-0.1.0/astrolabium/creator/__init__.py +5 -0
- astrolabium-0.1.0/astrolabium/creator/catalogue_creator.py +173 -0
- astrolabium-0.1.0/astrolabium/creator/galaxy.py +32 -0
- astrolabium-0.1.0/astrolabium/creator/star.py +129 -0
- astrolabium-0.1.0/astrolabium/creator/system.py +64 -0
- astrolabium-0.1.0/astrolabium/creator/wds_analyser.py +292 -0
- astrolabium-0.1.0/astrolabium/fileIO.py +89 -0
- astrolabium-0.1.0/astrolabium/parsers/__init__.py +6 -0
- astrolabium-0.1.0/astrolabium/parsers/data/__init__.py +7 -0
- astrolabium-0.1.0/astrolabium/parsers/data/entry_base.py +138 -0
- astrolabium-0.1.0/astrolabium/parsers/data/hip_entry.py +76 -0
- astrolabium-0.1.0/astrolabium/parsers/data/orb6_entry.py +120 -0
- astrolabium-0.1.0/astrolabium/parsers/data/text.py +307 -0
- astrolabium-0.1.0/astrolabium/parsers/data/wds_entry.py +88 -0
- astrolabium-0.1.0/astrolabium/parsers/data/wikidata_star.py +240 -0
- astrolabium-0.1.0/astrolabium/parsers/gliese_parser.py +47 -0
- astrolabium-0.1.0/astrolabium/parsers/hip_parser.py +85 -0
- astrolabium-0.1.0/astrolabium/parsers/orb6_parser.py +51 -0
- astrolabium-0.1.0/astrolabium/parsers/parser_base.py +132 -0
- astrolabium-0.1.0/astrolabium/parsers/wds_parser.py +55 -0
- astrolabium-0.1.0/astrolabium/queries/__init__.py +2 -0
- astrolabium-0.1.0/astrolabium/queries/gaia.py +119 -0
- astrolabium-0.1.0/astrolabium/queries/simbad.py +132 -0
- astrolabium-0.1.0/astrolabium/queries/wiki_entities.py +143 -0
- astrolabium-0.1.0/astrolabium/queries/wikidata.py +549 -0
- astrolabium-0.1.0/astrolabium/table.py +19 -0
- 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,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,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
|