From 311870e9570122af5a6c654272cd503fe4f41fb4 Mon Sep 17 00:00:00 2001 From: Travis Hunter Date: Sat, 22 Apr 2023 10:19:46 -0600 Subject: [PATCH] Initial Thrustcurve.org integration. Can get metadata and perform searches --- gui/AnalysisWindow.cpp | 2 + gui/MainWindow.cpp | 10 ++ gui/MainWindow.h | 2 + gui/MainWindow.ui | 15 ++- gui/ThrustCurveMotorSelector.cpp | 65 ++++++++++++ gui/ThrustCurveMotorSelector.h | 33 ++++++ gui/ThrustCurveMotorSelector.ui | 127 +++++++++++++++++++++++ model/MotorModel.h | 5 +- qtrocket.pro | 7 +- utils/ThrustCurveAPI.cpp | 167 +++++++++++++++++++++++++++++++ utils/ThrustCurveAPI.h | 52 +++++++++- 11 files changed, 479 insertions(+), 6 deletions(-) create mode 100644 gui/ThrustCurveMotorSelector.cpp create mode 100644 gui/ThrustCurveMotorSelector.h create mode 100644 gui/ThrustCurveMotorSelector.ui diff --git a/gui/AnalysisWindow.cpp b/gui/AnalysisWindow.cpp index e750905..bc8ffd2 100644 --- a/gui/AnalysisWindow.cpp +++ b/gui/AnalysisWindow.cpp @@ -21,6 +21,8 @@ AnalysisWindow::AnalysisWindow(QWidget *parent) : } */ auto& plot = ui->plotWidget; + plot->setInteraction(QCP::iRangeDrag, true); + plot->setInteraction(QCP::iRangeZoom, true); // generate some data: QVector tData(res.size()), zData(res.size()); for (int i = 0; i < tData.size(); ++i) diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index 243744d..6a19601 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -2,6 +2,7 @@ #include "ui_MainWindow.h" #include "gui/AboutWindow.h" #include "gui/AnalysisWindow.h" +#include "gui/ThrustCurveMotorSelector.h" #include "sim/RK4Solver.h" #include "model/Rocket.h" @@ -141,3 +142,12 @@ void MainWindow::on_loadRSE_button_clicked() } } + +void MainWindow::on_getTCMotorData_clicked() +{ + ThrustCurveMotorSelector window; + window.setModal(false); + window.exec(); + +} + diff --git a/gui/MainWindow.h b/gui/MainWindow.h index 07bcc53..740ea1e 100644 --- a/gui/MainWindow.h +++ b/gui/MainWindow.h @@ -27,6 +27,8 @@ private slots: void on_loadRSE_button_clicked(); + void on_getTCMotorData_clicked(); + private: Ui::MainWindow* ui; QtRocket* qtRocket; diff --git a/gui/MainWindow.ui b/gui/MainWindow.ui index d0d198d..bd6a333 100644 --- a/gui/MainWindow.ui +++ b/gui/MainWindow.ui @@ -188,7 +188,7 @@ 90 - 220 + 250 421 80 @@ -213,6 +213,19 @@ + + + + 240 + 360 + 201 + 25 + + + + Get Thrustcurve Motor Data + + diff --git a/gui/ThrustCurveMotorSelector.cpp b/gui/ThrustCurveMotorSelector.cpp new file mode 100644 index 0000000..0884309 --- /dev/null +++ b/gui/ThrustCurveMotorSelector.cpp @@ -0,0 +1,65 @@ +#include "ThrustCurveMotorSelector.h" +#include "ui_ThrustCurveMotorSelector.h" + +ThrustCurveMotorSelector::ThrustCurveMotorSelector(QWidget *parent) : + QDialog(parent), + ui(new Ui::ThrustCurveMotorSelector), + tcApi(new utils::ThrustCurveAPI) +{ + ui->setupUi(this); + this->setWindowModality(Qt::NonModal); + this->hide(); + this->show(); +} + +ThrustCurveMotorSelector::~ThrustCurveMotorSelector() +{ + delete ui; +} + +void ThrustCurveMotorSelector::on_getMetadata_clicked() +{ + // When the user clicks "Get Metadata", we want to pull in Metadata from thrustcurve.org + // and populate the Manufacturer, Diameter, and Impulse Class combo boxes + + utils::ThrustcurveMetadata metadata = tcApi->getMetadata(); + + for(const auto& i : metadata.diameters) + { + ui->diameter->addItem(QString::number(i)); + } + + for(const auto& i : metadata.manufacturers) + { + ui->manufacturer->addItem(QString::fromStdString(i.first)); + } + for(const auto& i : metadata.impulseClasses) + { + ui->impulseClass->addItem(QString::fromStdString(i)); + } +} + + +void ThrustCurveMotorSelector::on_searchButton_clicked() +{ + + //double diameter = ui->diameter-> + + std::string diameter = ui->diameter->currentText().toStdString(); + std::string manufacturer = ui->manufacturer->currentText().toStdString(); + std::string impulseClass = ui->impulseClass->currentText().toStdString(); + + utils::SearchCriteria c; + c.addCriteria("diameter", diameter); + c.addCriteria("manufacturer", manufacturer); + c.addCriteria("impulseClass", impulseClass); + + std::vector motors = tcApi->searchMotors(c); + + for(const auto& i : motors) + { + ui->motorSelection->addItem(QString::fromStdString(i.commonName)); + } + +} + diff --git a/gui/ThrustCurveMotorSelector.h b/gui/ThrustCurveMotorSelector.h new file mode 100644 index 0000000..94be155 --- /dev/null +++ b/gui/ThrustCurveMotorSelector.h @@ -0,0 +1,33 @@ +#ifndef THRUSTCURVEMOTORSELECTOR_H +#define THRUSTCURVEMOTORSELECTOR_H + +#include + +#include + +#include "utils/ThrustCurveAPI.h" + +namespace Ui { +class ThrustCurveMotorSelector; +} + +class ThrustCurveMotorSelector : public QDialog +{ + Q_OBJECT + +public: + explicit ThrustCurveMotorSelector(QWidget *parent = nullptr); + ~ThrustCurveMotorSelector(); + +private slots: + void on_getMetadata_clicked(); + + void on_searchButton_clicked(); + +private: + Ui::ThrustCurveMotorSelector *ui; + + std::unique_ptr tcApi; +}; + +#endif // THRUSTCURVEMOTORSELECTOR_H diff --git a/gui/ThrustCurveMotorSelector.ui b/gui/ThrustCurveMotorSelector.ui new file mode 100644 index 0000000..6cacd12 --- /dev/null +++ b/gui/ThrustCurveMotorSelector.ui @@ -0,0 +1,127 @@ + + + ThrustCurveMotorSelector + + + + 0 + 0 + 622 + 878 + + + + Dialog + + + + + 170 + 90 + 160 + 101 + + + + + + + Manufacturer + + + + + + + Diameter + + + + + + + Impulse Class + + + + + + + + + + + + + + + + + + 200 + 40 + 91 + 25 + + + + Get Metadata + + + + + + 10 + 320 + 601 + 551 + + + + + + + 210 + 210 + 80 + 25 + + + + Search + + + + + + 170 + 260 + 160 + 41 + + + + + + + Download + + + + + + + + + + + + QCustomPlot + QWidget +
gui/qcustomplot.h
+ 1 +
+
+ + +
diff --git a/model/MotorModel.h b/model/MotorModel.h index 8a201aa..bd4e628 100644 --- a/model/MotorModel.h +++ b/model/MotorModel.h @@ -29,6 +29,8 @@ public: OOP // Out of Production }; + + enum class CERTORG { AMRS, @@ -116,7 +118,6 @@ public: } }; - // TODO: make these private. Public just for testing //private: // Needed for boost serialize @@ -132,7 +133,7 @@ public: // int dataFiles std::vector delays; // -1 delay means no ejection charge std::string designation{""}; - int diameter{0}; + double diameter{0}; std::string impulseClass; // 'A', 'B', '1/2A', 'M', etc std::string infoUrl{""}; double length{0.0}; diff --git a/qtrocket.pro b/qtrocket.pro index d8c0aaf..a608b10 100644 --- a/qtrocket.pro +++ b/qtrocket.pro @@ -12,6 +12,7 @@ SOURCES += \ QtRocket.cpp \ gui/AboutWindow.cpp \ gui/AnalysisWindow.cpp \ + gui/ThrustCurveMotorSelector.cpp \ gui/qcustomplot.cpp \ main.cpp \ gui/RocketTreeView.cpp \ @@ -43,6 +44,7 @@ HEADERS += \ gui/AnalysisWindow.h \ gui/RocketTreeView.h \ gui/MainWindow.h \ + gui/ThrustCurveMotorSelector.h \ gui/qcustomplot.h \ model/MotorCase.h \ model/MotorModel.h \ @@ -76,7 +78,8 @@ HEADERS += \ FORMS += \ gui/AboutWindow.ui \ gui/AnalysisWindow.ui \ - gui/MainWindow.ui + gui/MainWindow.ui \ + gui/ThrustCurveMotorSelector.ui TRANSLATIONS += \ qtrocket_en_US.ts @@ -90,8 +93,8 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin unix: CONFIG += link_pkgconfig unix: PKGCONFIG += libcurl - unix: PKGCONFIG += fmt +unix: PKGCONFIG += jsoncpp RESOURCES += \ qtrocket.qrc diff --git a/utils/ThrustCurveAPI.cpp b/utils/ThrustCurveAPI.cpp index 6af1289..f40d762 100644 --- a/utils/ThrustCurveAPI.cpp +++ b/utils/ThrustCurveAPI.cpp @@ -1,5 +1,8 @@ #include "ThrustCurveAPI.h" +#include +#include "utils/Logger.h" + namespace utils { @@ -29,4 +32,168 @@ MotorModel ThrustCurveAPI::getMotorData(const std::string& motorId) return mm; } +ThrustcurveMetadata ThrustCurveAPI::getMetadata() +{ + + std::string endpoint = hostname; + endpoint += "metadata.json"; + std::string result = curlConnection.get(endpoint, extraHeaders); + ThrustcurveMetadata ret; + + if(!result.empty()) + { + try + { + Json::Reader reader; + Json::Value jsonResult; + reader.parse(result, jsonResult); + + for(Json::ValueConstIterator iter = jsonResult["certOrgs"].begin(); + iter != jsonResult["certOrgs"].end(); + ++iter) + { + std::string org = (*iter)["abbrev"].asString(); + + if(org == "AMRS") + ret.certOrgs.emplace_back(MotorModel::CERTORG::AMRS); + else if(org == "CAR") + ret.certOrgs.emplace_back(MotorModel::CERTORG::CAR); + else if(org == "NAR") + ret.certOrgs.emplace_back(MotorModel::CERTORG::NAR); + else if(org == "TRA") + ret.certOrgs.emplace_back(MotorModel::CERTORG::TRA); + else if(org == "UNC") + ret.certOrgs.emplace_back(MotorModel::CERTORG::UNC); + else + ret.certOrgs.emplace_back(MotorModel::CERTORG::UNK); + } + for(Json::ValueConstIterator iter = jsonResult["diameters"].begin(); + iter != jsonResult["diameters"].end(); + ++iter) + { + ret.diameters.push_back((*iter).asDouble()); + } + for(Json::ValueConstIterator iter = jsonResult["impulseClasses"].begin(); + iter != jsonResult["impulseClasses"].end(); + ++iter) + { + ret.impulseClasses.emplace_back((*iter).asString()); + } + for(Json::ValueConstIterator iter = jsonResult["manufacturers"].begin(); + iter != jsonResult["manufacturers"].end(); + ++iter) + { + ret.manufacturers[(*iter)["abbrev"].asString()] = (*iter)["name"].asString(); + } + for(Json::ValueConstIterator iter = jsonResult["types"].begin(); + iter != jsonResult["types"].end(); + ++iter) + { + std::string type = (*iter)["types"].asString(); + if(type == "SU") + ret.types.emplace_back(MotorModel::MOTORTYPE::SU); + else if(type == "reload") + ret.types.emplace_back(MotorModel::MOTORTYPE::RELOAD); + else + ret.types.emplace_back(MotorModel::MOTORTYPE::HYBRID); + } + } + catch(const std::exception& e) + { + + std::string err("Unable to parse JSON from Thrustcurve metadata request. Error: "); + err += e.what(); + + Logger::getInstance()->error(err); + } + } + + return ret; + +} + +std::vector ThrustCurveAPI::searchMotors(const SearchCriteria& c) +{ + std::vector retVal; + std::string endpoint = hostname; + endpoint += "search.json?"; + for(const auto& i : c.criteria) + { + endpoint += i.first; + endpoint += "="; + endpoint += i.second; + endpoint += "&"; + } + endpoint = endpoint.substr(0, endpoint.length() - 1); + + + Logger::getInstance()->debug("endpoint: " + endpoint); + std::string result = curlConnection.get(endpoint, extraHeaders); + if(!result.empty()) + { + try + { + Json::Reader reader; + Json::Value jsonResult; + reader.parse(result, jsonResult); + + for(Json::ValueConstIterator iter = jsonResult["results"].begin(); + iter != jsonResult["results"].end(); + ++iter) + { + MotorModel mm; + mm.commonName = (*iter)["commonName"].asString(); + + std::string availability = (*iter)["availability"].asString(); + if(availability == "regular") + mm.availability = MotorModel::MotorAvailability(MotorModel::AVAILABILITY::REGULAR); + else + mm.availability = MotorModel::MotorAvailability(MotorModel::AVAILABILITY::OOP); + + mm.avgThrust = (*iter)["avgThrustN"].asDouble(); + mm.burnTime = (*iter)["burnTimeS"].asDouble(); + // TODO fill in certOrg + // TODO fill in delays + mm.designation = (*iter)["designation"].asString(); + mm.diameter = (*iter)["diameter"].asDouble(); + mm.impulseClass = (*iter)["impulseClass"].asString(); + mm.length = (*iter)["length"].asDouble(); + mm.manufacturer = (*iter)["manufacturer"].asString(); + mm.maxThrust = (*iter)["maxThrustN"].asDouble(); + mm.motorIdTC = (*iter)["motorId"].asString(); + mm.propType = (*iter)["propInfo"].asString(); + mm.propWeight = (*iter)["propWeightG"].asDouble(); + mm.sparky = (*iter)["sparky"].asBool(); + mm.totalImpulse = (*iter)["totImpulseNs"].asDouble(); + mm.totalWeight = (*iter)["totalWeightG"].asDouble(); + + std::string type = (*iter)["type"].asString(); + if(type == "SU") + mm.type = MotorModel::MotorType(MotorModel::MOTORTYPE::SU); + else if(type == "reload") + mm.type = MotorModel::MotorType(MotorModel::MOTORTYPE::RELOAD); + else + mm.type = MotorModel::MotorType(MotorModel::MOTORTYPE::HYBRID); + + retVal.push_back(mm); + } + } + catch(const std::exception& e) + { + + std::string err("Unable to parse JSON from Thrustcurve metadata request. Error: "); + err += e.what(); + + Logger::getInstance()->error(err); + } + } + return retVal; +} + +void SearchCriteria::addCriteria(const std::string& name, + const std::string& value) +{ + criteria[name] = value; +} + } // namespace utils diff --git a/utils/ThrustCurveAPI.h b/utils/ThrustCurveAPI.h index 2fb2b40..ba25148 100644 --- a/utils/ThrustCurveAPI.h +++ b/utils/ThrustCurveAPI.h @@ -3,14 +3,53 @@ #include +#include #include "CurlConnection.h" #include "model/MotorModel.h" -#include "model/Thrustcurve.h" namespace utils { +class ThrustcurveMetadata +{ +public: + ThrustcurveMetadata() = default; + ~ThrustcurveMetadata() = default; + + ThrustcurveMetadata(const ThrustcurveMetadata&) = default; + ThrustcurveMetadata(ThrustcurveMetadata&&) = default; + + ThrustcurveMetadata& operator=(const ThrustcurveMetadata&) = default; + ThrustcurveMetadata& operator=(ThrustcurveMetadata&&) = default; + +//private: + std::vector certOrgs; + std::vector diameters; + std::vector impulseClasses; + std::map manufacturers; + std::vector types; + +}; + +class SearchCriteria +{ +public: + SearchCriteria() = default; + ~SearchCriteria() = default; + SearchCriteria(const SearchCriteria&) = default; + SearchCriteria(SearchCriteria&&) = default; + + SearchCriteria& operator=(const SearchCriteria&) = default; + SearchCriteria& operator=(SearchCriteria&&) = default; + + void addCriteria(const std::string& name, + const std::string& vaue); + + std::map criteria; + +}; + /** * @brief This API for Thrustcurve.org - It will provide an interface for querying thrustcurve.org * for motor data @@ -30,12 +69,23 @@ public: MotorModel getMotorData(const std::string& motorId); + /** + * @brief getMetaData + */ + + ThrustcurveMetadata getMetadata(); + + std::vector searchMotors(const SearchCriteria& c); + private: const std::string hostname; CurlConnection curlConnection; + + // no extra headers, but CurlConnection library wants them + const std::vector extraHeaders{}; }; } // namespace utils