File indexing completed on 2024-04-28 03:44:40

0001 /*
0002     SPDX-FileCopyrightText: 2021 Valentin Boettcher <hiro at protagon.space; @hiro98:tchncs.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include <pybind11/pybind11.h>
0008 #include <pybind11/chrono.h>
0009 #include "skyobjects/skypoint.h"
0010 #include "skymesh.h"
0011 #include "cachingdms.h"
0012 #include "sqlstatements.cpp"
0013 #include "catalogobject.h"
0014 #include "catalogsdb.h"
0015 #include <iostream>
0016 
0017 using namespace pybind11::literals;
0018 namespace py = pybind11;
0019 namespace pybind11
0020 {
0021 namespace detail
0022 {
0023 template <>
0024 struct type_caster<QString>
0025 {
0026   public:
0027     PYBIND11_TYPE_CASTER(QString, _("QString"));
0028 
0029     bool load(handle src, bool)
0030     {
0031         try
0032         {
0033             value = QString::fromStdString(src.cast<std::string>());
0034         }
0035         catch (const py::cast_error &)
0036         {
0037             return false;
0038         }
0039 
0040         return !PyErr_Occurred();
0041     }
0042 
0043     static handle cast(QString src, return_value_policy /* policy */, handle /* parent */)
0044     {
0045         const handle *obj = new py::object(py::cast(src.toUtf8().constData()));
0046         return *obj;
0047     }
0048 };
0049 
0050 template <>
0051 struct type_caster<QDateTime>
0052 {
0053   public:
0054     PYBIND11_TYPE_CASTER(QDateTime, _("QDateTime"));
0055 
0056     bool load(handle src, bool)
0057     {
0058         try
0059         {
0060             value = QDateTime::fromMSecsSinceEpoch(
0061                 std::chrono::duration_cast<std::chrono::milliseconds>(
0062                     src.cast<std::chrono::system_clock::time_point>().time_since_epoch())
0063                     .count());
0064         }
0065         catch (const py::cast_error &)
0066         {
0067             return false;
0068         }
0069 
0070         return !PyErr_Occurred();
0071     }
0072 
0073     static handle cast(QDateTime src, return_value_policy /* policy */,
0074                        handle /* parent */)
0075     {
0076         const handle *obj = new py::object(py::cast(std::chrono::system_clock::time_point(
0077             std::chrono::milliseconds(src.currentMSecsSinceEpoch()))));
0078         return *obj;
0079     }
0080 };
0081 } // namespace detail
0082 } // namespace pybind11
0083 
0084 /**
0085  * @struct Indexer
0086  * Provides a simple wrapper to generate trixel ids from python code.
0087  */
0088 struct Indexer
0089 {
0090     Indexer(int level) : m_mesh{ SkyMesh::Create(level) } {};
0091 
0092     int getLevel() const { return m_mesh->level(); };
0093     void setLevel(int level) { m_mesh = SkyMesh::Create(level); };
0094 
0095     int getTrixel(double ra, double dec, bool convert_epoch = false) const
0096     {
0097         SkyPoint p{ dms(ra), dms(dec) };
0098         if (convert_epoch)
0099         {
0100             p.B1950ToJ2000();
0101             p = SkyPoint{ p.ra(), p.dec() }; // resetting ra0, dec0
0102         }
0103 
0104         return m_mesh->index(&p);
0105     };
0106 
0107     SkyMesh *m_mesh;
0108 };
0109 
0110 ///////////////////////////////////////////////////////////////////////////////
0111 //                                   PYBIND                                  //
0112 ///////////////////////////////////////////////////////////////////////////////
0113 
0114 const CatalogObject DEFAULT_CATALOG_OBJECT{};
0115 
0116 template <typename T>
0117 T cast_default(const py::object &value, const T &default_value)
0118 {
0119     try
0120     {
0121         return py::cast<T>(value);
0122     }
0123     catch (const py::cast_error &)
0124     {
0125         return default_value;
0126     }
0127 }
0128 
0129 PYBIND11_MODULE(pykstars, m)
0130 {
0131     m.doc() = "Thin bindings for KStars to facilitate trixel indexation from python.";
0132 
0133     py::class_<Indexer>(m, "Indexer")
0134         .def(py::init<int>(), "level"_a,
0135              "Initializes an `Indexer` with the given `level`.\n"
0136              "If the level is greater then approx. 10 the initialization can take some "
0137              "time.")
0138         .def_property("level", &Indexer::getLevel, &Indexer::setLevel,
0139                       "Sets the level of the HTMesh/SkyMesh used to index points.")
0140         .def(
0141             "get_trixel", &Indexer::getTrixel, "ra"_a, "dec"_a, "convert_epoch"_a = false,
0142             "Calculates the trixel number from the right ascention and the declination.\n"
0143             "The epoch of coordinates is assumed to be J2000.\n\n"
0144             "If the epoch is B1950, `convert_epoch` has to be set to `True`.")
0145         .def("__repr__", [](const Indexer &indexer) {
0146             std::ostringstream lvl;
0147             lvl << indexer.getLevel();
0148             return "<Indexer level=" + lvl.str() + ">";
0149         });
0150 
0151     {
0152         using namespace CatalogsDB;
0153         py::class_<DBManager>(m, "DBManager")
0154             .def(py::init([](const std::string &filename) {
0155                      return new DBManager(QString::fromStdString(filename));
0156                  }),
0157                  "filename"_a)
0158             .def(
0159                 "register_catalog",
0160                 [](DBManager &self, const py::dict &cat) {
0161                     return self.register_catalog(
0162                         py::cast<int>(cat["id"]), py::cast<QString>(cat["name"]),
0163                         py::cast<bool>(cat["mut"]), py::cast<bool>(cat["enabled"]),
0164                         py::cast<double>(cat["precedence"]),
0165                         py::cast<QString>(cat["author"]),
0166                         py::cast<QString>(cat["source"]),
0167                         py::cast<QString>(cat["description"]),
0168                         py::cast<int>(cat["version"]), py::cast<QString>(cat["color"]),
0169                         py::cast<QString>(cat["license"]),
0170                         py::cast<QString>(cat["maintainer"]),
0171                         py::cast<QDateTime>(cat["timestamp"]));
0172                 },
0173                 "catalog"_a)
0174             .def(
0175                 "update_catalog_meta",
0176                 [](DBManager &self, const py::dict &cat) {
0177                     return self.update_catalog_meta(
0178                         { py::cast<int>(cat["id"]), py::cast<QString>(cat["name"]),
0179                           py::cast<double>(cat["precedence"]),
0180                           py::cast<QString>(cat["author"]),
0181                           py::cast<QString>(cat["source"]),
0182                           py::cast<QString>(cat["description"]),
0183                           py::cast<bool>(cat["mut"]), py::cast<bool>(cat["enabled"]),
0184                           py::cast<int>(cat["version"]), py::cast<QString>(cat["color"]),
0185                           py::cast<QString>(cat["license"]),
0186                           py::cast<QString>(cat["maintainer"]),
0187                           py::cast<QDateTime>(cat["timestamp"]) });
0188                 },
0189                 "catalog"_a)
0190             .def("__repr__",
0191                  [](const DBManager &manager) {
0192                      return QString("<DBManager filename=\"" + manager.db_file_name() +
0193                                     "\">");
0194                  })
0195             .def("update_catalog_views", &DBManager::update_catalog_views)
0196             .def("compile_master_catalog", &DBManager::compile_master_catalog)
0197             .def("dump_catalog", &DBManager::dump_catalog, "catalog_id"_a, "file_path"_a)
0198             .def("import_catalog", &DBManager::import_catalog, "file_path"_a,
0199                  "overwrite"_a)
0200             .def("remove_catalog", &DBManager::remove_catalog, "catalog_id"_a);
0201 
0202         py::register_exception<DatabaseError>(m, "DatabaseError");
0203     }
0204 
0205     py::enum_<SkyObject::TYPE>(m, "ObjectType", "The types of CatalogObjects",
0206                                py::arithmetic())
0207         .value("STAR", SkyObject::STAR)
0208         .value("CATALOG_STAR", SkyObject::CATALOG_STAR)
0209         .value("PLANET", SkyObject::TYPE::PLANET)
0210         .value("OPEN_CLUSTER", SkyObject::TYPE::OPEN_CLUSTER)
0211         .value("GLOBULAR_CLUSTER", SkyObject::TYPE::GLOBULAR_CLUSTER)
0212         .value("GASEOUS_NEBULA", SkyObject::TYPE::GASEOUS_NEBULA)
0213         .value("PLANETARY_NEBULA", SkyObject::TYPE::PLANETARY_NEBULA)
0214         .value("SUPERNOVA_REMNANT", SkyObject::TYPE::SUPERNOVA_REMNANT)
0215         .value("GALAXY", SkyObject::TYPE::GALAXY)
0216         .value("COMET", SkyObject::TYPE::COMET)
0217         .value("ASTEROID", SkyObject::TYPE::ASTEROID)
0218         .value("CONSTELLATION", SkyObject::TYPE::CONSTELLATION)
0219         .value("MOON", SkyObject::TYPE::MOON)
0220         .value("ASTERISM", SkyObject::TYPE::ASTERISM)
0221         .value("GALAXY_CLUSTER", SkyObject::TYPE::GALAXY_CLUSTER)
0222         .value("DARK_NEBULA", SkyObject::TYPE::DARK_NEBULA)
0223         .value("QUASAR", SkyObject::TYPE::QUASAR)
0224         .value("MULT_STAR", SkyObject::TYPE::MULT_STAR)
0225         .value("RADIO_SOURCE", SkyObject::TYPE::RADIO_SOURCE)
0226         .value("SATELLITE", SkyObject::TYPE::SATELLITE)
0227         .value("SUPERNOVA", SkyObject::TYPE::SUPERNOVA)
0228         .value("NUMBER_OF_KNOWN_TYPES", SkyObject::TYPE::NUMBER_OF_KNOWN_TYPES)
0229         .value("TYPE_UNKNOWN", SkyObject::TYPE::TYPE_UNKNOWN)
0230         .export_values();
0231 
0232     m.def(
0233         "get_id",
0234         [](const py::dict &obj) -> py::bytes {
0235             return CatalogObject::getId(
0236                        static_cast<SkyObject::TYPE>(py::cast<int>(obj["type"])),
0237                        py::cast<double>(obj["ra"]), py::cast<double>(obj["dec"]),
0238                        py::cast<QString>(obj["name"]),
0239                        py::cast<QString>(obj["catalog_identifier"]))
0240                 .toStdString();
0241         },
0242         "object"_a,
0243         R"(
0244         Calculate the id of an object.
0245 
0246         Parameters
0247         ----------)");
0248 
0249     ///////////////////////////////////////////////////////////////////////////
0250     //                             Sql Statements                            //
0251     ///////////////////////////////////////////////////////////////////////////
0252     auto s = m.def_submodule("sqlstatements");
0253     {
0254         using namespace CatalogsDB::SqlStatements;
0255 
0256         s.doc() = "Assorted sql statements to modify the catalog database.";
0257 
0258         s.def("insert_dso", &insert_dso, "catalog_id"_a);
0259         s.def("create_catalog_table", &create_catalog_table, "catalog_id"_a);
0260 
0261 #define ATTR(name)            \
0262     {                         \
0263         s.attr(#name) = name; \
0264     }
0265         ATTR(create_catalog_list_table);
0266         ATTR(insert_catalog);
0267         ATTR(get_catalog_by_id);
0268         ATTR(all_catalog_view);
0269         ATTR(master_catalog);
0270         ATTR(dso_by_name);
0271 #undef ATTR
0272     }
0273 }