From a9385870e023d73f90f5f6607f1cb4071b5b08ab Mon Sep 17 00:00:00 2001 From: Klaus Basan Date: Tue, 25 Feb 2014 01:06:47 +0100 Subject: [PATCH] refs #146 , CVatsimDataFileReader reads the data file for VATSIM and provides all ATC stations and aircrafts * Data can be completed by attributes from the data file * All voice servers are available * Predictions can be made, even if the user is not logged in --- src/blackcore/vatsimdatafilereader.cpp | 184 +++++++++++++++++++++++++ src/blackcore/vatsimdatafilereader.h | 84 +++++++++++ 2 files changed, 268 insertions(+) create mode 100644 src/blackcore/vatsimdatafilereader.cpp create mode 100644 src/blackcore/vatsimdatafilereader.h diff --git a/src/blackcore/vatsimdatafilereader.cpp b/src/blackcore/vatsimdatafilereader.cpp new file mode 100644 index 000000000..9a6876b2f --- /dev/null +++ b/src/blackcore/vatsimdatafilereader.cpp @@ -0,0 +1,184 @@ +#include "blackmisc/sequence.h" +#include "blackmisc/avatcstation.h" +#include "blackmisc/nwuser.h" +#include "blackmisc/nwserver.h" +#include "vatsimdatafilereader.h" + +using namespace BlackMisc; +using namespace BlackMisc::Aviation; +using namespace BlackMisc::Network; +using namespace BlackMisc::Geo; +using namespace BlackMisc::PhysicalQuantities; + + +namespace BlackCore +{ + + CVatsimDataFileReader::CVatsimDataFileReader(const QStringList &urls, QObject *parent) : QObject(parent), m_serviceUrls(urls), m_currentUrlIndex(0), m_networkManager(nullptr), m_updateTimer(nullptr) + { + this->m_networkManager = new QNetworkAccessManager(this); + this->m_updateTimer = new QTimer(this); + this->connect(this->m_networkManager, &QNetworkAccessManager::finished, this, &CVatsimDataFileReader::loadFinished); + this->connect(this->m_updateTimer, &QTimer::timeout, this, &CVatsimDataFileReader::read); + } + + void CVatsimDataFileReader::read() + { + if (this->m_serviceUrls.isEmpty()) return; + this->m_currentUrlIndex++; + if (this->m_serviceUrls.size() >= this->m_currentUrlIndex) this->m_currentUrlIndex = 0; + + QUrl url(this->m_serviceUrls.at(this->m_currentUrlIndex)); + if (url.isEmpty()) return; + Q_ASSERT(this->m_networkManager); + QNetworkRequest request(url); + this->m_networkManager->get(request); + } + + void CVatsimDataFileReader::setInterval(int updatePeriodMs) + { + Q_ASSERT(this->m_updateTimer); + if (updatePeriodMs < 1) + this->m_updateTimer->stop(); + else + this->m_updateTimer->start(updatePeriodMs); + } + + /* + * Data file read from XML + */ + void CVatsimDataFileReader::loadFinished(QNetworkReply *nwReply) + { + // Example: http://info.vroute.net/vatsim-data.txt + if (nwReply->error() == QNetworkReply::NoError) + { + const QString dataFileData = nwReply->readAll(); + if (dataFileData.isEmpty()) return; + QStringList lines = dataFileData.split(QRegExp("[\r\n]"), QString::SkipEmptyParts); + if (lines.isEmpty()) return; + + QStringList clientSectionAttributes; + Section section = SectionNone; + foreach(QString currentLine, lines) + { + currentLine = currentLine.trimmed(); + if (currentLine.isEmpty()) continue; + if (currentLine.startsWith(";")) + { + if (clientSectionAttributes.isEmpty() && currentLine.contains("!CLIENTS SECTION", Qt::CaseInsensitive)) + { + // ; !CLIENTS section + int i = currentLine.lastIndexOf(' '); + QString attributes = currentLine.mid(i).trimmed(); + clientSectionAttributes = attributes.split(':', QString::SkipEmptyParts); + section = SectionNone; // reset + } + continue; + } + else if (currentLine.startsWith("!")) + { + if (currentLine.contains("GENERAL", Qt::CaseInsensitive)) + { + section = SectionGeneral; + } + else if (currentLine.contains("VOICE SERVERS", Qt::CaseInsensitive)) + { + section = SectionVoiceServer; + this->m_voiceServers.clear(); + } + else if (currentLine.contains("CLIENTS", Qt::CaseInsensitive)) + { + section = SectionClients; + this->m_aircrafts.clear(); + this->m_atcStations.clear(); + } + else + { + section = SectionNone; + } + continue; + } + switch (section) + { + case SectionClients: + { + QMap clientPartsMap = clientPartsToMap(currentLine, clientSectionAttributes); + BlackMisc::Network::CUser user(clientPartsMap["cid"], clientPartsMap["realname"], CCallsign(clientPartsMap["callsign"])); + if (!user.hasValidCallsign()) continue; + const QString clientType = clientPartsMap["clienttype"].toLower(); + if (clientType.isEmpty()) break; // sometimes type is empty + double lat = clientPartsMap["latitude"].toDouble(); + double lng = clientPartsMap["longitude"].toDouble(); + double alt = clientPartsMap["altitude"].toDouble(); + CFrequency frequency = CFrequency(clientPartsMap["frequency"].toDouble(), CFrequencyUnit::MHz()); + CCoordinateGeodetic position(lat, lng, -1); + CAltitude altitude(alt, CAltitude::MeanSeaLevel, CLengthUnit::ft()); + + if (clientType.startsWith('p')) + { + // Pilot section + double groundspeed = clientPartsMap["groundspeed"].toDouble(); + CAircraftSituation situation(position, altitude); + situation.setGroundspeed(CSpeed(groundspeed, CSpeedUnit::kts())); + CAircraft aircraft(user.getCallsign().getStringAsSet(), user, situation); + this->m_aircrafts.push_back(aircraft); + } + else if (clientType.startsWith('a')) + { + // ATC section + CLength range; + position.setHeight(altitude); // the altitude is elevation for a station + CAtcStation station(user.getCallsign().getStringAsSet(), user, frequency, position, range); + station.setOnline(true); + this->m_atcStations.push_back(station); + } + else + { + Q_ASSERT_X(false, "CVatsimDataFileReader::loadFinished", "Wrong client type"); + } + } + break; + case SectionGeneral: + { + if (currentLine.contains("UPDATE")) + { + QStringList updateParts = currentLine.replace(" ", "").split('='); + if (updateParts.length() < 2) continue; + QString dts = updateParts.at(1).trimmed(); + QDateTime dt = QDateTime::fromString(dts, "yyyyMMddHHmmss"); + dt.setOffsetFromUtc(0); + if (dt == this->m_updateTimestamp) return; // still same data, terminate + this->m_updateTimestamp = dt; + } + } + break; + case SectionVoiceServer: + { + QStringList voiceServerParts = currentLine.split(':'); + if (voiceServerParts.size() < 3) continue; + BlackMisc::Network::CServer voiceServer(voiceServerParts.at(1), voiceServerParts.at(2), voiceServerParts.at(0), -1, CUser()); + this->m_voiceServers.push_back(voiceServer); + } + break; + case SectionNone: + default: + break; + } + } // for each + } + emit this->dataRead(); + } + + const QMap CVatsimDataFileReader::clientPartsToMap(const QString ¤tLine, const QStringList &clientSectionAttributes) + { + QStringList clientParts = currentLine.split(':'); + QMap parts; + for (int i = 0; i < clientSectionAttributes.size(); i++) + { + Q_ASSERT(i < clientSectionAttributes.size()); + Q_ASSERT(i < clientParts.size()); + parts.insert(clientSectionAttributes.at(i).toLower(), clientParts.at(i)); + } + return parts; + } +} // namespace diff --git a/src/blackcore/vatsimdatafilereader.h b/src/blackcore/vatsimdatafilereader.h new file mode 100644 index 000000000..94e6a4993 --- /dev/null +++ b/src/blackcore/vatsimdatafilereader.h @@ -0,0 +1,84 @@ +/* Copyright (C) 2013 VATSIM Community / authors + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BLACKCORE_VATSIMDATAFILEREADER_H +#define BLACKCORE_VATSIMDATAFILEREADER_H + +#include "blackmisc/avatcstationlist.h" +#include "blackmisc/avaircraftlist.h" +#include "blackmisc/nwserverlist.h" +#include +#include +#include + +namespace BlackCore +{ + /*! + * \brief Read bookings from VATSIM + */ + class CVatsimDataFileReader : public QObject + { + Q_OBJECT + + public: + //! \brief Constructor + explicit CVatsimDataFileReader(const QStringList &urls, QObject *parent = nullptr); + + //! \brief Update timestamp + QDateTime getUpdateTimestamp() const { return this->m_updateTimestamp; } + + //! \brief Read / re-read bookings + void read(); + + /*! + * \brief Set the update time + * \param updatePeriodMs 0 stops the timer + */ + void setInterval(int updatePeriodMs); + + //! \brief Get the timer interval (ms) + int interval() const { return this->m_updateTimer->interval();} + + //! \brief Get aircrafts + const BlackMisc::Aviation::CAircraftList &getAircrafts() { return this->m_aircrafts; } + + //! \brief Get aircrafts + const BlackMisc::Aviation::CAtcStationList &getAtcStations() { return this->m_atcStations; } + + //! \brief Get all voice servers + const BlackMisc::Network::CServerList &getVoiceServers() { return this->m_voiceServers; } + + + private slots: + //! \brief Data have been read + void loadFinished(QNetworkReply *nwReply); + + private: + QStringList m_serviceUrls; /*!< URL of the service */ + int m_currentUrlIndex; + QNetworkAccessManager *m_networkManager; + QDateTime m_updateTimestamp; + QTimer *m_updateTimer; + BlackMisc::Network::CServerList m_voiceServers; + BlackMisc::Aviation::CAtcStationList m_atcStations; + BlackMisc::Aviation::CAircraftList m_aircrafts; + static const QMap clientPartsToMap(const QString ¤tLine, const QStringList &clientSectionAttributes); + + //! \brief Section in file + enum Section + { + SectionNone, + SectionVoiceServer, + SectionClients, + SectionGeneral + }; + + signals: + //! \brief Data have been read + void dataRead(); + }; +} + +#endif // guard