1 /*
2 SPDX-FileCopyrightText: 2021 Valentin Boettcher <hiro at protagon.space; @hiro98:tchncs.de>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include <pybind11/pybind11.h>
8 #include <pybind11/chrono.h>
9 #include "skyobjects/skypoint.h"
10 #include "skymesh.h"
11 #include "cachingdms.h"
12 #include "sqlstatements.cpp"
13 #include "catalogobject.h"
14 #include "catalogsdb.h"
15 #include <iostream>
16
17 using namespace pybind11::literals;
18 namespace py = pybind11;
19 namespace pybind11
20 {
21 namespace detail
22 {
23 template <>
24 struct type_caster<QString>
25 {
26 public:
27 PYBIND11_TYPE_CASTER(QString, _("QString"));
28
loadpybind11::detail::type_caster29 bool load(handle src, bool)
30 {
31 try
32 {
33 value = QString::fromStdString(src.cast<std::string>());
34 }
35 catch (const py::cast_error &)
36 {
37 return false;
38 }
39
40 return !PyErr_Occurred();
41 }
42
castpybind11::detail::type_caster43 static handle cast(QString src, return_value_policy /* policy */, handle /* parent */)
44 {
45 const handle *obj = new py::object(py::cast(src.toUtf8().constData()));
46 return *obj;
47 }
48 };
49
50 template <>
51 struct type_caster<QDateTime>
52 {
53 public:
54 PYBIND11_TYPE_CASTER(QDateTime, _("QDateTime"));
55
loadpybind11::detail::type_caster56 bool load(handle src, bool)
57 {
58 try
59 {
60 value = QDateTime::fromMSecsSinceEpoch(
61 std::chrono::duration_cast<std::chrono::milliseconds>(
62 src.cast<std::chrono::system_clock::time_point>().time_since_epoch())
63 .count());
64 }
65 catch (const py::cast_error &)
66 {
67 return false;
68 }
69
70 return !PyErr_Occurred();
71 }
72
castpybind11::detail::type_caster73 static handle cast(QDateTime src, return_value_policy /* policy */,
74 handle /* parent */)
75 {
76 const handle *obj = new py::object(py::cast(std::chrono::system_clock::time_point(
77 std::chrono::milliseconds(src.currentMSecsSinceEpoch()))));
78 return *obj;
79 }
80 };
81 } // namespace detail
82 } // namespace pybind11
83
84 /**
85 * @struct Indexer
86 * Provides a simple wrapper to generate trixel ids from python code.
87 */
88 struct Indexer
89 {
IndexerIndexer90 Indexer(int level) : m_mesh{ SkyMesh::Create(level) } {};
91
getLevelIndexer92 int getLevel() const { return m_mesh->level(); };
setLevelIndexer93 void setLevel(int level) { m_mesh = SkyMesh::Create(level); };
94
getTrixelIndexer95 int getTrixel(double ra, double dec, bool convert_epoch = false) const
96 {
97 SkyPoint p{ dms(ra), dms(dec) };
98 if (convert_epoch)
99 {
100 p.B1950ToJ2000();
101 p = SkyPoint{ p.ra(), p.dec() }; // resetting ra0, dec0
102 }
103
104 return m_mesh->index(&p);
105 };
106
107 SkyMesh *m_mesh;
108 };
109
110 ///////////////////////////////////////////////////////////////////////////////
111 // PYBIND //
112 ///////////////////////////////////////////////////////////////////////////////
113
114 const CatalogObject DEFAULT_CATALOG_OBJECT{};
115
116 template <typename T>
cast_default(const py::object & value,const T & default_value)117 T cast_default(const py::object &value, const T &default_value)
118 {
119 try
120 {
121 return py::cast<T>(value);
122 }
123 catch (const py::cast_error &)
124 {
125 return default_value;
126 }
127 }
128
PYBIND11_MODULE(pykstars,m)129 PYBIND11_MODULE(pykstars, m)
130 {
131 m.doc() = "Thin bindings for KStars to facilitate trixel indexation from python.";
132
133 py::class_<Indexer>(m, "Indexer")
134 .def(py::init<int>(), "level"_a,
135 "Initializes an `Indexer` with the given `level`.\n"
136 "If the level is greater then approx. 10 the initialization can take some "
137 "time.")
138 .def_property("level", &Indexer::getLevel, &Indexer::setLevel,
139 "Sets the level of the HTMesh/SkyMesh used to index points.")
140 .def(
141 "get_trixel", &Indexer::getTrixel, "ra"_a, "dec"_a, "convert_epoch"_a = false,
142 "Calculates the trixel number from the right ascention and the declination.\n"
143 "The epoch of coordinates is assumed to be J2000.\n\n"
144 "If the epoch is B1950, `convert_epoch` has to be set to `True`.")
145 .def("__repr__", [](const Indexer &indexer) {
146 std::ostringstream lvl;
147 lvl << indexer.getLevel();
148 return "<Indexer level=" + lvl.str() + ">";
149 });
150
151 {
152 using namespace CatalogsDB;
153 py::class_<DBManager>(m, "DBManager")
154 .def(py::init([](const std::string &filename) {
155 return new DBManager(QString::fromStdString(filename));
156 }),
157 "filename"_a)
158 .def(
159 "register_catalog",
160 [](DBManager &self, const py::dict &cat) {
161 return self.register_catalog(
162 py::cast<int>(cat["id"]), py::cast<QString>(cat["name"]),
163 py::cast<bool>(cat["mut"]), py::cast<bool>(cat["enabled"]),
164 py::cast<double>(cat["precedence"]),
165 py::cast<QString>(cat["author"]),
166 py::cast<QString>(cat["source"]),
167 py::cast<QString>(cat["description"]),
168 py::cast<int>(cat["version"]), py::cast<QString>(cat["color"]),
169 py::cast<QString>(cat["license"]),
170 py::cast<QString>(cat["maintainer"]),
171 py::cast<QDateTime>(cat["timestamp"]));
172 },
173 "catalog"_a)
174 .def(
175 "update_catalog_meta",
176 [](DBManager &self, const py::dict &cat) {
177 return self.update_catalog_meta(
178 { py::cast<int>(cat["id"]), py::cast<QString>(cat["name"]),
179 py::cast<double>(cat["precedence"]),
180 py::cast<QString>(cat["author"]),
181 py::cast<QString>(cat["source"]),
182 py::cast<QString>(cat["description"]),
183 py::cast<bool>(cat["mut"]), py::cast<bool>(cat["enabled"]),
184 py::cast<int>(cat["version"]), py::cast<QString>(cat["color"]),
185 py::cast<QString>(cat["license"]),
186 py::cast<QString>(cat["maintainer"]),
187 py::cast<QDateTime>(cat["timestamp"]) });
188 },
189 "catalog"_a)
190 .def("__repr__",
191 [](const DBManager &manager) {
192 return QString("<DBManager filename=\"" + manager.db_file_name() +
193 "\">");
194 })
195 .def("update_catalog_views", &DBManager::update_catalog_views)
196 .def("compile_master_catalog", &DBManager::compile_master_catalog)
197 .def("dump_catalog", &DBManager::dump_catalog, "catalog_id"_a, "file_path"_a)
198 .def("import_catalog", &DBManager::import_catalog, "file_path"_a,
199 "overwrite"_a)
200 .def("remove_catalog", &DBManager::remove_catalog, "catalog_id"_a);
201
202 py::register_exception<DatabaseError>(m, "DatabaseError");
203 }
204
205 py::enum_<SkyObject::TYPE>(m, "ObjectType", "The types of CatalogObjects",
206 py::arithmetic())
207 .value("STAR", SkyObject::STAR)
208 .value("CATALOGSTAR", SkyObject::CATALOG_STAR)
209 .value("PLANET", SkyObject::TYPE::PLANET)
210 .value("OPEN_CLUSTER", SkyObject::TYPE::OPEN_CLUSTER)
211 .value("GLOBULAR_CLUSTER", SkyObject::TYPE::GLOBULAR_CLUSTER)
212 .value("GASEOUS_NEBULA", SkyObject::TYPE::GASEOUS_NEBULA)
213 .value("PLANETARY_NEBULA", SkyObject::TYPE::PLANETARY_NEBULA)
214 .value("SUPERNOVA_REMNANT", SkyObject::TYPE::SUPERNOVA_REMNANT)
215 .value("GALAXY", SkyObject::TYPE::GALAXY)
216 .value("COMET", SkyObject::TYPE::COMET)
217 .value("ASTEROID", SkyObject::TYPE::ASTEROID)
218 .value("CONSTELLATION", SkyObject::TYPE::CONSTELLATION)
219 .value("MOON", SkyObject::TYPE::MOON)
220 .value("ASTERISM", SkyObject::TYPE::ASTERISM)
221 .value("GALAXY_CLUSTER", SkyObject::TYPE::GALAXY_CLUSTER)
222 .value("DARK_NEBULA", SkyObject::TYPE::DARK_NEBULA)
223 .value("QUASAR", SkyObject::TYPE::QUASAR)
224 .value("MULT_STAR", SkyObject::TYPE::MULT_STAR)
225 .value("RADIO_SOURCE", SkyObject::TYPE::RADIO_SOURCE)
226 .value("SATELLITE", SkyObject::TYPE::SATELLITE)
227 .value("SUPERNOVA", SkyObject::TYPE::SUPERNOVA)
228 .value("NUMBER_OF_KNOWN_TYPES", SkyObject::TYPE::NUMBER_OF_KNOWN_TYPES)
229 .value("TYPE_UNKNOWN", SkyObject::TYPE::TYPE_UNKNOWN)
230 .export_values();
231
232 m.def(
233 "get_id",
234 [](const py::dict &obj) -> py::bytes {
235 return CatalogObject::getId(
236 static_cast<SkyObject::TYPE>(py::cast<int>(obj["type"])),
237 py::cast<double>(obj["ra"]), py::cast<double>(obj["dec"]),
238 py::cast<QString>(obj["name"]),
239 py::cast<QString>(obj["catalog_identifier"]))
240 .toStdString();
241 },
242 "object"_a,
243 R"(
244 Calculate the id of an object.
245
246 Parameters
247 ----------)");
248
249 ///////////////////////////////////////////////////////////////////////////
250 // Sql Statements //
251 ///////////////////////////////////////////////////////////////////////////
252 auto s = m.def_submodule("sqlstatements");
253 {
254 using namespace CatalogsDB::SqlStatements;
255
256 s.doc() = "Assorted sql statements to modify the catalog database.";
257
258 s.def("insert_dso", &insert_dso, "catalog_id"_a);
259 s.def("create_catalog_table", &create_catalog_table, "catalog_id"_a);
260
261 #define ATTR(name) \
262 { \
263 s.attr(#name) = name; \
264 }
265 ATTR(create_catalog_list_table);
266 ATTR(insert_catalog);
267 ATTR(get_catalog_by_id);
268 ATTR(all_catalog_view);
269 ATTR(master_catalog);
270 ATTR(dso_by_name);
271 #undef ATTR
272 }
273 }
274