mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-31 13:00:26 -04:00 
			
		
		
		
	
		
			
	
	
		
			420 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			420 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|  | ///////////////////////////////////////////////////////////////////////////////////
 | ||
|  | // Copyright (C) 2020 Jon Beniston, M7RCE                                        //
 | ||
|  | //                                                                               //
 | ||
|  | // This program is free software; you can redistribute it and/or modify          //
 | ||
|  | // it under the terms of the GNU General Public License as published by          //
 | ||
|  | // the Free Software Foundation as version 3 of the License, or                  //
 | ||
|  | // (at your option) any later version.                                           //
 | ||
|  | //                                                                               //
 | ||
|  | // This program is distributed in the hope that it will be useful,               //
 | ||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of                //
 | ||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                  //
 | ||
|  | // GNU General Public License V3 for more details.                               //
 | ||
|  | //                                                                               //
 | ||
|  | // You should have received a copy of the GNU General Public License             //
 | ||
|  | // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | ||
|  | ///////////////////////////////////////////////////////////////////////////////////
 | ||
|  | 
 | ||
|  | #include <QFile>
 | ||
|  | #include <QFileInfo>
 | ||
|  | #include <QByteArray>
 | ||
|  | #include <QList>
 | ||
|  | #include <QDebug>
 | ||
|  | #include <QLocale>
 | ||
|  | #include <QStandardPaths>
 | ||
|  | 
 | ||
|  | #include "util/ourairportsdb.h"
 | ||
|  | 
 | ||
|  | QMutex OurAirportsDB::m_mutex; | ||
|  | QSharedPointer<QHash<int, AirportInformation *>> OurAirportsDB::m_airportsById; | ||
|  | QSharedPointer<QHash<QString, AirportInformation *>> OurAirportsDB::m_airportsByIdent; | ||
|  | QDateTime OurAirportsDB::m_modifiedDateTime; | ||
|  | 
 | ||
|  | AirportInformation::~AirportInformation() | ||
|  | { | ||
|  |     qDeleteAll(m_frequencies); | ||
|  | } | ||
|  | 
 | ||
|  | QString AirportInformation::getImageName() const | ||
|  | { | ||
|  |     switch (m_type) | ||
|  |     { | ||
|  |     case AirportType::Large: | ||
|  |         return "airport_large.png"; | ||
|  |     case AirportType::Medium: | ||
|  |         return "airport_medium.png"; | ||
|  |     case AirportType::Heliport: | ||
|  |         return "heliport.png"; | ||
|  |     default: | ||
|  |         return "airport_small.png"; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | OurAirportsDB::OurAirportsDB(QObject *parent) : | ||
|  |     QObject(parent) | ||
|  | { | ||
|  |     connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OurAirportsDB::downloadFinished); | ||
|  | } | ||
|  | 
 | ||
|  | OurAirportsDB::~OurAirportsDB() | ||
|  | { | ||
|  |     disconnect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OurAirportsDB::downloadFinished); | ||
|  | } | ||
|  | 
 | ||
|  | void OurAirportsDB::downloadAirportInformation() | ||
|  | { | ||
|  |     // Download airport database
 | ||
|  |     QString urlString = AIRPORTS_URL; | ||
|  |     QUrl dbURL(urlString); | ||
|  |     qDebug() << "OurAirportsDB::downloadAirportInformation: Downloading " << urlString; | ||
|  |     emit downloadingURL(urlString); | ||
|  |     QNetworkReply *reply = m_dlm.download(dbURL, OurAirportsDB::getAirportDBFilename()); | ||
|  |     connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 bytesRead, qint64 totalBytes) { | ||
|  |         emit downloadProgress(bytesRead, totalBytes); | ||
|  |     }); | ||
|  | } | ||
|  | 
 | ||
