Browse Source

Adding an interface to query the FlightAware API

The FlightAware API can be queried to retrieve basic flight information. Given a date and a flight identifier (IATA/ICAO flight ID), this data can then be used to prepopulate a new flight entry with only minimal user input required to finish logging a flight. Querying the API requires an API key, which is stored locally.

- The FlightAwareQuery class encapsulates a network manager and handles the request to the API
- The FlightAwareFlightData class encapsulates the data received from the API
- The FlightAwareJsonParser class parses the data received from the API
Felix Turowsky 1 năm trước cách đây
mục cha
commit
92d09d33ec

+ 8 - 0
CMakeLists.txt

@@ -184,6 +184,14 @@ set(PROJECT_SOURCES
     assets/themes/stylesheets/breeze/breeze.qrc
     assets/themes/stylesheets/qdarkstyle/qdarkstyle.qrc
 
+    # network
+    src/network/flightawareflightdata.h
+    src/network/flightawarequery.h
+    src/network/flightawarequery.cpp
+    src/network/flightawarejsonparser.h
+    src/network/flightawarejsonparser.cpp
+
+
     # Testing / Debug
     src/testing/importCrewlounge/importcrewlounge.h
     src/testing/importCrewlounge/importcrewlounge.cpp

+ 11 - 4
mainwindow.cpp

@@ -27,13 +27,20 @@
 #include "src/gui/dialogues/firstrundialog.h"
 #include "src/database/databasecache.h"
 #include "src/classes/settings.h"
+
 // Quick and dirty Debug area
+#include "src/network/flightawarequery.h"
 void MainWindow::doDebugStuff()
 {
-//    LogbookTableEditWidget *widget = new LogbookTableEditWidget(this);
-//    widget->init();
-//    widget->setWindowFlags(Qt::Dialog);
-//    widget->show();
+    FlightAwareQuery query;
+    auto result = query.getFlightData("DY606", QDate::currentDate());
+    LOG << "Querying API...";
+    if(result.isEmpty()) {
+        LOG << "No flights found.";
+    }
+    for(const auto &flight : result) {
+        flight.print();
+    }
 }
 
 MainWindow::MainWindow(QWidget *parent)

+ 9 - 0
src/classes/settings.h

@@ -185,6 +185,13 @@ public:
      */
     static void setFlightNumberPrefix(const QString &value) { settingsInstance->setValue(LOG_PREFIX, value); }
 
+    /*!
+     * \brief reads the FlightAware API key from the user settings
+     */
+    static QByteArray getFlightAwareApiKey() { return settingsInstance->value(FLIGHT_AWARE_APY_KEY).toByteArray(); }
+
+    static void setFlightAwareApiKey(const QString &apiKey) { settingsInstance->setValue(FLIGHT_AWARE_APY_KEY, apiKey); }
+
     /*!
      * \brief sets how the logbook owner is shown in the view
      * \details
@@ -243,6 +250,8 @@ private:
     const static inline QString FORMAT_TIME_FORMAT	= QStringLiteral("format/timeFormat");
     const static inline QString FORMAT_TIME_STRING 	= QStringLiteral("format/timeFormatString");
 
+    const static inline QString FLIGHT_AWARE_APY_KEY = QStringLiteral("flightAware/apiKey");
+
 
 };
 

+ 35 - 22
src/gui/widgets/debugwidget.cpp

@@ -16,8 +16,12 @@
  *along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 #include "debugwidget.h"
+#include "src/classes/time.h"
+#include "src/gui/dialogues/newflightdialog.h"
 #include "src/gui/verification/completerprovider.h"
 #include "src/gui/verification/pilotinput.h"
+#include "src/network/flightawarequery.h"
+#include "src/opl.h"
 #include "src/testing/importCrewlounge/processaircraft.h"
 #include "src/testing/importCrewlounge/processflights.h"
 #include "src/testing/importCrewlounge/processpilots.h"
@@ -31,21 +35,30 @@
 
 void DebugWidget::on_debugPushButton_clicked()
 {
-    auto rawCsvData = CSV::readCsvAsRows("/home/felix/git/importMCC/assets/data/felix.csv");
-    // Process Pilots
-    auto proc_pilots = ProcessPilots(rawCsvData);
-    proc_pilots.init();
-    const auto p_maps = proc_pilots.getProcessedPilotMaps();
-    // Process Tails
-    auto proc_tails = ProcessAircraft(rawCsvData);
-    proc_tails.init();
-    const auto t_maps = proc_tails.getProcessedTailMaps();
-    // Process Flights
-    auto proc_flights = ProcessFlights(rawCsvData,proc_pilots.getProcessedPilotsIds(), proc_tails.getProcessedTailIds());
-    proc_flights.init();
-
-    auto flights = proc_flights.getProcessedFlights();
-    DEB << "Flight:" << flights[1000];
+    // FlightAwareQuery query;
+    // const auto result = query.getFlightData(ui->debugLineEdit->text(), ui->dateEdit->date());
+    // if(result.isEmpty()) {
+    //     WARN("No flight found.");
+    //     return;
+    // }
+
+    // const FlightAwareFlightData data = result.first();
+    // const auto format = OPL::DateTimeFormat();
+    // int departureTime = OPL::Time(data.out.time(), format).toMinutes();
+    // int arrivalTime = OPL::Time(data.in.time(), format).toMinutes();
+    OPL::RowData_T flight_data;
+    // flight_data.insert(OPL::FlightEntry::DEPT, data.departure);
+    // flight_data.insert(OPL::FlightEntry::DEST ,data.destination);
+    // flight_data.insert(OPL::FlightEntry::TOFB , departureTime);
+    // flight_data.insert(OPL::FlightEntry::TONB , arrivalTime);
+    // flight_data.insert(OPL::FlightEntry::ACFT , data.registration);
+    // flight_data.insert(OPL::FlightEntry::FLIGHTNUMBER, data.iataFlightNumber);
+
+    // DEB << "Parsed Flight Data:" << flight_data;
+
+    // NewFlightDialog nfd(flight_data, this);
+    flight_data.insert(OPL::FlightEntry::DOFT, 22000);
+    // NewFlightDialog nfd(flight_data, this);
 }
 
 DebugWidget::DebugWidget(QWidget *parent) :
@@ -281,13 +294,13 @@ void DebugWidget::changeEvent(QEvent *event)
 
 void DebugWidget::on_debugLineEdit_editingFinished()
 {
-    PilotInput user_input = PilotInput(ui->debugLineEdit->text());
-    if(user_input.isValid())
-        DEB << "Good Input";
-    else {
-        DEB << "Fixing...";
-        ui->debugLineEdit->setText(user_input.fixup());
-    }
+    // PilotInput user_input = PilotInput(ui->debugLineEdit->text());
+    // if(user_input.isValid())
+    //     DEB << "Good Input";
+    // else {
+    //     DEB << "Fixing...";
+    //     ui->debugLineEdit->setText(user_input.fixup());
+    // }
 }
 
 

+ 3 - 0
src/gui/widgets/debugwidget.ui

@@ -169,6 +169,9 @@
          </property>
         </widget>
        </item>
+       <item row="5" column="4">
+        <widget class="QDateEdit" name="dateEdit"/>
+       </item>
       </layout>
      </widget>
     </widget>

+ 30 - 0
src/network/FlightAwareFlightData.h

@@ -0,0 +1,30 @@
+#ifndef FLIGHTAWAREFLIGHTDATA_H
+#define FLIGHTAWAREFLIGHTDATA_H
+
+#include <QDateTime>
+#include "src/opl.h"
+
+/*!
+ * \brief A struct for storing data retreived from the FlightAware API
+ */
+class FlightAwareFlightData
+{
+public:
+    FlightAwareFlightData() = default;
+
+    QString departure;
+    QString destination;
+    QString registration;
+    QString icaoFlightNumber;
+    QString iataFlightNumber;
+    QDateTime out, off, on, in;
+
+    void print() const {
+        DEB << "Flight Aware Data:";
+        DEB << "Departure: " << departure << "Destination" << destination;
+        DEB << "Out:" << out << "Off:" << off << "On:" << on << "in:" << in;
+        DEB << "Icao #" << icaoFlightNumber << "Iata #" << iataFlightNumber << "Registration" << registration;
+    }
+};
+
+#endif // FLIGHTAWAREFLIGHTDATA_H

+ 29 - 0
src/network/flightawarejsonparser.cpp

@@ -0,0 +1,29 @@
+#include "flightawarejsonparser.h"
+
+QList<FlightAwareFlightData> FlightAwareJsonParser::readFlights(const QByteArray &rawJsonData)
+{
+    // interpret the raw network reply as JSON data
+    const auto replyDocument = QJsonDocument::fromJson(rawJsonData);
+    DEB << replyDocument;
+    const auto flightsArray = replyDocument.object()[FLIGHTS].toArray();
+
+    // Loop over the results and parse the input
+    QList<FlightAwareFlightData> flightList;
+    for(const auto &flight : flightsArray) {
+        const QJsonObject flightObject = flight.toObject();
+
+        FlightAwareFlightData flightData;
+        flightData.departure = flightObject[ORIGIN].toObject()[CODE_ICAO].toString();
+        flightData.destination = flightObject[DESTINATION].toObject()[CODE_ICAO].toString();
+        flightData.out = QDateTime::fromString(flightObject[OUT].toString(), Qt::ISODate);
+        flightData.off = QDateTime::fromString(flightObject[OFF].toString(), Qt::ISODate);
+        flightData.on = QDateTime::fromString(flightObject[ON].toString(), Qt::ISODate);
+        flightData.in = QDateTime::fromString(flightObject[IN].toString(), Qt::ISODate);
+        flightData.registration = flightObject[REGISTRATION].toString();
+        flightData.iataFlightNumber = flightObject[IATA_FN].toString();
+        flightData.icaoFlightNumber = flightObject[ICAO_FN].toString();
+        flightList.append(flightData);
+    }
+
+    return flightList;
+}

+ 34 - 0
src/network/flightawarejsonparser.h

@@ -0,0 +1,34 @@
+#ifndef FLIGHTAWAREJSONPARSER_H
+#define FLIGHTAWAREJSONPARSER_H
+
+#include "flightawareflightdata.h"
+
+/*!
+ * \brief The FlightAwareJsonParser reads the reply of a call to the FlightAware API and extracts
+ * the relevant data points
+ */
+class FlightAwareJsonParser
+{
+public:
+    FlightAwareJsonParser() = delete;
+
+    /*!
+     * \brief read the reply of a FlightAware API call and parse the data into a list of FlightAwareData objects
+     */
+    static QList<FlightAwareFlightData> readFlights(const QByteArray &rawJsonData);
+private:
+    const static inline QString FLIGHTS = QStringLiteral("flights");
+    const static inline QString ORIGIN = QStringLiteral("origin");
+    const static inline QString CODE_ICAO = QStringLiteral("code_icao");
+    const static inline QString CODE_IATA = QStringLiteral("code_iata");
+    const static inline QString DESTINATION = QStringLiteral("destination");
+    const static inline QString OUT = QStringLiteral("actual_out");
+    const static inline QString OFF = QStringLiteral("actual_off");
+    const static inline QString ON = QStringLiteral("actual_on");
+    const static inline QString IN = QStringLiteral("actual_in");
+    const static inline QString REGISTRATION = QStringLiteral("registration");
+    const static inline QString IATA_FN = QStringLiteral("ident_iata");
+    const static inline QString ICAO_FN = QStringLiteral("ident_icao");
+};
+
+#endif // FLIGHTAWAREJSONPARSER_H

+ 79 - 0
src/network/flightawarequery.cpp

@@ -0,0 +1,79 @@
+#include "flightawarequery.h"
+#include "flightawarejsonparser.h"
+#include <QNetworkReply>
+
+FlightAwareQuery::FlightAwareQuery(QObject *parent)
+    : QObject{parent}
+{
+    m_networkManager = new QNetworkAccessManager(this);
+}
+
+QList<FlightAwareFlightData> FlightAwareQuery::getFlightData(const QString &flightId, const QDate &date)
+{
+    LOG << "Preparing query data";
+    // prepare the query data
+    const QDateTime startDateTime = QDateTime(date, QTime(0, 0));
+    const QDateTime endDateTime = QDateTime(date, QTime(23, 59));
+    const QString queryUrl = QUERY_BASE_URL + flightId + QString("?start=%1&end=%2").arg(startDateTime.toString(Qt::ISODate), endDateTime.toString(Qt::ISODate));
+
+    // construct the network request
+    const QByteArray apiKey = Settings::getFlightAwareApiKey();
+    if (apiKey.isEmpty()) {
+        LOG << "API key not found.";
+        return {};
+    }
+    LOG << "Sending network request: " << queryUrl << "with API key: " << apiKey;
+    QNetworkRequest request;
+    request.setRawHeader("x-apikey", apiKey);
+    request.setRawHeader("Accept", "application/json; charset=UTF-8");
+    request.setUrl(queryUrl);
+
+    // invoke the network manager to execute the request
+    m_networkReply = m_networkManager->get(request);
+    // QObject::connect(m_networkReply, &QNetworkReply::finished,
+    //                  this,           &FlightAwareQuery::replyFinished);
+
+
+    // Wait for finishing the network request or a timeout
+    LOG << "Setting timer and staring event loop";
+    QTimer timer;
+    timer.setSingleShot(true);
+    QObject::connect(&timer, &QTimer::timeout, this, &FlightAwareQuery::timeOut);
+    timer.start(5000); //5s
+
+    QEventLoop loop;
+    QObject::connect(m_networkReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+    loop.exec();
+
+    LOG << "Reading reply...";
+    // check the reply
+    if(m_networkReply->error() != QNetworkReply::NoError) {
+        LOG << m_networkReply->error();
+        LOG << m_networkReply->errorString();
+        return {};
+    }
+
+    LOG << "Parsing reply...";
+    // parse the reply and return the result
+    return FlightAwareJsonParser::readFlights(m_networkReply->readAll());
+}
+
+// void FlightAwareQuery::replyFinished()
+// {
+//     if(m_networkReply->error() != QNetworkReply::NoError) {
+//         qDebug() << m_networkReply->error();
+//         qDebug() << m_networkReply->errorString();
+//         return;
+//     }
+
+//     // parse the reply
+//     m_flightList.clear();
+//     m_flightList = FlightAwareJsonParser::readFlights(m_networkReply->readAll());
+//     emit readyToRead();
+// }
+
+void FlightAwareQuery::timeOut()
+{
+    m_networkReply->abort(); // triggers replyFinished() with an error code
+}
+

+ 39 - 0
src/network/flightawarequery.h

@@ -0,0 +1,39 @@
+#ifndef FLIGHTAWAREQUERY_H
+#define FLIGHTAWAREQUERY_H
+#include <QNetworkAccessManager>
+#include "src/network/flightawareflightdata.h"
+#include "src/classes/settings.h"
+
+/*!
+ * \brief The FlightAwareQuery class handles API requests to FlightAware
+ * \details The FlightAware API allows retreiving most of the details required to log a flight.
+ * Given a date and a flight number, which can either be an icao or iata flight id, most
+ * base data can be auto-populated with only little user input required to complete a given flight.
+ *
+ * When the Flight Data is ready to be read (i.e. network request finished and data parsed), readyToRead()
+ * is emitted and the parsed data can be retreived by calling flightList(). A list is used because some flights
+ * are multi-sector flights that share a common flight number.
+ */
+class FlightAwareQuery : public QObject
+{
+    Q_OBJECT
+    QNetworkAccessManager *m_networkManager;
+    QNetworkReply* m_networkReply;
+
+    const static inline QString QUERY_BASE_URL = QStringLiteral("https://aeroapi.flightaware.com/aeroapi/flights/");
+    const QByteArray getApiKey() const { return Settings::getFlightAwareApiKey(); }
+
+public:
+    explicit FlightAwareQuery(QObject *parent = nullptr);
+    QList<FlightAwareFlightData> getFlightData(const QString &flightId, const QDate &date);
+
+// signals:
+//     void readyToRead();
+
+private slots:
+    // void replyFinished();
+    void timeOut();
+
+};
+
+#endif // FLIGHTAWAREQUERY_H