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