|  | void OurAirportsDB::downloadFinished(const QString& filename, bool success) | ||
|  | { | ||
|  |     if (!success) | ||
|  |     { | ||
|  |         qWarning() << "OurAirportsDB::downloadFinished: Failed to download: " << filename; | ||
|  |         emit downloadError(QString("Failed to download: %1").arg(filename)); | ||
|  |     } | ||
|  |     else if (filename == OurAirportsDB::getAirportDBFilename()) | ||
|  |     { | ||
|  |         // Now download airport frequencies
 | ||
|  |         QString urlString = AIRPORT_FREQUENCIES_URL; | ||
|  |         QUrl dbURL(urlString); | ||
|  |         qDebug() << "OurAirportsDB::downloadFinished: Downloading " << urlString; | ||
|  |         emit downloadingURL(urlString); | ||
|  |         QNetworkReply *reply = m_dlm.download(dbURL, OurAirportsDB::getAirportFrequenciesDBFilename()); | ||
|  |         connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 bytesRead, qint64 totalBytes) { | ||
|  |             emit downloadProgress(bytesRead, totalBytes); | ||
|  |         }); | ||
|  |     } | ||
|  |     else if (filename == OurAirportsDB::getAirportFrequenciesDBFilename()) | ||
|  |     { | ||
|  |         emit downloadAirportInformationFinished(); | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         qDebug() << "OurAirportsDB::downloadFinished: Unexpected filename: " << filename; | ||
|  |         emit downloadError(QString("Unexpected filename: %1").arg(filename)); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | QSharedPointer<const QHash<int, AirportInformation *>> OurAirportsDB::getAirportsById() | ||
|  | { | ||
|  |     QMutexLocker locker(&m_mutex); | ||
|  | 
 | ||
|  |     readDB(); | ||
|  |     return m_airportsById; | ||
|  | } | ||
|  | 
 | ||
|  | QSharedPointer<const QHash<QString, AirportInformation *>> OurAirportsDB::getAirportsByIdent() | ||
|  | { | ||
|  |     QMutexLocker locker(&m_mutex); | ||
|  | 
 | ||
|  |     readDB(); | ||
|  |     return m_airportsByIdent; | ||
|  | } | ||
|  | 
 | ||
|  | void OurAirportsDB::readDB() | ||
|  | { | ||
|  |     QFileInfo airportDBFileInfo(getAirportDBFilename()); | ||
|  |     QDateTime airportDBModifiedDateTime = airportDBFileInfo.lastModified(); | ||
|  | 
 | ||
|  |     if (!m_airportsById || (airportDBModifiedDateTime > m_modifiedDateTime)) | ||
|  |     { | ||
|  |         // Using shared pointer, so old object, if it exists, will be deleted, when no longer user
 | ||
|  |         m_airportsById = QSharedPointer<QHash<int, AirportInformation *>>(OurAirportsDB::readAirportsDB(getAirportDBFilename())); | ||
|  |         if (m_airportsById != nullptr) | ||
|  |         { | ||
|  |             OurAirportsDB::readFrequenciesDB(getAirportFrequenciesDBFilename(), m_airportsById.get()); | ||
|  |             m_airportsByIdent = QSharedPointer<QHash<QString, AirportInformation *>>(identHash(m_airportsById.get())); | ||
|  |         } | ||
|  | 
 | ||
|  |         m_modifiedDateTime = airportDBModifiedDateTime; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | QString OurAirportsDB::getDataDir() | ||
|  | { | ||
|  |     // Get directory to store app data in
 | ||
|  |     QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); | ||
|  |     // First dir is writable
 | ||
|  |     return locations[0]; | ||
|  | } | ||
|  | 
 | ||
|  | QString OurAirportsDB::getAirportDBFilename() | ||
|  | { | ||
|  |     return getDataDir() + "/airportDatabase.csv"; | ||
|  | } | ||
|  | 
 | ||
|  | QString OurAirportsDB::getAirportFrequenciesDBFilename() | ||
|  | { | ||
|  |     return getDataDir() + "/airportFrequenciesDatabase.csv"; | ||
|  | } | ||
|  | 
 | ||
|  | QString OurAirportsDB::trimQuotes(const QString s) | ||
|  | { | ||
|  |     if (s.startsWith('\"') && s.endsWith('\"')) { | ||
|  |         return s.mid(1, s.size() - 2); | ||
|  |     } else { | ||
|  |         return s; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | // Read OurAirport's airport CSV file
 | ||
|  | // See comments for readOSNDB
 | ||
|  | QHash<int, AirportInformation *> *OurAirportsDB::readAirportsDB(const QString &filename) | ||
|  | { | ||
|  |     int cnt = 0; | ||
|  |     QHash<int, AirportInformation *> *airportInfo = nullptr; | ||
|  | 
 | ||
|  |     // Column numbers used for the data as of 2020/10/28
 | ||
|  |     int idCol = 0; | ||
|  |     int identCol = 1; | ||
|  |     int typeCol = 2; | ||
|  |     int nameCol = 3; | ||
|  |     int latitudeCol = 4; | ||
|  |     int longitudeCol = 5; | ||
|  |     int elevationCol = 6; | ||
|  | 
 | ||
|  |     qDebug() << "OurAirportsDB::readAirportsDB: " << filename; | ||
|  | 
 | ||
|  |     FILE *file; | ||
|  |     QByteArray utfFilename = filename.toUtf8(); | ||
|  |     QLocale cLocale(QLocale::C); | ||
|  |     if ((file = fopen(utfFilename.constData(), "r")) != NULL) | ||
|  |     { | ||
|  |         char row[2048]; | ||
|  | 
 | ||
|  |         if (fgets(row, sizeof(row), file)) | ||
|  |         { | ||
|  |             airportInfo = new QHash<int, AirportInformation *>(); | ||
|  |             airportInfo->reserve(70000); | ||
|  | 
 | ||
|  |             // Read header
 | ||
|  |             int idx = 0; | ||
|  |             char *p = strtok(row, ","); | ||
|  |             while (p != NULL) | ||
|  |             { | ||
|  |                 if (!strcmp(p, "id")) | ||
|  |                     idCol = idx; | ||
|  |                 else if (!strcmp(p, "ident")) | ||
|  |                     identCol = idx; | ||
|  |                 else if (!strcmp(p, "type")) | ||
|  |                     typeCol = idx; | ||
|  |                 else if (!strcmp(p, "name")) | ||
|  |                     nameCol = idx; | ||
|  |                 else if (!strcmp(p, "latitude_deg")) | ||
|  |                     latitudeCol = idx; | ||
|  |                 else if (!strcmp(p, "longitude_deg")) | ||
|  |                     longitudeCol = idx; | ||
|  |                 else if (!strcmp(p, "elevation_ft")) | ||
|  |                     elevationCol = idx; | ||
|  |                 p = strtok(NULL, ","); | ||
|  |                 idx++; | ||
|  |             } | ||
|  |             // Read data
 | ||
|  |             while (fgets(row, sizeof(row), file)) | ||
|  |             { | ||
|  |                 int id = 0; | ||
|  |                 char *idString = NULL; | ||
|  |                 char *ident = NULL; | ||
|  |                 size_t identLen = 0; | ||
|  |                 char *type = NULL; | ||
|  |                 size_t typeLen = 0; | ||
|  |                 char *name = NULL; | ||
|  |                 size_t nameLen = 0; | ||
|  |                 float latitude = 0.0f; | ||
|  |                 char *latitudeString = NULL; | ||
|  |                 size_t latitudeLen = 0; | ||
|  |                 float longitude = 0.0f; | ||
|  |                 char *longitudeString = NULL; | ||
|  |                 size_t longitudeLen = 0; | ||
|  |                 float elevation = 0.0f; | ||
|  |                 char *elevationString = NULL; | ||
|  |                 size_t elevationLen = 0; | ||
|  | 
 | ||
|  |                 p = strtok(row, ","); | ||
|  |                 idx = 0; | ||
|  |                 while (p != NULL) | ||
|  |                 { | ||
|  |                     // Read strings, stripping quotes
 | ||
|  |                     if (idx == idCol) | ||
|  |                     { | ||
|  |                         idString = p; | ||
|  |                         idString[strlen(idString)] = '\0'; | ||
|  |                         id = strtol(idString, NULL, 10); | ||
|  |                     } | ||
|  |                     else if (idx == identCol) | ||
|  |                     { | ||
|  |                         ident = p+1; | ||
|  |                         identLen = strlen(ident)-1; | ||
|  |                         ident[identLen] = '\0'; | ||
|  |                     } | ||
|  |                     else if (idx == typeCol) | ||
|  |                     { | ||
|  |                         type = p+1; | ||
|  |                         typeLen = strlen(type)-1; | ||
|  |                         type[typeLen] = '\0'; | ||
|  |                     } | ||
|  |                     else if (idx == nameCol) | ||
|  |                     { | ||
|  |                         name = p+1; | ||
|  |                         nameLen = strlen(name)-1; | ||
|  |                         name[nameLen] = '\0'; | ||
|  |                     } | ||
|  |                     else if (idx == latitudeCol) | ||
|  |                     { | ||
|  |                         latitudeString = p; | ||
|  |                         latitudeLen = strlen(latitudeString)-1; | ||
|  |                         latitudeString[latitudeLen] = '\0'; | ||
|  |                         latitude = cLocale.toFloat(latitudeString); | ||
|  |                     } | ||
|  |                     else if (idx == longitudeCol) | ||
|  |                     { | ||
|  |                         longitudeString = p; | ||
|  |                         longitudeLen = strlen(longitudeString)-1; | ||
|  |                         longitudeString[longitudeLen] = '\0'; | ||
|  |                         longitude = cLocale.toFloat(longitudeString); | ||
|  |                     } | ||
|  |                     else if (idx == elevationCol) | ||
|  |                     { | ||
|  |                         elevationString = p; | ||
|  |                         elevationLen = strlen(elevationString)-1; | ||
|  |                         elevationString[elevationLen] = '\0'; | ||
|  |                         elevation = cLocale.toFloat(elevationString); | ||
|  |                     } | ||
|  |                     p = strtok(NULL, ","); | ||
|  |                     idx++; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 // Only create the entry if we have some interesting data
 | ||
|  |                 if (((latitude != 0.0f) || (longitude != 0.0f)) && (type && strcmp(type, "closed"))) | ||
|  |                 { | ||
|  |                     AirportInformation *airport = new AirportInformation(); | ||
|  |                     airport->m_id = id; | ||
|  |                     airport->m_ident = QString(ident); | ||
|  |                     if (!strcmp(type, "small_airport")) { | ||
|  |                         airport->m_type = AirportInformation::AirportType::Small; | ||
|  |                     } else if (!strcmp(type, "medium_airport")) { | ||
|  |                         airport->m_type = AirportInformation::AirportType::Medium; | ||
|  |                     } else if (!strcmp(type, "large_airport")) { | ||
|  |                         airport->m_type = AirportInformation::AirportType::Large; | ||
|  |                     } else if (!strcmp(type, "heliport")) { | ||
|  |                         airport->m_type = AirportInformation::AirportType::Heliport; | ||
|  |                     } | ||
|  |                     airport->m_name = QString(name); | ||
|  |                     airport->m_latitude = latitude; | ||
|  |                     airport->m_longitude = longitude; | ||
|  |                     airport->m_elevation = elevation; | ||
|  |                     airportInfo->insert(id, airport); | ||
|  |                     cnt++; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |         fclose(file); | ||
|  |     } | ||
|  |     else | ||
|  |         qDebug() << "OurAirportsDB::readAirportsDB: Failed to open " << filename; | ||
|  | 
 | ||
|  |     qDebug() << "OurAirportsDB::readAirportsDB: Read " << cnt << " airports"; | ||
|  | 
 | ||
|  |     return airportInfo; | ||
|  | } | ||
|  | 
 | ||
|  | // Create hash table using ICAO identifier as key
 | ||
|  | QHash<QString, AirportInformation *> *OurAirportsDB::identHash(QHash<int, AirportInformation *> *in) | ||
|  | { | ||
|  |     QHash<QString, AirportInformation *> *out = new QHash<QString, AirportInformation *>(); | ||
|  |     QHashIterator<int, AirportInformation *> i(*in); | ||
|  |     while (i.hasNext()) | ||
|  |     { | ||
|  |         i.next(); | ||
|  |         AirportInformation *info = i.value(); | ||
|  |         out->insert(info->m_ident, info); | ||
|  |     } | ||
|  |     return out; | ||
|  | } | ||
|  | 
 | ||
|  | // Read OurAirport's airport frequencies CSV file
 | ||
|  | bool OurAirportsDB::readFrequenciesDB(const QString &filename, QHash<int, AirportInformation *> *airportInfo) | ||
|  | { | ||
|  |     int cnt = 0; | ||
|  | 
 | ||
|  |     // Column numbers used for the data as of 2020/10/28
 | ||
|  |     int airportRefCol = 1; | ||
|  |     int typeCol = 3; | ||
|  |     int descriptionCol = 4; | ||
|  |     int frequencyCol = 5; | ||
|  | 
 | ||
|  |     qDebug() << "OurAirportsDB::readFrequenciesDB: " << filename; | ||
|  | 
 | ||
|  |     QFile file(filename); | ||
|  |     if (file.open(QIODevice::ReadOnly)) | ||
|  |     { | ||
|  |         QList<QByteArray> colNames; | ||
|  |         int idx; | ||
|  | 
 | ||
|  |         // Read header
 | ||
|  |         if (!file.atEnd()) | ||
|  |         { | ||
|  |             QByteArray row = file.readLine().trimmed(); | ||
|  |             colNames = row.split(','); | ||
|  |             // Work out which columns the data is in, based on the headers
 | ||
|  |             idx = colNames.indexOf("airport_ref"); | ||
|  |             if (idx >= 0) | ||
|  |                  airportRefCol = idx; | ||
|  |             idx = colNames.indexOf("type"); | ||
|  |             if (idx >= 0) | ||
|  |                  typeCol = idx; | ||
|  |             idx = colNames.indexOf("descrption"); | ||
|  |             if (idx >= 0) | ||
|  |                  descriptionCol = idx; | ||
|  |             idx = colNames.indexOf("frequency_mhz"); | ||
|  |             if (idx >= 0) | ||
|  |                  frequencyCol = idx; | ||
|  |         } | ||
|  |         // Read data
 | ||
|  |         while (!file.atEnd()) | ||
|  |         { | ||
|  |             QByteArray row = file.readLine(); | ||
|  |             QList<QByteArray> cols = row.split(','); | ||
|  | 
 | ||
|  |             bool ok = false; | ||
|  |             int airportRef = cols[airportRefCol].toInt(&ok, 10); | ||
|  |             if (ok) | ||
|  |             { | ||
|  |                 if (airportInfo->contains(airportRef)) | ||
|  |                 { | ||
|  |                     QString type = trimQuotes(cols[typeCol]); | ||
|  |                     QString description = trimQuotes(cols[descriptionCol]); | ||
|  |                     float frequency = cols[frequencyCol].toFloat(); | ||
|  | 
 | ||
|  |                     AirportInformation::FrequencyInformation *frequencyInfo = new AirportInformation::FrequencyInformation(); | ||
|  |                     frequencyInfo->m_type = type; | ||
|  |                     frequencyInfo->m_description = description; | ||
|  |                     frequencyInfo->m_frequency = frequency; | ||
|  |                     airportInfo->value(airportRef)->m_frequencies.append(frequencyInfo); | ||
|  |                     cnt++; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |         file.close(); | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         qDebug() << "Failed to open " << filename << " " << file.errorString(); | ||
|  |         return false; | ||
|  |     } | ||
|  | 
 | ||
|  |     qDebug() << "OurAirportsDB::readFrequenciesDB: - read " << cnt << " airports"; | ||
|  | 
 | ||
|  |     return true; | ||
|  | } | ||
|  | 
 |