mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-11-03 21:20:31 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			421 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			421 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
///////////////////////////////////////////////////////////////////////////////////
 | 
						|
// Copyright (C) 2023 Daniele Forsi <iu5hkx@gmail.com>                           //
 | 
						|
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.com>                     //
 | 
						|
//                                                                               //
 | 
						|
// 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("description");
 | 
						|
            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;
 | 
						|
}
 | 
						|
 |