1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-22 08:04:49 -05:00

Map Updates

Allow OpenSkyNetwork DB, OpenAIP and OurAirports DB stuctures to be
shared by different plugins, to speed up loading.
Perform map anti-aliasing on the whole map, rather than just info boxes,
to improve rendering speed when there are many items. Add map
multisampling as a preference.
Add plotting of airspaces, airports, navaids on Map feature.
Add support for polylines and polygons to be plotted on Map feature.
Add support for images to 2D Map feature.
Add distance and name filters to Map feature.
Filter map items when zoomed out or if off screen, to improve rendering
performance.
Add UK DAB, FM and AM transmitters to Map feature.
Use labelless maps for 2D transmit maps in Map feature (same as in ADS-B
demod).
This commit is contained in:
Jon Beniston 2023-02-14 14:46:08 +00:00
parent 74dcf31bb9
commit 9c7aa8b333
61 changed files with 8373 additions and 1994 deletions

View File

@ -29,7 +29,6 @@ set(adsb_HEADERS
include_directories( include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${Boost_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}
${Qt${QT_DEFAULT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS}
) )
if(NOT SERVER_MODE) if(NOT SERVER_MODE)

View File

@ -35,8 +35,6 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QtGui/private/qzipreader_p.h>
#include "ui_adsbdemodgui.h" #include "ui_adsbdemodgui.h"
#include "device/deviceapi.h" #include "device/deviceapi.h"
#include "channel/channelwebapiutils.h" #include "channel/channelwebapiutils.h"
@ -531,14 +529,7 @@ QVariant AirportModel::data(const QModelIndex &index, int role) const
else if (role == AirportModel::airportImageRole) else if (role == AirportModel::airportImageRole)
{ {
// Select an image to use for the airport // Select an image to use for the airport
if (m_airports[row]->m_type == ADSBDemodSettings::AirportType::Large) return QVariant::fromValue(m_airports[row]->getImageName());
return QVariant::fromValue(QString("airport_large.png"));
else if (m_airports[row]->m_type == ADSBDemodSettings::AirportType::Medium)
return QVariant::fromValue(QString("airport_medium.png"));
else if (m_airports[row]->m_type == ADSBDemodSettings::AirportType::Heliport)
return QVariant::fromValue(QString("heliport.png"));
else
return QVariant::fromValue(QString("airport_small.png"));
} }
else if (role == AirportModel::bubbleColourRole) else if (role == AirportModel::bubbleColourRole)
{ {
@ -618,23 +609,17 @@ QVariant AirspaceModel::data(const QModelIndex &index, int role) const
} }
else if (role == AirspaceModel::airspaceBorderColorRole) else if (role == AirspaceModel::airspaceBorderColorRole)
{ {
if (m_airspaces[row]->m_category == "D") if (m_airspaces[row]->m_category == "D") {
{ return QVariant::fromValue(QColor(0x00, 0x00, 0xff, 0x00));
return QVariant::fromValue(QColor("blue")); } else {
} return QVariant::fromValue(QColor(0xff, 0x00, 0x00, 0x00));
else
{
return QVariant::fromValue(QColor("red"));
} }
} }
else if (role == AirspaceModel::airspaceFillColorRole) else if (role == AirspaceModel::airspaceFillColorRole)
{ {
if (m_airspaces[row]->m_category == "D") if (m_airspaces[row]->m_category == "D") {
{
return QVariant::fromValue(QColor(0x00, 0x00, 0xff, 0x10)); return QVariant::fromValue(QColor(0x00, 0x00, 0xff, 0x10));
} } else {
else
{
return QVariant::fromValue(QColor(0xff, 0x00, 0x00, 0x10)); return QVariant::fromValue(QColor(0xff, 0x00, 0x00, 0x10));
} }
} }
@ -789,6 +774,22 @@ bool ADSBDemodGUI::updateLocalPosition(Aircraft *aircraft, double latitude, doub
} }
} }
void ADSBDemodGUI::clearFromMap(const QString& name)
{
QList<ObjectPipe*> mapPipes;
MainCore::instance()->getMessagePipes().getMessagePipes(m_adsbDemod, "mapitems", mapPipes);
for (const auto& pipe : mapPipes)
{
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(name));
swgMapItem->setImage(new QString(""));
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_adsbDemod, swgMapItem);
messageQueue->push(msg);
}
}
void ADSBDemodGUI::sendToMap(Aircraft *aircraft, QList<SWGSDRangel::SWGMapAnimation *> *animations) void ADSBDemodGUI::sendToMap(Aircraft *aircraft, QList<SWGSDRangel::SWGMapAnimation *> *animations)
{ {
// Send to Map feature // Send to Map feature
@ -817,6 +818,7 @@ void ADSBDemodGUI::sendToMap(Aircraft *aircraft, QList<SWGSDRangel::SWGMapAnimat
swgMapItem->setAltitude(altitudeM); swgMapItem->setAltitude(altitudeM);
swgMapItem->setPositionDateTime(new QString(aircraft->m_positionDateTime.toString(Qt::ISODateWithMs))); swgMapItem->setPositionDateTime(new QString(aircraft->m_positionDateTime.toString(Qt::ISODateWithMs)));
swgMapItem->setFixedPosition(false); swgMapItem->setFixedPosition(false);
swgMapItem->setAvailableUntil(new QString(aircraft->m_positionDateTime.addSecs(60).toString(Qt::ISODateWithMs)));
swgMapItem->setImage(new QString(QString("qrc:///map/%1").arg(aircraft->getImage()))); swgMapItem->setImage(new QString(QString("qrc:///map/%1").arg(aircraft->getImage())));
swgMapItem->setImageRotation(aircraft->m_heading); swgMapItem->setImageRotation(aircraft->m_heading);
swgMapItem->setText(new QString(aircraft->getText(true))); swgMapItem->setText(new QString(aircraft->getText(true)));
@ -996,9 +998,12 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft)
} }
} }
if (m_settings.m_autoResizeTableColumns) if (!m_loadingData)
ui->adsbData->resizeColumnsToContents(); {
ui->adsbData->setSortingEnabled(true); if (m_settings.m_autoResizeTableColumns)
ui->adsbData->resizeColumnsToContents();
ui->adsbData->setSortingEnabled(true);
}
// Check to see if we need to emit a notification about this new aircraft // Check to see if we need to emit a notification about this new aircraft
checkStaticNotification(aircraft); checkStaticNotification(aircraft);
} }
@ -3586,17 +3591,10 @@ void ADSBDemodGUI::on_getOSNDB_clicked()
// Don't try to download while already in progress // Don't try to download while already in progress
if (m_progressDialog == nullptr) if (m_progressDialog == nullptr)
{ {
QString osnDBFilename = AircraftInformation::getOSNDBZipFilename(); m_progressDialog = new QProgressDialog(this);
if (confirmDownload(osnDBFilename)) m_progressDialog->setCancelButton(nullptr);
{ m_progressDialog->setWindowFlag(Qt::WindowCloseButtonHint, false);
// Download Opensky network database to a file m_osnDB.downloadAircraftInformation();
QUrl dbURL(QString(OSNDB_URL));
m_progressDialog = new QProgressDialog(this);
m_progressDialog->setCancelButton(nullptr);
m_progressDialog->setLabelText(QString("Downloading %1.").arg(OSNDB_URL));
QNetworkReply *reply = m_dlm.download(dbURL, osnDBFilename);
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updateDownloadProgress(qint64,qint64)));
}
} }
} }
@ -3605,17 +3603,10 @@ void ADSBDemodGUI::on_getAirportDB_clicked()
// Don't try to download while already in progress // Don't try to download while already in progress
if (m_progressDialog == nullptr) if (m_progressDialog == nullptr)
{ {
QString airportDBFile = getAirportDBFilename(); m_progressDialog = new QProgressDialog(this);
if (confirmDownload(airportDBFile)) m_progressDialog->setCancelButton(nullptr);
{ m_progressDialog->setWindowFlag(Qt::WindowCloseButtonHint, false);
// Download Opensky network database to a file m_ourAirportsDB.downloadAirportInformation();
QUrl dbURL(QString(AIRPORTS_URL));
m_progressDialog = new QProgressDialog(this);
m_progressDialog->setCancelButton(nullptr);
m_progressDialog->setLabelText(QString("Downloading %1.").arg(AIRPORTS_URL));
QNetworkReply *reply = m_dlm.download(dbURL, airportDBFile);
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updateDownloadProgress(qint64,qint64)));
}
} }
} }
@ -3627,6 +3618,7 @@ void ADSBDemodGUI::on_getAirspacesDB_clicked()
m_progressDialog = new QProgressDialog(this); m_progressDialog = new QProgressDialog(this);
m_progressDialog->setMaximum(OpenAIP::m_countryCodes.size()); m_progressDialog->setMaximum(OpenAIP::m_countryCodes.size());
m_progressDialog->setCancelButton(nullptr); m_progressDialog->setCancelButton(nullptr);
m_progressDialog->setWindowFlag(Qt::WindowCloseButtonHint, false);
m_openAIP.downloadAirspaces(); m_openAIP.downloadAirspaces();
} }
} }
@ -3651,136 +3643,6 @@ QString ADSBDemodGUI::getDataDir()
return locations[0]; return locations[0];
} }
QString ADSBDemodGUI::getAirportDBFilename()
{
return getDataDir() + "/airportDatabase.csv";
}
QString ADSBDemodGUI::getAirportFrequenciesDBFilename()
{
return getDataDir() + "/airportFrequenciesDatabase.csv";
}
qint64 ADSBDemodGUI::fileAgeInDays(QString filename)
{
QFile file(filename);
if (file.exists())
{
QDateTime modified = file.fileTime(QFileDevice::FileModificationTime);
if (modified.isValid())
return modified.daysTo(QDateTime::currentDateTime());
else
return -1;
}
return -1;
}
bool ADSBDemodGUI::confirmDownload(QString filename)
{
qint64 age = fileAgeInDays(filename);
if ((age == -1) || (age > 100))
return true;
else
{
QMessageBox::StandardButton reply;
if (age == 0)
reply = QMessageBox::question(this, "Confirm download", "This file was last downloaded today. Are you sure you wish to redownload it?", QMessageBox::Yes|QMessageBox::No);
else if (age == 1)
reply = QMessageBox::question(this, "Confirm download", "This file was last downloaded yesterday. Are you sure you wish to redownload it?", QMessageBox::Yes|QMessageBox::No);
else
reply = QMessageBox::question(this, "Confirm download", QString("This file was last downloaded %1 days ago. Are you sure you wish to redownload this file?").arg(age), QMessageBox::Yes|QMessageBox::No);
return reply == QMessageBox::Yes;
}
}
bool ADSBDemodGUI::readOSNDB(const QString& filename)
{
m_aircraftInfo = AircraftInformation::readOSNDB(filename);
return m_aircraftInfo != nullptr;
}
bool ADSBDemodGUI::readFastDB(const QString& filename)
{
m_aircraftInfo = AircraftInformation::readFastDB(filename);
return m_aircraftInfo != nullptr;
}
void ADSBDemodGUI::updateDownloadProgress(qint64 bytesRead, qint64 totalBytes)
{
if (m_progressDialog)
{
m_progressDialog->setMaximum(totalBytes);
m_progressDialog->setValue(bytesRead);
}
}
void ADSBDemodGUI::downloadFinished(const QString& filename, bool success)
{
bool closeDialog = true;
if (success)
{
if (filename == AircraftInformation::getOSNDBZipFilename())
{
// Extract .csv file from .zip file
QZipReader reader(filename);
QByteArray database = reader.fileData("media/data/samples/metadata/aircraftDatabase.csv");
if (database.size() > 0)
{
QFile file(AircraftInformation::getOSNDBFilename());
if (file.open(QIODevice::WriteOnly))
{
file.write(database);
file.close();
}
else
{
qWarning() << "ADSBDemodGUI::downloadFinished - Failed to open " << file.fileName() << " for writing";
}
}
else
{
qWarning() << "ADSBDemodGUI::downloadFinished - aircraftDatabase.csv not in expected dir. Extracting all.";
if (!reader.extractAll(getDataDir())) {
qWarning() << "ADSBDemodGUI::downloadFinished - Failed to extract files from " << filename;
}
}
readOSNDB(AircraftInformation::getOSNDBFilename());
// Convert to condensed format for faster loading later
m_progressDialog->setLabelText("Processing.");
AircraftInformation::writeFastDB(AircraftInformation::getFastDBFilename(), m_aircraftInfo);
}
else if (filename == getAirportDBFilename())
{
m_airportInfo = AirportInformation::readAirportsDB(filename);
// Now download airport frequencies
QUrl dbURL(QString(AIRPORT_FREQUENCIES_URL));
m_progressDialog->setLabelText(QString("Downloading %1.").arg(AIRPORT_FREQUENCIES_URL));
QNetworkReply *reply = m_dlm.download(dbURL, getAirportFrequenciesDBFilename());
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updateDownloadProgress(qint64,qint64)));
closeDialog = false;
}
else if (filename == getAirportFrequenciesDBFilename())
{
if (m_airportInfo != nullptr)
{
AirportInformation::readFrequenciesDB(filename, m_airportInfo);
// Update airports on map
updateAirports();
}
}
else
{
qDebug() << "ADSBDemodGUI::downloadFinished: Unexpected filename: " << filename;
}
}
if (closeDialog && m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}
void ADSBDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) void ADSBDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{ {
(void) widget; (void) widget;
@ -4335,12 +4197,12 @@ void ADSBDemodGUI::updateAirports()
} }
m_airportModel.removeAllAirports(); m_airportModel.removeAllAirports();
QHash<int, AirportInformation *>::iterator i = m_airportInfo->begin(); QHash<int, AirportInformation *>::const_iterator i = m_airportInfo->begin();
AzEl azEl = m_azEl; AzEl azEl = m_azEl;
while (i != m_airportInfo->end()) while (i != m_airportInfo->end())
{ {
AirportInformation *airportInfo = i.value(); const AirportInformation *airportInfo = i.value();
// Calculate distance and az/el to airport from My Position // Calculate distance and az/el to airport from My Position
azEl.setTarget(airportInfo->m_latitude, airportInfo->m_longitude, Units::feetToMetres(airportInfo->m_elevation)); azEl.setTarget(airportInfo->m_latitude, airportInfo->m_longitude, Units::feetToMetres(airportInfo->m_elevation));
@ -4370,7 +4232,7 @@ void ADSBDemodGUI::updateAirspaces()
{ {
AzEl azEl = m_azEl; AzEl azEl = m_azEl;
m_airspaceModel.removeAllAirspaces(); m_airspaceModel.removeAllAirspaces();
for (const auto& airspace: m_airspaces) for (const auto airspace: *m_airspaces)
{ {
if (m_settings.m_airspaces.contains(airspace->m_category)) if (m_settings.m_airspaces.contains(airspace->m_category))
{ {
@ -4392,7 +4254,7 @@ void ADSBDemodGUI::updateNavAids()
m_navAidModel.removeAllNavAids(); m_navAidModel.removeAllNavAids();
if (m_settings.m_displayNavAids) if (m_settings.m_displayNavAids)
{ {
for (const auto& navAid: m_navAids) for (const auto navAid: *m_navAids)
{ {
// Calculate distance to NavAid from My Position // Calculate distance to NavAid from My Position
azEl.setTarget(navAid->m_latitude, navAid->m_longitude, Units::feetToMetres(navAid->m_elevation)); azEl.setTarget(navAid->m_latitude, navAid->m_longitude, Units::feetToMetres(navAid->m_elevation));
@ -4745,7 +4607,8 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
m_airspaceModel(this), m_airspaceModel(this),
m_trackAircraft(nullptr), m_trackAircraft(nullptr),
m_highlightAircraft(nullptr), m_highlightAircraft(nullptr),
m_progressDialog(nullptr) m_progressDialog(nullptr),
m_loadingData(false)
{ {
setAttribute(Qt::WA_DeleteOnClose, true); setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/channelrx/demodadsb/readme.md"; m_helpURL = "plugins/channelrx/demodadsb/readme.md";
@ -4755,7 +4618,17 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
rollupContents->arrangeRollups(); rollupContents->arrangeRollups();
connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
m_osmPort = 0; // Pick a free port // Enable MSAA antialiasing on 2D map
// This is much faster than using layer.smooth in the QML, when there are many items
int multisamples = MainCore::instance()->getSettings().getMapMultisampling();
if (multisamples > 0)
{
QSurfaceFormat format;
format.setSamples(multisamples);
ui->map->setFormat(format);
}
m_osmPort = 0; // Pick a free port
m_templateServer = new ADSBOSMTemplateServer("q2RVNAe3eFKCH4XsrE3r", m_osmPort); m_templateServer = new ADSBOSMTemplateServer("q2RVNAe3eFKCH4XsrE3r", m_osmPort);
ui->map->setAttribute(Qt::WA_AcceptTouchEvents, true); ui->map->setAttribute(Qt::WA_AcceptTouchEvents, true);
@ -4767,7 +4640,6 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map.qml"))); ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map.qml")));
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &ADSBDemodGUI::downloadFinished);
m_adsbDemod = reinterpret_cast<ADSBDemod*>(rxChannel); //new ADSBDemod(m_deviceUISet->m_deviceSourceAPI); m_adsbDemod = reinterpret_cast<ADSBDemod*>(rxChannel); //new ADSBDemod(m_deviceUISet->m_deviceSourceAPI);
m_adsbDemod->setMessageQueueToGUI(getInputMessageQueue()); m_adsbDemod->setMessageQueueToGUI(getInputMessageQueue());
@ -4836,28 +4708,28 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
ui->flightDetails->setVisible(false); ui->flightDetails->setVisible(false);
ui->aircraftDetails->setVisible(false); ui->aircraftDetails->setVisible(false);
AircraftInformation::init();
// Read aircraft information database, if it has previously been downloaded // Read aircraft information database, if it has previously been downloaded
if (!readFastDB(AircraftInformation::getFastDBFilename())) AircraftInformation::init();
{ connect(&m_osnDB, &OsnDB::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
if (readOSNDB(AircraftInformation::getOSNDBFilename())) connect(&m_osnDB, &OsnDB::downloadError, this, &ADSBDemodGUI::downloadError);
AircraftInformation::writeFastDB(AircraftInformation::getFastDBFilename(), m_aircraftInfo); connect(&m_osnDB, &OsnDB::downloadProgress, this, &ADSBDemodGUI::downloadProgress);
} connect(&m_osnDB, &OsnDB::downloadAircraftInformationFinished, this, &ADSBDemodGUI::downloadAircraftInformationFinished);
// Read airport information database, if it has previously been downloaded m_aircraftInfo = OsnDB::getAircraftInformation();
m_airportInfo = AirportInformation::readAirportsDB(getAirportDBFilename());
if (m_airportInfo != nullptr)
AirportInformation::readFrequenciesDB(getAirportFrequenciesDBFilename(), m_airportInfo);
// Read airport information database, if it has previously been downloaded
connect(&m_ourAirportsDB, &OurAirportsDB::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
connect(&m_ourAirportsDB, &OurAirportsDB::downloadError, this, &ADSBDemodGUI::downloadError);
connect(&m_ourAirportsDB, &OurAirportsDB::downloadProgress, this, &ADSBDemodGUI::downloadProgress);
connect(&m_ourAirportsDB, &OurAirportsDB::downloadAirportInformationFinished, this, &ADSBDemodGUI::downloadAirportInformationFinished);
m_airportInfo = OurAirportsDB::getAirportsById();
// Read airspaces and NAVAIDs
connect(&m_openAIP, &OpenAIP::downloadingURL, this, &ADSBDemodGUI::downloadingURL); connect(&m_openAIP, &OpenAIP::downloadingURL, this, &ADSBDemodGUI::downloadingURL);
connect(&m_openAIP, &OpenAIP::downloadError, this, &ADSBDemodGUI::downloadError); connect(&m_openAIP, &OpenAIP::downloadError, this, &ADSBDemodGUI::downloadError);
connect(&m_openAIP, &OpenAIP::downloadAirspaceFinished, this, &ADSBDemodGUI::downloadAirspaceFinished); connect(&m_openAIP, &OpenAIP::downloadAirspaceFinished, this, &ADSBDemodGUI::downloadAirspaceFinished);
connect(&m_openAIP, &OpenAIP::downloadNavAidsFinished, this, &ADSBDemodGUI::downloadNavAidsFinished); connect(&m_openAIP, &OpenAIP::downloadNavAidsFinished, this, &ADSBDemodGUI::downloadNavAidsFinished);
m_airspaces = OpenAIP::getAirspaces();
// Read airspaces m_navAids = OpenAIP::getNavAids();
m_airspaces = OpenAIP::readAirspaces();
// Read NavAids
m_navAids = OpenAIP::readNavAids();
// Get station position // Get station position
Real stationLatitude = MainCore::instance()->getSettings().getLatitude(); Real stationLatitude = MainCore::instance()->getSettings().getLatitude();
@ -4929,26 +4801,24 @@ ADSBDemodGUI::~ADSBDemodGUI()
disconnect(&m_openAIP, &OpenAIP::downloadNavAidsFinished, this, &ADSBDemodGUI::downloadNavAidsFinished); disconnect(&m_openAIP, &OpenAIP::downloadNavAidsFinished, this, &ADSBDemodGUI::downloadNavAidsFinished);
disconnect(&m_planeSpotters, &PlaneSpotters::aircraftPhoto, this, &ADSBDemodGUI::aircraftPhoto); disconnect(&m_planeSpotters, &PlaneSpotters::aircraftPhoto, this, &ADSBDemodGUI::aircraftPhoto);
disconnect(&m_redrawMapTimer, &QTimer::timeout, this, &ADSBDemodGUI::redrawMap); disconnect(&m_redrawMapTimer, &QTimer::timeout, this, &ADSBDemodGUI::redrawMap);
disconnect(&MainCore::instance()->getMasterTimer(), &QTimer::timeout, this, &ADSBDemodGUI::tick);
m_redrawMapTimer.stop(); m_redrawMapTimer.stop();
// Remove aircraft from Map feature
QHash<int, Aircraft *>::iterator i = m_aircraft.begin();
while (i != m_aircraft.end())
{
Aircraft *aircraft = i.value();
clearFromMap(QString("%1").arg(aircraft->m_icao, 0, 16));
++i;
}
delete ui; delete ui;
qDeleteAll(m_aircraft); qDeleteAll(m_aircraft);
if (m_airportInfo) {
qDeleteAll(*m_airportInfo);
}
if (m_aircraftInfo) {
qDeleteAll(*m_aircraftInfo);
}
if (m_flightInformation) if (m_flightInformation)
{ {
disconnect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated); disconnect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated);
delete m_flightInformation; delete m_flightInformation;
} }
if (m_aviationWeather) delete m_aviationWeather;
{
delete m_aviationWeather;
}
qDeleteAll(m_airspaces);
qDeleteAll(m_navAids);
qDeleteAll(m_3DModelMatch); qDeleteAll(m_3DModelMatch);
delete m_networkManager; delete m_networkManager;
} }
@ -5116,7 +4986,7 @@ void ADSBDemodGUI::tick()
// Tick is called 20x a second - lets check this every 10 seconds // Tick is called 20x a second - lets check this every 10 seconds
if (m_tickCount % (20*10) == 0) if (m_tickCount % (20*10) == 0)
{ {
// Remove aircraft that haven't been heard of for a minute as probably out of range // Remove aircraft that haven't been heard of for a user-defined time, as probably out of range
QDateTime now = QDateTime::currentDateTime(); QDateTime now = QDateTime::currentDateTime();
qint64 nowSecs = now.toSecsSinceEpoch(); qint64 nowSecs = now.toSecsSinceEpoch();
QHash<int, Aircraft *>::iterator i = m_aircraft.begin(); QHash<int, Aircraft *>::iterator i = m_aircraft.begin();
@ -5142,18 +5012,7 @@ void ADSBDemodGUI::tick()
// Remove aircraft from hash // Remove aircraft from hash
i = m_aircraft.erase(i); i = m_aircraft.erase(i);
// Remove from map feature // Remove from map feature
QList<ObjectPipe*> mapPipes; clearFromMap(QString("%1").arg(aircraft->m_icao, 0, 16));
MainCore::instance()->getMessagePipes().getMessagePipes(this, "mapitems", mapPipes);
for (const auto& pipe : mapPipes)
{
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(QString("%1").arg(aircraft->m_icao, 0, 16)));
swgMapItem->setImage(new QString(""));
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_adsbDemod, swgMapItem);
messageQueue->push(msg);
}
// And finally free its memory // And finally free its memory
delete aircraft; delete aircraft;
@ -5376,6 +5235,9 @@ void ADSBDemodGUI::on_logOpen_clicked()
QFile file(fileNames[0]); QFile file(fileNames[0]);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{ {
QDateTime startTime = QDateTime::currentDateTime();
m_loadingData = true;
ui->adsbData->blockSignals(true);
QTextStream in(&file); QTextStream in(&file);
QString error; QString error;
QHash<QString, int> colIndexes = CSV::readHeader(in, {"Data", "Correlation"}, error); QHash<QString, int> colIndexes = CSV::readHeader(in, {"Data", "Correlation"}, error);
@ -5414,7 +5276,7 @@ void ADSBDemodGUI::on_logOpen_clicked()
} }
//qDebug() << "bytes.szie " << bytes.size() << " crc " << Qt::hex << crcCalc; //qDebug() << "bytes.szie " << bytes.size() << " crc " << Qt::hex << crcCalc;
handleADSB(bytes, dateTime, correlation, correlation, crcCalc, false); handleADSB(bytes, dateTime, correlation, correlation, crcCalc, false);
if ((count > 0) && (count % 10000 == 0)) if ((count > 0) && (count % 100000 == 0))
{ {
dialog.setText(QString("Reading ADS-B data\n%1 (Skipped %2)").arg(count).arg(countOtherDF)); dialog.setText(QString("Reading ADS-B data\n%1 (Skipped %2)").arg(count).arg(countOtherDF));
QApplication::processEvents(); QApplication::processEvents();
@ -5437,6 +5299,13 @@ void ADSBDemodGUI::on_logOpen_clicked()
{ {
QMessageBox::critical(this, "ADS-B", error); QMessageBox::critical(this, "ADS-B", error);
} }
ui->adsbData->blockSignals(false);
m_loadingData = false;
if (m_settings.m_autoResizeTableColumns)
ui->adsbData->resizeColumnsToContents();
ui->adsbData->setSortingEnabled(true);
QDateTime finishTime = QDateTime::currentDateTime();
qDebug() << "Read CSV in " << startTime.secsTo(finishTime);
} }
else else
{ {
@ -5455,6 +5324,15 @@ void ADSBDemodGUI::downloadingURL(const QString& url)
} }
} }
void ADSBDemodGUI::downloadProgress(qint64 bytesRead, qint64 totalBytes)
{
if (m_progressDialog)
{
m_progressDialog->setMaximum(totalBytes);
m_progressDialog->setValue(bytesRead);
}
}
void ADSBDemodGUI::downloadError(const QString& error) void ADSBDemodGUI::downloadError(const QString& error)
{ {
QMessageBox::critical(this, "ADS-B", error); QMessageBox::critical(this, "ADS-B", error);
@ -5471,7 +5349,7 @@ void ADSBDemodGUI::downloadAirspaceFinished()
if (m_progressDialog) { if (m_progressDialog) {
m_progressDialog->setLabelText("Reading airspaces."); m_progressDialog->setLabelText("Reading airspaces.");
} }
m_airspaces = OpenAIP::readAirspaces(); m_airspaces = OpenAIP::getAirspaces();
updateAirspaces(); updateAirspaces();
m_openAIP.downloadNavAids(); m_openAIP.downloadNavAids();
} }
@ -5481,7 +5359,7 @@ void ADSBDemodGUI::downloadNavAidsFinished()
if (m_progressDialog) { if (m_progressDialog) {
m_progressDialog->setLabelText("Reading NAVAIDs."); m_progressDialog->setLabelText("Reading NAVAIDs.");
} }
m_navAids = OpenAIP::readNavAids(); m_navAids = OpenAIP::getNavAids();
updateNavAids(); updateNavAids();
if (m_progressDialog) if (m_progressDialog)
{ {
@ -5491,6 +5369,50 @@ void ADSBDemodGUI::downloadNavAidsFinished()
} }
} }
void ADSBDemodGUI::downloadAircraftInformationFinished()
{
if (m_progressDialog)
{
delete m_progressDialog;
m_progressDialog = new QProgressDialog("Reading Aircraft Information.", "", 0, 1, this);
m_progressDialog->setCancelButton(nullptr);
m_progressDialog->setWindowFlag(Qt::WindowCloseButtonHint, false);
m_progressDialog->setWindowModality(Qt::WindowModal);
m_progressDialog->show();
QApplication::processEvents();
}
m_aircraftInfo = OsnDB::getAircraftInformation();
m_aircraftModel.updateAircraftInformation(m_aircraftInfo);
if (m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}
void ADSBDemodGUI::downloadAirportInformationFinished()
{
if (m_progressDialog)
{
delete m_progressDialog;
m_progressDialog = new QProgressDialog("Reading Airport Information.", "", 0, 1, this);
m_progressDialog->setCancelButton(nullptr);
m_progressDialog->setWindowFlag(Qt::WindowCloseButtonHint, false);
m_progressDialog->setWindowModality(Qt::WindowModal);
m_progressDialog->show();
QApplication::processEvents();
}
m_airportInfo = OurAirportsDB::getAirportsById();
updateAirports();
if (m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}
int ADSBDemodGUI::squawkDecode(int modeA) const int ADSBDemodGUI::squawkDecode(int modeA) const
{ {
int a, b, c, d; int a, b, c, d;
@ -5771,16 +5693,21 @@ void ADSBDemodGUI::preferenceChanged(int elementType)
Real stationLongitude = MainCore::instance()->getSettings().getLongitude(); Real stationLongitude = MainCore::instance()->getSettings().getLongitude();
Real stationAltitude = MainCore::instance()->getSettings().getAltitude(); Real stationAltitude = MainCore::instance()->getSettings().getAltitude();
if ( (stationLatitude != m_azEl.getLocationSpherical().m_latitude) QGeoCoordinate stationPosition(stationLatitude, stationLongitude, stationAltitude);
|| (stationLongitude != m_azEl.getLocationSpherical().m_longitude) QGeoCoordinate previousPosition(m_azEl.getLocationSpherical().m_latitude, m_azEl.getLocationSpherical().m_longitude, m_azEl.getLocationSpherical().m_altitude);
|| (stationAltitude != m_azEl.getLocationSpherical().m_altitude))
if (stationPosition != previousPosition)
{ {
m_azEl.setLocation(stationLatitude, stationLongitude, stationAltitude); m_azEl.setLocation(stationLatitude, stationLongitude, stationAltitude);
// Update distances and what is visible // Update distances and what is visible, but only do it if position has changed significantly
updateAirports(); if (!m_lastFullUpdatePosition.isValid() || (stationPosition.distanceTo(m_lastFullUpdatePosition) >= 1000))
updateAirspaces(); {
updateNavAids(); updateAirports();
updateAirspaces();
updateNavAids();
m_lastFullUpdatePosition = stationPosition;
}
// Update icon position on Map // Update icon position on Map
QQuickItem *item = ui->map->rootObject(); QQuickItem *item = ui->map->rootObject();
@ -5875,3 +5802,4 @@ void ADSBDemodGUI::updateAbsoluteCenterFrequency()
{ {
setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset); setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
} }

View File

@ -37,7 +37,6 @@
#include "util/messagequeue.h" #include "util/messagequeue.h"
#include "util/azel.h" #include "util/azel.h"
#include "util/movingaverage.h" #include "util/movingaverage.h"
#include "util/httpdownloadmanager.h"
#include "util/flightinformation.h" #include "util/flightinformation.h"
#include "util/openaip.h" #include "util/openaip.h"
#include "util/planespotters.h" #include "util/planespotters.h"
@ -47,7 +46,7 @@
#include "SWGMapItem.h" #include "SWGMapItem.h"
#include "adsbdemodsettings.h" #include "adsbdemodsettings.h"
#include "ourairportsdb.h" #include "util/ourairportsdb.h"
#include "util/osndb.h" #include "util/osndb.h"
class PluginAPI; class PluginAPI;
@ -464,7 +463,19 @@ public:
allAircraftUpdated(); allAircraftUpdated();
} }
Q_INVOKABLE void findOnMap(int index); Q_INVOKABLE void findOnMap(int index);
void updateAircraftInformation(QSharedPointer<const QHash<int, AircraftInformation *>> aircraftInfo)
{
for (auto aircraft : m_aircrafts)
{
if (aircraftInfo->contains(aircraft->m_icao)) {
aircraft->m_aircraftInfo = aircraftInfo->value(aircraft->m_icao);
} else {
aircraft->m_aircraftInfo = nullptr;
}
}
}
private: private:
QList<Aircraft *> m_aircrafts; QList<Aircraft *> m_aircrafts;
@ -493,7 +504,7 @@ public:
{ {
} }
Q_INVOKABLE void addAirport(AirportInformation *airport, float az, float el, float distance) { Q_INVOKABLE void addAirport(const AirportInformation *airport, float az, float el, float distance) {
QString text; QString text;
int rows; int rows;
@ -558,7 +569,7 @@ public:
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
} }
void airportFreq(AirportInformation *airport, float az, float el, float distance, QString& text, int& rows) { void airportFreq(const AirportInformation *airport, float az, float el, float distance, QString& text, int& rows) {
// Create the text to go in the bubble next to the airport // Create the text to go in the bubble next to the airport
// Display name and frequencies // Display name and frequencies
QStringList list; QStringList list;
@ -567,7 +578,7 @@ public:
rows = 1; rows = 1;
for (int i = 0; i < airport->m_frequencies.size(); i++) for (int i = 0; i < airport->m_frequencies.size(); i++)
{ {
AirportInformation::FrequencyInformation *frequencyInfo = airport->m_frequencies[i]; const AirportInformation::FrequencyInformation *frequencyInfo = airport->m_frequencies[i];
list.append(QString("%1: %2 MHz").arg(frequencyInfo->m_type).arg(frequencyInfo->m_frequency)); list.append(QString("%1: %2 MHz").arg(frequencyInfo->m_type).arg(frequencyInfo->m_frequency));
rows++; rows++;
} }
@ -577,7 +588,7 @@ public:
text = list.join("\n"); text = list.join("\n");
} }
void airportUpdated(AirportInformation *airport) { void airportUpdated(const AirportInformation *airport) {
int row = m_airports.indexOf(airport); int row = m_airports.indexOf(airport);
if (row >= 0) if (row >= 0)
{ {
@ -614,7 +625,7 @@ public:
private: private:
ADSBDemodGUI *m_gui; ADSBDemodGUI *m_gui;
QList<AirportInformation *> m_airports; QList<const AirportInformation *> m_airports;
QList<QString> m_airportDataFreq; QList<QString> m_airportDataFreq;
QList<int> m_airportDataFreqRows; QList<int> m_airportDataFreqRows;
QList<bool> m_showFreq; QList<bool> m_showFreq;
@ -894,14 +905,15 @@ private:
MessageQueue m_inputMessageQueue; MessageQueue m_inputMessageQueue;
QHash<int, Aircraft *> m_aircraft; // Hashed on ICAO QHash<int, Aircraft *> m_aircraft; // Hashed on ICAO
QHash<int, AircraftInformation *> *m_aircraftInfo; QSharedPointer<const QHash<int, AircraftInformation *>> m_aircraftInfo;
QHash<int, AirportInformation *> *m_airportInfo; // Hashed on id QSharedPointer<const QHash<int, AirportInformation *>> m_airportInfo; // Hashed on id
AircraftModel m_aircraftModel; AircraftModel m_aircraftModel;
AirportModel m_airportModel; AirportModel m_airportModel;
AirspaceModel m_airspaceModel; AirspaceModel m_airspaceModel;
NavAidModel m_navAidModel; NavAidModel m_navAidModel;
QList<Airspace *> m_airspaces; QSharedPointer<const QList<Airspace *>> m_airspaces;
QList<NavAid *> m_navAids; QSharedPointer<const QList<NavAid *>> m_navAids;
QGeoCoordinate m_lastFullUpdatePosition;
AzEl m_azEl; // Position of station AzEl m_azEl; // Position of station
Aircraft *m_trackAircraft; // Aircraft we want to track in Channel Report Aircraft *m_trackAircraft; // Aircraft we want to track in Channel Report
@ -920,10 +932,11 @@ private:
AviationWeather *m_aviationWeather; AviationWeather *m_aviationWeather;
QString m_photoLink; QString m_photoLink;
WebAPIAdapterInterface *m_webAPIAdapterInterface; WebAPIAdapterInterface *m_webAPIAdapterInterface;
HttpDownloadManager m_dlm;
QProgressDialog *m_progressDialog; QProgressDialog *m_progressDialog;
quint16 m_osmPort; quint16 m_osmPort;
OpenAIP m_openAIP; OpenAIP m_openAIP;
OsnDB m_osnDB;
OurAirportsDB m_ourAirportsDB;
ADSBOSMTemplateServer *m_templateServer; ADSBOSMTemplateServer *m_templateServer;
QRandomGenerator m_random; QRandomGenerator m_random;
QHash<QString, QString> m_3DModels; // Hashed aircraft_icao or just aircraft QHash<QString, QString> m_3DModels; // Hashed aircraft_icao or just aircraft
@ -935,6 +948,7 @@ private:
QTimer m_importTimer; QTimer m_importTimer;
QTimer m_redrawMapTimer; QTimer m_redrawMapTimer;
QNetworkAccessManager *m_networkManager; QNetworkAccessManager *m_networkManager;
bool m_loadingData;
static const char m_idMap[]; static const char m_idMap[];
static const QString m_categorySetA[]; static const QString m_categorySetA[];
@ -957,6 +971,7 @@ private:
void updatePosition(Aircraft *aircraft); void updatePosition(Aircraft *aircraft);
bool updateLocalPosition(Aircraft *aircraft, double latitude, double longitude, bool surfacePosition); bool updateLocalPosition(Aircraft *aircraft, double latitude, double longitude, bool surfacePosition);
void clearFromMap(const QString& name);
void sendToMap(Aircraft *aircraft, QList<SWGSDRangel::SWGMapAnimation *> *animations); void sendToMap(Aircraft *aircraft, QList<SWGSDRangel::SWGMapAnimation *> *animations);
Aircraft *getAircraft(int icao, bool &newAircraft); Aircraft *getAircraft(int icao, bool &newAircraft);
void callsignToFlight(Aircraft *aircraft); void callsignToFlight(Aircraft *aircraft);
@ -984,17 +999,8 @@ private:
QString subAircraftString(Aircraft *aircraft, const QString &string); QString subAircraftString(Aircraft *aircraft, const QString &string);
void resizeTable(); void resizeTable();
QString getDataDir(); QString getDataDir();
QString getAirportDBFilename();
QString getAirportFrequenciesDBFilename();
QString getOSNDBZipFilename();
QString getOSNDBFilename();
QString getFastDBFilename();
qint64 fileAgeInDays(QString filename);
bool confirmDownload(QString filename);
void readAirportDB(const QString& filename); void readAirportDB(const QString& filename);
void readAirportFrequenciesDB(const QString& filename); void readAirportFrequenciesDB(const QString& filename);
bool readOSNDB(const QString& filename);
bool readFastDB(const QString& filename);
void update3DModels(); void update3DModels();
void updateAirports(); void updateAirports();
void updateAirspaces(); void updateAirspaces();
@ -1047,8 +1053,6 @@ private slots:
void onMenuDialogCalled(const QPoint& p); void onMenuDialogCalled(const QPoint& p);
void handleInputMessages(); void handleInputMessages();
void tick(); void tick();
void updateDownloadProgress(qint64 bytesRead, qint64 totalBytes);
void downloadFinished(const QString& filename, bool success);
void on_device_currentIndexChanged(int index); void on_device_currentIndexChanged(int index);
void feedSelect(const QPoint& p); void feedSelect(const QPoint& p);
void on_displaySettings_clicked(); void on_displaySettings_clicked();
@ -1057,8 +1061,11 @@ private slots:
void on_logOpen_clicked(); void on_logOpen_clicked();
void downloadingURL(const QString& url); void downloadingURL(const QString& url);
void downloadError(const QString& error); void downloadError(const QString& error);
void downloadProgress(qint64 bytesRead, qint64 totalBytes);
void downloadAirspaceFinished(); void downloadAirspaceFinished();
void downloadNavAidsFinished(); void downloadNavAidsFinished();
void downloadAircraftInformationFinished();
void downloadAirportInformationFinished();
void photoClicked(); void photoClicked();
virtual void showEvent(QShowEvent *event) override; virtual void showEvent(QShowEvent *event) override;
virtual bool eventFilter(QObject *obj, QEvent *event) override; virtual bool eventFilter(QObject *obj, QEvent *event) override;
@ -1073,3 +1080,4 @@ signals:
}; };
#endif // INCLUDE_ADSBDEMODGUI_H #endif // INCLUDE_ADSBDEMODGUI_H

View File

@ -844,6 +844,9 @@
<property name="selectionMode"> <property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum> <enum>QAbstractItemView::SingleSelection</enum>
</property> </property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<column> <column>
<property name="text"> <property name="text">
<string>ICAO ID</string> <string>ICAO ID</string>

View File

@ -140,8 +140,8 @@ Item {
Grid { Grid {
horizontalItemAlignment: Grid.AlignHCenter horizontalItemAlignment: Grid.AlignHCenter
columnSpacing: 5 columnSpacing: 5
layer.enabled: true //layer.enabled: true
layer.smooth: true //layer.smooth: true
Image { Image {
id: image id: image
source: navAidImage source: navAidImage
@ -206,8 +206,8 @@ Item {
sourceItem: Grid { sourceItem: Grid {
columns: 1 columns: 1
Grid { Grid {
layer.enabled: true //layer.enabled: true
layer.smooth: true //layer.smooth: true
horizontalItemAlignment: Grid.AlignHCenter horizontalItemAlignment: Grid.AlignHCenter
Text { Text {
id: airspaceText id: airspaceText
@ -239,8 +239,8 @@ Item {
sourceItem: Grid { sourceItem: Grid {
columns: 1 columns: 1
Grid { Grid {
layer.enabled: true //layer.enabled: true
layer.smooth: true //layer.smooth: true
horizontalItemAlignment: Grid.AlignHCenter horizontalItemAlignment: Grid.AlignHCenter
Image { Image {
id: image id: image
@ -334,8 +334,8 @@ Item {
columns: 1 columns: 1
Grid { Grid {
horizontalItemAlignment: Grid.AlignHCenter horizontalItemAlignment: Grid.AlignHCenter
layer.enabled: true //layer.enabled: true
layer.smooth: true //layer.smooth: true
Image { Image {
id: image id: image
source: airportImage source: airportImage

View File

@ -1,303 +0,0 @@
///////////////////////////////////////////////////////////////////////////////////
// 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_OURAIRPORTSDB_H
#define INCLUDE_OURAIRPORTSDB_H
#include <QString>
#include <QFile>
#include <QByteArray>
#include <QHash>
#include <QList>
#include <QDebug>
#include <QLocale>
#include <stdio.h>
#include <string.h>
#include "util/csv.h"
#include "adsbdemodsettings.h"
#define AIRPORTS_URL "https://davidmegginson.github.io/ourairports-data/airports.csv"
#define AIRPORT_FREQUENCIES_URL "https://davidmegginson.github.io/ourairports-data/airport-frequencies.csv"
struct AirportInformation {
struct FrequencyInformation {
QString m_type;
QString m_description;
float m_frequency; // In MHz
};
int m_id;
QString m_ident;
ADSBDemodSettings::AirportType m_type;
QString m_name;
float m_latitude;
float m_longitude;
float m_elevation;
QVector<FrequencyInformation *> m_frequencies;
~AirportInformation()
{
qDeleteAll(m_frequencies);
}
static QString 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
static QHash<int, AirportInformation *> *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() << "AirportInformation::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 = ADSBDemodSettings::AirportType::Small;
else if (!strcmp(type, "medium_airport"))
airport->m_type = ADSBDemodSettings::AirportType::Medium;
else if (!strcmp(type, "large_airport"))
airport->m_type = ADSBDemodSettings::AirportType::Large;
else if (!strcmp(type, "heliport"))
airport->m_type = ADSBDemodSettings::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() << "AirportInformation::readAirportsDB: Failed to open " << filename;
qDebug() << "AirportInformation::readAirportsDB: Read " << cnt << " airports";
return airportInfo;
}
// Read OurAirport's airport frequencies CSV file
static bool 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() << "AirportInformation::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();
FrequencyInformation *frequencyInfo = new 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();
qDebug() << "AirportInformation::readFrequenciesDB: - read " << cnt << " airports";
return airportInfo;
}
};
#endif

View File

@ -54,12 +54,14 @@ if(NOT SERVER_MODE)
mapradiotimedialog.ui mapradiotimedialog.ui
mapcolordialog.cpp mapcolordialog.cpp
mapmodel.cpp mapmodel.cpp
mapitem.cpp
mapwebsocketserver.cpp mapwebsocketserver.cpp
cesiuminterface.cpp cesiuminterface.cpp
czml.cpp czml.cpp
map.qrc map.qrc
icons.qrc icons.qrc
cesium.qrc cesium.qrc
data.qrc
) )
set(map_HEADERS set(map_HEADERS
${map_HEADERS} ${map_HEADERS}
@ -72,6 +74,7 @@ if(NOT SERVER_MODE)
mapradiotimedialog.h mapradiotimedialog.h
mapcolordialog.h mapcolordialog.h
mapmodel.h mapmodel.h
mapitem.h
mapwebsocketserver.h mapwebsocketserver.h
cesiuminterface.h cesiuminterface.h
czml.h czml.h

View File

@ -231,8 +231,25 @@ void CesiumInterface::czml(QJsonObject &obj)
send(obj); send(obj);
} }
void CesiumInterface::update(MapItem *mapItem, bool isTarget, bool isSelected) void CesiumInterface::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
{ {
QJsonObject obj = m_czml.update(mapItem, isTarget, isSelected); QJsonObject obj = m_czml.update(mapItem, isTarget, isSelected);
czml(obj); czml(obj);
} }
void CesiumInterface::update(PolygonMapItem *mapItem)
{
QJsonObject obj = m_czml.update(mapItem);
czml(obj);
}
void CesiumInterface::update(PolylineMapItem *mapItem)
{
QJsonObject obj = m_czml.update(mapItem);
czml(obj);
}
void CesiumInterface::setPosition(const QGeoCoordinate& position)
{
m_czml.setPosition(position);
}

View File

@ -22,7 +22,9 @@
#include "czml.h" #include "czml.h"
#include "SWGMapAnimation.h" #include "SWGMapAnimation.h"
class MapItem; class ObjectMapItem;
class PolygonMapItem;
class PolylineMapItem;
class CesiumInterface : public MapWebSocketServer class CesiumInterface : public MapWebSocketServer
{ {
@ -72,7 +74,10 @@ public:
void removeAllCZMLEntities(); void removeAllCZMLEntities();
void initCZML(); void initCZML();
void czml(QJsonObject &obj); void czml(QJsonObject &obj);
void update(MapItem *mapItem, bool isTarget, bool isSelected); void update(ObjectMapItem *mapItem, bool isTarget, bool isSelected);
void update(PolygonMapItem *mapItem);
void update(PolylineMapItem *mapItem);
void setPosition(const QGeoCoordinate& position);
protected: protected:

View File

@ -28,6 +28,22 @@ CZML::CZML(const MapSettings *settings) :
{ {
} }
// Set position from which distance filter is calculated
void CZML::setPosition(const QGeoCoordinate& position)
{
m_position = position;
}
bool CZML::filter(const MapItem *mapItem) const
{
return ( !mapItem->m_itemSettings->m_filterName.isEmpty()
&& !mapItem->m_itemSettings->m_filterNameRE.match(mapItem->m_name).hasMatch()
)
|| ( (mapItem->m_itemSettings->m_filterDistance > 0)
&& (m_position.distanceTo(QGeoCoordinate(mapItem->m_latitude, mapItem->m_longitude, mapItem->m_altitude)) > mapItem->m_itemSettings->m_filterDistance)
);
}
QJsonObject CZML::init() QJsonObject CZML::init()
{ {
QString start = QDateTime::currentDateTimeUtc().toString(Qt::ISODate); QString start = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
@ -48,11 +64,150 @@ QJsonObject CZML::init()
return doc; return doc;
} }
QJsonObject CZML::update(PolygonMapItem *mapItem)
{
QString id = mapItem->m_name;
QJsonObject CZML::update(MapItem *mapItem, bool isTarget, bool isSelected) QJsonObject obj {
{"id", id} // id must be unique
};
if ( !mapItem->m_itemSettings->m_enabled
|| !mapItem->m_itemSettings->m_display3DTrack
|| filter(mapItem)
)
{
// Delete obj completely (including any history)
obj.insert("delete", true);
return obj;
}
QJsonArray positions;
for (const auto c : mapItem->m_points)
{
positions.append(c->longitude());
positions.append(c->latitude());
positions.append(c->altitude());
}
QJsonObject positionList {
{"cartographicDegrees", positions},
};
QColor color = QColor::fromRgba(mapItem->m_itemSettings->m_3DTrackColor);
QJsonArray colorRGBA {
color.red(), color.green(), color.blue(), color.alpha()
};
QJsonObject colorObj {
{"rgba", colorRGBA}
};
QJsonObject solidColor {
{"color", colorObj},
};
QJsonObject material {
{"solidColor", solidColor}
};
QJsonArray outlineColorRGBA {
0, 0, 0, 255
};
QJsonObject outlineColor {
{"rgba", outlineColorRGBA}
};
QJsonObject polygon {
{"positions", positionList},
{"height", mapItem->m_altitude},
{"extrudedHeight", mapItem->m_extrudedHeight},
{"material", material},
{"outline", true},
{"outlineColor", outlineColor}
};
obj.insert("polygon", polygon);
obj.insert("description", mapItem->m_label);
//qDebug() << "Polygon " << obj;
return obj;
}
QJsonObject CZML::update(PolylineMapItem *mapItem)
{
QString id = mapItem->m_name;
QJsonObject obj {
{"id", id} // id must be unique
};
if ( !mapItem->m_itemSettings->m_enabled
|| !mapItem->m_itemSettings->m_display3DTrack
|| filter(mapItem)
)
{
// Delete obj completely (including any history)
obj.insert("delete", true);
return obj;
}
QJsonArray positions;
for (const auto c : mapItem->m_points)
{
positions.append(c->longitude());
positions.append(c->latitude());
positions.append(c->altitude());
}
QJsonObject positionList {
{"cartographicDegrees", positions},
};
QColor color = QColor::fromRgba(mapItem->m_itemSettings->m_3DTrackColor);
QJsonArray colorRGBA {
color.red(), color.green(), color.blue(), color.alpha()
};
QJsonObject colorObj {
{"rgba", colorRGBA}
};
QJsonObject solidColor {
{"color", colorObj},
};
QJsonObject material {
{"solidColor", solidColor}
};
QJsonObject polyline {
{"positions", positionList},
{"material", material}
};
obj.insert("polyline", polyline);
obj.insert("description", mapItem->m_label);
//qDebug() << "Polyline " << obj;
return obj;
}
QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
{ {
(void) isTarget; (void) isTarget;
QString id = mapItem->m_name;
QJsonObject obj {
{"id", id} // id must be unique
};
if (!mapItem->m_itemSettings->m_enabled || filter(mapItem))
{
// Delete obj completely (including any history)
obj.insert("delete", true);
return obj;
}
// Don't currently use CLIP_TO_GROUND in Cesium due to Jitter bug // Don't currently use CLIP_TO_GROUND in Cesium due to Jitter bug
// https://github.com/CesiumGS/cesium/issues/4049 // https://github.com/CesiumGS/cesium/issues/4049
// Instead we implement our own clipping code in map3d.html // Instead we implement our own clipping code in map3d.html
@ -65,8 +220,6 @@ QJsonObject CZML::update(MapItem *mapItem, bool isTarget, bool isSelected)
dt = QDateTime::currentDateTimeUtc().toString(Qt::ISODateWithMs); dt = QDateTime::currentDateTimeUtc().toString(Qt::ISODateWithMs);
} }
QString id = mapItem->m_name;
// Keep a hash of the time we first saw each item // Keep a hash of the time we first saw each item
bool existingId = m_ids.contains(id); bool existingId = m_ids.contains(id);
if (!existingId) { if (!existingId) {
@ -77,7 +230,7 @@ QJsonObject CZML::update(MapItem *mapItem, bool isTarget, bool isSelected)
bool fixedPosition = mapItem->m_fixedPosition; bool fixedPosition = mapItem->m_fixedPosition;
if (mapItem->m_image == "") if (mapItem->m_image == "")
{ {
// Need to remove this from the map // Need to remove this from the map (but history is retained)
removeObj = true; removeObj = true;
} }
@ -143,10 +296,10 @@ QJsonObject CZML::update(MapItem *mapItem, bool isTarget, bool isSelected)
hasMoved = true; hasMoved = true;
m_hasMoved.insert(id, true); m_hasMoved.insert(id, true);
} }
if (hasMoved) if (hasMoved && (mapItem->m_itemSettings->m_extrapolate > 0))
{ {
position.insert("forwardExtrapolationType", "EXTRAPOLATE"); position.insert("forwardExtrapolationType", "EXTRAPOLATE");
position.insert("forwardExtrapolationDuration", 60); position.insert("forwardExtrapolationDuration", mapItem->m_itemSettings->m_extrapolate);
// Use linear interpolation for now - other two can go crazy with aircraft on the ground // Use linear interpolation for now - other two can go crazy with aircraft on the ground
//position.insert("interpolationAlgorithm", "HERMITE"); //position.insert("interpolationAlgorithm", "HERMITE");
//position.insert("interpolationDegree", "2"); //position.insert("interpolationDegree", "2");
@ -182,7 +335,7 @@ QJsonObject CZML::update(MapItem *mapItem, bool isTarget, bool isSelected)
QJsonObject orientation { QJsonObject orientation {
{"unitQuaternion", quaternion}, {"unitQuaternion", quaternion},
{"forwardExtrapolationType", "HOLD"}, // If we extrapolate, aircraft tend to spin around {"forwardExtrapolationType", "HOLD"}, // If we extrapolate, aircraft tend to spin around
{"forwardExtrapolationDuration", 60}, {"forwardExtrapolationDuration", mapItem->m_itemSettings->m_extrapolate},
// {"interpolationAlgorithm", "LAGRANGE"} // {"interpolationAlgorithm", "LAGRANGE"}
}; };
QJsonObject orientationPosition { QJsonObject orientationPosition {
@ -269,7 +422,10 @@ QJsonObject CZML::update(MapItem *mapItem, bool isTarget, bool isSelected)
// Prevent labels from being too cluttered when zoomed out // Prevent labels from being too cluttered when zoomed out
// FIXME: These values should come from mapItem or mapItemSettings // FIXME: These values should come from mapItem or mapItemSettings
float displayDistanceMax = std::numeric_limits<float>::max(); float displayDistanceMax = std::numeric_limits<float>::max();
if ((mapItem->m_group == "Beacons") || (mapItem->m_group == "AM") || (mapItem->m_group == "FM") || (mapItem->m_group == "DAB")) { if ((mapItem->m_group == "Beacons")
|| (mapItem->m_group == "AM") || (mapItem->m_group == "FM") || (mapItem->m_group == "DAB")
|| (mapItem->m_group == "NavAid")
) {
displayDistanceMax = 1000000; displayDistanceMax = 1000000;
} else if ((mapItem->m_group == "Station") || (mapItem->m_group == "Radar") || (mapItem->m_group == "Radio Time Transmitters")) { } else if ((mapItem->m_group == "Station") || (mapItem->m_group == "Radar") || (mapItem->m_group == "Radio Time Transmitters")) {
displayDistanceMax = 10000000; displayDistanceMax = 10000000;
@ -330,10 +486,6 @@ QJsonObject CZML::update(MapItem *mapItem, bool isTarget, bool isSelected)
{"verticalOrigin", "BOTTOM"} // To stop it being cut in half when zoomed out {"verticalOrigin", "BOTTOM"} // To stop it being cut in half when zoomed out
}; };
QJsonObject obj {
{"id", id} // id must be unique
};
if (!removeObj) if (!removeObj)
{ {
obj.insert("position", position); obj.insert("position", position);
@ -368,9 +520,11 @@ QJsonObject CZML::update(MapItem *mapItem, bool isTarget, bool isSelected)
} }
else else
{ {
QString oneMin = QDateTime::currentDateTimeUtc().addSecs(60).toString(Qt::ISODateWithMs); if (mapItem->m_availableUntil.isValid())
QString createdToNow = QString("%1/%2").arg(m_ids[id]).arg(oneMin); // From when object was created to now {
obj.insert("availability", createdToNow); QString period = QString("%1/%2").arg(m_ids[id]).arg(mapItem->m_availableUntil.toString(Qt::ISODateWithMs));
obj.insert("availability", period);
}
} }
} }
m_lastPosition.insert(id, coords); m_lastPosition.insert(id, coords);
@ -391,3 +545,4 @@ QJsonObject CZML::update(MapItem *mapItem, bool isTarget, bool isSelected)
return obj; return obj;
} }

View File

@ -21,9 +21,13 @@
#include <QHash> #include <QHash>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include <QGeoCoordinate>
struct MapSettings; struct MapSettings;
class MapItem; class MapItem;
class ObjectMapItem;
class PolygonMapItem;
class PolylineMapItem;
class CZML class CZML
{ {
@ -32,11 +36,16 @@ private:
QHash<QString, QString> m_ids; QHash<QString, QString> m_ids;
QHash<QString, QJsonArray> m_lastPosition; QHash<QString, QJsonArray> m_lastPosition;
QHash<QString, bool> m_hasMoved; QHash<QString, bool> m_hasMoved;
QGeoCoordinate m_position;
public: public:
CZML(const MapSettings *settings); CZML(const MapSettings *settings);
QJsonObject init(); QJsonObject init();
QJsonObject update(MapItem *mapItem, bool isTarget, bool isSelected); QJsonObject update(ObjectMapItem *mapItem, bool isTarget, bool isSelected);
QJsonObject update(PolygonMapItem *mapItem);
QJsonObject update(PolylineMapItem *mapItem);
bool filter(const MapItem *mapItem) const;
void setPosition(const QGeoCoordinate& position);
signals: signals:
void connected(); void connected();

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/map/">
<file>data/transmitters.csv</file>
</qresource>
</RCC>

File diff suppressed because it is too large Load Diff

View File

@ -5,5 +5,7 @@
<file>icons/ibp.png</file> <file>icons/ibp.png</file>
<file>icons/muf.png</file> <file>icons/muf.png</file>
<file>icons/fof2.png</file> <file>icons/fof2.png</file>
<file>icons/controltower.png</file>
<file>icons/vor.png</file>
</qresource> </qresource>
</RCC> </RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

View File

@ -8,6 +8,18 @@
<file>map/antennafm.png</file> <file>map/antennafm.png</file>
<file>map/antennaam.png</file> <file>map/antennaam.png</file>
<file>map/ionosonde.png</file> <file>map/ionosonde.png</file>
<file>map/VOR.png</file>
<file>map/VOR-DME.png</file>
<file>map/VORTAC.png</file>
<file>map/DVOR.png</file>
<file>map/DVOR-DME.png</file>
<file>map/DVORTAC.png</file>
<file>map/NDB.png</file>
<file>map/DME.png</file>
<file>map/airport_large.png</file>
<file>map/airport_medium.png</file>
<file>map/airport_small.png</file>
<file>map/heliport.png</file>
<file>map/map3d.html</file> <file>map/map3d.html</file>
</qresource> </qresource>
<qresource prefix="/"> <qresource prefix="/">

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1015 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -67,40 +67,73 @@ Item {
gesture.enabled: true gesture.enabled: true
gesture.acceptedGestures: MapGestureArea.PinchGesture | MapGestureArea.PanGesture gesture.acceptedGestures: MapGestureArea.PinchGesture | MapGestureArea.PanGesture
MapItemView {
model: imageModelFiltered
delegate: imageComponent
}
MapItemView {
model: polygonModelFiltered
delegate: polygonComponent
}
MapItemView {
model: polygonModelFiltered
delegate: polygonNameComponent
}
MapItemView {
model: polylineModelFiltered
delegate: polylineComponent
}
MapItemView {
model: polylineModelFiltered
delegate: polylineNameComponent
}
// Tracks first, so drawn under other items // Tracks first, so drawn under other items
MapItemView { MapItemView {
model: mapModel model: mapModelFiltered
delegate: groundTrack1Component delegate: groundTrack1Component
} }
MapItemView { MapItemView {
model: mapModel model: mapModelFiltered
delegate: groundTrack2Component delegate: groundTrack2Component
} }
MapItemView { MapItemView {
model: mapModel model: mapModelFiltered
delegate: predictedGroundTrack1Component delegate: predictedGroundTrack1Component
} }
MapItemView { MapItemView {
model: mapModel model: mapModelFiltered
delegate: predictedGroundTrack2Component delegate: predictedGroundTrack2Component
} }
MapItemView { MapItemView {
model: mapModel model: mapModelFiltered
delegate: mapComponent delegate: mapComponent
} }
onZoomLevelChanged: { onZoomLevelChanged: {
mapZoomLevel = zoomLevel mapZoomLevel = zoomLevel
mapModelFiltered.viewChanged(visibleRegion.boundingGeoRectangle().topLeft.longitude, visibleRegion.boundingGeoRectangle().topLeft.latitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude, visibleRegion.boundingGeoRectangle().bottomRight.latitude, zoomLevel);
imageModelFiltered.viewChanged(visibleRegion.boundingGeoRectangle().topLeft.longitude, visibleRegion.boundingGeoRectangle().topLeft.latitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude, visibleRegion.boundingGeoRectangle().bottomRight.latitude, zoomLevel);
polygonModelFiltered.viewChanged(visibleRegion.boundingGeoRectangle().topLeft.longitude, visibleRegion.boundingGeoRectangle().topLeft.latitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude, visibleRegion.boundingGeoRectangle().bottomRight.latitude, zoomLevel);
polylineModelFiltered.viewChanged(visibleRegion.boundingGeoRectangle().topLeft.longitude, visibleRegion.boundingGeoRectangle().topLeft.latitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude, visibleRegion.boundingGeoRectangle().bottomRight.latitude, zoomLevel);
} }
// The map displays MapPolyLines in the wrong place (+360 degrees) if // The map displays MapPolyLines in the wrong place (+360 degrees) if
// they start to the left of the visible region, so we need to // they start to the left of the visible region, so we need to
// split them so they don't, each time the visible region is changed. meh. // split them so they don't, each time the visible region is changed. meh.
onCenterChanged: { onCenterChanged: {
polylineModelFiltered.viewChanged(visibleRegion.boundingGeoRectangle().topLeft.longitude, visibleRegion.boundingGeoRectangle().topLeft.latitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude, visibleRegion.boundingGeoRectangle().bottomRight.latitude, zoomLevel);
polygonModelFiltered.viewChanged(visibleRegion.boundingGeoRectangle().topLeft.longitude, visibleRegion.boundingGeoRectangle().topLeft.latitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude, visibleRegion.boundingGeoRectangle().bottomRight.latitude, zoomLevel);
imageModelFiltered.viewChanged(visibleRegion.boundingGeoRectangle().topLeft.longitude, visibleRegion.boundingGeoRectangle().topLeft.latitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude, visibleRegion.boundingGeoRectangle().bottomRight.latitude, zoomLevel);
mapModelFiltered.viewChanged(visibleRegion.boundingGeoRectangle().topLeft.longitude, visibleRegion.boundingGeoRectangle().topLeft.latitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude, visibleRegion.boundingGeoRectangle().bottomRight.latitude, zoomLevel);
mapModel.viewChanged(visibleRegion.boundingGeoRectangle().bottomLeft.longitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude); mapModel.viewChanged(visibleRegion.boundingGeoRectangle().bottomLeft.longitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude);
} }
@ -118,6 +151,86 @@ Item {
return null; return null;
} }
Component {
id: imageComponent
MapQuickItem {
coordinate: position
anchorPoint.x: imageId.width/2
anchorPoint.y: imageId.height/2
zoomLevel: imageZoomLevel
sourceItem: Image {
id: imageId
source: imageData
}
autoFadeIn: false
}
}
Component {
id: polygonComponent
MapPolygon {
border.width: 1
border.color: borderColor
color: fillColor
path: polygon
autoFadeIn: false
}
}
Component {
id: polygonNameComponent
MapQuickItem {
coordinate: position
anchorPoint.x: polygonText.width/2
anchorPoint.y: polygonText.height/2
zoomLevel: mapZoomLevel > 11 ? mapZoomLevel : 11
sourceItem: Grid {
columns: 1
Grid {
//layer.enabled: true
//layer.smooth: true
horizontalItemAlignment: Grid.AlignHCenter
Text {
id: polygonText
text: label
}
}
}
}
}
Component {
id: polylineComponent
MapPolyline {
line.width: 1
line.color: lineColor
path: coordinates
autoFadeIn: false
}
}
Component {
id: polylineNameComponent
MapQuickItem {
coordinate: position
anchorPoint.x: polylineText.width/2
anchorPoint.y: polylineText.height/2
zoomLevel: mapZoomLevel > 11 ? mapZoomLevel : 11
sourceItem: Grid {
columns: 1
Grid {
//layer.enabled: true
//layer.smooth: true
horizontalItemAlignment: Grid.AlignHCenter
Text {
id: polylineText
text: label
}
}
}
}
}
Component { Component {
id: mapComponent id: mapComponent
MapQuickItem { MapQuickItem {
@ -134,8 +247,9 @@ Item {
Grid { Grid {
horizontalItemAlignment: Grid.AlignHCenter horizontalItemAlignment: Grid.AlignHCenter
columnSpacing: 5 columnSpacing: 5
layer.enabled: true // This is very slow with lots of items, so we use MSAA for the whole map instead
layer.smooth: true //layer.enabled: true
//layer.smooth: true
Image { Image {
id: image id: image
rotation: mapImageRotation rotation: mapImageRotation
@ -149,7 +263,7 @@ Item {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
selected = !selected selected = !selected
if (selected) { if (selected) {
mapModel.moveToFront(index) mapModel.moveToFront(mapModelFiltered.mapRowToSource(index))
} }
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
if (frequency > 0) { if (frequency > 0) {
@ -159,6 +273,8 @@ Item {
freqMenuItem.text = "No frequency available" freqMenuItem.text = "No frequency available"
freqMenuItem.enabled = false freqMenuItem.enabled = false
} }
var c = mapPtr.toCoordinate(Qt.point(mouse.x, mouse.y))
coordsMenuItem.text = "Coords: " + c.latitude.toFixed(6) + ", " + c.longitude.toFixed(6)
contextMenu.popup() contextMenu.popup()
} }
} }
@ -186,7 +302,7 @@ Item {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
selected = !selected selected = !selected
if (selected) { if (selected) {
mapModel.moveToFront(index) mapModel.moveToFront(mapModelFiltered.mapRowToSource(index))
} }
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
if (frequency > 0) { if (frequency > 0) {
@ -196,6 +312,8 @@ Item {
freqMenuItem.text = "No frequency available" freqMenuItem.text = "No frequency available"
freqMenuItem.enabled = false freqMenuItem.enabled = false
} }
var c = mapPtr.toCoordinate(Qt.point(mouse.x, mouse.y))
coordsMenuItem.text = "Coords: " + c.latitude.toFixed(6) + ", " + c.longitude.toFixed(6)
contextMenu.popup() contextMenu.popup()
} }
} }
@ -212,15 +330,19 @@ Item {
} }
MenuItem { MenuItem {
text: "Move to front" text: "Move to front"
onTriggered: mapModel.moveToFront(index) onTriggered: mapModel.moveToFront(mapModelFiltered.mapRowToSource(index))
} }
MenuItem { MenuItem {
text: "Move to back" text: "Move to back"
onTriggered: mapModel.moveToBack(index) onTriggered: mapModel.moveToBack(mapModelFiltered.mapRowToSource(index))
} }
MenuItem { MenuItem {
text: "Track on 3D map" text: "Track on 3D map"
onTriggered: mapModel.track3D(index) onTriggered: mapModel.track3D(mapModelFiltered.mapRowToSource(index))
}
MenuItem {
id: coordsMenuItem
text: ""
} }
} }
} }

View File

@ -147,6 +147,30 @@
} }
} }
// Polygons (such as for airspaces) should be prioritized behind other entities
function pickEntityPrioritized(e)
{
var picked = viewer.scene.drillPick(e.position);
if (Cesium.defined(picked)) {
var firstPolygon = null;
for (let i = 0; i < picked.length; i++) {
var id = Cesium.defaultValue(picked[i].id, picked[i].primitive.id);
if (id instanceof Cesium.Entity) {
if (!Cesium.defined(id.polygon)) {
return id;
} else if (firstPolygon == null) {
firstPolygon = id;
}
}
}
return firstPolygon;
}
}
function pickEntity(e) {
viewer.selectedEntity = pickEntityPrioritized(e);
}
Cesium.Ion.defaultAccessToken = '$CESIUM_ION_API_KEY$'; Cesium.Ion.defaultAccessToken = '$CESIUM_ION_API_KEY$';
const viewer = new Cesium.Viewer('cesiumContainer', { const viewer = new Cesium.Viewer('cesiumContainer', {
@ -161,6 +185,7 @@
terrainProviderViewModels: [] // User should adjust terrain via dialog, so depthTestAgainstTerrain doesn't get set terrainProviderViewModels: [] // User should adjust terrain via dialog, so depthTestAgainstTerrain doesn't get set
}); });
viewer.scene.globe.depthTestAgainstTerrain = false; // So labels/points aren't clipped by terrain viewer.scene.globe.depthTestAgainstTerrain = false; // So labels/points aren't clipped by terrain
viewer.screenSpaceEventHandler.setInputAction(pickEntity, Cesium.ScreenSpaceEventType.LEFT_CLICK);
var buildings = undefined; var buildings = undefined;
const images = new Map(); const images = new Map();
@ -351,7 +376,7 @@
fabric: { fabric: {
type: 'Image', type: 'Image',
uniforms: { uniforms: {
image: 'data:image/png;base64,' + command.data, image: command.data,
} }
}, },
translucent: true translucent: true

View File

@ -37,6 +37,7 @@
#include "device/deviceuiset.h" #include "device/deviceuiset.h"
#include "util/units.h" #include "util/units.h"
#include "util/maidenhead.h" #include "util/maidenhead.h"
#include "util/morse.h"
#include "maplocationdialog.h" #include "maplocationdialog.h"
#include "mapmaidenheaddialog.h" #include "mapmaidenheaddialog.h"
#include "mapsettingsdialog.h" #include "mapsettingsdialog.h"
@ -181,7 +182,10 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
m_pluginAPI(pluginAPI), m_pluginAPI(pluginAPI),
m_featureUISet(featureUISet), m_featureUISet(featureUISet),
m_doApplySettings(true), m_doApplySettings(true),
m_mapModel(this), m_objectMapModel(this),
m_imageMapModel(this),
m_polygonMapModel(this),
m_polylineMapModel(this),
m_beacons(nullptr), m_beacons(nullptr),
m_beaconDialog(this), m_beaconDialog(this),
m_ibpBeaconDialog(this), m_ibpBeaconDialog(this),
@ -197,6 +201,16 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
rollupContents->arrangeRollups(); rollupContents->arrangeRollups();
connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
// Enable MSAA antialiasing on 2D map, otherwise text is not clear
// This is much faster than using layer.smooth in the QML, when there are many items
int multisamples = MainCore::instance()->getSettings().getMapMultisampling();
if (multisamples > 0)
{
QSurfaceFormat format;
format.setSamples(multisamples);
ui->map->setFormat(format);
}
m_osmPort = 0; m_osmPort = 0;
m_templateServer = new OSMTemplateServer(thunderforestAPIKey(), maptilerAPIKey(), m_osmPort); m_templateServer = new OSMTemplateServer(thunderforestAPIKey(), maptilerAPIKey(), m_osmPort);
@ -206,13 +220,17 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
ui->map->setAttribute(Qt::WA_AcceptTouchEvents, true); ui->map->setAttribute(Qt::WA_AcceptTouchEvents, true);
ui->map->rootContext()->setContextProperty("mapModel", &m_mapModel); m_objectMapFilter.setSourceModel(&m_objectMapModel);
// 5.12 doesn't display map items when fully zoomed out m_imageMapFilter.setSourceModel(&m_imageMapModel);
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) m_polygonMapFilter.setSourceModel(&m_polygonMapModel);
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map/map_5_12.qml"))); m_polylineMapFilter.setSourceModel(&m_polylineMapModel);
#else
ui->map->rootContext()->setContextProperty("mapModelFiltered", &m_objectMapFilter);
ui->map->rootContext()->setContextProperty("mapModel", &m_objectMapModel);
ui->map->rootContext()->setContextProperty("imageModelFiltered", &m_imageMapFilter);
ui->map->rootContext()->setContextProperty("polygonModelFiltered", &m_polygonMapFilter);
ui->map->rootContext()->setContextProperty("polylineModelFiltered", &m_polylineMapFilter);
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map/map.qml"))); ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map/map.qml")));
#endif
m_settings.m_modelURL = QString("http://127.0.0.1:%1/3d/").arg(m_webPort); m_settings.m_modelURL = QString("http://127.0.0.1:%1/3d/").arg(m_webPort);
m_webServer->addPathSubstitution("3d", m_settings.m_modelDir); m_webServer->addPathSubstitution("3d", m_settings.m_modelDir);
@ -236,6 +254,11 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
float stationLongitude = MainCore::instance()->getSettings().getLongitude(); float stationLongitude = MainCore::instance()->getSettings().getLongitude();
float stationAltitude = MainCore::instance()->getSettings().getAltitude(); float stationAltitude = MainCore::instance()->getSettings().getAltitude();
m_azEl.setLocation(stationLatitude, stationLongitude, stationAltitude); m_azEl.setLocation(stationLatitude, stationLongitude, stationAltitude);
QGeoCoordinate stationPosition(stationLatitude, stationLongitude, stationAltitude);
m_objectMapFilter.setPosition(stationPosition);
m_imageMapFilter.setPosition(stationPosition);
m_polygonMapFilter.setPosition(stationPosition);
m_polylineMapFilter.setPosition(stationPosition);
// Centre map at My Position // Centre map at My Position
QQuickItem *item = ui->map->rootObject(); QQuickItem *item = ui->map->rootObject();
@ -257,7 +280,9 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
m_antennaMapItem.setImageRotation(0); m_antennaMapItem.setImageRotation(0);
m_antennaMapItem.setText(new QString(MainCore::instance()->getSettings().getStationName())); m_antennaMapItem.setText(new QString(MainCore::instance()->getSettings().getStationName()));
m_antennaMapItem.setModel(new QString("antenna.glb")); m_antennaMapItem.setModel(new QString("antenna.glb"));
m_antennaMapItem.setFixedPosition(true); m_antennaMapItem.setFixedPosition(false);
m_antennaMapItem.setPositionDateTime(new QString(QDateTime::currentDateTime().toString(Qt::ISODateWithMs)));
m_antennaMapItem.setAvailableUntil(new QString(QDateTime::currentDateTime().addDays(365).toString(Qt::ISODateWithMs)));
m_antennaMapItem.setOrientation(0); m_antennaMapItem.setOrientation(0);
m_antennaMapItem.setLabel(new QString(MainCore::instance()->getSettings().getStationName())); m_antennaMapItem.setLabel(new QString(MainCore::instance()->getSettings().getStationName()));
m_antennaMapItem.setLabelAltitudeOffset(4.5); m_antennaMapItem.setLabelAltitudeOffset(4.5);
@ -277,6 +302,10 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
addRadioTimeTransmitters(); addRadioTimeTransmitters();
addRadar(); addRadar();
addIonosonde(); addIonosonde();
addBroadcast();
addNavAids();
addAirspace();
addAirports();
displaySettings(); displaySettings();
applySettings(true); applySettings(true);
@ -317,31 +346,18 @@ void MapGUI::setWorkspaceIndex(int index)
m_feature->setWorkspaceIndex(index); m_feature->setWorkspaceIndex(index);
} }
// Update a map item or image // Update a map item
void MapGUI::update(const QObject *source, SWGSDRangel::SWGMapItem *swgMapItem, const QString &group) void MapGUI::update(const QObject *source, SWGSDRangel::SWGMapItem *swgMapItem, const QString &group)
{ {
if (swgMapItem->getType() == 0) int type = swgMapItem->getType();
{ if (type == 0) {
m_mapModel.update(source, swgMapItem, group); m_objectMapModel.update(source, swgMapItem, group);
} } else if (type == 1) {
else if (m_cesium) m_imageMapModel.update(source, swgMapItem, group);
{ } else if (type == 2) {
QString name = *swgMapItem->getName(); m_polygonMapModel.update(source, swgMapItem, group);
QString image = *swgMapItem->getImage(); } else if (type == 3) {
if (!image.isEmpty()) m_polylineMapModel.update(source, swgMapItem, group);
{
m_cesium->updateImage(name,
swgMapItem->getImageTileEast(),
swgMapItem->getImageTileWest(),
swgMapItem->getImageTileNorth(),
swgMapItem->getImageTileSouth(),
swgMapItem->getAltitude(),
image);
}
else
{
m_cesium->removeImage(name);
}
} }
} }
@ -401,11 +417,15 @@ void MapGUI::addIBPBeacons()
} }
const QList<RadioTimeTransmitter> MapGUI::m_radioTimeTransmitters = { const QList<RadioTimeTransmitter> MapGUI::m_radioTimeTransmitters = {
{"MSF", 60000, 54.9075f, -3.27333f, 17}, {"MSF", 60000, 54.9075f, -3.27333f, 17}, // UK
{"DCF77", 77500, 50.01611111f, 9.00805556f, 50}, {"DCF77", 77500, 50.01611111f, 9.00805556f, 50}, // Germany
{"TDF", 162000, 47.1694f, 2.2044f, 800}, {"TDF", 162000, 47.1694f, 2.2044f, 800}, // France
{"WWVB", 60000, 40.67805556f, -105.04666667f, 70}, {"WWVB", 60000, 40.67805556f, -105.04666667f, 70}, // USA
{"JJY", 60000, 33.465f, 130.175555f, 50} {"JJY-40", 40000, 37.3725f, 140.848889f, 50}, // Japan
{"JJY-60", 60000, 33.465556f, 130.175556f, 50}, // Japan
{"RTZ", 50000, 52.436111f, 103.685833f, 10}, // Russia - On 22:00 to 21:00 UTC
{"RBU", 66666, 56.733333f, 37.663333f, 10}, // Russia
{"BPC", 68500, 34.457f, 115.837f, 90}, // China - On 1:00 to 21:00 UTC
}; };
void MapGUI::addRadioTimeTransmitters() void MapGUI::addRadioTimeTransmitters()
@ -539,7 +559,111 @@ static QString arrayToString(QJsonArray array)
return s; return s;
} }
void MapGUI::addBroadcast()
{
QFile file(":/map/data/transmitters.csv");
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream in(&file);
QString error;
QHash<QString, int> colIndexes = CSV::readHeader(in, {"Type", "Id", "Name", "Frequency (Hz)", "Latitude", "Longitude", "Altitude (m)", "Power", "TII Main", "TII Sub"}, error);
if (error.isEmpty())
{
int typeCol = colIndexes.value("Type");
int idCol = colIndexes.value("Id");
int nameCol = colIndexes.value("Name");
int frequencyCol = colIndexes.value("Frequency (Hz)");
int latCol = colIndexes.value("Latitude");
int longCol = colIndexes.value("Longitude");
int altCol = colIndexes.value("Altitude (m)");
int powerCol = colIndexes.value("Power");
int tiiMainCol = colIndexes.value("TII Main");
int tiiSubCol = colIndexes.value("TII Sub");
QStringList cols;
while (CSV::readRow(in, &cols))
{
QString type = cols[typeCol];
QString id = cols[idCol];
QString name = cols[nameCol];
QString frequency = cols[frequencyCol];
double latitude = cols[latCol].toDouble();
double longitude = cols[longCol].toDouble();
double altitude = cols[altCol].toDouble();
QString power = cols[powerCol];
SWGSDRangel::SWGMapItem mapItem;
mapItem.setLatitude(latitude);
mapItem.setLongitude(longitude);
mapItem.setAltitude(altitude);
mapItem.setImageRotation(0);
mapItem.setModel(new QString("antenna.glb"));
mapItem.setFixedPosition(true);
mapItem.setOrientation(0);
mapItem.setLabelAltitudeOffset(4.5);
mapItem.setAltitudeReference(1);
if (type == "AM")
{
// Name should be unique
mapItem.setName(new QString(id));
mapItem.setImage(new QString("antennaam.png"));
mapItem.setLabel(new QString(name));
QString text = QString("%1 Transmitter\nStation: %2\nFrequency: %3 kHz\nPower (ERMP): %4 kW")
.arg(type)
.arg(name)
.arg(frequency.toDouble() / 1e3)
.arg(power)
;
mapItem.setText(new QString(text));
update(m_map, &mapItem, "AM");
}
else if (type == "FM")
{
mapItem.setName(new QString(id));
mapItem.setImage(new QString("antennafm.png"));
mapItem.setLabel(new QString(name));
QString text = QString("%1 Transmitter\nStation: %2\nFrequency: %3 MHz\nPower (ERP): %4 kW")
.arg(type)
.arg(name)
.arg(frequency.toDouble() / 1e6)
.arg(power)
;
mapItem.setText(new QString(text));
update(m_map, &mapItem, "FM");
}
else if (type == "DAB")
{
mapItem.setName(new QString(id));
mapItem.setImage(new QString("antennadab.png"));
mapItem.setLabel(new QString(name));
QString text = QString("%1 Transmitter\nEnsemble: %2\nFrequency: %3 MHz\nPower (ERP): %4 kW\nTII: %5 %6")
.arg(type)
.arg(name)
.arg(frequency.toDouble() / 1e6)
.arg(power)
.arg(cols[tiiMainCol])
.arg(cols[tiiSubCol])
;
mapItem.setText(new QString(text));
update(m_map, &mapItem, "DAB");
}
}
}
else
{
qCritical() << "MapGUI::addBroadcast: Failed reading transmitters.csv - " << error;
}
}
else
{
qCritical() << "MapGUI::addBroadcast: Failed to open transmitters.csv";
}
}
// Coming soon // Coming soon
/* Code for FM list / DAB list, should they allow access
void MapGUI::addDAB() void MapGUI::addDAB()
{ {
QFile file("stationlist_SI.json"); QFile file("stationlist_SI.json");
@ -663,6 +787,201 @@ void MapGUI::addDAB()
qDebug() << "MapGUI::addDAB: Failed to open DAB json"; qDebug() << "MapGUI::addDAB: Failed to open DAB json";
} }
} }
*/
void MapGUI::addNavAids()
{
m_navAids = OpenAIP::getNavAids();
for (const auto navAid : *m_navAids)
{
SWGSDRangel::SWGMapItem navAidMapItem;
navAidMapItem.setName(new QString(navAid->m_name + " " + navAid->m_ident)); // Neither name or ident are unique...
navAidMapItem.setLatitude(navAid->m_latitude);
navAidMapItem.setLongitude(navAid->m_longitude);
navAidMapItem.setAltitude(Units::feetToMetres(navAid->m_elevation));
QString image = QString("%1.png").arg(navAid->m_type);
navAidMapItem.setImage(new QString(image));
navAidMapItem.setImageRotation(0);
QString text = QString("NAVAID\nName: %1").arg(navAid->m_name);
if (navAid->m_type == "NDB") {
text.append(QString("\nFrequency: %1 kHz").arg(navAid->m_frequencykHz, 0, 'f', 1));
} else {
text.append(QString("\nFrequency: %1 MHz").arg(navAid->m_frequencykHz / 1000.0f, 0, 'f', 2));
}
if (navAid->m_channel != "") {
text.append(QString("\nChannel: %1").arg(navAid->m_channel));
}
text.append(QString("\nIdent: %1 %2").arg(navAid->m_ident).arg(Morse::toSpacedUnicodeMorse(navAid->m_ident)));
text.append(QString("\nRange: %1 nm").arg(navAid->m_range));
if (navAid->m_alignedTrueNorth) {
text.append(QString("\nMagnetic declination: Aligned to true North"));
} else if (navAid->m_magneticDeclination != 0.0f) {
text.append(QString("\nMagnetic declination: %1%2").arg(std::round(navAid->m_magneticDeclination)).arg(QChar(0x00b0)));
}
navAidMapItem.setText(new QString(text));
navAidMapItem.setModel(new QString("antenna.glb"));
navAidMapItem.setFixedPosition(true);
navAidMapItem.setOrientation(0);
navAidMapItem.setLabel(new QString(navAid->m_name));
navAidMapItem.setLabelAltitudeOffset(4.5);
navAidMapItem.setAltitudeReference(1);
update(m_map, &navAidMapItem, "NavAid");
}
}
void MapGUI::addAirspace(const Airspace *airspace, const QString& group, int cnt)
{
//MapSettings::MapItemSettings *itemSettings = getItemSettings(group);
QString details;
details.append(airspace->m_name);
details.append(QString("\n%1 - %2")
.arg(airspace->getAlt(&airspace->m_bottom))
.arg(airspace->getAlt(&airspace->m_top)));
QString name = QString("Airspace %1 (%2)").arg(airspace->m_name).arg(cnt);
SWGSDRangel::SWGMapItem airspaceMapItem;
airspaceMapItem.setName(new QString(name));
airspaceMapItem.setLatitude(airspace->m_position.y());
airspaceMapItem.setLongitude(airspace->m_position.x());
airspaceMapItem.setAltitude(airspace->bottomHeightInMetres());
QString image = QString("none");
airspaceMapItem.setImage(new QString(image));
airspaceMapItem.setImageRotation(0);
airspaceMapItem.setText(new QString(details)); // Not used - label is used instead for now
airspaceMapItem.setFixedPosition(true);
airspaceMapItem.setLabel(new QString(details));
airspaceMapItem.setAltitudeReference(0);
QList<SWGSDRangel::SWGMapCoordinate *> *coords = new QList<SWGSDRangel::SWGMapCoordinate *>();
for (const auto p : airspace->m_polygon)
{
SWGSDRangel::SWGMapCoordinate* c = new SWGSDRangel::SWGMapCoordinate();
c->setLatitude(p.y());
c->setLongitude(p.x());
c->setAltitude(airspace->bottomHeightInMetres());
coords->append(c);
}
airspaceMapItem.setCoordinates(coords);
airspaceMapItem.setExtrudedHeight(airspace->topHeightInMetres());
airspaceMapItem.setType(2);
update(m_map, &airspaceMapItem, group);
}
void MapGUI::addAirspace()
{
m_airspaces = OpenAIP::getAirspaces();
int cnt = 0;
for (const auto airspace : *m_airspaces)
{
static const QMap<QString, QString> groupMap {
{"A", "Airspace (A)"},
{"B", "Airspace (B)"},
{"C", "Airspace (C)"},
{"D", "Airspace (D)"},
{"E", "Airspace (E)"},
{"F", "Airspace (F)"},
{"G", "Airspace (G)"},
{"FIR", "Airspace (FIR)"},
{"CTR", "Airspace (CTR)"},
{"RMZ", "Airspace (RMZ)"},
{"TMA", "Airspace (TMA)"},
{"TMZ", "Airspace (TMZ)"},
{"OTH", "Airspace (OTH)"},
{"RESTRICTED", "Airspace (Restricted)"},
{"GLIDING", "Airspace (Gliding)"},
{"DANGER", "Airspace (Danger)"},
{"PROHIBITED", "Airspace (Prohibited)"},
{"WAVE", "Airspace (Wave)"},
};
if (groupMap.contains(airspace->m_category))
{
QString group = groupMap[airspace->m_category];
addAirspace(airspace, group, cnt);
cnt++;
if ( (airspace->bottomHeightInMetres() == 0)
&& ((airspace->m_category == "D") || (airspace->m_category == "G") || (airspace->m_category == "CTR"))
&& (!((airspace->m_country == "IT") && (airspace->m_name.startsWith("FIR"))))
&& (!((airspace->m_country == "PL") && (airspace->m_name.startsWith("FIS"))))
)
{
group = "Airspace (Airports)";
addAirspace(airspace, group, cnt);
cnt++;
}
}
else
{
qDebug() << "MapGUI::addAirspace: No group for airspace category " << airspace->m_category;
}
}
}
void MapGUI::addAirports()
{
m_airportInfo = OurAirportsDB::getAirportsById();
if (m_airportInfo)
{
QHashIterator<int, AirportInformation *> i(*m_airportInfo);
while (i.hasNext())
{
i.next();
AirportInformation *airport = i.value();
SWGSDRangel::SWGMapItem airportMapItem;
airportMapItem.setName(new QString(airport->m_ident));
airportMapItem.setLatitude(airport->m_latitude);
airportMapItem.setLongitude(airport->m_longitude);
airportMapItem.setAltitude(Units::feetToMetres(airport->m_elevation));
airportMapItem.setImage(new QString(airport->getImageName()));
airportMapItem.setImageRotation(0);
QStringList list;
list.append(QString("%1: %2").arg(airport->m_ident).arg(airport->m_name));
for (int i = 0; i < airport->m_frequencies.size(); i++)
{
const AirportInformation::FrequencyInformation *frequencyInfo = airport->m_frequencies[i];
list.append(QString("%1: %2 MHz").arg(frequencyInfo->m_type).arg(frequencyInfo->m_frequency));
}
airportMapItem.setText(new QString(list.join("\n")));
airportMapItem.setModel(new QString("airport.glb")); // No such model currently, but we don't really want one
airportMapItem.setFixedPosition(true);
airportMapItem.setOrientation(0);
airportMapItem.setLabel(new QString(airport->m_ident));
airportMapItem.setLabelAltitudeOffset(4.5);
airportMapItem.setAltitudeReference(1);
QString group;
if (airport->m_type == AirportInformation::Small) {
group = "Airport (Small)";
} else if (airport->m_type == AirportInformation::Medium) {
group = "Airport (Medium)";
} else if (airport->m_type == AirportInformation::Large) {
group = "Airport (Large)";
} else {
group = "Heliport";
}
update(m_map, &airportMapItem, group);
}
}
}
void MapGUI::navAidsUpdated()
{
addNavAids();
}
void MapGUI::airspacesUpdated()
{
addAirspace();
}
void MapGUI::airportsUpdated()
{
addAirports();
}
void MapGUI::blockApplySettings(bool block) void MapGUI::blockApplySettings(bool block)
{ {
@ -799,7 +1118,7 @@ void MapGUI::redrawMap()
if (object) if (object)
{ {
double zoom = object->property("zoomLevel").value<double>(); double zoom = object->property("zoomLevel").value<double>();
object->setProperty("zoomLevel", QVariant::fromValue(zoom+1)); object->setProperty("zoomLevel", QVariant::fromValue(zoom+1.0));
object->setProperty("zoomLevel", QVariant::fromValue(zoom)); object->setProperty("zoomLevel", QVariant::fromValue(zoom));
} }
} }
@ -834,6 +1153,15 @@ bool MapGUI::eventFilter(QObject *obj, QEvent *event)
return FeatureGUI::eventFilter(obj, event); return FeatureGUI::eventFilter(obj, event);
} }
MapSettings::MapItemSettings *MapGUI::getItemSettings(const QString &group)
{
if (m_settings.m_itemSettings.contains(group)) {
return m_settings.m_itemSettings[group];
} else {
return nullptr;
}
}
void MapGUI::supportedMapsChanged() void MapGUI::supportedMapsChanged()
{ {
QQuickItem *item = ui->map->rootObject(); QQuickItem *item = ui->map->rootObject();
@ -893,7 +1221,8 @@ void MapGUI::applyMap3DSettings(bool reloadMap)
#ifdef QT_WEBENGINE_FOUND #ifdef QT_WEBENGINE_FOUND
if (m_settings.m_map3DEnabled && ((m_cesium == nullptr) || reloadMap)) if (m_settings.m_map3DEnabled && ((m_cesium == nullptr) || reloadMap))
{ {
if (m_cesium == nullptr) { if (m_cesium == nullptr)
{
m_cesium = new CesiumInterface(&m_settings); m_cesium = new CesiumInterface(&m_settings);
connect(m_cesium, &CesiumInterface::connected, this, &MapGUI::init3DMap); connect(m_cesium, &CesiumInterface::connected, this, &MapGUI::init3DMap);
connect(m_cesium, &CesiumInterface::received, this, &MapGUI::receivedCesiumEvent); connect(m_cesium, &CesiumInterface::received, this, &MapGUI::receivedCesiumEvent);
@ -924,6 +1253,10 @@ void MapGUI::applyMap3DSettings(bool reloadMap)
m_cesium->getDateTime(); m_cesium->getDateTime();
m_cesium->showMUF(m_settings.m_displayMUF); m_cesium->showMUF(m_settings.m_displayMUF);
m_cesium->showfoF2(m_settings.m_displayfoF2); m_cesium->showfoF2(m_settings.m_displayfoF2);
m_objectMapModel.allUpdated();
m_imageMapModel.allUpdated();
m_polygonMapModel.allUpdated();
m_polylineMapModel.allUpdated();
} }
MapSettings::MapItemSettings *ionosondeItemSettings = getItemSettings("Ionosonde Stations"); MapSettings::MapItemSettings *ionosondeItemSettings = getItemSettings("Ionosonde Stations");
if (ionosondeItemSettings) { if (ionosondeItemSettings) {
@ -934,9 +1267,10 @@ void MapGUI::applyMap3DSettings(bool reloadMap)
#else #else
ui->displayMUF->setVisible(false); ui->displayMUF->setVisible(false);
ui->displayfoF2->setVisible(false); ui->displayfoF2->setVisible(false);
m_mapModel.allUpdated(); m_objectMapModel.allUpdated();
float stationLatitude = MainCore::instance()->getSettings().getLatitude(); m_imageMapModel.allUpdated();
float stationLongitude = MainCore::instance()->getSettings().getLongitude(); m_polygonMapModel.allUpdated();
m_polylineMapModel.allUpdated();
#endif #endif
} }
@ -947,6 +1281,11 @@ void MapGUI::init3DMap()
m_cesium->initCZML(); m_cesium->initCZML();
float stationLatitude = MainCore::instance()->getSettings().getLatitude();
float stationLongitude = MainCore::instance()->getSettings().getLongitude();
float stationAltitude = MainCore::instance()->getSettings().getLongitude();
m_cesium->setPosition(QGeoCoordinate(stationLatitude, stationLongitude, stationAltitude));
m_cesium->setTerrain(m_settings.m_terrain, maptilerAPIKey()); m_cesium->setTerrain(m_settings.m_terrain, maptilerAPIKey());
m_cesium->setBuildings(m_settings.m_buildings); m_cesium->setBuildings(m_settings.m_buildings);
m_cesium->setSunLight(m_settings.m_sunLightEnabled); m_cesium->setSunLight(m_settings.m_sunLightEnabled);
@ -954,9 +1293,10 @@ void MapGUI::init3DMap()
m_cesium->setAntiAliasing(m_settings.m_antiAliasing); m_cesium->setAntiAliasing(m_settings.m_antiAliasing);
m_cesium->getDateTime(); m_cesium->getDateTime();
m_mapModel.allUpdated(); m_objectMapModel.allUpdated();
float stationLatitude = MainCore::instance()->getSettings().getLatitude(); m_imageMapModel.allUpdated();
float stationLongitude = MainCore::instance()->getSettings().getLongitude(); m_polygonMapModel.allUpdated();
m_polylineMapModel.allUpdated();
// Set 3D view after loading initial objects // Set 3D view after loading initial objects
m_cesium->setHomeView(stationLatitude, stationLongitude); m_cesium->setHomeView(stationLatitude, stationLongitude);
@ -977,10 +1317,13 @@ void MapGUI::displaySettings()
ui->displayAllGroundTracks->setChecked(m_settings.m_displayAllGroundTracks); ui->displayAllGroundTracks->setChecked(m_settings.m_displayAllGroundTracks);
ui->displayMUF->setChecked(m_settings.m_displayMUF); ui->displayMUF->setChecked(m_settings.m_displayMUF);
ui->displayfoF2->setChecked(m_settings.m_displayfoF2); ui->displayfoF2->setChecked(m_settings.m_displayfoF2);
m_mapModel.setDisplayNames(m_settings.m_displayNames); m_objectMapModel.setDisplayNames(m_settings.m_displayNames);
m_mapModel.setDisplaySelectedGroundTracks(m_settings.m_displaySelectedGroundTracks); m_objectMapModel.setDisplaySelectedGroundTracks(m_settings.m_displaySelectedGroundTracks);
m_mapModel.setDisplayAllGroundTracks(m_settings.m_displayAllGroundTracks); m_objectMapModel.setDisplayAllGroundTracks(m_settings.m_displayAllGroundTracks);
m_mapModel.updateItemSettings(m_settings.m_itemSettings); m_objectMapModel.updateItemSettings(m_settings.m_itemSettings);
m_imageMapModel.updateItemSettings(m_settings.m_itemSettings);
m_polygonMapModel.updateItemSettings(m_settings.m_itemSettings);
m_polylineMapModel.updateItemSettings(m_settings.m_itemSettings);
applyMap2DSettings(true); applyMap2DSettings(true);
applyMap3DSettings(true); applyMap3DSettings(true);
getRollupContents()->restoreState(m_rollupState); getRollupContents()->restoreState(m_rollupState);
@ -1049,19 +1392,19 @@ void MapGUI::on_maidenhead_clicked()
void MapGUI::on_displayNames_clicked(bool checked) void MapGUI::on_displayNames_clicked(bool checked)
{ {
m_settings.m_displayNames = checked; m_settings.m_displayNames = checked;
m_mapModel.setDisplayNames(checked); m_objectMapModel.setDisplayNames(checked);
} }
void MapGUI::on_displaySelectedGroundTracks_clicked(bool checked) void MapGUI::on_displaySelectedGroundTracks_clicked(bool checked)
{ {
m_settings.m_displaySelectedGroundTracks = checked; m_settings.m_displaySelectedGroundTracks = checked;
m_mapModel.setDisplaySelectedGroundTracks(checked); m_objectMapModel.setDisplaySelectedGroundTracks(checked);
} }
void MapGUI::on_displayAllGroundTracks_clicked(bool checked) void MapGUI::on_displayAllGroundTracks_clicked(bool checked)
{ {
m_settings.m_displayAllGroundTracks = checked; m_settings.m_displayAllGroundTracks = checked;
m_mapModel.setDisplayAllGroundTracks(checked); m_objectMapModel.setDisplayAllGroundTracks(checked);
} }
void MapGUI::on_displayMUF_clicked(bool checked) void MapGUI::on_displayMUF_clicked(bool checked)
@ -1183,13 +1526,15 @@ void MapGUI::find(const QString& target)
} }
else else
{ {
MapItem *mapItem = m_mapModel.findMapItem(target); // FIXME: Support polygon/polyline
ObjectMapItem *mapItem = m_objectMapModel.findMapItem(target);
if (mapItem != nullptr) if (mapItem != nullptr)
{ {
map->setProperty("center", QVariant::fromValue(mapItem->getCoordinates())); map->setProperty("center", QVariant::fromValue(mapItem->getCoordinates()));
if (m_cesium) { if (m_cesium) {
m_cesium->track(target); m_cesium->track(target);
} }
m_objectMapModel.moveToFront(m_objectMapModel.findMapItemIndex(target).row());
} }
else else
{ {
@ -1224,7 +1569,10 @@ void MapGUI::track3D(const QString& target)
void MapGUI::on_deleteAll_clicked() void MapGUI::on_deleteAll_clicked()
{ {
m_mapModel.removeAll(); m_objectMapModel.removeAll();
m_imageMapModel.removeAll();
m_polygonMapModel.removeAll();
m_polylineMapModel.removeAll();
if (m_cesium) if (m_cesium)
{ {
m_cesium->removeAllCZMLEntities(); m_cesium->removeAllCZMLEntities();
@ -1235,6 +1583,9 @@ void MapGUI::on_deleteAll_clicked()
void MapGUI::on_displaySettings_clicked() void MapGUI::on_displaySettings_clicked()
{ {
MapSettingsDialog dialog(&m_settings); MapSettingsDialog dialog(&m_settings);
connect(&dialog, &MapSettingsDialog::navAidsUpdated, this, &MapGUI::navAidsUpdated);
connect(&dialog, &MapSettingsDialog::airspacesUpdated, this, &MapGUI::airspacesUpdated);
connect(&dialog, &MapSettingsDialog::airportsUpdated, this, &MapGUI::airportsUpdated);
new DialogPositioner(&dialog, true); new DialogPositioner(&dialog, true);
if (dialog.exec() == QDialog::Accepted) if (dialog.exec() == QDialog::Accepted)
{ {
@ -1244,7 +1595,10 @@ void MapGUI::on_displaySettings_clicked()
applyMap2DSettings(dialog.m_map2DSettingsChanged); applyMap2DSettings(dialog.m_map2DSettingsChanged);
applyMap3DSettings(dialog.m_map3DSettingsChanged); applyMap3DSettings(dialog.m_map3DSettingsChanged);
applySettings(); applySettings();
m_mapModel.allUpdated(); m_objectMapModel.allUpdated();
m_imageMapModel.allUpdated();
m_polygonMapModel.allUpdated();
m_polylineMapModel.allUpdated();
} }
} }
@ -1289,17 +1643,17 @@ void MapGUI::receivedCesiumEvent(const QJsonObject &obj)
if (event == "selected") if (event == "selected")
{ {
if (obj.contains("id")) { if (obj.contains("id")) {
m_mapModel.setSelected3D(obj.value("id").toString()); m_objectMapModel.setSelected3D(obj.value("id").toString());
} else { } else {
m_mapModel.setSelected3D(""); m_objectMapModel.setSelected3D("");
} }
} }
else if (event == "tracking") else if (event == "tracking")
{ {
if (obj.contains("id")) { if (obj.contains("id")) {
//m_mapModel.setTarget(obj.value("id").toString()); //m_objectMapModel.setTarget(obj.value("id").toString());
} else { } else {
//m_mapModel.setTarget(""); //m_objectMapModel.setTarget("");
} }
} }
else if (event == "clock") else if (event == "clock")
@ -1346,9 +1700,10 @@ void MapGUI::preferenceChanged(int elementType)
float stationLongitude = MainCore::instance()->getSettings().getLongitude(); float stationLongitude = MainCore::instance()->getSettings().getLongitude();
float stationAltitude = MainCore::instance()->getSettings().getAltitude(); float stationAltitude = MainCore::instance()->getSettings().getAltitude();
if ( (stationLatitude != m_azEl.getLocationSpherical().m_latitude) QGeoCoordinate stationPosition(stationLatitude, stationLongitude, stationAltitude);
|| (stationLongitude != m_azEl.getLocationSpherical().m_longitude) QGeoCoordinate previousPosition(m_azEl.getLocationSpherical().m_latitude, m_azEl.getLocationSpherical().m_longitude, m_azEl.getLocationSpherical().m_altitude);
|| (stationAltitude != m_azEl.getLocationSpherical().m_altitude))
if (stationPosition != previousPosition)
{ {
// Update position of station // Update position of station
m_azEl.setLocation(stationLatitude, stationLongitude, stationAltitude); m_azEl.setLocation(stationLatitude, stationLongitude, stationAltitude);
@ -1356,7 +1711,24 @@ void MapGUI::preferenceChanged(int elementType)
m_antennaMapItem.setLatitude(stationLatitude); m_antennaMapItem.setLatitude(stationLatitude);
m_antennaMapItem.setLongitude(stationLongitude); m_antennaMapItem.setLongitude(stationLongitude);
m_antennaMapItem.setAltitude(stationAltitude); m_antennaMapItem.setAltitude(stationAltitude);
delete m_antennaMapItem.getPositionDateTime();
m_antennaMapItem.setPositionDateTime(new QString(QDateTime::currentDateTime().toString(Qt::ISODateWithMs)));
update(m_map, &m_antennaMapItem, "Station"); update(m_map, &m_antennaMapItem, "Station");
m_objectMapFilter.setPosition(stationPosition);
m_imageMapFilter.setPosition(stationPosition);
m_polygonMapFilter.setPosition(stationPosition);
m_polylineMapFilter.setPosition(stationPosition);
if (m_cesium)
{
m_cesium->setPosition(stationPosition);
if (!m_lastFullUpdatePosition.isValid() || (stationPosition.distanceTo(m_lastFullUpdatePosition) >= 1000))
{
// Update all objects so distance filter is reapplied
m_objectMapModel.allUpdated();
m_lastFullUpdatePosition = stationPosition;
}
}
} }
} }
if (pref == Preferences::StationName) if (pref == Preferences::StationName)
@ -1384,3 +1756,4 @@ void MapGUI::makeUIConnections()
QObject::connect(ui->ibpBeacons, &QToolButton::clicked, this, &MapGUI::on_ibpBeacons_clicked); QObject::connect(ui->ibpBeacons, &QToolButton::clicked, this, &MapGUI::on_ibpBeacons_clicked);
QObject::connect(ui->radiotime, &QToolButton::clicked, this, &MapGUI::on_radiotime_clicked); QObject::connect(ui->radiotime, &QToolButton::clicked, this, &MapGUI::on_radiotime_clicked);
} }

View File

@ -33,6 +33,8 @@
#include "util/messagequeue.h" #include "util/messagequeue.h"
#include "util/giro.h" #include "util/giro.h"
#include "util/azel.h" #include "util/azel.h"
#include "util/openaip.h"
#include "util/ourairportsdb.h"
#include "settings/rollupstate.h" #include "settings/rollupstate.h"
#include "SWGMapItem.h" #include "SWGMapItem.h"
@ -152,11 +154,16 @@ public:
void addRadioTimeTransmitters(); void addRadioTimeTransmitters();
void addRadar(); void addRadar();
void addIonosonde(); void addIonosonde();
void addBroadcast();
void addDAB(); void addDAB();
void addNavAids();
void addAirspace(const Airspace *airspace, const QString& group, int cnt);
void addAirspace();
void addAirports();
void find(const QString& target); void find(const QString& target);
void track3D(const QString& target); void track3D(const QString& target);
Q_INVOKABLE void supportedMapsChanged(); Q_INVOKABLE void supportedMapsChanged();
MapSettings::MapItemSettings *getItemSettings(const QString &group) { return m_settings.m_itemSettings[group]; } MapSettings::MapItemSettings *getItemSettings(const QString &group);
CesiumInterface *cesium() { return m_cesium; } CesiumInterface *cesium() { return m_cesium; }
private: private:
@ -171,7 +178,14 @@ private:
Map* m_map; Map* m_map;
MessageQueue m_inputMessageQueue; MessageQueue m_inputMessageQueue;
MapModel m_mapModel; ObjectMapModel m_objectMapModel;
ObjectMapFilter m_objectMapFilter;
ImageMapModel m_imageMapModel;
ImageFilter m_imageMapFilter;
PolygonMapModel m_polygonMapModel;
PolygonFilter m_polygonMapFilter;
PolylineMapModel m_polylineMapModel;
PolylineFilter m_polylineMapFilter;
AzEl m_azEl; // Position of station AzEl m_azEl; // Position of station
SWGSDRangel::SWGMapItem m_antennaMapItem; SWGSDRangel::SWGMapItem m_antennaMapItem;
QList<Beacon *> *m_beacons; QList<Beacon *> *m_beacons;
@ -183,6 +197,10 @@ private:
QTimer m_redrawMapTimer; QTimer m_redrawMapTimer;
GIRO *m_giro; GIRO *m_giro;
QHash<QString, IonosondeStation *> m_ionosondeStations; QHash<QString, IonosondeStation *> m_ionosondeStations;
QSharedPointer<const QList<NavAid *>> m_navAids;
QSharedPointer<const QList<Airspace *>> m_airspaces;
QSharedPointer<const QHash<int, AirportInformation *>> m_airportInfo;
QGeoCoordinate m_lastFullUpdatePosition;
CesiumInterface *m_cesium; CesiumInterface *m_cesium;
WebServer *m_webServer; WebServer *m_webServer;
@ -238,6 +256,9 @@ private slots:
void giroDataUpdated(const GIRO::GIROStationData& data); void giroDataUpdated(const GIRO::GIROStationData& data);
void mufUpdated(const QJsonDocument& document); void mufUpdated(const QJsonDocument& document);
void foF2Updated(const QJsonDocument& document); void foF2Updated(const QJsonDocument& document);
void navAidsUpdated();
void airspacesUpdated();
void airportsUpdated();
}; };

View File

@ -0,0 +1,288 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 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 "mapitem.h"
MapItem::MapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem) :
m_altitude(0.0)
{
m_sourcePipe = sourcePipe;
m_group = group;
m_itemSettings = itemSettings;
m_name = *mapItem->getName();
m_hashKey = m_sourcePipe->objectName() + m_name;
}
void MapItem::update(SWGSDRangel::SWGMapItem *mapItem)
{
if (mapItem->getLabel()) {
m_label = *mapItem->getLabel();
} else {
m_label = "";
}
m_latitude = mapItem->getLatitude();
m_longitude = mapItem->getLongitude();
m_altitude = mapItem->getAltitude();
}
void ObjectMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
{
MapItem::update(mapItem);
if (mapItem->getPositionDateTime()) {
m_positionDateTime = QDateTime::fromString(*mapItem->getPositionDateTime(), Qt::ISODateWithMs);
} else {
m_positionDateTime = QDateTime();
}
m_useHeadingPitchRoll = mapItem->getOrientation() == 1;
m_heading = mapItem->getHeading();
m_pitch = mapItem->getPitch();
m_roll = mapItem->getRoll();
if (mapItem->getOrientationDateTime()) {
m_orientationDateTime = QDateTime::fromString(*mapItem->getOrientationDateTime(), Qt::ISODateWithMs);
} else {
m_orientationDateTime = QDateTime();
}
m_image = *mapItem->getImage();
m_imageRotation = mapItem->getImageRotation();
QString *text = mapItem->getText();
if (text != nullptr) {
m_text = text->replace("\n", "<br>"); // Convert to HTML
} else {
m_text = "";
}
if (mapItem->getModel()) {
m_model = *mapItem->getModel();
} else {
m_model = "";
}
m_labelAltitudeOffset = mapItem->getLabelAltitudeOffset();
m_modelAltitudeOffset = mapItem->getModelAltitudeOffset();
m_altitudeReference = mapItem->getAltitudeReference();
m_fixedPosition = mapItem->getFixedPosition();
QList<SWGSDRangel::SWGMapAnimation *> *animations = mapItem->getAnimations();
if (animations)
{
for (auto animation : *animations) {
m_animations.append(new CesiumInterface::Animation(animation));
}
}
findFrequency();
if (!m_fixedPosition)
{
updateTrack(mapItem->getTrack());
updatePredictedTrack(mapItem->getPredictedTrack());
}
if (mapItem->getAvailableUntil()) {
m_availableUntil = QDateTime::fromString(*mapItem->getAvailableUntil(), Qt::ISODateWithMs);
} else {
m_availableUntil = QDateTime();
}
}
void ImageMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
{
MapItem::update(mapItem);
m_image = "data:image/png;base64," + *mapItem->getImage();
m_imageZoomLevel = mapItem->getImageZoomLevel();
float east = mapItem->getImageTileEast();
float west = mapItem->getImageTileWest();
float north = mapItem->getImageTileNorth();
float south = mapItem->getImageTileSouth();
m_latitude = north - (north - south) / 2.0;
m_longitude = east - (east - west) / 2.0;
m_bounds = QGeoRectangle(QGeoCoordinate(north, west), QGeoCoordinate(south, east));
}
void PolygonMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
{
MapItem::update(mapItem);
m_extrudedHeight = mapItem->getExtrudedHeight();
qDeleteAll(m_points);
m_points.clear();
QList<SWGSDRangel::SWGMapCoordinate *> *coords = mapItem->getCoordinates();
if (coords)
{
for (int i = 0; i < coords->size(); i++)
{
SWGSDRangel::SWGMapCoordinate* p = coords->at(i);
QGeoCoordinate *c = new QGeoCoordinate(p->getLatitude(), p->getLongitude(), p->getAltitude());
m_points.append(c);
}
}
// Calculate bounds
m_polygon.clear();
qreal latMin = 90.0, latMax = -90.0, lonMin = 180.0, lonMax = -180.0;
for (const auto p : m_points)
{
QGeoCoordinate coord = *p;
latMin = std::min(latMin, coord.latitude());
latMax = std::max(latMax, coord.latitude());
lonMin = std::min(lonMin, coord.longitude());
lonMax = std::max(lonMax, coord.longitude());
m_polygon.push_back(QVariant::fromValue(coord));
}
m_bounds = QGeoRectangle(QGeoCoordinate(latMax, lonMin), QGeoCoordinate(latMin, lonMax));
}
void PolylineMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
{
MapItem::update(mapItem);
qDeleteAll(m_points);
m_points.clear();
QList<SWGSDRangel::SWGMapCoordinate *> *coords = mapItem->getCoordinates();
if (coords)
{
for (int i = 0; i < coords->size(); i++)
{
SWGSDRangel::SWGMapCoordinate* p = coords->at(i);
QGeoCoordinate *c = new QGeoCoordinate(p->getLatitude(), p->getLongitude(), p->getAltitude());
m_points.append(c);
}
}
// Calculate bounds
m_polyline.clear();
qreal latMin = 90.0, latMax = -90.0, lonMin = 180.0, lonMax = -180.0;
for (const auto p : m_points)
{
QGeoCoordinate coord = *p;
latMin = std::min(latMin, coord.latitude());
latMax = std::max(latMax, coord.latitude());
lonMin = std::min(lonMin, coord.longitude());
lonMax = std::max(lonMax, coord.longitude());
m_polyline.push_back(QVariant::fromValue(coord));
}
m_bounds = QGeoRectangle(QGeoCoordinate(latMax, lonMin), QGeoCoordinate(latMin, lonMax));
}
QGeoCoordinate ObjectMapItem::getCoordinates()
{
QGeoCoordinate coords;
coords.setLatitude(m_latitude);
coords.setLongitude(m_longitude);
return coords;
}
void ObjectMapItem::findFrequency()
{
// Look for a frequency in the text for this object
QRegExp re("(([0-9]+(\\.[0-9]+)?) *([kMG])?Hz)");
if (re.indexIn(m_text) != -1)
{
QStringList capture = re.capturedTexts();
m_frequency = capture[2].toDouble();
if (capture.length() == 5)
{
QChar unit = capture[4][0];
if (unit == 'k')
m_frequency *= 1000.0;
else if (unit == 'M')
m_frequency *= 1000000.0;
else if (unit == 'G')
m_frequency *= 1000000000.0;
}
m_frequencyString = capture[0];
}
else
{
m_frequency = 0.0;
}
}
void ObjectMapItem::updateTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track)
{
if (track != nullptr)
{
qDeleteAll(m_takenTrackCoords);
m_takenTrackCoords.clear();
qDeleteAll(m_takenTrackDateTimes);
m_takenTrackDateTimes.clear();
m_takenTrack.clear();
m_takenTrack1.clear();
m_takenTrack2.clear();
for (int i = 0; i < track->size(); i++)
{
SWGSDRangel::SWGMapCoordinate* p = track->at(i);
QGeoCoordinate *c = new QGeoCoordinate(p->getLatitude(), p->getLongitude(), p->getAltitude());
QDateTime *d = new QDateTime(QDateTime::fromString(*p->getDateTime(), Qt::ISODate));
m_takenTrackCoords.push_back(c);
m_takenTrackDateTimes.push_back(d);
m_takenTrack.push_back(QVariant::fromValue(*c));
}
}
else
{
// Automatically create a track
if (m_takenTrackCoords.size() == 0)
{
QGeoCoordinate *c = new QGeoCoordinate(m_latitude, m_longitude, m_altitude);
m_takenTrackCoords.push_back(c);
if (m_positionDateTime.isValid()) {
m_takenTrackDateTimes.push_back(new QDateTime(m_positionDateTime));
} else {
m_takenTrackDateTimes.push_back(new QDateTime(QDateTime::currentDateTime()));
}
m_takenTrack.push_back(QVariant::fromValue(*c));
}
else
{
QGeoCoordinate *prev = m_takenTrackCoords.last();
QDateTime *prevDateTime = m_takenTrackDateTimes.last();
if ((prev->latitude() != m_latitude) || (prev->longitude() != m_longitude)
|| (prev->altitude() != m_altitude) || (*prevDateTime != m_positionDateTime))
{
QGeoCoordinate *c = new QGeoCoordinate(m_latitude, m_longitude, m_altitude);
m_takenTrackCoords.push_back(c);
if (m_positionDateTime.isValid()) {
m_takenTrackDateTimes.push_back(new QDateTime(m_positionDateTime));
} else {
m_takenTrackDateTimes.push_back(new QDateTime(QDateTime::currentDateTime()));
}
m_takenTrack.push_back(QVariant::fromValue(*c));
}
}
}
}
void ObjectMapItem::updatePredictedTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track)
{
if (track != nullptr)
{
qDeleteAll(m_predictedTrackCoords);
m_predictedTrackCoords.clear();
qDeleteAll(m_predictedTrackDateTimes);
m_predictedTrackDateTimes.clear();
m_predictedTrack.clear();
m_predictedTrack1.clear();
m_predictedTrack2.clear();
for (int i = 0; i < track->size(); i++)
{
SWGSDRangel::SWGMapCoordinate* p = track->at(i);
QGeoCoordinate *c = new QGeoCoordinate(p->getLatitude(), p->getLongitude(), p->getAltitude());
QDateTime *d = new QDateTime(QDateTime::fromString(*p->getDateTime(), Qt::ISODate));
m_predictedTrackCoords.push_back(c);
m_predictedTrackDateTimes.push_back(d);
m_predictedTrack.push_back(QVariant::fromValue(*c));
}
}
}

View File

@ -0,0 +1,184 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_MAPITEM_H_
#define INCLUDE_FEATURE_MAPITEM_H_
#include <QDateTime>
#include <QGeoCoordinate>
#include <QGeoRectangle>
#include "mapsettings.h"
#include "cesiuminterface.h"
#include "SWGMapItem.h"
class MapModel;
class ObjectMapModel;
class PolygonMapModel;
class PolylineMapModel;
class ImageMapModel;
class CZML;
class MapItem {
public:
MapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem);
virtual void update(SWGSDRangel::SWGMapItem *mapItem);
protected:
friend CZML;
friend MapModel;
friend ObjectMapModel;
friend PolygonMapModel;
friend PolylineMapModel;
QString m_group;
MapSettings::MapItemSettings *m_itemSettings;
const QObject *m_sourcePipe; // Channel/feature that created the item
QString m_hashKey;
QString m_name; // Unique id
QString m_label;
float m_latitude; // Position for label
float m_longitude;
float m_altitude; // In metres
QDateTime m_availableUntil; // Date & time this item is visible until (for 3D map). Invalid date/time is forever
};
// Information required about each item displayed on the map
class ObjectMapItem : public MapItem {
public:
ObjectMapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem) :
MapItem(sourcePipe, group, itemSettings, mapItem)
{
update(mapItem);
}
void update(SWGSDRangel::SWGMapItem *mapItem) override;
QGeoCoordinate getCoordinates();
protected:
void findFrequency();
void updateTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track);
void updatePredictedTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track);
friend ObjectMapModel;
friend CZML;
QDateTime m_positionDateTime;
bool m_useHeadingPitchRoll;
float m_heading;
float m_pitch;
float m_roll;
QDateTime m_orientationDateTime;
QString m_image;
int m_imageRotation;
QString m_text;
double m_frequency; // Frequency to set
QString m_frequencyString;
bool m_fixedPosition; // Don't record/display track
QList<QGeoCoordinate *> m_predictedTrackCoords;
QList<QDateTime *> m_predictedTrackDateTimes;
QVariantList m_predictedTrack; // Line showing where the object is going
QVariantList m_predictedTrack1;
QVariantList m_predictedTrack2;
QGeoCoordinate m_predictedStart1;
QGeoCoordinate m_predictedStart2;
QGeoCoordinate m_predictedEnd1;
QGeoCoordinate m_predictedEnd2;
QList<QGeoCoordinate *> m_takenTrackCoords;
QList<QDateTime *> m_takenTrackDateTimes;
QVariantList m_takenTrack; // Line showing where the object has been
QVariantList m_takenTrack1;
QVariantList m_takenTrack2;
QGeoCoordinate m_takenStart1;
QGeoCoordinate m_takenStart2;
QGeoCoordinate m_takenEnd1;
QGeoCoordinate m_takenEnd2;
// For 3D map
QString m_model;
int m_altitudeReference;
float m_labelAltitudeOffset;
float m_modelAltitudeOffset;
QList<CesiumInterface::Animation *> m_animations;
};
class PolygonMapItem : public MapItem {
public:
PolygonMapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem) :
MapItem(sourcePipe, group, itemSettings, mapItem)
{
update(mapItem);
}
void update(SWGSDRangel::SWGMapItem *mapItem) override;
protected:
friend PolygonMapModel;
friend CZML;
QList<QGeoCoordinate *> m_points; // FIXME: Remove?
float m_extrudedHeight; // In metres
QVariantList m_polygon;
QGeoRectangle m_bounds; // Bounding boxes for the polygons, for view clipping
};
class PolylineMapItem : public MapItem {
public:
PolylineMapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem) :
MapItem(sourcePipe, group, itemSettings, mapItem)
{
update(mapItem);
}
void update(SWGSDRangel::SWGMapItem *mapItem) override;
protected:
friend PolylineMapModel;
friend CZML;
QList<QGeoCoordinate *> m_points; // FIXME: Remove?
QVariantList m_polyline;
QGeoRectangle m_bounds; // Bounding boxes for the polyline, for view clipping
};
class ImageMapItem : public MapItem {
public:
ImageMapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem) :
MapItem(sourcePipe, group, itemSettings, mapItem)
{
update(mapItem);
}
void update(SWGSDRangel::SWGMapItem *mapItem) override;
protected:
friend ImageMapModel;
friend CZML;
QString m_image;
float m_imageZoomLevel;
QGeoRectangle m_bounds;
};
#endif // INCLUDE_FEATURE_MAPITEM_H_

File diff suppressed because it is too large Load Diff

View File

@ -19,135 +19,227 @@
#define INCLUDE_FEATURE_MAPMODEL_H_ #define INCLUDE_FEATURE_MAPMODEL_H_
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QSortFilterProxyModel>
#include <QGeoCoordinate> #include <QGeoCoordinate>
#include <QGeoRectangle>
#include <QColor> #include <QColor>
#include "util/azel.h" #include "util/azel.h"
#include "util/openaip.h"
#include "mapsettings.h" #include "mapsettings.h"
#include "mapitem.h"
#include "cesiuminterface.h" #include "cesiuminterface.h"
#include "SWGMapItem.h" #include "SWGMapItem.h"
class MapModel;
class MapGUI; class MapGUI;
class CZML; class CZML;
// Information required about each item displayed on the map
class MapItem {
public:
MapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem);
void update(SWGSDRangel::SWGMapItem *mapItem);
QGeoCoordinate getCoordinates();
private:
void findFrequency();
void updateTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track);
void updatePredictedTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track);
friend MapModel;
friend CZML;
QString m_group;
MapSettings::MapItemSettings *m_itemSettings;
const QObject *m_sourcePipe; // Channel/feature that created the item
QString m_name;
QString m_label;
float m_latitude;
float m_longitude;
float m_altitude; // In metres
QDateTime m_positionDateTime;
bool m_useHeadingPitchRoll;
float m_heading;
float m_pitch;
float m_roll;
QDateTime m_orientationDateTime;
QString m_image;
int m_imageRotation;
QString m_text;
double m_frequency; // Frequency to set
QString m_frequencyString;
QList<QGeoCoordinate *> m_predictedTrackCoords;
QList<QDateTime *> m_predictedTrackDateTimes;
QVariantList m_predictedTrack; // Line showing where the object is going
QVariantList m_predictedTrack1;
QVariantList m_predictedTrack2;
QGeoCoordinate m_predictedStart1;
QGeoCoordinate m_predictedStart2;
QGeoCoordinate m_predictedEnd1;
QGeoCoordinate m_predictedEnd2;
QList<QGeoCoordinate *> m_takenTrackCoords;
QList<QDateTime *> m_takenTrackDateTimes;
QVariantList m_takenTrack; // Line showing where the object has been
QVariantList m_takenTrack1;
QVariantList m_takenTrack2;
QGeoCoordinate m_takenStart1;
QGeoCoordinate m_takenStart2;
QGeoCoordinate m_takenEnd1;
QGeoCoordinate m_takenEnd2;
// For 3D map
QString m_model;
int m_altitudeReference;
float m_labelAltitudeOffset;
float m_modelAltitudeOffset;
bool m_fixedPosition;
QList<CesiumInterface::Animation *> m_animations;
};
// Model used for each item on the map
class MapModel : public QAbstractListModel { class MapModel : public QAbstractListModel {
Q_OBJECT Q_OBJECT
public: public:
using QAbstractListModel::QAbstractListModel;
enum MarkerRoles { enum MarkerRoles {
positionRole = Qt::UserRole + 1, itemSettingsRole = Qt::UserRole + 1,
mapTextRole = Qt::UserRole + 2, nameRole = Qt::UserRole + 2,
mapTextVisibleRole = Qt::UserRole + 3, labelRole = Qt::UserRole + 3,
mapImageVisibleRole = Qt::UserRole + 4, positionRole = Qt::UserRole + 4,
mapImageRole = Qt::UserRole + 5, mapImageMinZoomRole = Qt::UserRole + 5,
mapImageRotationRole = Qt::UserRole + 6, lastRole = Qt::UserRole + 6
mapImageMinZoomRole = Qt::UserRole + 7,
bubbleColourRole = Qt::UserRole + 8,
selectedRole = Qt::UserRole + 9,
targetRole = Qt::UserRole + 10,
frequencyRole = Qt::UserRole + 11,
frequencyStringRole = Qt::UserRole + 12,
predictedGroundTrack1Role = Qt::UserRole + 13,
predictedGroundTrack2Role = Qt::UserRole + 14,
groundTrack1Role = Qt::UserRole + 15,
groundTrack2Role = Qt::UserRole + 16,
groundTrackColorRole = Qt::UserRole + 17,
predictedGroundTrackColorRole = Qt::UserRole + 18
}; };
MapModel(MapGUI *gui); MapModel(MapGUI *gui) :
m_gui(gui)
{
connect(this, &MapModel::dataChanged, this, &MapModel::update3DMap);
}
void playAnimations(MapItem *item); virtual void add(MapItem *item);
virtual void remove(MapItem *item);
Q_INVOKABLE void add(MapItem *item); virtual void removeAll();
void update(const QObject *source, SWGSDRangel::SWGMapItem *swgMapItem, const QString &group=""); void update(const QObject *source, SWGSDRangel::SWGMapItem *swgMapItem, const QString &group="");
void update(MapItem *item);
void remove(MapItem *item);
void allUpdated();
void removeAll();
void updateItemSettings(QHash<QString, MapSettings::MapItemSettings *> m_itemSettings); void updateItemSettings(QHash<QString, MapSettings::MapItemSettings *> m_itemSettings);
void allUpdated();
MapItem *findMapItem(const QObject *source, const QString& name);
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
(void) parent;
return m_items.count();
}
Qt::ItemFlags flags(const QModelIndex &index) const override
{
(void) index;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
}
public slots:
void update3DMap(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>());
protected:
MapGUI *m_gui;
QList<MapItem *> m_items;
QHash<QString, MapItem *> m_itemsHash;
virtual void update(MapItem *item);
virtual void update3D(MapItem *item) = 0;
virtual MapItem *newMapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem) = 0;
};
class ImageMapModel : public MapModel {
Q_OBJECT
public:
enum MarkerRoles {
imageRole = MapModel::lastRole + 0,
imageZoomLevelRole = MapModel::lastRole + 1,
boundsRole = MapModel::lastRole + 2
};
ImageMapModel(MapGUI *gui) :
MapModel(gui)
{}
QHash<int, QByteArray> roleNames() const override
{
QHash<int, QByteArray> roles = MapModel::roleNames();
roles[imageRole] = "imageData";
roles[imageZoomLevelRole] = "imageZoomLevel";
roles[boundsRole] = "bounds";
return roles;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
protected:
MapItem *newMapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem) override;
void update3D(MapItem *item) override;
};
class PolygonMapModel : public MapModel {
Q_OBJECT
public:
enum MarkerRoles {
borderColorRole = MapModel::lastRole + 0,
fillColorRole = MapModel::lastRole + 1,
polygonRole = MapModel::lastRole + 2,
boundsRole = MapModel::lastRole + 3
};
PolygonMapModel(MapGUI *gui) :
MapModel(gui)
{}
QHash<int, QByteArray> roleNames() const override
{
QHash<int, QByteArray> roles = MapModel::roleNames();
roles[borderColorRole] = "borderColor";
roles[fillColorRole] = "fillColor";
roles[polygonRole] = "polygon";
roles[boundsRole] = "bounds";
return roles;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
protected:
void update3D(MapItem *item) override;
MapItem *newMapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem) override;
};
class PolylineMapModel : public MapModel {
Q_OBJECT
public:
enum MarkerRoles {
lineColorRole = MapModel::lastRole + 0,
coordinatesRole = MapModel::lastRole + 1,
boundsRole = MapModel::lastRole + 2,
};
PolylineMapModel(MapGUI *gui) :
MapModel(gui)
{}
QHash<int, QByteArray> roleNames() const override
{
QHash<int, QByteArray> roles = MapModel::roleNames();
roles[lineColorRole] = "lineColor";
roles[coordinatesRole] = "coordinates";
roles[boundsRole] = "bounds";
return roles;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
protected:
void update3D(MapItem *item) override;
MapItem *newMapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem) override;
};
// Model used for each item on the map
class ObjectMapModel : public MapModel {
Q_OBJECT
public:
enum MarkerRoles {
//itemSettingsRole = Qt::UserRole + 1,
//nameRole = Qt::UserRole + 2,
//positionRole = Qt::UserRole + 3,
// label? mapTextRole = Qt::UserRole + 4,
mapTextVisibleRole =MapModel::lastRole + 0,
mapImageVisibleRole = MapModel::lastRole + 1,
mapImageRole = MapModel::lastRole + 2,
mapImageRotationRole = MapModel::lastRole + 3,
//mapImageMinZoomRole = MapModel::lastRole + 9,
bubbleColourRole = MapModel::lastRole + 4,
selectedRole = MapModel::lastRole + 5,
targetRole = MapModel::lastRole + 6,
frequencyRole = MapModel::lastRole + 7,
frequencyStringRole = MapModel::lastRole + 8,
predictedGroundTrack1Role = MapModel::lastRole + 9,
predictedGroundTrack2Role = MapModel::lastRole + 10,
groundTrack1Role = MapModel::lastRole + 11,
groundTrack2Role = MapModel::lastRole + 12,
groundTrackColorRole = MapModel::lastRole + 13,
predictedGroundTrackColorRole = MapModel::lastRole + 14,
hasTracksRole = MapModel::lastRole + 15
};
ObjectMapModel(MapGUI *gui);
Q_INVOKABLE void add(MapItem *item) override;
//void update(const QObject *source, SWGSDRangel::SWGMapItem *swgMapItem, const QString &group="");
using MapModel::update;
void remove(MapItem *item) override;
void removeAll() override;
QHash<int, QByteArray> roleNames() const override;
void updateTarget(); void updateTarget();
void setTarget(const QString& name); void setTarget(const QString& name);
bool isTarget(const MapItem *mapItem) const; bool isTarget(const ObjectMapItem *mapItem) const;
Q_INVOKABLE void moveToFront(int oldRow); Q_INVOKABLE void moveToFront(int oldRow);
Q_INVOKABLE void moveToBack(int oldRow); Q_INVOKABLE void moveToBack(int oldRow);
MapItem *findMapItem(const QObject *source, const QString& name); ObjectMapItem *findMapItem(const QString& name);
MapItem *findMapItem(const QString& name);
QModelIndex findMapItemIndex(const QString& name); QModelIndex findMapItemIndex(const QString& name);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override; bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
void setDisplayNames(bool displayNames); void setDisplayNames(bool displayNames);
void setDisplaySelectedGroundTracks(bool displayGroundTracks); void setDisplaySelectedGroundTracks(bool displayGroundTracks);
@ -155,48 +247,9 @@ public:
Q_INVOKABLE void setFrequency(double frequency); Q_INVOKABLE void setFrequency(double frequency);
Q_INVOKABLE void track3D(int index); Q_INVOKABLE void track3D(int index);
void interpolateEast(QGeoCoordinate *c1, QGeoCoordinate *c2, double x, QGeoCoordinate *ci, bool offScreen);
void interpolateWest(QGeoCoordinate *c1, QGeoCoordinate *c2, double x, QGeoCoordinate *ci, bool offScreen);
void interpolate(QGeoCoordinate *c1, QGeoCoordinate *c2, double bottomLeftLongitude, double bottomRightLongitude, QGeoCoordinate* ci, bool offScreen);
void splitTracks(MapItem *item);
void splitTrack(const QList<QGeoCoordinate *>& coords, const QVariantList& track,
QVariantList& track1, QVariantList& track2,
QGeoCoordinate& start1, QGeoCoordinate& start2,
QGeoCoordinate& end1, QGeoCoordinate& end2);
Q_INVOKABLE void viewChanged(double bottomLeftLongitude, double bottomRightLongitude); Q_INVOKABLE void viewChanged(double bottomLeftLongitude, double bottomRightLongitude);
QHash<int, QByteArray> roleNames() const bool isSelected3D(const ObjectMapItem *item) const
{
QHash<int, QByteArray> roles;
roles[positionRole] = "position";
roles[mapTextRole] = "mapText";
roles[mapTextVisibleRole] = "mapTextVisible";
roles[mapImageVisibleRole] = "mapImageVisible";
roles[mapImageRole] = "mapImage";
roles[mapImageRotationRole] = "mapImageRotation";
roles[mapImageMinZoomRole] = "mapImageMinZoom";
roles[bubbleColourRole] = "bubbleColour";
roles[selectedRole] = "selected";
roles[targetRole] = "target";
roles[frequencyRole] = "frequency";
roles[frequencyStringRole] = "frequencyString";
roles[predictedGroundTrack1Role] = "predictedGroundTrack1";
roles[predictedGroundTrack2Role] = "predictedGroundTrack2";
roles[groundTrack1Role] = "groundTrack1";
roles[groundTrack2Role] = "groundTrack2";
roles[groundTrackColorRole] = "groundTrackColor";
roles[predictedGroundTrackColorRole] = "predictedGroundTrackColor";
return roles;
}
// Linear interpolation
double interpolate(double x0, double y0, double x1, double y1, double x)
{
return (y0*(x1-x) + y1*(x-x0)) / (x1-x0);
}
bool isSelected3D(const MapItem *item) const
{ {
return m_selected3D == item->m_name; return m_selected3D == item->m_name;
} }
@ -206,13 +259,24 @@ public:
m_selected3D = selected; m_selected3D = selected;
} }
public slots: //public slots:
void update3DMap(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>()); // void update3DMap(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>()) override;
protected:
void playAnimations(ObjectMapItem *item);
MapItem *newMapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem) override;
void update(MapItem *item) override;
void update3D(MapItem *item) override;
// Linear interpolation
double interpolate(double x0, double y0, double x1, double y1, double x) const
{
return (y0*(x1-x) + y1*(x-x0)) / (x1-x0);
}
private: private:
MapGUI *m_gui; QList<bool> m_selected; // Whether each item on 2D map is selected
QList<MapItem *> m_items; QString m_selected3D; // Name of item selected on 3D map - only supports 1 item, unlike 2D map
QList<bool> m_selected;
int m_target; // Row number of current target, or -1 for none int m_target; // Row number of current target, or -1 for none
bool m_displayNames; bool m_displayNames;
bool m_displaySelectedGroundTracks; bool m_displaySelectedGroundTracks;
@ -221,8 +285,112 @@ private:
double m_bottomLeftLongitude; double m_bottomLeftLongitude;
double m_bottomRightLongitude; double m_bottomRightLongitude;
QString m_selected3D; // Name of item selected on 3D map - only supports 1 item, unlike 2D map void interpolateEast(QGeoCoordinate *c1, QGeoCoordinate *c2, double x, QGeoCoordinate *ci, bool offScreen);
void interpolateWest(QGeoCoordinate *c1, QGeoCoordinate *c2, double x, QGeoCoordinate *ci, bool offScreen);
void interpolate(QGeoCoordinate *c1, QGeoCoordinate *c2, double bottomLeftLongitude, double bottomRightLongitude, QGeoCoordinate* ci, bool offScreen);
void splitTracks(ObjectMapItem *item);
void splitTrack(const QList<QGeoCoordinate *>& coords, const QVariantList& track,
QVariantList& track1, QVariantList& track2,
QGeoCoordinate& start1, QGeoCoordinate& start2,
QGeoCoordinate& end1, QGeoCoordinate& end2);
}; };
class PolygonFilter : public QSortFilterProxyModel {
Q_OBJECT
public:
PolygonFilter() :
m_zoomLevel(10),
m_settings(nullptr)
{
}
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
Q_INVOKABLE void viewChanged(double topLeftLongitude, double topLeftLatitude, double bottomRightLongitude, double bottomRightLatitude, double zoomLevel);
Q_INVOKABLE int mapRowToSource(int row);
void setPosition(const QGeoCoordinate& position);
private:
QGeoRectangle m_view;
double m_zoomLevel;
MapSettings *m_settings;
QGeoCoordinate m_position;;
};
class PolylineFilter : public QSortFilterProxyModel {
Q_OBJECT
public:
PolylineFilter() :
m_zoomLevel(10),
m_settings(nullptr)
{
}
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
Q_INVOKABLE void viewChanged(double topLeftLongitude, double topLeftLatitude, double bottomRightLongitude, double bottomRightLatitude, double zoomLevel);
Q_INVOKABLE int mapRowToSource(int row);
void setPosition(const QGeoCoordinate& position);
private:
QGeoRectangle m_view;
double m_zoomLevel;
MapSettings *m_settings;
QGeoCoordinate m_position;;
};
class ObjectMapFilter : public QSortFilterProxyModel {
Q_OBJECT
public:
ObjectMapFilter() :
m_topLeftLongitude(0),
m_topLeftLatitude(0),
m_bottomRightLongitude(0),
m_bottomRightLatitude(0),
m_zoomLevel(10),
m_settings(nullptr)
{
}
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
Q_INVOKABLE void viewChanged(double topLeftLongitude, double topLeftLatitude, double bottomRightLongitude, double bottomRightLatitude, double zoomLevel);
void applySettings(MapSettings *settings);
Q_INVOKABLE int mapRowToSource(int row);
void setPosition(const QGeoCoordinate& position);
private:
double m_topLeftLongitude;
double m_topLeftLatitude;
double m_bottomRightLongitude;
double m_bottomRightLatitude;
double m_zoomLevel;
MapSettings *m_settings;
QGeoCoordinate m_position;
};
class ImageFilter : public QSortFilterProxyModel {
Q_OBJECT
public:
ImageFilter() :
m_zoomLevel(10),
m_settings(nullptr)
{
}
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
Q_INVOKABLE void viewChanged(double topLeftLongitude, double topLeftLatitude, double bottomRightLongitude, double bottomRightLatitude, double zoomLevel);
Q_INVOKABLE int mapRowToSource(int row);
void setPosition(const QGeoCoordinate& position);
private:
QGeoRectangle m_view;
double m_zoomLevel;
MapSettings *m_settings;
QGeoCoordinate m_position;;
};
#endif // INCLUDE_FEATURE_MAPMODEL_H_ #endif // INCLUDE_FEATURE_MAPMODEL_H_

View File

@ -26,25 +26,31 @@
#include "mapsettings.h" #include "mapsettings.h"
const QStringList MapSettings::m_pipeTypes = { const QStringList MapSettings::m_pipeTypes = {
QStringLiteral("ACARSDemod"),
QStringLiteral("ADSBDemod"), QStringLiteral("ADSBDemod"),
QStringLiteral("AIS"), QStringLiteral("AIS"),
QStringLiteral("APRS"), QStringLiteral("APRS"),
QStringLiteral("APTDemod"), QStringLiteral("APTDemod"),
QStringLiteral("FT8Demod"), QStringLiteral("FT8Demod"),
QStringLiteral("HeatMap"),
QStringLiteral("Radiosonde"), QStringLiteral("Radiosonde"),
QStringLiteral("StarTracker"), QStringLiteral("StarTracker"),
QStringLiteral("SatelliteTracker") QStringLiteral("SatelliteTracker"),
QStringLiteral("VORLocalizer")
}; };
const QStringList MapSettings::m_pipeURIs = { const QStringList MapSettings::m_pipeURIs = {
QStringLiteral("sdrangel.channel.acarsdemod"),
QStringLiteral("sdrangel.channel.adsbdemod"), QStringLiteral("sdrangel.channel.adsbdemod"),
QStringLiteral("sdrangel.feature.ais"), QStringLiteral("sdrangel.feature.ais"),
QStringLiteral("sdrangel.feature.aprs"), QStringLiteral("sdrangel.feature.aprs"),
QStringLiteral("sdrangel.channel.aptdemod"), QStringLiteral("sdrangel.channel.aptdemod"),
QStringLiteral("sdrangel.channel.ft8demod"), QStringLiteral("sdrangel.channel.ft8demod"),
QStringLiteral("sdrangel.channel.heatmap"),
QStringLiteral("sdrangel.feature.radiosonde"), QStringLiteral("sdrangel.feature.radiosonde"),
QStringLiteral("sdrangel.feature.startracker"), QStringLiteral("sdrangel.feature.startracker"),
QStringLiteral("sdrangel.feature.satellitetracker") QStringLiteral("sdrangel.feature.satellitetracker"),
QStringLiteral("sdrangel.feature.vorlocalizer")
}; };
// GUI combo box should match ordering in this list // GUI combo box should match ordering in this list
@ -62,24 +68,70 @@ MapSettings::MapSettings() :
// Source names should match m_pipeTypes // Source names should match m_pipeTypes
// Colors currently match color of rollup widget for that plugin // Colors currently match color of rollup widget for that plugin
int modelMinPixelSize = 50; int modelMinPixelSize = 50;
m_itemSettings.insert("ADSBDemod", new MapItemSettings("ADSBDemod", QColor(244, 151, 57), false, 11, modelMinPixelSize)); MapItemSettings *aptSettings = new MapItemSettings("APTDemod", true, QColor(216, 112, 169), true, false, 11);
m_itemSettings.insert("AIS", new MapItemSettings("AIS", QColor(102, 0, 0), false, 11, modelMinPixelSize)); aptSettings->m_display2DIcon = false; // Disabled as 2D projection is wrong
m_itemSettings.insert("APRS", new MapItemSettings("APRS", QColor(255, 255, 0), false, 11)); m_itemSettings.insert("APTDemod", aptSettings);
m_itemSettings.insert("StarTracker", new MapItemSettings("StarTracker", QColor(230, 230, 230), true, 3)); m_itemSettings.insert("ACARSDemod", new MapItemSettings("ACARSDemod", true, QColor(244, 151, 57), true, false, 11, modelMinPixelSize));
m_itemSettings.insert("SatelliteTracker", new MapItemSettings("SatelliteTracker", QColor(0, 0, 255), false, 0, modelMinPixelSize)); m_itemSettings.insert("ADSBDemod", new MapItemSettings("ADSBDemod", true, QColor(244, 151, 57), true, false, 11, modelMinPixelSize));
m_itemSettings.insert("Beacons", new MapItemSettings("Beacons", QColor(255, 0, 0), true, 8)); m_itemSettings.insert("AIS", new MapItemSettings("AIS", true, QColor(102, 0, 0), true, false, 11, modelMinPixelSize));
m_itemSettings.insert("Radiosonde", new MapItemSettings("Radiosonde", QColor(102, 0, 102), false, 11, modelMinPixelSize)); MapItemSettings *aprsSettings = new MapItemSettings("APRS", true, QColor(255, 255, 0), true, false, 11);
m_itemSettings.insert("Radio Time Transmitters", new MapItemSettings("Radio Time Transmitters", QColor(255, 0, 0), true, 8)); aprsSettings->m_extrapolate = 0;
m_itemSettings.insert("Radar", new MapItemSettings("Radar", QColor(255, 0, 0), true, 8)); m_itemSettings.insert("APRS", aprsSettings);
m_itemSettings.insert("FT8Demod", new MapItemSettings("FT8Demod", QColor(0, 192, 255), true, 8)); m_itemSettings.insert("StarTracker", new MapItemSettings("StarTracker", true, QColor(230, 230, 230), true, true, 3));
m_itemSettings.insert("SatelliteTracker", new MapItemSettings("SatelliteTracker", true, QColor(0, 0, 255), true, false, 0, modelMinPixelSize));
m_itemSettings.insert("Beacons", new MapItemSettings("Beacons", true, QColor(255, 0, 0), false, true, 8));
m_itemSettings.insert("Radiosonde", new MapItemSettings("Radiosonde", true, QColor(102, 0, 102), true, false, 11, modelMinPixelSize));
m_itemSettings.insert("Radio Time Transmitters", new MapItemSettings("Radio Time Transmitters", true, QColor(255, 0, 0), false, true, 8));
m_itemSettings.insert("Radar", new MapItemSettings("Radar", true, QColor(255, 0, 0), false, true, 8));
m_itemSettings.insert("FT8Demod", new MapItemSettings("FT8Demod", true, QColor(0, 192, 255), true, true, 8));
m_itemSettings.insert("HeatMap", new MapItemSettings("HeatMap", true, QColor(102, 40, 220), true, false, 11));
MapItemSettings *ionosondeItemSettings = new MapItemSettings("Ionosonde Stations", QColor(255, 255, 0), true, 4); m_itemSettings.insert("AM", new MapItemSettings("AM", false, QColor(255, 0, 0), false, true, 10));
MapItemSettings *fmSettings = new MapItemSettings("FM", false, QColor(255, 0, 0), false, true, 12);
fmSettings->m_filterDistance = 150000;
m_itemSettings.insert("FM", fmSettings);
MapItemSettings *dabSettings = new MapItemSettings("DAB", false, QColor(255, 0, 0), false, true, 12);
dabSettings->m_filterDistance = 75000;
m_itemSettings.insert("DAB", dabSettings);
MapItemSettings *navAidSettings = new MapItemSettings("NavAid", false, QColor(255, 0, 255), false, true, 11);
navAidSettings->m_filterDistance = 500000;
m_itemSettings.insert("NavAid", navAidSettings);
m_itemSettings.insert("Airport (Large)", new MapItemSettings("Airport (Large)", false, QColor(255, 0, 255), false, true, 11));
m_itemSettings.insert("Airport (Medium)", new MapItemSettings("Airport (Medium)", false, QColor(255, 0, 255), false, true, 11));
m_itemSettings.insert("Airport (Small)", new MapItemSettings("Airport (Small)", false, QColor(255, 0, 255), false, true, 13));
m_itemSettings.insert("Heliport", new MapItemSettings("Heliport", false, QColor(255, 0, 255), false, true, 12));
MapItemSettings *stationSettings = new MapItemSettings("Station", true, QColor(255, 0, 0), false, true, 11);
stationSettings->m_extrapolate = 0;
stationSettings->m_display3DTrack = false;
m_itemSettings.insert("Station", stationSettings);
m_itemSettings.insert("VORLocalizer", new MapItemSettings("VORLocalizer", true, QColor(255, 255, 0), false, true, 11));
MapItemSettings *ionosondeItemSettings = new MapItemSettings("Ionosonde Stations", true, QColor(255, 255, 0), false, true, 4);
ionosondeItemSettings->m_display2DIcon = false; ionosondeItemSettings->m_display2DIcon = false;
m_itemSettings.insert("Ionosonde Stations", ionosondeItemSettings); m_itemSettings.insert("Ionosonde Stations", ionosondeItemSettings);
MapItemSettings *stationItemSettings = new MapItemSettings("Station", QColor(255, 0, 0), true, 11); m_itemSettings.insert("Airspace (A)", new MapItemSettings("Airspace (A)", false, QColor(255, 0, 0, 0x20), false, false, 7));
stationItemSettings->m_display2DTrack = false; m_itemSettings.insert("Airspace (B)", new MapItemSettings("Airspace (B)", false, QColor(255, 0, 0, 0x20), false, false, 7));
m_itemSettings.insert("Station", stationItemSettings); m_itemSettings.insert("Airspace (C)", new MapItemSettings("Airspace (C)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (D)", new MapItemSettings("Airspace (D)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (E)", new MapItemSettings("Airspace (E)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (F)", new MapItemSettings("Airspace (F)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (G)", new MapItemSettings("Airspace (G)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (FIR)", new MapItemSettings("Airspace (FIR)", false, QColor(255, 0, 0, 0x20), false, false, 7));
m_itemSettings.insert("Airspace (CTR)", new MapItemSettings("Airspace (CTR)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (RMZ)", new MapItemSettings("Airspace (RMZ)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (TMA)", new MapItemSettings("Airspace (TMA)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (TMZ)", new MapItemSettings("Airspace (TMZ)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (OTH)", new MapItemSettings("Airspace (OTH)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (Restricted)", new MapItemSettings("Airspace (Restricted)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (Gliding)", new MapItemSettings("Airspace (Gliding)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (Danger)", new MapItemSettings("Airspace (Danger)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (Prohibited)", new MapItemSettings("Airspace (Prohibited)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (Wave)", new MapItemSettings("Airspace (Wave)", false, QColor(255, 0, 0, 0x20), false, false, 11));
m_itemSettings.insert("Airspace (Airports)", new MapItemSettings("Airspace (Airports)", false, QColor(0, 0, 255, 0x20), false, false, 11));
resetToDefaults(); resetToDefaults();
} }
@ -104,7 +156,7 @@ void MapSettings::resetToDefaults()
m_displaySelectedGroundTracks = true; m_displaySelectedGroundTracks = true;
m_displayAllGroundTracks = true; m_displayAllGroundTracks = true;
m_title = "Map"; m_title = "Map";
m_displayAllGroundTracks = QColor(225, 25, 99).rgb(); m_displayAllGroundTracks = QColor(225, 25, 99).rgba();
m_useReverseAPI = false; m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1"; m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888; m_reverseAPIPort = 8888;
@ -121,6 +173,7 @@ void MapSettings::resetToDefaults()
m_displayMUF = false; m_displayMUF = false;
m_displayfoF2 = false; m_displayfoF2 = false;
m_workspaceIndex = 0; m_workspaceIndex = 0;
m_checkWXAPIKey = "";
} }
QByteArray MapSettings::serialize() const QByteArray MapSettings::serialize() const
@ -165,6 +218,8 @@ QByteArray MapSettings::serialize() const
s.writeBool(35, m_displayMUF); s.writeBool(35, m_displayMUF);
s.writeBool(36, m_displayfoF2); s.writeBool(36, m_displayfoF2);
s.writeString(46, m_checkWXAPIKey);
return s.final(); return s.final();
} }
@ -184,6 +239,7 @@ bool MapSettings::deserialize(const QByteArray& data)
uint32_t utmp; uint32_t utmp;
QString strtmp; QString strtmp;
QByteArray blob; QByteArray blob;
QString string;
d.readBool(1, &m_displayNames, true); d.readBool(1, &m_displayNames, true);
d.readString(2, &m_mapProvider, "osm"); d.readString(2, &m_mapProvider, "osm");
@ -195,7 +251,7 @@ bool MapSettings::deserialize(const QByteArray& data)
d.readString(3, &m_mapBoxAPIKey, ""); d.readString(3, &m_mapBoxAPIKey, "");
d.readString(4, &m_mapBoxStyles, ""); d.readString(4, &m_mapBoxStyles, "");
d.readString(8, &m_title, "Map"); d.readString(8, &m_title, "Map");
d.readU32(9, &m_rgbColor, QColor(225, 25, 99).rgb()); d.readU32(9, &m_rgbColor, QColor(225, 25, 99).rgba());
d.readBool(10, &m_useReverseAPI, false); d.readBool(10, &m_useReverseAPI, false);
d.readString(11, &m_reverseAPIAddress, "127.0.0.1"); d.readString(11, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(12, &utmp, 0); d.readU32(12, &utmp, 0);
@ -241,6 +297,8 @@ bool MapSettings::deserialize(const QByteArray& data)
d.readBool(35, &m_displayMUF, false); d.readBool(35, &m_displayMUF, false);
d.readBool(36, &m_displayfoF2, false); d.readBool(36, &m_displayfoF2, false);
d.readString(46, &m_checkWXAPIKey, "");
return true; return true;
} }
else else
@ -251,16 +309,20 @@ bool MapSettings::deserialize(const QByteArray& data)
} }
MapSettings::MapItemSettings::MapItemSettings(const QString& group, MapSettings::MapItemSettings::MapItemSettings(const QString& group,
bool enabled,
const QColor color, const QColor color,
bool display2DTrack,
bool display3DPoint, bool display3DPoint,
int minZoom, int minZoom,
int modelMinPixelSize) int modelMinPixelSize)
{ {
m_group = group; m_group = group;
resetToDefaults(); resetToDefaults();
m_3DPointColor = color.rgb(); m_enabled = enabled,
m_2DTrackColor = color.darker().rgb(); m_3DPointColor = color.rgba();
m_3DTrackColor = color.darker().rgb(); m_2DTrackColor = color.darker().rgba();
m_3DTrackColor = color.darker().rgba();
m_display2DTrack = display2DTrack;
m_display3DPoint = display3DPoint; m_display3DPoint = display3DPoint;
m_2DMinZoom = minZoom; m_2DMinZoom = minZoom;
m_3DModelMinPixelSize = modelMinPixelSize; m_3DModelMinPixelSize = modelMinPixelSize;
@ -277,16 +339,19 @@ void MapSettings::MapItemSettings::resetToDefaults()
m_display2DIcon = true; m_display2DIcon = true;
m_display2DLabel = true; m_display2DLabel = true;
m_display2DTrack = true; m_display2DTrack = true;
m_2DTrackColor = QColor(150, 0, 20).rgb(); m_2DTrackColor = QColor(150, 0, 20).rgba();
m_2DMinZoom = 1; m_2DMinZoom = 1;
m_display3DModel = true; m_display3DModel = true;
m_display3DPoint = false; m_display3DPoint = false;
m_3DPointColor = QColor(225, 0, 0).rgb(); m_3DPointColor = QColor(225, 0, 0).rgba();
m_display3DLabel = true; m_display3DLabel = true;
m_display3DTrack = true; m_display3DTrack = true;
m_3DTrackColor = QColor(150, 0, 20).rgb(); m_3DTrackColor = QColor(150, 0, 20).rgba();
m_3DModelMinPixelSize = 0; m_3DModelMinPixelSize = 0;
m_3DLabelScale = 0.5f; m_3DLabelScale = 0.5f;
m_filterName = "";
m_filterDistance = 0;
m_extrapolate = 60;
} }
QByteArray MapSettings::MapItemSettings::serialize() const QByteArray MapSettings::MapItemSettings::serialize() const
@ -308,6 +373,9 @@ QByteArray MapSettings::MapItemSettings::serialize() const
s.writeU32(13, m_3DTrackColor); s.writeU32(13, m_3DTrackColor);
s.writeS32(14, m_3DModelMinPixelSize); s.writeS32(14, m_3DModelMinPixelSize);
s.writeFloat(15, m_3DLabelScale); s.writeFloat(15, m_3DLabelScale);
s.writeString(16, m_filterName);
s.writeS32(17, m_filterDistance);
s.writeS32(18, m_extrapolate);
return s.final(); return s.final();
} }
@ -329,16 +397,21 @@ bool MapSettings::MapItemSettings::deserialize(const QByteArray& data)
d.readBool(3, &m_display2DIcon, true); d.readBool(3, &m_display2DIcon, true);
d.readBool(4, &m_display2DLabel, true); d.readBool(4, &m_display2DLabel, true);
d.readBool(5, &m_display2DTrack, true); d.readBool(5, &m_display2DTrack, true);
d.readU32(6, &m_2DTrackColor, QColor(150, 0, 0).rgb()); d.readU32(6, &m_2DTrackColor, QColor(150, 0, 0).rgba());
d.readS32(7, &m_2DMinZoom, 1); d.readS32(7, &m_2DMinZoom, 1);
d.readBool(8, &m_display3DModel, true); d.readBool(8, &m_display3DModel, true);
d.readBool(9, &m_display3DLabel, true); d.readBool(9, &m_display3DLabel, true);
d.readBool(10, &m_display3DPoint, true); d.readBool(10, &m_display3DPoint, true);
d.readU32(11, &m_3DPointColor, QColor(255, 0, 0).rgb()); d.readU32(11, &m_3DPointColor, QColor(255, 0, 0).rgba());
d.readBool(12, &m_display3DTrack, true); d.readBool(12, &m_display3DTrack, true);
d.readU32(13, &m_3DTrackColor, QColor(150, 0, 20).rgb()); d.readU32(13, &m_3DTrackColor, QColor(150, 0, 20).rgba());
d.readS32(14, &m_3DModelMinPixelSize, 0); d.readS32(14, &m_3DModelMinPixelSize, 0);
d.readFloat(15, &m_3DLabelScale, 0.5f); d.readFloat(15, &m_3DLabelScale, 0.5f);
d.readString(16, &m_filterName, "");
d.readS32(17, &m_filterDistance, 0);
d.readS32(18, &m_extrapolate, 60);
m_filterNameRE.setPattern(m_filterName);
m_filterNameRE.optimize();
return true; return true;
} }
else else
@ -567,3 +640,4 @@ QString MapSettings::getDebugString(const QStringList& settingsKeys, bool force)
return QString(ostr.str().c_str()); return QString(ostr.str().c_str());
} }

View File

@ -22,6 +22,7 @@
#include <QByteArray> #include <QByteArray>
#include <QString> #include <QString>
#include <QHash> #include <QHash>
#include <QRegularExpression>
class Serializable; class Serializable;
@ -32,7 +33,7 @@ struct MapSettings
bool m_enabled; // Whether enabled at all on 2D or 3D map bool m_enabled; // Whether enabled at all on 2D or 3D map
bool m_display2DIcon; // Display image 2D map bool m_display2DIcon; // Display image 2D map
bool m_display2DLabel; // Display label on 2D map bool m_display2DLabel; // Display label on 2D map
bool m_display2DTrack; // Display tracks on 2D map bool m_display2DTrack; // Display tracks (or polygon) on 2D map
quint32 m_2DTrackColor; quint32 m_2DTrackColor;
int m_2DMinZoom; int m_2DMinZoom;
bool m_display3DModel; // Draw 3D model for item bool m_display3DModel; // Draw 3D model for item
@ -43,14 +44,26 @@ struct MapSettings
quint32 m_3DTrackColor; quint32 m_3DTrackColor;
int m_3DModelMinPixelSize; int m_3DModelMinPixelSize;
float m_3DLabelScale; float m_3DLabelScale;
QString m_filterName;
QRegularExpression m_filterNameRE;
int m_filterDistance; // Filter items > this distance in metres away from My Position. <= 0 don't filter
int m_extrapolate; // Extrapolate duration in seconds on 3D map
MapItemSettings(const QString& group, const QColor color, bool display3DPoint=true, int minZoom=11, int modelMinPixelSize=0); MapItemSettings(const QString& group, bool enabled, const QColor color, bool display2DTrack=true, bool display3DPoint=true, int minZoom=11, int modelMinPixelSize=0);
MapItemSettings(const QByteArray& data); MapItemSettings(const QByteArray& data);
void resetToDefaults(); void resetToDefaults();
QByteArray serialize() const; QByteArray serialize() const;
bool deserialize(const QByteArray& data); bool deserialize(const QByteArray& data);
}; };
struct MapPolygonSettings {
QString m_group;
bool m_enabled;
quint32 m_2DColor;
int m_2DMinZoom;
quint32 m_3DColor;
};
struct AvailableChannelOrFeature struct AvailableChannelOrFeature
{ {
QString m_kind; //!< "R" for channel, "F" for feature QString m_kind; //!< "R" for channel, "F" for feature
@ -107,6 +120,8 @@ struct MapSettings
bool m_displayMUF; // Plot MUF contours bool m_displayMUF; // Plot MUF contours
bool m_displayfoF2; // Plot foF2 contours bool m_displayfoF2; // Plot foF2 contours
QString m_checkWXAPIKey; //!< checkwxapi.com API key
// Per source settings // Per source settings
QHash<QString, MapItemSettings *> m_itemSettings; QHash<QString, MapItemSettings *> m_itemSettings;
@ -127,4 +142,7 @@ struct MapSettings
static const QStringList m_mapProviders; static const QStringList m_mapProviders;
}; };
Q_DECLARE_METATYPE(MapSettings::MapItemSettings *);
#endif // INCLUDE_FEATURE_MAPSETTINGS_H_ #endif // INCLUDE_FEATURE_MAPSETTINGS_H_

View File

@ -109,6 +109,7 @@ MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) :
QDialog(parent), QDialog(parent),
m_settings(settings), m_settings(settings),
m_downloadDialog(this), m_downloadDialog(this),
m_progressDialog(nullptr),
ui(new Ui::MapSettingsDialog) ui(new Ui::MapSettingsDialog)
{ {
ui->setupUi(this); ui->setupUi(this);
@ -117,6 +118,7 @@ MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) :
ui->maptilerAPIKey->setText(settings->m_maptilerAPIKey); ui->maptilerAPIKey->setText(settings->m_maptilerAPIKey);
ui->mapBoxAPIKey->setText(settings->m_mapBoxAPIKey); ui->mapBoxAPIKey->setText(settings->m_mapBoxAPIKey);
ui->cesiumIonAPIKey->setText(settings->m_cesiumIonAPIKey); ui->cesiumIonAPIKey->setText(settings->m_cesiumIonAPIKey);
ui->checkWXAPIKey->setText(settings->m_checkWXAPIKey);
ui->osmURL->setText(settings->m_osmURL); ui->osmURL->setText(settings->m_osmURL);
ui->mapBoxStyles->setText(settings->m_mapBoxStyles); ui->mapBoxStyles->setText(settings->m_mapBoxStyles);
ui->map2DEnabled->setChecked(m_settings->m_map2DEnabled); ui->map2DEnabled->setChecked(m_settings->m_map2DEnabled);
@ -129,10 +131,10 @@ MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) :
// Sort groups in table alphabetically // Sort groups in table alphabetically
QList<MapSettings::MapItemSettings *> itemSettings = m_settings->m_itemSettings.values(); QList<MapSettings::MapItemSettings *> itemSettings = m_settings->m_itemSettings.values();
std::sort(itemSettings.begin(), itemSettings.end(), std::sort(itemSettings.begin(), itemSettings.end(),
[](const MapSettings::MapItemSettings* a, const MapSettings::MapItemSettings* b) -> bool { [](const MapSettings::MapItemSettings* a, const MapSettings::MapItemSettings* b) -> bool {
return a->m_group < b->m_group; return a->m_group < b->m_group;
}); });
QListIterator<MapSettings::MapItemSettings *> itr(itemSettings); QListIterator<MapSettings::MapItemSettings *> itr(itemSettings);
while (itr.hasNext()) while (itr.hasNext())
{ {
@ -163,6 +165,14 @@ MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) :
item->setCheckState(itemSettings->m_display3DLabel ? Qt::Checked : Qt::Unchecked); item->setCheckState(itemSettings->m_display3DLabel ? Qt::Checked : Qt::Unchecked);
ui->mapItemSettings->setItem(row, COL_3D_LABEL, item); ui->mapItemSettings->setItem(row, COL_3D_LABEL, item);
item = new QTableWidgetItem(itemSettings->m_filterName);
ui->mapItemSettings->setItem(row, COL_FILTER_NAME, item);
item = new QTableWidgetItem();
if (itemSettings->m_filterDistance > 0) {
item->setText(QString::number(itemSettings->m_filterDistance / 1000));
}
ui->mapItemSettings->setItem(row, COL_FILTER_DISTANCE, item);
MapItemSettingsGUI *gui = new MapItemSettingsGUI(ui->mapItemSettings, row, itemSettings); MapItemSettingsGUI *gui = new MapItemSettingsGUI(ui->mapItemSettings, row, itemSettings);
m_mapItemSettingsGUIs.append(gui); m_mapItemSettingsGUIs.append(gui);
} }
@ -172,6 +182,15 @@ MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) :
on_map3DEnabled_clicked(m_settings->m_map3DEnabled); on_map3DEnabled_clicked(m_settings->m_map3DEnabled);
connect(&m_dlm, &HttpDownloadManagerGUI::downloadComplete, this, &MapSettingsDialog::downloadComplete); connect(&m_dlm, &HttpDownloadManagerGUI::downloadComplete, this, &MapSettingsDialog::downloadComplete);
connect(&m_openAIP, &OpenAIP::downloadingURL, this, &MapSettingsDialog::downloadingURL);
connect(&m_openAIP, &OpenAIP::downloadError, this, &MapSettingsDialog::downloadError);
connect(&m_openAIP, &OpenAIP::downloadAirspaceFinished, this, &MapSettingsDialog::downloadAirspaceFinished);
connect(&m_openAIP, &OpenAIP::downloadNavAidsFinished, this, &MapSettingsDialog::downloadNavAidsFinished);
connect(&m_ourAirportsDB, &OurAirportsDB::downloadingURL, this, &MapSettingsDialog::downloadingURL);
connect(&m_ourAirportsDB, &OurAirportsDB::downloadProgress, this, &MapSettingsDialog::downloadProgress);
connect(&m_ourAirportsDB, &OurAirportsDB::downloadError, this, &MapSettingsDialog::downloadError);
connect(&m_ourAirportsDB, &OurAirportsDB::downloadAirportInformationFinished, this, &MapSettingsDialog::downloadAirportInformationFinished);
#ifndef QT_WEBENGINE_FOUND #ifndef QT_WEBENGINE_FOUND
ui->map3DSettings->setVisible(false); ui->map3DSettings->setVisible(false);
ui->downloadModels->setVisible(false); ui->downloadModels->setVisible(false);
@ -257,6 +276,16 @@ void MapSettingsDialog::accept()
itemSettings->m_3DTrackColor = gui->m_track3D.m_color; itemSettings->m_3DTrackColor = gui->m_track3D.m_color;
itemSettings->m_3DModelMinPixelSize = gui->m_minPixels->value(); itemSettings->m_3DModelMinPixelSize = gui->m_minPixels->value();
itemSettings->m_3DLabelScale = gui->m_labelScale->value(); itemSettings->m_3DLabelScale = gui->m_labelScale->value();
itemSettings->m_filterName = ui->mapItemSettings->item(row, COL_FILTER_NAME)->text();
itemSettings->m_filterNameRE.setPattern(itemSettings->m_filterName);
itemSettings->m_filterNameRE.optimize();
bool ok;
int filterDistance = ui->mapItemSettings->item(row, COL_FILTER_DISTANCE)->text().toInt(&ok);
if (ok && filterDistance > 0) {
itemSettings->m_filterDistance = filterDistance * 1000;
} else {
itemSettings->m_filterDistance = 0;
}
} }
QDialog::accept(); QDialog::accept();
@ -307,6 +336,7 @@ void MapSettingsDialog::on_map3DEnabled_clicked(bool checked)
ui->buildings->setEnabled(checked); ui->buildings->setEnabled(checked);
ui->sunLightEnabled->setEnabled(checked); ui->sunLightEnabled->setEnabled(checked);
ui->eciCamera->setEnabled(checked); ui->eciCamera->setEnabled(checked);
ui->antiAliasing->setEnabled(checked);
} }
// Models have individual licensing. See LICENSE on github // Models have individual licensing. See LICENSE on github
@ -423,3 +453,95 @@ void MapSettingsDialog::downloadComplete(const QString &filename, bool success,
QMessageBox::warning(this, "Download failed", QString("Failed to download %1 to %2\n%3").arg(url).arg(filename).arg(errorMessage)); QMessageBox::warning(this, "Download failed", QString("Failed to download %1 to %2\n%3").arg(url).arg(filename).arg(errorMessage));
} }
} }
void MapSettingsDialog::on_getAirportDB_clicked()
{
// Don't try to download while already in progress
if (m_progressDialog == nullptr)
{
m_progressDialog = new QProgressDialog(this);
m_progressDialog->setCancelButton(nullptr);
m_progressDialog->setWindowFlag(Qt::WindowCloseButtonHint, false);
m_ourAirportsDB.downloadAirportInformation();
}
}
void MapSettingsDialog::on_getAirspacesDB_clicked()
{
// Don't try to download while already in progress
if (m_progressDialog == nullptr)
{
m_progressDialog = new QProgressDialog(this);
m_progressDialog->setMaximum(OpenAIP::m_countryCodes.size());
m_progressDialog->setCancelButton(nullptr);
m_progressDialog->setWindowFlag(Qt::WindowCloseButtonHint, false);
m_openAIP.downloadAirspaces();
}
}
void MapSettingsDialog::downloadingURL(const QString& url)
{
if (m_progressDialog)
{
m_progressDialog->setLabelText(QString("Downloading %1.").arg(url));
m_progressDialog->setValue(m_progressDialog->value() + 1);
}
}
void MapSettingsDialog::downloadProgress(qint64 bytesRead, qint64 totalBytes)
{
if (m_progressDialog)
{
m_progressDialog->setMaximum(totalBytes);
m_progressDialog->setValue(bytesRead);
}
}
void MapSettingsDialog::downloadError(const QString& error)
{
QMessageBox::critical(this, "Map", error);
if (m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}
void MapSettingsDialog::downloadAirspaceFinished()
{
if (m_progressDialog) {
m_progressDialog->setLabelText("Reading airspaces.");
}
emit airspacesUpdated();
m_openAIP.downloadNavAids();
}
void MapSettingsDialog::downloadNavAidsFinished()
{
if (m_progressDialog) {
m_progressDialog->setLabelText("Reading NAVAIDs.");
}
emit navAidsUpdated();
if (m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}
void MapSettingsDialog::downloadAirportInformationFinished()
{
if (m_progressDialog) {
m_progressDialog->setLabelText("Reading airports.");
}
emit airportsUpdated();
if (m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}

View File

@ -21,8 +21,11 @@
#include <QSpinBox> #include <QSpinBox>
#include <QDoubleSpinBox> #include <QDoubleSpinBox>
#include <QMessageBox> #include <QMessageBox>
#include <QProgressDialog>
#include "gui/httpdownloadmanagergui.h" #include "gui/httpdownloadmanagergui.h"
#include "util/openaip.h"
#include "util/ourairportsdb.h"
#include "ui_mapsettingsdialog.h" #include "ui_mapsettingsdialog.h"
#include "mapsettings.h" #include "mapsettings.h"
@ -78,7 +81,9 @@ public:
COL_3D_LABEL, COL_3D_LABEL,
COL_3D_POINT, COL_3D_POINT,
COL_3D_TRACK, COL_3D_TRACK,
COL_3D_LABEL_SCALE COL_3D_LABEL_SCALE,
COL_FILTER_NAME,
COL_FILTER_DISTANCE
}; };
public: public:
@ -92,6 +97,9 @@ private:
HttpDownloadManagerGUI m_dlm; HttpDownloadManagerGUI m_dlm;
int m_fileIdx; int m_fileIdx;
QMessageBox m_downloadDialog; QMessageBox m_downloadDialog;
QProgressDialog *m_progressDialog;
OpenAIP m_openAIP;
OurAirportsDB m_ourAirportsDB;
void unzip(const QString &filename); void unzip(const QString &filename);
@ -100,7 +108,20 @@ private slots:
void on_map2DEnabled_clicked(bool checked=false); void on_map2DEnabled_clicked(bool checked=false);
void on_map3DEnabled_clicked(bool checked=false); void on_map3DEnabled_clicked(bool checked=false);
void on_downloadModels_clicked(); void on_downloadModels_clicked();
void on_getAirportDB_clicked();
void on_getAirspacesDB_clicked();
void downloadComplete(const QString &filename, bool success, const QString &url, const QString &errorMessage); void downloadComplete(const QString &filename, bool success, const QString &url, const QString &errorMessage);
void downloadingURL(const QString& url);
void downloadProgress(qint64 bytesRead, qint64 totalBytes);
void downloadError(const QString& error);
void downloadAirspaceFinished();
void downloadNavAidsFinished();
void downloadAirportInformationFinished();
signals:
void navAidsUpdated();
void airspacesUpdated();
void airportsUpdated();
private: private:
Ui::MapSettingsDialog* ui; Ui::MapSettingsDialog* ui;

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1016</width> <width>1267</width>
<height>720</height> <height>648</height>
</rect> </rect>
</property> </property>
<property name="font"> <property name="font">
@ -35,68 +35,6 @@
<string>Maps</string> <string>Maps</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTableWidget" name="mapItemSettings">
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<column>
<property name="text">
<string>Enabled</string>
</property>
</column>
<column>
<property name="text">
<string>2D Icon</string>
</property>
</column>
<column>
<property name="text">
<string>2D Label</string>
</property>
</column>
<column>
<property name="text">
<string>2D Min Zoom</string>
</property>
</column>
<column>
<property name="text">
<string>2D Track</string>
</property>
</column>
<column>
<property name="text">
<string>3D Model</string>
</property>
</column>
<column>
<property name="text">
<string>3D Min Pixels</string>
</property>
</column>
<column>
<property name="text">
<string>3D Label</string>
</property>
</column>
<column>
<property name="text">
<string>3D Point</string>
</property>
</column>
<column>
<property name="text">
<string>3D Track</string>
</property>
</column>
<column>
<property name="text">
<string>3D Label Scale</string>
</property>
</column>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="map2DSettings"> <widget class="QGroupBox" name="map2DSettings">
<property name="title"> <property name="title">
@ -346,6 +284,19 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
@ -358,6 +309,34 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QToolButton" name="getAirportDB">
<property name="toolTip">
<string>Download the latest OurAirports airport databases (10MB)</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/map/icons/controltower.png</normaloff>:/map/icons/controltower.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="getAirspacesDB">
<property name="toolTip">
<string>Download airspaces and NAVAIDs from OpenAIP (40MB)</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/map/icons/vor.png</normaloff>:/map/icons/vor.png</iconset>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
@ -375,6 +354,115 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="mapItemsTab">
<attribute name="title">
<string>Map Items</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QTableWidget" name="mapItemSettings">
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<column>
<property name="text">
<string>Enabled</string>
</property>
<property name="toolTip">
<string>Whether items of this type will be displayed on the maps</string>
</property>
</column>
<column>
<property name="text">
<string>2D Icon</string>
</property>
<property name="toolTip">
<string>Whether to display icon on 2D map</string>
</property>
</column>
<column>
<property name="text">
<string>2D Label</string>
</property>
<property name="toolTip">
<string>Whether to display label on 2D map</string>
</property>
</column>
<column>
<property name="text">
<string>2D Min Zoom</string>
</property>
</column>
<column>
<property name="text">
<string>2D Colour</string>
</property>
<property name="toolTip">
<string>Track colour or 2D map</string>
</property>
</column>
<column>
<property name="text">
<string>3D Model</string>
</property>
<property name="toolTip">
<string>Whether to display model on 3D map</string>
</property>
</column>
<column>
<property name="text">
<string>3D Min Pixels</string>
</property>
</column>
<column>
<property name="text">
<string>3D Label</string>
</property>
<property name="toolTip">
<string>Whether to display label on 3D map</string>
</property>
</column>
<column>
<property name="text">
<string>3D Point</string>
</property>
<property name="toolTip">
<string>Whether to display a point on 3D map</string>
</property>
</column>
<column>
<property name="text">
<string>3D Colour</string>
</property>
</column>
<column>
<property name="text">
<string>3D Label Scale</string>
</property>
<property name="toolTip">
<string>Scale factor for label on 3D map</string>
</property>
</column>
<column>
<property name="text">
<string>Filter Name</string>
</property>
<property name="toolTip">
<string>Regular expression to filter by name (Only items with names matching the pattern will be displayed)</string>
</property>
</column>
<column>
<property name="text">
<string>Filter Distance (km)</string>
</property>
<property name="toolTip">
<string>Filter objects further than this distance in km away from My Position</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="apiKeysTab"> <widget class="QWidget" name="apiKeysTab">
<attribute name="title"> <attribute name="title">
<string>API Keys</string> <string>API Keys</string>
@ -466,6 +554,20 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0">
<widget class="QLabel" name="checkWXAPIKeyLabel">
<property name="text">
<string>CheckWX API key</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="checkWXAPIKey">
<property name="toolTip">
<string>checkwxapi.com API key for accessing airport weather (METARs)</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -489,8 +591,6 @@
</layout> </layout>
</widget> </widget>
<tabstops> <tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>mapItemSettings</tabstop>
<tabstop>map2DEnabled</tabstop> <tabstop>map2DEnabled</tabstop>
<tabstop>mapProvider</tabstop> <tabstop>mapProvider</tabstop>
<tabstop>osmURL</tabstop> <tabstop>osmURL</tabstop>
@ -507,7 +607,10 @@
<tabstop>mapBoxAPIKey</tabstop> <tabstop>mapBoxAPIKey</tabstop>
<tabstop>cesiumIonAPIKey</tabstop> <tabstop>cesiumIonAPIKey</tabstop>
</tabstops> </tabstops>
<resources/> <resources>
<include location="../../../sdrgui/resources/res.qrc"/>
<include location="icons.qrc"/>
</resources>
<connections> <connections>
<connection> <connection>
<sender>buttonBox</sender> <sender>buttonBox</sender>

View File

@ -94,6 +94,25 @@ private slots:
\"DataCopyRight\" : \"<a href='http://maptiler.com'>Maptiler</a>\"\ \"DataCopyRight\" : \"<a href='http://maptiler.com'>Maptiler</a>\"\
}").arg(hiresURL).arg(m_maptilerAPIKey); }").arg(hiresURL).arg(m_maptilerAPIKey);
} }
else if (tokens[1].contains("transit"))
{
QStringList map({"/night-transit", "/night-transit-hires", "/transit", "/transit-hires"});
QStringList mapId({"thf-nighttransit", "thf-nighttransit-hires", "thf-transit", "thf-transit-hires"});
QStringList mapUrl({"dark_nolabels", "dark_nolabels", "light_nolabels", "light_nolabels"});
// Use CartoDB maps without labels for aviation maps
int idx = map.indexOf(tokens[1]);
xml = QString("\
{\
\"UrlTemplate\" : \"http://1.basemaps.cartocdn.com/%2/%z/%x/%y.png%1\",\
\"ImageFormat\" : \"png\",\
\"QImageFormat\" : \"Indexed8\",\
\"ID\" : \"%3\",\
\"MaximumZoomLevel\" : 20,\
\"MapCopyRight\" : \"<a href='https://carto.com'>CartoDB</a>\",\
\"DataCopyRight\" : \"<a href='https://carto.com'>CartoDB</a>\"\
}").arg(hiresURL).arg(mapUrl[idx]).arg(mapId[idx]);
}
else else
{ {
int idx = map.indexOf(tokens[1]); int idx = map.indexOf(tokens[1]);

View File

@ -11,10 +11,13 @@ On top of this, it can plot data from other plugins, such as:
* Satellites from the Satellite Tracker, * Satellites from the Satellite Tracker,
* Weather imagery from APT Demodulator, * Weather imagery from APT Demodulator,
* The Sun, Moon and Stars from the Star Tracker, * The Sun, Moon and Stars from the Star Tracker,
* Weather ballons from the RadioSonde feature. * Weather ballons from the RadioSonde feature,
* Radials and estimated position from the VOR localizer feature.
As well as other other data sources: As well as other data sources:
* AM, FM and DAB transmitters in the UK,
* Airports, NavAids and airspaces,
* Beacons based on the IARU Region 1 beacon database and International Beacon Project, * Beacons based on the IARU Region 1 beacon database and International Beacon Project,
* Radio time transmitters, * Radio time transmitters,
* GRAVES radar, * GRAVES radar,

View File

@ -197,6 +197,7 @@ set(sdrbase_SOURCES
util/morse.cpp util/morse.cpp
util/openaip.cpp util/openaip.cpp
util/osndb.cpp util/osndb.cpp
util/ourairportsdb.cpp
util/peakfinder.cpp util/peakfinder.cpp
util/planespotters.cpp util/planespotters.cpp
util/png.cpp util/png.cpp
@ -424,6 +425,7 @@ set(sdrbase_HEADERS
util/movingaverage.h util/movingaverage.h
util/openaip.h util/openaip.h
util/osndb.h util/osndb.h
util/outairportsdb.h
util/peakfinder.h util/peakfinder.h
util/planespotters.h util/planespotters.h
util/png.h util/png.h
@ -466,6 +468,7 @@ include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${Boost_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}
${OPUS_INCLUDE_DIRS} ${OPUS_INCLUDE_DIRS}
${Qt${QT_DEFAULT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS}
) )
add_library(sdrbase SHARED add_library(sdrbase SHARED

View File

@ -196,6 +196,13 @@ public:
emit preferenceChanged(Preferences::Multisampling); emit preferenceChanged(Preferences::Multisampling);
} }
int getMapMultisampling() const { return m_preferences.getMapMultisampling(); }
void setMapMultisampling(int samples)
{
m_preferences.setMapMultisampling(samples);
emit preferenceChanged(Preferences::MapMultisampling);
}
signals: signals:
void preferenceChanged(int); void preferenceChanged(int);

View File

@ -23,6 +23,7 @@ void Preferences::resetToDefaults()
m_consoleMinLogLevel = QtDebugMsg; m_consoleMinLogLevel = QtDebugMsg;
m_fileMinLogLevel = QtDebugMsg; m_fileMinLogLevel = QtDebugMsg;
m_multisampling = 0; m_multisampling = 0;
m_mapMultisampling = 16;
} }
QByteArray Preferences::serialize() const QByteArray Preferences::serialize() const
@ -43,6 +44,7 @@ QByteArray Preferences::serialize() const
s.writeS32((int) SourceItemIndex, m_sourceItemIndex); s.writeS32((int) SourceItemIndex, m_sourceItemIndex);
s.writeS32((int) Multisampling, m_multisampling); s.writeS32((int) Multisampling, m_multisampling);
s.writeBool((int) AutoUpdatePosition, m_autoUpdatePosition); s.writeBool((int) AutoUpdatePosition, m_autoUpdatePosition);
s.writeS32((int) MapMultisampling, m_mapMultisampling);
return s.final(); return s.final();
} }
@ -98,6 +100,7 @@ bool Preferences::deserialize(const QByteArray& data)
d.readS32((int) Multisampling, &m_multisampling, 0); d.readS32((int) Multisampling, &m_multisampling, 0);
d.readBool((int) AutoUpdatePosition, &m_autoUpdatePosition, true); d.readBool((int) AutoUpdatePosition, &m_autoUpdatePosition, true);
d.readS32((int) MapMultisampling, &m_mapMultisampling, 16);
return true; return true;
} }

View File

@ -23,7 +23,8 @@ public:
Altitude, Altitude,
SourceItemIndex, SourceItemIndex,
Multisampling, Multisampling,
AutoUpdatePosition AutoUpdatePosition,
MapMultisampling
}; };
Preferences(); Preferences();
@ -79,6 +80,9 @@ public:
int getMultisampling() const { return m_multisampling; } int getMultisampling() const { return m_multisampling; }
void setMultisampling(int samples) { m_multisampling = samples; } void setMultisampling(int samples) { m_multisampling = samples; }
int getMapMultisampling() const { return m_mapMultisampling; }
void setMapMultisampling(int samples) { m_mapMultisampling = samples; }
protected: protected:
QString m_sourceDevice; //!< Identification of the source used in R0 tab (GUI flavor) at startup QString m_sourceDevice; //!< Identification of the source used in R0 tab (GUI flavor) at startup
int m_sourceIndex; //!< Index of the source used in R0 tab (GUI flavor) at startup int m_sourceIndex; //!< Index of the source used in R0 tab (GUI flavor) at startup
@ -98,7 +102,8 @@ protected:
bool m_useLogFile; bool m_useLogFile;
QString m_logFileName; QString m_logFileName;
int m_multisampling; //!< Number of samples to use for multisampling anti-aliasing (typically 0 or 4) int m_multisampling; //!< Number of samples to use for multisampling anti-aliasing for spectrums (typically 0 or 4)
int m_mapMultisampling; //!< Number of samples to use for multisampling anti-aliasing for 2D maps (16 gives best text)
}; };
#endif // INCLUDE_PREFERENCES_H #endif // INCLUDE_PREFERENCES_H

View File

@ -17,6 +17,251 @@
#include "openaip.h" #include "openaip.h"
QList<Airspace *> Airspace::readXML(const QString &filename)
{
QList<Airspace *> airspaces;
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QXmlStreamReader xmlReader(&file);
while(!xmlReader.atEnd() && !xmlReader.hasError())
{
if (xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("ASP"))
{
Airspace *airspace = new Airspace();
airspace->m_category = xmlReader.attributes().value("CATEGORY").toString();
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("COUNTRY"))
{
airspace->m_country = xmlReader.readElementText();
}
else if (xmlReader.name() == QLatin1String("NAME"))
{
airspace->m_name = xmlReader.readElementText();
}
else if (xmlReader.name() == QLatin1String("ALTLIMIT_TOP"))
{
while(xmlReader.readNextStartElement())
{
airspace->m_top.m_reference = xmlReader.attributes().value("REFERENCE").toString();
airspace->m_top.m_altUnit = xmlReader.attributes().value("UNIT").toString();
if (xmlReader.name() == QLatin1String("ALT"))
{
airspace->m_top.m_alt = xmlReader.readElementText().toInt();
}
else
{
xmlReader.skipCurrentElement();
}
}
}
else if (xmlReader.name() == QLatin1String("ALTLIMIT_BOTTOM"))
{
while(xmlReader.readNextStartElement())
{
airspace->m_bottom.m_reference = xmlReader.attributes().value("REFERENCE").toString();
airspace->m_bottom.m_altUnit = xmlReader.attributes().value("UNIT").toString();
if (xmlReader.name() == QLatin1String("ALT"))
{
airspace->m_bottom.m_alt = xmlReader.readElementText().toInt();
}
else
{
xmlReader.skipCurrentElement();
}
}
}
else if (xmlReader.name() == QLatin1String("GEOMETRY"))
{
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("POLYGON"))
{
QString pointsString = xmlReader.readElementText();
QStringList points = pointsString.split(",");
for (const auto& ps : points)
{
QStringList split = ps.trimmed().split(" ");
if (split.size() == 2)
{
QPointF pf(split[0].toDouble(), split[1].toDouble());
airspace->m_polygon.append(pf);
}
else
{
qDebug() << "Airspace::readXML - Unexpected polygon point format: " << ps;
}
}
}
else
{
xmlReader.skipCurrentElement();
}
}
}
else
{
xmlReader.skipCurrentElement();
}
}
airspace->calculatePosition();
//qDebug() << "Adding airspace: " << airspace->m_name << " " << airspace->m_category;
airspaces.append(airspace);
}
}
}
file.close();
}
else
{
// Don't warn, as many countries don't have files
//qDebug() << "Airspace::readXML: Could not open " << filename << " for reading.";
}
return airspaces;
}
QList<NavAid *> NavAid::readXML(const QString &filename)
{
int uniqueId = 1;
QList<NavAid *> navAidInfo;
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QXmlStreamReader xmlReader(&file);
while(!xmlReader.atEnd() && !xmlReader.hasError())
{
if (xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("NAVAID"))
{
QStringView typeRef = xmlReader.attributes().value("TYPE");
if ((typeRef == QLatin1String("NDB"))
|| (typeRef == QLatin1String("DME"))
|| (typeRef == QLatin1String("VOR"))
|| (typeRef == QLatin1String("VOR-DME"))
|| (typeRef == QLatin1String("VORTAC"))
|| (typeRef == QLatin1String("DVOR"))
|| (typeRef == QLatin1String("DVOR-DME"))
|| (typeRef == QLatin1String("DVORTAC")))
{
QString type = typeRef.toString();
QString name;
QString id;
float lat = 0.0f;
float lon = 0.0f;
float elevation = 0.0f;
float frequency = 0.0f;
QString channel;
int range = 25;
float declination = 0.0f;
bool alignedTrueNorth = false;
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("NAME"))
{
name = xmlReader.readElementText();
}
else if (xmlReader.name() == QLatin1String("ID"))
{
id = xmlReader.readElementText();
}
else if (xmlReader.name() == QLatin1String("GEOLOCATION"))
{
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("LAT")) {
lat = xmlReader.readElementText().toFloat();
} else if (xmlReader.name() == QLatin1String("LON")) {
lon = xmlReader.readElementText().toFloat();
} else if (xmlReader.name() == QLatin1String("ELEV")) {
elevation = xmlReader.readElementText().toFloat();
} else {
xmlReader.skipCurrentElement();
}
}
}
else if (xmlReader.name() == QLatin1String("RADIO"))
{
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("FREQUENCY"))
{
if (type == "NDB") {
frequency = xmlReader.readElementText().toFloat();
} else {
frequency = xmlReader.readElementText().toFloat() * 1000.0;
}
} else if (xmlReader.name() == QLatin1String("CHANNEL")) {
channel = xmlReader.readElementText();
} else {
xmlReader.skipCurrentElement();
}
}
}
else if (xmlReader.name() == QLatin1String("PARAMS"))
{
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("RANGE")) {
range = xmlReader.readElementText().toInt();
} else if (xmlReader.name() == QLatin1String("DECLINATION")) {
declination = xmlReader.readElementText().toFloat();
} else if (xmlReader.name() == QLatin1String("ALIGNEDTOTRUENORTH")) {
alignedTrueNorth = xmlReader.readElementText() == "TRUE";
} else {
xmlReader.skipCurrentElement();
}
}
}
else
{
xmlReader.skipCurrentElement();
}
}
NavAid *navAid = new NavAid();
navAid->m_id = uniqueId++;
navAid->m_ident = id;
// Check idents conform to our filtering rules
if (navAid->m_ident.size() < 2) {
qDebug() << "NavAid::readXML: Ident less than 2 characters: " << navAid->m_ident;
} else if (navAid->m_ident.size() > 3) {
qDebug() << "NavAid::readXML: Ident greater than 3 characters: " << navAid->m_ident;
}
navAid->m_type = type;
navAid->m_name = name;
navAid->m_frequencykHz = frequency;
navAid->m_channel = channel;
navAid->m_latitude = lat;
navAid->m_longitude = lon;
navAid->m_elevation = elevation;
navAid->m_range = range;
navAid->m_magneticDeclination = declination;
navAid->m_alignedTrueNorth = alignedTrueNorth;
navAidInfo.append(navAid);
}
}
}
}
file.close();
}
else
{
// Don't warn, as many countries don't have files
//qDebug() << "NavAid::readNavAidsXML: Could not open " << filename << " for reading.";
}
return navAidInfo;
}
const QStringList OpenAIP::m_countryCodes = { const QStringList OpenAIP::m_countryCodes = {
"ad", "ad",
"ae", "ae",
@ -268,6 +513,12 @@ const QStringList OpenAIP::m_countryCodes = {
"zw" "zw"
}; };
QSharedPointer<QList<Airspace *>> OpenAIP::m_airspaces;
QSharedPointer<QList<NavAid *>> OpenAIP::m_navAids;
QDateTime OpenAIP::m_airspacesModifiedDateTime;
QDateTime OpenAIP::m_navAidsModifiedDateTime;
OpenAIP::OpenAIP(QObject *parent) : OpenAIP::OpenAIP(QObject *parent) :
QObject(parent) QObject(parent)
{ {
@ -390,11 +641,11 @@ void OpenAIP::downloadFinished(const QString& filename, bool success)
} }
// Read airspaces for all countries // Read airspaces for all countries
QList<Airspace *> OpenAIP::readAirspaces() QList<Airspace *> *OpenAIP::readAirspaces()
{ {
QList<Airspace *> airspaces; QList<Airspace *> *airspaces = new QList<Airspace *>();
for (const auto& countryCode : m_countryCodes) { for (const auto& countryCode : m_countryCodes) {
airspaces.append(readAirspaces(countryCode)); airspaces->append(readAirspaces(countryCode));
} }
return airspaces; return airspaces;
} }
@ -406,11 +657,11 @@ QList<Airspace *> OpenAIP::readAirspaces(const QString& countryCode)
} }
// Read NavAids for all countries // Read NavAids for all countries
QList<NavAid *> OpenAIP::readNavAids() QList<NavAid *> *OpenAIP::readNavAids()
{ {
QList<NavAid *> navAids; QList<NavAid *> *navAids = new QList<NavAid *>();
for (const auto& countryCode : m_countryCodes) { for (const auto& countryCode : m_countryCodes) {
navAids.append(readNavAids(countryCode)); navAids->append(readNavAids(countryCode));
} }
return navAids; return navAids;
} }
@ -420,3 +671,60 @@ QList<NavAid *> OpenAIP::readNavAids(const QString& countryCode)
{ {
return NavAid::readXML(getNavAidsFilename(countryCode)); return NavAid::readXML(getNavAidsFilename(countryCode));
} }
QSharedPointer<const QList<Airspace *>> OpenAIP::getAirspaces()
{
QDateTime filesDateTime = getAirspacesModifiedDateTime();
if (!m_airspaces || (filesDateTime > m_airspacesModifiedDateTime))
{
// Using shared pointer, so old object, if it exists, will be deleted, when no longer user
m_airspaces = QSharedPointer<QList<Airspace *>>(readAirspaces());
m_airspacesModifiedDateTime = filesDateTime;
}
return m_airspaces;
}
QSharedPointer<const QList<NavAid *>> OpenAIP::getNavAids()
{
QDateTime filesDateTime = getNavAidsModifiedDateTime();
if (!m_navAids || (filesDateTime > m_navAidsModifiedDateTime))
{
// Using shared pointer, so old object, if it exists, will be deleted, when no longer user
m_navAids = QSharedPointer<QList<NavAid *>>(readNavAids());
m_navAidsModifiedDateTime = filesDateTime;
}
return m_navAids;
}
// Gets the date and time the airspaces files were most recently modified
QDateTime OpenAIP::getAirspacesModifiedDateTime()
{
QDateTime dateTime;
for (const auto& countryCode : m_countryCodes)
{
QFileInfo fileInfo(getAirspaceFilename(countryCode));
QDateTime fileModifiedDateTime = fileInfo.lastModified();
if (fileModifiedDateTime > dateTime) {
dateTime = fileModifiedDateTime;
}
}
return dateTime;
}
// Gets the date and time the navaid files were most recently modified
QDateTime OpenAIP::getNavAidsModifiedDateTime()
{
QDateTime dateTime;
for (const auto& countryCode : m_countryCodes)
{
QFileInfo fileInfo(getNavAidsFilename(countryCode));
QDateTime fileModifiedDateTime = fileInfo.lastModified();
if (fileModifiedDateTime > dateTime) {
dateTime = fileModifiedDateTime;
}
}
return dateTime;
}

View File

@ -118,116 +118,7 @@ struct SDRBASE_API Airspace {
} }
// Read OpenAIP XML file // Read OpenAIP XML file
static QList<Airspace *> readXML(const QString &filename) static QList<Airspace *> readXML(const QString &filename);
{
QList<Airspace *> airspaces;
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QXmlStreamReader xmlReader(&file);
while(!xmlReader.atEnd() && !xmlReader.hasError())
{
if (xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("ASP"))
{
Airspace *airspace = new Airspace();
airspace->m_category = xmlReader.attributes().value("CATEGORY").toString();
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("COUNTRY"))
{
airspace->m_country = xmlReader.readElementText();
}
else if (xmlReader.name() == QLatin1String("NAME"))
{
airspace->m_name = xmlReader.readElementText();
}
else if (xmlReader.name() == QLatin1String("ALTLIMIT_TOP"))
{
while(xmlReader.readNextStartElement())
{
airspace->m_top.m_reference = xmlReader.attributes().value("REFERENCE").toString();
airspace->m_top.m_altUnit = xmlReader.attributes().value("UNIT").toString();
if (xmlReader.name() == QLatin1String("ALT"))
{
airspace->m_top.m_alt = xmlReader.readElementText().toInt();
}
else
{
xmlReader.skipCurrentElement();
}
}
}
else if (xmlReader.name() == QLatin1String("ALTLIMIT_BOTTOM"))
{
while(xmlReader.readNextStartElement())
{
airspace->m_bottom.m_reference = xmlReader.attributes().value("REFERENCE").toString();
airspace->m_bottom.m_altUnit = xmlReader.attributes().value("UNIT").toString();
if (xmlReader.name() == QLatin1String("ALT"))
{
airspace->m_bottom.m_alt = xmlReader.readElementText().toInt();
}
else
{
xmlReader.skipCurrentElement();
}
}
}
else if (xmlReader.name() == QLatin1String("GEOMETRY"))
{
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("POLYGON"))
{
QString pointsString = xmlReader.readElementText();
QStringList points = pointsString.split(",");
for (const auto& ps : points)
{
QStringList split = ps.trimmed().split(" ");
if (split.size() == 2)
{
QPointF pf(split[0].toDouble(), split[1].toDouble());
airspace->m_polygon.append(pf);
}
else
{
qDebug() << "Airspace::readXML - Unexpected polygon point format: " << ps;
}
}
}
else
{
xmlReader.skipCurrentElement();
}
}
}
else
{
xmlReader.skipCurrentElement();
}
}
airspace->calculatePosition();
//qDebug() << "Adding airspace: " << airspace->m_name << " " << airspace->m_category;
airspaces.append(airspace);
}
}
}
file.close();
}
else
{
// Don't warn, as many countries don't have files
//qDebug() << "Airspace::readXML: Could not open " << filename << " for reading.";
}
return airspaces;
}
}; };
@ -251,140 +142,7 @@ struct SDRBASE_API NavAid {
} }
// OpenAIP XML file // OpenAIP XML file
static QList<NavAid *> readXML(const QString &filename) static QList<NavAid *> readXML(const QString &filename);
{
int uniqueId = 1;
QList<NavAid *> navAidInfo;
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QXmlStreamReader xmlReader(&file);
while(!xmlReader.atEnd() && !xmlReader.hasError())
{
if (xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("NAVAID"))
{
QStringView typeRef = xmlReader.attributes().value("TYPE");
if ((typeRef == QLatin1String("NDB"))
|| (typeRef == QLatin1String("DME"))
|| (typeRef == QLatin1String("VOR"))
|| (typeRef == QLatin1String("VOR-DME"))
|| (typeRef == QLatin1String("VORTAC"))
|| (typeRef == QLatin1String("DVOR"))
|| (typeRef == QLatin1String("DVOR-DME"))
|| (typeRef == QLatin1String("DVORTAC")))
{
QString type = typeRef.toString();
QString name;
QString id;
float lat = 0.0f;
float lon = 0.0f;
float elevation = 0.0f;
float frequency = 0.0f;
QString channel;
int range = 25;
float declination = 0.0f;
bool alignedTrueNorth = false;
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("NAME"))
{
name = xmlReader.readElementText();
}
else if (xmlReader.name() == QLatin1String("ID"))
{
id = xmlReader.readElementText();
}
else if (xmlReader.name() == QLatin1String("GEOLOCATION"))
{
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("LAT")) {
lat = xmlReader.readElementText().toFloat();
} else if (xmlReader.name() == QLatin1String("LON")) {
lon = xmlReader.readElementText().toFloat();
} else if (xmlReader.name() == QLatin1String("ELEV")) {
elevation = xmlReader.readElementText().toFloat();
} else {
xmlReader.skipCurrentElement();
}
}
}
else if (xmlReader.name() == QLatin1String("RADIO"))
{
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("FREQUENCY"))
{
if (type == "NDB") {
frequency = xmlReader.readElementText().toFloat();
} else {
frequency = xmlReader.readElementText().toFloat() * 1000.0;
}
} else if (xmlReader.name() == QLatin1String("CHANNEL")) {
channel = xmlReader.readElementText();
} else {
xmlReader.skipCurrentElement();
}
}
}
else if (xmlReader.name() == QLatin1String("PARAMS"))
{
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("RANGE")) {
range = xmlReader.readElementText().toInt();
} else if (xmlReader.name() == QLatin1String("DECLINATION")) {
declination = xmlReader.readElementText().toFloat();
} else if (xmlReader.name() == QLatin1String("ALIGNEDTOTRUENORTH")) {
alignedTrueNorth = xmlReader.readElementText() == "TRUE";
} else {
xmlReader.skipCurrentElement();
}
}
}
else
{
xmlReader.skipCurrentElement();
}
}
NavAid *navAid = new NavAid();
navAid->m_id = uniqueId++;
navAid->m_ident = id;
// Check idents conform to our filtering rules
if (navAid->m_ident.size() < 2) {
qDebug() << "NavAid::readXML: Ident less than 2 characters: " << navAid->m_ident;
} else if (navAid->m_ident.size() > 3) {
qDebug() << "NavAid::readXML: Ident greater than 3 characters: " << navAid->m_ident;
}
navAid->m_type = type;
navAid->m_name = name;
navAid->m_frequencykHz = frequency;
navAid->m_channel = channel;
navAid->m_latitude = lat;
navAid->m_longitude = lon;
navAid->m_elevation = elevation;
navAid->m_range = range;
navAid->m_magneticDeclination = declination;
navAid->m_alignedTrueNorth = alignedTrueNorth;
navAidInfo.append(navAid);
}
}
}
}
file.close();
}
else
{
// Don't warn, as many countries don't have files
//qDebug() << "NavAid::readNavAidsXML: Could not open " << filename << " for reading.";
}
return navAidInfo;
}
}; };
class SDRBASE_API OpenAIP : public QObject { class SDRBASE_API OpenAIP : public QObject {
@ -396,18 +154,27 @@ public:
static const QStringList m_countryCodes; static const QStringList m_countryCodes;
static QList<Airspace *> readAirspaces();
static QList<Airspace *> readAirspaces(const QString& countryCode);
static QList<NavAid *> readNavAids();
static QList<NavAid *> readNavAids(const QString& countryCode);
void downloadAirspaces(); void downloadAirspaces();
void downloadNavAids(); void downloadNavAids();
static QSharedPointer<const QList<Airspace *>> getAirspaces();
static QSharedPointer<const QList<NavAid *>> getNavAids();
private: private:
HttpDownloadManager m_dlm; HttpDownloadManager m_dlm;
int m_countryIndex; int m_countryIndex;
static QSharedPointer<QList<Airspace *>> m_airspaces;
static QSharedPointer<QList<NavAid *>> m_navAids;
static QDateTime m_airspacesModifiedDateTime;
static QDateTime m_navAidsModifiedDateTime;
static QList<Airspace *> *readAirspaces();
static QList<Airspace *> readAirspaces(const QString& countryCode);
static QList<NavAid *> *readNavAids();
static QList<NavAid *> readNavAids(const QString& countryCode);
static QString getDataDir(); static QString getDataDir();
static QString getAirspaceFilename(int i); static QString getAirspaceFilename(int i);
static QString getAirspaceFilename(const QString& countryCode); static QString getAirspaceFilename(const QString& countryCode);
@ -415,6 +182,8 @@ private:
static QString getNavAidsFilename(int i); static QString getNavAidsFilename(int i);
static QString getNavAidsFilename(const QString& countryCode); static QString getNavAidsFilename(const QString& countryCode);
static QString getNavAidsURL(int i); static QString getNavAidsURL(int i);
static QDateTime getAirspacesModifiedDateTime();
static QDateTime getNavAidsModifiedDateTime();
void downloadAirspace(); void downloadAirspace();
void downloadNavAid(); void downloadNavAid();
@ -431,3 +200,4 @@ signals:
}; };
#endif // INCLUDE_OPENAIP_H #endif // INCLUDE_OPENAIP_H

View File

@ -15,6 +15,11 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // // along with this program. If not, see <http://www.gnu.org/licenses/>. //
/////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////
#include <QFileInfo>
#include <QResource>
#include <QtGui/private/qzipreader_p.h>
#include "util/osndb.h" #include "util/osndb.h"
QHash<QString, QIcon *> AircraftInformation::m_airlineIcons; QHash<QString, QIcon *> AircraftInformation::m_airlineIcons;
@ -23,3 +28,544 @@ QHash<QString, QIcon *> AircraftInformation::m_flagIcons;
QHash<QString, QString> *AircraftInformation::m_prefixMap; QHash<QString, QString> *AircraftInformation::m_prefixMap;
QHash<QString, QString> *AircraftInformation::m_militaryMap; QHash<QString, QString> *AircraftInformation::m_militaryMap;
QMutex AircraftInformation::m_mutex; QMutex AircraftInformation::m_mutex;
QSharedPointer<const QHash<int, AircraftInformation *>> OsnDB::m_aircraftInformation;
QSharedPointer<const QHash<QString, AircraftInformation *>> OsnDB::m_aircraftInformationByReg;
QDateTime OsnDB::m_modifiedDateTime;
OsnDB::OsnDB(QObject *parent) :
QObject(parent)
{
connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OsnDB::downloadFinished);
}
OsnDB::~OsnDB()
{
disconnect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &OsnDB::downloadFinished);
}
void OsnDB::downloadAircraftInformation()
{
QString filename = OsnDB::getOSNDBZipFilename();
QString urlString = OSNDB_URL;
QUrl dbURL(urlString);
qDebug() << "OsnDB::downloadAircraftInformation: Downloading " << urlString;
emit downloadingURL(urlString);
QNetworkReply *reply = m_dlm.download(dbURL, filename);
connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 bytesRead, qint64 totalBytes) {
emit downloadProgress(bytesRead, totalBytes);
});
}
void OsnDB::downloadFinished(const QString& filename, bool success)
{
if (!success)
{
qWarning() << "OsnDB::downloadFinished: Failed to download: " << filename;
emit downloadError(QString("Failed to download: %1").arg(filename));
}
else if (filename == OsnDB::getOSNDBZipFilename())
{
// Extract .csv file from .zip file
QZipReader reader(filename);
QByteArray database = reader.fileData("media/data/samples/metadata/aircraftDatabase.csv");
if (database.size() > 0)
{
QFile file(OsnDB::getOSNDBFilename());
if (file.open(QIODevice::WriteOnly))
{
file.write(database);
file.close();
emit downloadAircraftInformationFinished();
}
else
{
qWarning() << "OsnDB::downloadFinished - Failed to open " << file.fileName() << " for writing";
emit downloadError(QString("Failed to open %1 for writing").arg(file.fileName()));
}
}
else
{
qWarning() << "OsnDB::downloadFinished - aircraftDatabase.csv not in expected dir. Extracting all.";
if (reader.extractAll(getDataDir()))
{
emit downloadAircraftInformationFinished();
}
else
{
qWarning() << "OsnDB::downloadFinished - Failed to extract files from " << filename;
emit downloadError(QString("Failed to extract files from ").arg(filename));
}
}
}
else
{
qDebug() << "OsnDB::downloadFinished: Unexpected filename: " << filename;
emit downloadError(QString("Unexpected filename: %1").arg(filename));
}
}
QSharedPointer<const QHash<int, AircraftInformation *>> OsnDB::getAircraftInformation()
{
QFileInfo fastFileInfo(getFastDBFilename());
QFileInfo fullFileInfo(getOSNDBFilename());
QDateTime fastModifiedDateTime = fastFileInfo.lastModified();
QDateTime fullModifiedDateTime = fullFileInfo.lastModified();
// Update fast database, if full database is newer
if (fullModifiedDateTime > fastModifiedDateTime)
{
qDebug() << "AircraftInformation::getAircraftInformation: Creating fast database";
m_aircraftInformation = QSharedPointer<const QHash<int, AircraftInformation *>>(OsnDB::readOSNDB(getOSNDBFilename()));
if (m_aircraftInformation)
{
OsnDB::writeFastDB(OsnDB::getFastDBFilename(), m_aircraftInformation.get());
fastModifiedDateTime = fastFileInfo.lastModified();
m_modifiedDateTime = fastModifiedDateTime;
m_aircraftInformationByReg = QSharedPointer<const QHash<QString, AircraftInformation *>> (OsnDB::registrationHash(m_aircraftInformation.get()));
}
}
if (!m_aircraftInformation || (fastModifiedDateTime > m_modifiedDateTime))
{
m_aircraftInformation = QSharedPointer<const QHash<int, AircraftInformation *>>(OsnDB::readFastDB(getFastDBFilename()));
if (m_aircraftInformation)
{
m_modifiedDateTime = fastModifiedDateTime;
m_aircraftInformationByReg = QSharedPointer<const QHash<QString, AircraftInformation *>> (OsnDB::registrationHash(m_aircraftInformation.get()));
}
}
return m_aircraftInformation;
}
QSharedPointer<const QHash<QString, AircraftInformation *>> OsnDB::getAircraftInformationByReg()
{
getAircraftInformation();
return m_aircraftInformationByReg;
}
QHash<int, AircraftInformation *> *OsnDB::readOSNDB(const QString &filename)
{
int cnt = 0;
QHash<int, AircraftInformation *> *aircraftInfo = nullptr;
// Column numbers used for the data as of 2020/10/28
int icaoCol = 0;
int registrationCol = 1;
int manufacturerNameCol = 3;
int modelCol = 4;
int ownerCol = 13;
int operatorCol = 9;
int operatorICAOCol = 11;
int registeredCol = 15;
qDebug() << "AircraftInformation::readOSNDB: " << filename;
FILE *file;
QByteArray utfFilename = filename.toUtf8();
if ((file = fopen(utfFilename.constData(), "r")) != NULL)
{
char row[2048];
if (fgets(row, sizeof(row), file))
{
aircraftInfo = new QHash<int, AircraftInformation *>();
aircraftInfo->reserve(500000);
// Read header
int idx = 0;
char *p = strtok(row, ",");
while (p != NULL)
{
if (!strcmp(p, "icao24"))
icaoCol = idx;
else if (!strcmp(p, "registration"))
registrationCol = idx;
else if (!strcmp(p, "manufacturername"))
manufacturerNameCol = idx;
else if (!strcmp(p, "model"))
modelCol = idx;
else if (!strcmp(p, "owner"))
ownerCol = idx;
else if (!strcmp(p, "operator"))
operatorCol = idx;
else if (!strcmp(p, "operatoricao"))
operatorICAOCol = idx;
else if (!strcmp(p, "registered"))
registeredCol = idx;
p = strtok(NULL, ",");
idx++;
}
// Read data
while (fgets(row, sizeof(row), file))
{
int icao = 0;
char *icaoString = NULL;
char *registration = NULL;
size_t registrationLen = 0;
char *manufacturerName = NULL;
size_t manufacturerNameLen = 0;
char *model = NULL;
size_t modelLen = 0;
char *owner = NULL;
size_t ownerLen = 0;
char *operatorName = NULL;
size_t operatorNameLen = 0;
char *operatorICAO = NULL;
size_t operatorICAOLen = 0;
char *registered = NULL;
size_t registeredLen = 0;
p = strtok(row, ",");
idx = 0;
while (p != NULL)
{
// Read strings, stripping quotes
if (idx == icaoCol)
{
icaoString = p+1;
icaoString[strlen(icaoString)-1] = '\0';
icao = strtol(icaoString, NULL, 16);
// Ignore entries with uppercase - might be better to try and merge?
// See: https://opensky-network.org/forum/bug-reports/652-are-the-aircraft-database-dumps-working
for (int i = 0; icaoString[i] != '\0'; i++)
{
char c = icaoString[i];
if ((c >= 'A') && (c <= 'F'))
{
icao = 0;
break;
}
}
}
else if (idx == registrationCol)
{
registration = p+1;
registrationLen = strlen(registration)-1;
registration[registrationLen] = '\0';
}
else if (idx == manufacturerNameCol)
{
manufacturerName = p+1;
manufacturerNameLen = strlen(manufacturerName)-1;
manufacturerName[manufacturerNameLen] = '\0';
}
else if (idx == modelCol)
{
model = p+1;
modelLen = strlen(model)-1;
model[modelLen] = '\0';
}
else if (idx == ownerCol)
{
owner = p+1;
ownerLen = strlen(owner)-1;
owner[ownerLen] = '\0';
}
else if (idx == operatorCol)
{
operatorName = p+1;
operatorNameLen = strlen(operatorName)-1;
operatorName[operatorNameLen] = '\0';
}
else if (idx == operatorICAOCol)
{
operatorICAO = p+1;
operatorICAOLen = strlen(operatorICAO)-1;
operatorICAO[operatorICAOLen] = '\0';
}
else if (idx == registeredCol)
{
registered = p+1;
registeredLen = strlen(registered)-1;
registered[registeredLen] = '\0';
}
p = strtok(NULL, ",");
idx++;
}
// Only create the entry if we have some interesting data
if ((icao != 0) && (registrationLen > 0 || modelLen > 0 || ownerLen > 0 || operatorNameLen > 0 || operatorICAOLen > 0))
{
QString modelQ = QString(model);
// Tidy up the model names
if (modelQ.endsWith(" (Boeing)"))
modelQ = modelQ.left(modelQ.size() - 9);
else if (modelQ.startsWith("BOEING "))
modelQ = modelQ.right(modelQ.size() - 7);
else if (modelQ.startsWith("Boeing "))
modelQ = modelQ.right(modelQ.size() - 7);
else if (modelQ.startsWith("AIRBUS "))
modelQ = modelQ.right(modelQ.size() - 7);
else if (modelQ.startsWith("Airbus "))
modelQ = modelQ.right(modelQ.size() - 7);
else if (modelQ.endsWith(" (Cessna)"))
modelQ = modelQ.left(modelQ.size() - 9);
AircraftInformation *aircraft = new AircraftInformation();
aircraft->m_icao = icao;
aircraft->m_registration = QString(registration);
aircraft->m_manufacturerName = QString(manufacturerName);
aircraft->m_model = modelQ;
aircraft->m_owner = QString(owner);
aircraft->m_operator = QString(operatorName);
aircraft->m_operatorICAO = QString(operatorICAO);
aircraft->m_registered = QString(registered);
aircraftInfo->insert(icao, aircraft);
cnt++;
}
}
}
fclose(file);
}
else
qDebug() << "AircraftInformation::readOSNDB: Failed to open " << filename;
qDebug() << "AircraftInformation::readOSNDB: Read " << cnt << " aircraft";
return aircraftInfo;
}
QHash<QString, AircraftInformation *> *OsnDB::registrationHash(const QHash<int, AircraftInformation *> *in)
{
QHash<QString, AircraftInformation *> *out = new QHash<QString, AircraftInformation *>();
QHashIterator<int, AircraftInformation *> i(*in);
while (i.hasNext())
{
i.next();
AircraftInformation *info = i.value();
out->insert(info->m_registration, info);
}
return out;
}
bool OsnDB::writeFastDB(const QString &filename, const QHash<int, AircraftInformation *> *aircraftInfo)
{
QFile file(filename);
if (file.open(QIODevice::WriteOnly))
{
file.write("icao24,registration,manufacturername,model,owner,operator,operatoricao,registered\n");
QHash<int, AircraftInformation *>::const_iterator i = aircraftInfo->begin();
while (i != aircraftInfo->end())
{
AircraftInformation *info = i.value();
file.write(QString("%1").arg(info->m_icao, 1, 16).toUtf8());
file.write(",");
file.write(info->m_registration.toUtf8());
file.write(",");
file.write(info->m_manufacturerName.toUtf8());
file.write(",");
file.write(info->m_model.toUtf8());
file.write(",");
file.write(info->m_owner.toUtf8());
file.write(",");
file.write(info->m_operator.toUtf8());
file.write(",");
file.write(info->m_operatorICAO.toUtf8());
file.write(",");
file.write(info->m_registered.toUtf8());
file.write("\n");
++i;
}
file.close();
return true;
}
else
{
qCritical() << "AircraftInformation::writeFastDB failed to open " << filename << " for writing: " << file.errorString();
return false;
}
}
QHash<int, AircraftInformation *> *OsnDB::readFastDB(const QString &filename)
{
int cnt = 0;
QHash<int, AircraftInformation *> *aircraftInfo = nullptr;
qDebug() << "AircraftInformation::readFastDB: " << filename;
FILE *file;
QByteArray utfFilename = filename.toUtf8();
if ((file = fopen(utfFilename.constData(), "r")) != NULL)
{
char row[2048];
if (fgets(row, sizeof(row), file))
{
// Check header
if (!strcmp(row, "icao24,registration,manufacturername,model,owner,operator,operatoricao,registered\n"))
{
aircraftInfo = new QHash<int, AircraftInformation *>();
aircraftInfo->reserve(500000);
// Read data
while (fgets(row, sizeof(row), file))
{
char *p = row;
AircraftInformation *aircraft = new AircraftInformation();
char *icaoString = csvNext(&p);
int icao = strtol(icaoString, NULL, 16);
aircraft->m_icao = icao;
aircraft->m_registration = QString(csvNext(&p));
aircraft->m_manufacturerName = QString(csvNext(&p));
aircraft->m_model = QString(csvNext(&p));
aircraft->m_owner = QString(csvNext(&p));
aircraft->m_operator = QString(csvNext(&p));
aircraft->m_operatorICAO = QString(csvNext(&p));
aircraft->m_registered = QString(csvNext(&p));
aircraftInfo->insert(icao, aircraft);
cnt++;
}
}
else
qDebug() << "AircraftInformation::readFastDB: Unexpected header";
}
else
qDebug() << "AircraftInformation::readFastDB: Empty file";
fclose(file);
}
else
qDebug() << "AircraftInformation::readFastDB: Failed to open " << filename;
qDebug() << "AircraftInformation::readFastDB - read " << cnt << " aircraft";
return aircraftInfo;
}
QString AircraftInformation::getFlag() const
{
QString flag;
if (m_prefixMap)
{
int idx = m_registration.indexOf('-');
if (idx >= 0)
{
QString prefix;
// Some countries use AA-A - try these first as first letters are common
prefix = m_registration.left(idx + 2);
if (m_prefixMap->contains(prefix))
{
flag = m_prefixMap->value(prefix);
}
else
{
// Try letters before '-'
prefix = m_registration.left(idx);
if (m_prefixMap->contains(prefix)) {
flag = m_prefixMap->value(prefix);
}
}
}
else
{
// No '-' Could be one of a few countries or military.
// See: https://en.wikipedia.org/wiki/List_of_aircraft_registration_prefixes
if (m_registration.startsWith("N")) {
flag = m_prefixMap->value("N"); // US
} else if (m_registration.startsWith("JA")) {
flag = m_prefixMap->value("JA"); // Japan
} else if (m_registration.startsWith("HL")) {
flag = m_prefixMap->value("HL"); // Korea
} else if (m_registration.startsWith("YV")) {
flag = m_prefixMap->value("YV"); // Venezuela
} else if ((m_militaryMap != nullptr) && (m_militaryMap->contains(m_operator))) {
flag = m_militaryMap->value(m_operator);
}
}
}
return flag;
}
QString AircraftInformation::getAirlineIconPath(const QString &operatorICAO)
{
QString endPath = QString("/airlinelogos/%1.bmp").arg(operatorICAO);
// Try in user directory first, so they can customise
QString userIconPath = OsnDB::getDataDir() + endPath;
QFile file(userIconPath);
if (file.exists())
{
return userIconPath;
}
else
{
// Try in resources
QString resourceIconPath = ":" + endPath;
QResource resource(resourceIconPath);
if (resource.isValid())
{
return resourceIconPath;
}
}
return QString();
}
QIcon *AircraftInformation::getAirlineIcon(const QString &operatorICAO)
{
if (m_airlineIcons.contains(operatorICAO))
{
return m_airlineIcons.value(operatorICAO);
}
else
{
QIcon *icon = nullptr;
QString path = getAirlineIconPath(operatorICAO);
if (!path.isEmpty())
{
icon = new QIcon(path);
m_airlineIcons.insert(operatorICAO, icon);
}
else
{
if (!m_airlineMissingIcons.contains(operatorICAO))
{
qDebug() << "ADSBDemodGUI: No airline logo for " << operatorICAO;
m_airlineMissingIcons.insert(operatorICAO, true);
}
}
return icon;
}
}
QString AircraftInformation::getFlagIconPath(const QString &country)
{
QString endPath = QString("/flags/%1.bmp").arg(country);
// Try in user directory first, so they can customise
QString userIconPath = OsnDB::getDataDir() + endPath;
QFile file(userIconPath);
if (file.exists())
{
return userIconPath;
}
else
{
// Try in resources
QString resourceIconPath = ":" + endPath;
QResource resource(resourceIconPath);
if (resource.isValid())
{
return resourceIconPath;
}
}
return QString();
}
QIcon *AircraftInformation::getFlagIcon(const QString &country)
{
if (m_flagIcons.contains(country))
{
return m_flagIcons.value(country);
}
else
{
QIcon *icon = nullptr;
QString path = getFlagIconPath(country);
if (!path.isEmpty())
{
icon = new QIcon(path);
m_flagIcons.insert(country, icon);
}
return icon;
}
}

View File

@ -19,20 +19,19 @@
#define INCLUDE_OSNDB_H #define INCLUDE_OSNDB_H
#include <QString> #include <QString>
#include <QFile>
#include <QByteArray>
#include <QHash> #include <QHash>
#include <QList> #include <QList>
#include <QDebug> #include <QDebug>
#include <QIcon> #include <QIcon>
#include <QMutex> #include <QMutex>
#include <QStandardPaths> #include <QStandardPaths>
#include <QResource> #include <QDateTime>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "util/csv.h" #include "util/csv.h"
#include "util/httpdownloadmanager.h"
#include "export.h" #include "export.h"
#define OSNDB_URL "https://opensky-network.org/datasets/metadata/aircraftDatabase.zip" #define OSNDB_URL "https://opensky-network.org/datasets/metadata/aircraftDatabase.zip"
@ -48,6 +47,34 @@ struct SDRBASE_API AircraftInformation {
QString m_operatorICAO; QString m_operatorICAO;
QString m_registered; QString m_registered;
static void init()
{
QMutexLocker locker(&m_mutex);
if (!m_prefixMap)
{
// Read registration prefix to country map
m_prefixMap = CSV::hash(":/flags/regprefixmap.csv");
// Read operator air force to military map
m_militaryMap = CSV::hash(":/flags/militarymap.csv");
}
}
// Get flag based on registration
QString getFlag() const;
static QString getAirlineIconPath(const QString &operatorICAO);
// Try to find an airline logo based on ICAO
static QIcon *getAirlineIcon(const QString &operatorICAO);
static QString getFlagIconPath(const QString &country);
// Try to find an flag logo based on a country
static QIcon *getFlagIcon(const QString &country);
private:
static QHash<QString, QIcon *> m_airlineIcons; // Hashed on airline ICAO static QHash<QString, QIcon *> m_airlineIcons; // Hashed on airline ICAO
static QHash<QString, bool> m_airlineMissingIcons; // Hash containing which ICAOs we don't have icons for static QHash<QString, bool> m_airlineMissingIcons; // Hash containing which ICAOs we don't have icons for
static QHash<QString, QIcon *> m_flagIcons; // Hashed on country static QHash<QString, QIcon *> m_flagIcons; // Hashed on country
@ -55,345 +82,20 @@ struct SDRBASE_API AircraftInformation {
static QHash<QString, QString> *m_militaryMap; // Operator airforce to military (flag name) static QHash<QString, QString> *m_militaryMap; // Operator airforce to military (flag name)
static QMutex m_mutex; static QMutex m_mutex;
static void init() };
{
QMutexLocker locker(&m_mutex);
// Read registration prefix to country map class SDRBASE_API OsnDB : public QObject {
m_prefixMap = CSV::hash(":/flags/regprefixmap.csv"); Q_OBJECT
// Read operator air force to military map
m_militaryMap = CSV::hash(":/flags/militarymap.csv");
}
// Read OpenSky Network CSV file public:
// This is large and contains lots of data we don't want, so we convert to
// a smaller version to speed up loading time
// Note that we use C file functions rather than QT, as these are ~30% faster
// and the QT version seemed to occasionally crash
static QHash<int, AircraftInformation *> *readOSNDB(const QString &filename)
{
int cnt = 0;
QHash<int, AircraftInformation *> *aircraftInfo = nullptr;
// Column numbers used for the data as of 2020/10/28 OsnDB(QObject* parent=nullptr);
int icaoCol = 0; ~OsnDB();
int registrationCol = 1;
int manufacturerNameCol = 3;
int modelCol = 4;
int ownerCol = 13;
int operatorCol = 9;
int operatorICAOCol = 11;
int registeredCol = 15;
qDebug() << "AircraftInformation::readOSNDB: " << filename; void downloadAircraftInformation();
FILE *file; static QSharedPointer<const QHash<int, AircraftInformation *>> getAircraftInformation();
QByteArray utfFilename = filename.toUtf8(); static QSharedPointer<const QHash<QString, AircraftInformation *>> getAircraftInformationByReg();
if ((file = fopen(utfFilename.constData(), "r")) != NULL)
{
char row[2048];
if (fgets(row, sizeof(row), file))
{
aircraftInfo = new QHash<int, AircraftInformation *>();
aircraftInfo->reserve(500000);
// Read header
int idx = 0;
char *p = strtok(row, ",");
while (p != NULL)
{
if (!strcmp(p, "icao24"))
icaoCol = idx;
else if (!strcmp(p, "registration"))
registrationCol = idx;
else if (!strcmp(p, "manufacturername"))
manufacturerNameCol = idx;
else if (!strcmp(p, "model"))
modelCol = idx;
else if (!strcmp(p, "owner"))
ownerCol = idx;
else if (!strcmp(p, "operator"))
operatorCol = idx;
else if (!strcmp(p, "operatoricao"))
operatorICAOCol = idx;
else if (!strcmp(p, "registered"))
registeredCol = idx;
p = strtok(NULL, ",");
idx++;
}
// Read data
while (fgets(row, sizeof(row), file))
{
int icao = 0;
char *icaoString = NULL;
char *registration = NULL;
size_t registrationLen = 0;
char *manufacturerName = NULL;
size_t manufacturerNameLen = 0;
char *model = NULL;
size_t modelLen = 0;
char *owner = NULL;
size_t ownerLen = 0;
char *operatorName = NULL;
size_t operatorNameLen = 0;
char *operatorICAO = NULL;
size_t operatorICAOLen = 0;
char *registered = NULL;
size_t registeredLen = 0;
p = strtok(row, ",");
idx = 0;
while (p != NULL)
{
// Read strings, stripping quotes
if (idx == icaoCol)
{
icaoString = p+1;
icaoString[strlen(icaoString)-1] = '\0';
icao = strtol(icaoString, NULL, 16);
}
else if (idx == registrationCol)
{
registration = p+1;
registrationLen = strlen(registration)-1;
registration[registrationLen] = '\0';
}
else if (idx == manufacturerNameCol)
{
manufacturerName = p+1;
manufacturerNameLen = strlen(manufacturerName)-1;
manufacturerName[manufacturerNameLen] = '\0';
}
else if (idx == modelCol)
{
model = p+1;
modelLen = strlen(model)-1;
model[modelLen] = '\0';
}
else if (idx == ownerCol)
{
owner = p+1;
ownerLen = strlen(owner)-1;
owner[ownerLen] = '\0';
}
else if (idx == operatorCol)
{
operatorName = p+1;
operatorNameLen = strlen(operatorName)-1;
operatorName[operatorNameLen] = '\0';
}
else if (idx == operatorICAOCol)
{
operatorICAO = p+1;
operatorICAOLen = strlen(operatorICAO)-1;
operatorICAO[operatorICAOLen] = '\0';
}
else if (idx == registeredCol)
{
registered = p+1;
registeredLen = strlen(registered)-1;
registered[registeredLen] = '\0';
}
p = strtok(NULL, ",");
idx++;
}
// Only create the entry if we have some interesting data
if ((icao != 0) && (registrationLen > 0 || modelLen > 0 || ownerLen > 0 || operatorNameLen > 0 || operatorICAOLen > 0))
{
QString modelQ = QString(model);
// Tidy up the model names
if (modelQ.endsWith(" (Boeing)"))
modelQ = modelQ.left(modelQ.size() - 9);
else if (modelQ.startsWith("BOEING "))
modelQ = modelQ.right(modelQ.size() - 7);
else if (modelQ.startsWith("Boeing "))
modelQ = modelQ.right(modelQ.size() - 7);
else if (modelQ.startsWith("AIRBUS "))
modelQ = modelQ.right(modelQ.size() - 7);
else if (modelQ.startsWith("Airbus "))
modelQ = modelQ.right(modelQ.size() - 7);
else if (modelQ.endsWith(" (Cessna)"))
modelQ = modelQ.left(modelQ.size() - 9);
AircraftInformation *aircraft = new AircraftInformation();
aircraft->m_icao = icao;
aircraft->m_registration = QString(registration);
aircraft->m_manufacturerName = QString(manufacturerName);
aircraft->m_model = modelQ;
aircraft->m_owner = QString(owner);
aircraft->m_operator = QString(operatorName);
aircraft->m_operatorICAO = QString(operatorICAO);
aircraft->m_registered = QString(registered);
aircraftInfo->insert(icao, aircraft);
cnt++;
}
}
}
fclose(file);
}
else
qDebug() << "AircraftInformation::readOSNDB: Failed to open " << filename;
qDebug() << "AircraftInformation::readOSNDB: Read " << cnt << " aircraft";
return aircraftInfo;
}
// Create hash table using registration as key
static QHash<QString, AircraftInformation *> *registrationHash(const QHash<int, AircraftInformation *> *in)
{
QHash<QString, AircraftInformation *> *out = new QHash<QString, AircraftInformation *>();
QHashIterator<int, AircraftInformation *> i(*in);
while (i.hasNext())
{
i.next();
AircraftInformation *info = i.value();
out->insert(info->m_registration, info);
}
return out;
}
// Write a reduced size and validated version of the DB, so it loads quicker
static bool writeFastDB(const QString &filename, QHash<int, AircraftInformation *> *aircraftInfo)
{
QFile file(filename);
if (file.open(QIODevice::WriteOnly))
{
file.write("icao24,registration,manufacturername,model,owner,operator,operatoricao,registered\n");
QHash<int, AircraftInformation *>::iterator i = aircraftInfo->begin();
while (i != aircraftInfo->end())
{
AircraftInformation *info = i.value();
file.write(QString("%1").arg(info->m_icao, 1, 16).toUtf8());
file.write(",");
file.write(info->m_registration.toUtf8());
file.write(",");
file.write(info->m_manufacturerName.toUtf8());
file.write(",");
file.write(info->m_model.toUtf8());
file.write(",");
file.write(info->m_owner.toUtf8());
file.write(",");
file.write(info->m_operator.toUtf8());
file.write(",");
file.write(info->m_operatorICAO.toUtf8());
file.write(",");
file.write(info->m_registered.toUtf8());
file.write("\n");
++i;
}
file.close();
return true;
}
else
{
qCritical() << "AircraftInformation::writeFastDB failed to open " << filename << " for writing: " << file.errorString();
return false;
}
}
// Read smaller CSV file with no validation. Takes about 0.5s instead of 2s.
static QHash<int, AircraftInformation *> *readFastDB(const QString &filename)
{
int cnt = 0;
QHash<int, AircraftInformation *> *aircraftInfo = nullptr;
qDebug() << "AircraftInformation::readFastDB: " << filename;
FILE *file;
QByteArray utfFilename = filename.toUtf8();
if ((file = fopen(utfFilename.constData(), "r")) != NULL)
{
char row[2048];
if (fgets(row, sizeof(row), file))
{
// Check header
if (!strcmp(row, "icao24,registration,manufacturername,model,owner,operator,operatoricao,registered\n"))
{
aircraftInfo = new QHash<int, AircraftInformation *>();
aircraftInfo->reserve(500000);
// Read data
while (fgets(row, sizeof(row), file))
{
char *p = row;
AircraftInformation *aircraft = new AircraftInformation();
char *icaoString = csvNext(&p);
int icao = strtol(icaoString, NULL, 16);
aircraft->m_icao = icao;
aircraft->m_registration = QString(csvNext(&p));
aircraft->m_manufacturerName = QString(csvNext(&p));
aircraft->m_model = QString(csvNext(&p));
aircraft->m_owner = QString(csvNext(&p));
aircraft->m_operator = QString(csvNext(&p));
aircraft->m_operatorICAO = QString(csvNext(&p));
aircraft->m_registered = QString(csvNext(&p));
aircraftInfo->insert(icao, aircraft);
cnt++;
}
}
else
qDebug() << "AircraftInformation::readFastDB: Unexpected header";
}
else
qDebug() << "AircraftInformation::readFastDB: Empty file";
fclose(file);
}
else
qDebug() << "AircraftInformation::readFastDB: Failed to open " << filename;
qDebug() << "AircraftInformation::readFastDB - read " << cnt << " aircraft";
return aircraftInfo;
}
// Get flag based on registration
QString getFlag() const
{
QString flag;
if (m_prefixMap)
{
int idx = m_registration.indexOf('-');
if (idx >= 0)
{
QString prefix;
// Some countries use AA-A - try these first as first letters are common
prefix = m_registration.left(idx + 2);
if (m_prefixMap->contains(prefix))
{
flag = m_prefixMap->value(prefix);
}
else
{
// Try letters before '-'
prefix = m_registration.left(idx);
if (m_prefixMap->contains(prefix)) {
flag = m_prefixMap->value(prefix);
}
}
}
else
{
// No '-' Could be one of a few countries or military.
// See: https://en.wikipedia.org/wiki/List_of_aircraft_registration_prefixes
if (m_registration.startsWith("N")) {
flag = m_prefixMap->value("N"); // US
} else if (m_registration.startsWith("JA")) {
flag = m_prefixMap->value("JA"); // Japan
} else if (m_registration.startsWith("HL")) {
flag = m_prefixMap->value("HL"); // Korea
} else if (m_registration.startsWith("YV")) {
flag = m_prefixMap->value("YV"); // Venezuela
} else if ((m_militaryMap != nullptr) && (m_militaryMap->contains(m_operator))) {
flag = m_militaryMap->value(m_operator);
}
}
}
return flag;
}
static QString getOSNDBZipFilename() static QString getOSNDBZipFilename()
{ {
@ -405,11 +107,6 @@ struct SDRBASE_API AircraftInformation {
return getDataDir() + "/aircraftDatabase.csv"; return getDataDir() + "/aircraftDatabase.csv";
} }
static QString getFastDBFilename()
{
return getDataDir() + "/aircraftDatabaseFast.csv";
}
static QString getDataDir() static QString getDataDir()
{ {
// Get directory to store app data in (aircraft & airport databases and user-definable icons) // Get directory to store app data in (aircraft & airport databases and user-definable icons)
@ -418,100 +115,44 @@ struct SDRBASE_API AircraftInformation {
return locations[0]; return locations[0];
} }
static QString getAirlineIconPath(const QString &operatorICAO) private:
HttpDownloadManager m_dlm;
static QSharedPointer<const QHash<int, AircraftInformation *>> m_aircraftInformation;
static QSharedPointer<const QHash<QString, AircraftInformation *>> m_aircraftInformationByReg;
static QDateTime m_modifiedDateTime;
// Write a reduced size and validated version of the DB, so it loads quicker
static bool writeFastDB(const QString &filename, const QHash<int, AircraftInformation *> *aircraftInfo);
// Read smaller CSV file with no validation. Takes about 0.5s instead of 2s.
static QHash<int, AircraftInformation *> *readFastDB(const QString &filename);
// Read OpenSky Network CSV file
// This is large and contains lots of data we don't want, so we convert to
// a smaller version to speed up loading time
// Note that we use C file functions rather than QT, as these are ~30% faster
// and the QT version seemed to occasionally crash
static QHash<int, AircraftInformation *> *readOSNDB(const QString &filename);
static QString getFastDBFilename()
{ {
QString endPath = QString("/airlinelogos/%1.bmp").arg(operatorICAO); return getDataDir() + "/aircraftDatabaseFast.csv";
// Try in user directory first, so they can customise
QString userIconPath = getDataDir() + endPath;
QFile file(userIconPath);
if (file.exists())
{
return userIconPath;
}
else
{
// Try in resources
QString resourceIconPath = ":" + endPath;
QResource resource(resourceIconPath);
if (resource.isValid())
{
return resourceIconPath;
}
}
return QString();
} }
// Try to find an airline logo based on ICAO // Create hash table using registration as key
static QIcon *getAirlineIcon(const QString &operatorICAO) static QHash<QString, AircraftInformation *> *registrationHash(const QHash<int, AircraftInformation *> *in);
{
if (m_airlineIcons.contains(operatorICAO))
{
return m_airlineIcons.value(operatorICAO);
}
else
{
QIcon *icon = nullptr;
QString path = getAirlineIconPath(operatorICAO);
if (!path.isEmpty())
{
icon = new QIcon(path);
m_airlineIcons.insert(operatorICAO, icon);
}
else
{
if (!m_airlineMissingIcons.contains(operatorICAO))
{
qDebug() << "ADSBDemodGUI: No airline logo for " << operatorICAO;
m_airlineMissingIcons.insert(operatorICAO, true);
}
}
return icon;
}
}
static QString getFlagIconPath(const QString &country) private slots:
{ void downloadFinished(const QString& filename, bool success);
QString endPath = QString("/flags/%1.bmp").arg(country);
// Try in user directory first, so they can customise
QString userIconPath = getDataDir() + endPath;
QFile file(userIconPath);
if (file.exists())
{
return userIconPath;
}
else
{
// Try in resources
QString resourceIconPath = ":" + endPath;
QResource resource(resourceIconPath);
if (resource.isValid())
{
return resourceIconPath;
}
}
return QString();
}
// Try to find an flag logo based on a country signals:
static QIcon *getFlagIcon(const QString &country) void downloadingURL(const QString& url);
{ void downloadError(const QString& error);
if (m_flagIcons.contains(country)) void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
{ void downloadAircraftInformationFinished();
return m_flagIcons.value(country);
}
else
{
QIcon *icon = nullptr;
QString path = getFlagIconPath(country);
if (!path.isEmpty())
{
icon = new QIcon(path);
m_flagIcons.insert(country, icon);
}
return icon;
}
}
}; };
#endif #endif

View File

@ -0,0 +1,419 @@
///////////////////////////////////////////////////////////////////////////////////
// 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;
}

View File

@ -0,0 +1,119 @@
///////////////////////////////////////////////////////////////////////////////////
// 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_UTIL_OURAIRPORTSDB_H
#define INCLUDE_UTIL_OURAIRPORTSDB_H
#include <QString>
#include <QMutex>
#include <QDateTime>
#include <QHash>
#include <stdio.h>
#include <string.h>
#include "util/csv.h"
#include "util/httpdownloadmanager.h"
#include "export.h"
#define AIRPORTS_URL "https://davidmegginson.github.io/ourairports-data/airports.csv"
#define AIRPORT_FREQUENCIES_URL "https://davidmegginson.github.io/ourairports-data/airport-frequencies.csv"
class SDRBASE_API AirportInformation {
public:
enum AirportType {
Small,
Medium,
Large,
Heliport
};
struct FrequencyInformation {
QString m_type;
QString m_description;
float m_frequency; // In MHz
};
int m_id;
QString m_ident;
AirportType m_type;
QString m_name;
float m_latitude;
float m_longitude;
float m_elevation;
QVector<FrequencyInformation *> m_frequencies;
~AirportInformation();
QString getImageName() const;
};
class SDRBASE_API OurAirportsDB : public QObject {
Q_OBJECT
public:
OurAirportsDB(QObject *parent=nullptr);
~OurAirportsDB();
void downloadAirportInformation();
static QSharedPointer<const QHash<int, AirportInformation *>> getAirportsById();
static QSharedPointer<const QHash<QString, AirportInformation *>> getAirportsByIdent();
private:
HttpDownloadManager m_dlm;
static QSharedPointer<QHash<int, AirportInformation *>> m_airportsById;
static QSharedPointer<QHash<QString, AirportInformation *>> m_airportsByIdent;
static QDateTime m_modifiedDateTime;
static QMutex m_mutex;
static QString getDataDir();
static void readDB();
// Read OurAirport's airport CSV file
// See comments for readOSNDB
static QHash<int, AirportInformation *> *readAirportsDB(const QString &filename);
// Create hash table using ICAO identifier as key
static QHash<QString, AirportInformation *> *identHash(QHash<int, AirportInformation *> *in);
// Read OurAirport's airport frequencies CSV file
static bool readFrequenciesDB(const QString &filename, QHash<int, AirportInformation *> *airportInfo);
static QString trimQuotes(const QString s);
static QString getAirportDBFilename();
static QString getAirportFrequenciesDBFilename();
private slots:
void downloadFinished(const QString& filename, bool success);
signals:
void downloadingURL(const QString& url);
void downloadError(const QString& error);
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void downloadAirportInformationFinished();
};
#endif

View File

@ -32,6 +32,12 @@ GraphicsDialog::GraphicsDialog(MainSettings& mainSettings, QWidget* parent) :
} else { } else {
ui->multisampling->setCurrentText(QString::number(samples)); ui->multisampling->setCurrentText(QString::number(samples));
} }
samples = m_mainSettings.getMapMultisampling();
if (samples == 0) {
ui->mapMultisampling->setCurrentText("Off");
} else {
ui->mapMultisampling->setCurrentText(QString::number(samples));
}
} }
GraphicsDialog::~GraphicsDialog() GraphicsDialog::~GraphicsDialog()
@ -42,5 +48,6 @@ GraphicsDialog::~GraphicsDialog()
void GraphicsDialog::accept() void GraphicsDialog::accept()
{ {
m_mainSettings.setMultisampling(ui->multisampling->currentText().toInt()); m_mainSettings.setMultisampling(ui->multisampling->currentText().toInt());
m_mainSettings.setMapMultisampling(ui->mapMultisampling->currentText().toInt());
QDialog::accept(); QDialog::accept();
} }

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>277</width> <width>282</width>
<height>98</height> <height>131</height>
</rect> </rect>
</property> </property>
<property name="font"> <property name="font">
@ -23,49 +23,106 @@
<item> <item>
<layout class="QFormLayout" name="formLayout"> <layout class="QFormLayout" name="formLayout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="multisamplingLabel"> <widget class="QGroupBox" name="antialiasingGroup">
<property name="minimumSize"> <property name="title">
<size> <string>Anti-aliasing</string>
<width>150</width>
<height>0</height>
</size>
</property> </property>
<property name="text"> <layout class="QFormLayout" name="formLayout_2">
<string>Multisampling (MSAA)</string> <item row="0" column="0">
</property> <widget class="QLabel" name="multisamplingLabel">
</widget> <property name="minimumSize">
</item> <size>
<item row="0" column="1"> <width>150</width>
<widget class="QComboBox" name="multisampling"> <height>0</height>
<property name="minimumSize"> </size>
<size> </property>
<width>80</width> <property name="text">
<height>0</height> <string>Spectrum (MSAA)</string>
</size> </property>
</property> </widget>
<property name="toolTip"> </item>
<string>Number of samples to use for mulitsampling anti-aliasing (MSAA) - Requires windows to be reopened</string> <item row="0" column="1">
</property> <widget class="QComboBox" name="multisampling">
<item> <property name="minimumSize">
<property name="text"> <size>
<string>Off</string> <width>80</width>
</property> <height>0</height>
</item> </size>
<item> </property>
<property name="text"> <property name="toolTip">
<string>2</string> <string>Number of samples to use for mulitsampling anti-aliasing (MSAA) for spectrums
</property>
</item> Requires windows to be reopened to take effect</string>
<item> </property>
<property name="text"> <item>
<string>4</string> <property name="text">
</property> <string>Off</string>
</item> </property>
<item> </item>
<property name="text"> <item>
<string>8</string> <property name="text">
</property> <string>2</string>
</item> </property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
<item>
<property name="text">
<string>16</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="mapMultisamplingLabel">
<property name="toolTip">
<string>Number of samples to use for mulitsampling anti-aliasing (MSAA) for 2D maps
Requires windows to be reopened to take effect</string>
</property>
<property name="text">
<string>2D Map (MSAA)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="mapMultisampling">
<item>
<property name="text">
<string>Off</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
<item>
<property name="text">
<string>16</string>
</property>
</item>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -118,7 +118,7 @@ MapItem:
type: number type: number
format: float format: float
altitudeReference: altitudeReference:
description: "0 - NONE (Absolule), 1 - CLAMP_TO_GROUND, 2 - RELATIVE_TO_GROUND, 3 - CLIP_TO_GROUND" description: "0 - NONE (Absolute), 1 - CLAMP_TO_GROUND, 2 - RELATIVE_TO_GROUND, 3 - CLIP_TO_GROUND"
type: integer type: integer
animations: animations:
description: "Animations to play" description: "Animations to play"
@ -126,7 +126,7 @@ MapItem:
items: items:
$ref: "http://swgserver:8081/api/swagger/include/Map.yaml#/MapAnimation" $ref: "http://swgserver:8081/api/swagger/include/Map.yaml#/MapAnimation"
type: type:
description: "(0 - Map Item, 1 - Image Tile)" description: "(0 - Map Item, 1 - Image Tile, 2 - Polygon, 3 - Polyline)"
type: integer type: integer
imageTileWest: imageTileWest:
type: number type: number
@ -140,6 +140,22 @@ MapItem:
imageTileNorth: imageTileNorth:
type: number type: number
format: float format: float
imageZoomLevel:
description: "For 2D map"
type: number
format: float
coordinates:
description: "Polygon/polyline coordinates"
type: array
items:
$ref: "http://swgserver:8081/api/swagger/include/Map.yaml#/MapCoordinate"
extrudedHeight:
description: "Extruded height (from surface) for polygons"
type: number
format: float
availableUntil:
description: "Date and time until after which this item should no longer appear on 3D map"
type: string
MapAnimation: MapAnimation:
description: "Animation to play in the model on the 3D map" description: "Animation to play in the model on the 3D map"

View File

@ -82,6 +82,14 @@ SWGMapItem::SWGMapItem() {
m_image_tile_east_isSet = false; m_image_tile_east_isSet = false;
image_tile_north = 0.0f; image_tile_north = 0.0f;
m_image_tile_north_isSet = false; m_image_tile_north_isSet = false;
image_zoom_level = 0.0f;
m_image_zoom_level_isSet = false;
coordinates = nullptr;
m_coordinates_isSet = false;
extruded_height = 0.0f;
m_extruded_height_isSet = false;
available_until = nullptr;
m_available_until_isSet = false;
} }
SWGMapItem::~SWGMapItem() { SWGMapItem::~SWGMapItem() {
@ -144,6 +152,14 @@ SWGMapItem::init() {
m_image_tile_east_isSet = false; m_image_tile_east_isSet = false;
image_tile_north = 0.0f; image_tile_north = 0.0f;
m_image_tile_north_isSet = false; m_image_tile_north_isSet = false;
image_zoom_level = 0.0f;
m_image_zoom_level_isSet = false;
coordinates = new QList<SWGMapCoordinate*>();
m_coordinates_isSet = false;
extruded_height = 0.0f;
m_extruded_height_isSet = false;
available_until = new QString("");
m_available_until_isSet = false;
} }
void void
@ -207,6 +223,18 @@ SWGMapItem::cleanup() {
if(coordinates != nullptr) {
auto arr = coordinates;
for(auto o: *arr) {
delete o;
}
delete coordinates;
}
if(available_until != nullptr) {
delete available_until;
}
} }
SWGMapItem* SWGMapItem*
@ -274,6 +302,14 @@ SWGMapItem::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&image_tile_north, pJson["imageTileNorth"], "float", ""); ::SWGSDRangel::setValue(&image_tile_north, pJson["imageTileNorth"], "float", "");
::SWGSDRangel::setValue(&image_zoom_level, pJson["imageZoomLevel"], "float", "");
::SWGSDRangel::setValue(&coordinates, pJson["coordinates"], "QList", "SWGMapCoordinate");
::SWGSDRangel::setValue(&extruded_height, pJson["extrudedHeight"], "float", "");
::SWGSDRangel::setValue(&available_until, pJson["availableUntil"], "QString", "QString");
} }
QString QString
@ -371,6 +407,18 @@ SWGMapItem::asJsonObject() {
if(m_image_tile_north_isSet){ if(m_image_tile_north_isSet){
obj->insert("imageTileNorth", QJsonValue(image_tile_north)); obj->insert("imageTileNorth", QJsonValue(image_tile_north));
} }
if(m_image_zoom_level_isSet){
obj->insert("imageZoomLevel", QJsonValue(image_zoom_level));
}
if(coordinates && coordinates->size() > 0){
toJsonArray((QList<void*>*)coordinates, obj, "coordinates", "SWGMapCoordinate");
}
if(m_extruded_height_isSet){
obj->insert("extrudedHeight", QJsonValue(extruded_height));
}
if(available_until != nullptr && *available_until != QString("")){
toJsonValue(QString("availableUntil"), available_until, obj, QString("QString"));
}
return obj; return obj;
} }
@ -645,6 +693,46 @@ SWGMapItem::setImageTileNorth(float image_tile_north) {
this->m_image_tile_north_isSet = true; this->m_image_tile_north_isSet = true;
} }
float
SWGMapItem::getImageZoomLevel() {
return image_zoom_level;
}
void
SWGMapItem::setImageZoomLevel(float image_zoom_level) {
this->image_zoom_level = image_zoom_level;
this->m_image_zoom_level_isSet = true;
}
QList<SWGMapCoordinate*>*
SWGMapItem::getCoordinates() {
return coordinates;
}
void
SWGMapItem::setCoordinates(QList<SWGMapCoordinate*>* coordinates) {
this->coordinates = coordinates;
this->m_coordinates_isSet = true;
}
float
SWGMapItem::getExtrudedHeight() {
return extruded_height;
}
void
SWGMapItem::setExtrudedHeight(float extruded_height) {
this->extruded_height = extruded_height;
this->m_extruded_height_isSet = true;
}
QString*
SWGMapItem::getAvailableUntil() {
return available_until;
}
void
SWGMapItem::setAvailableUntil(QString* available_until) {
this->available_until = available_until;
this->m_available_until_isSet = true;
}
bool bool
SWGMapItem::isSet(){ SWGMapItem::isSet(){
@ -731,6 +819,18 @@ SWGMapItem::isSet(){
if(m_image_tile_north_isSet){ if(m_image_tile_north_isSet){
isObjectUpdated = true; break; isObjectUpdated = true; break;
} }
if(m_image_zoom_level_isSet){
isObjectUpdated = true; break;
}
if(coordinates && (coordinates->size() > 0)){
isObjectUpdated = true; break;
}
if(m_extruded_height_isSet){
isObjectUpdated = true; break;
}
if(available_until && *available_until != QString("")){
isObjectUpdated = true; break;
}
}while(false); }while(false);
return isObjectUpdated; return isObjectUpdated;
} }

View File

@ -126,6 +126,18 @@ public:
float getImageTileNorth(); float getImageTileNorth();
void setImageTileNorth(float image_tile_north); void setImageTileNorth(float image_tile_north);
float getImageZoomLevel();
void setImageZoomLevel(float image_zoom_level);
QList<SWGMapCoordinate*>* getCoordinates();
void setCoordinates(QList<SWGMapCoordinate*>* coordinates);
float getExtrudedHeight();
void setExtrudedHeight(float extruded_height);
QString* getAvailableUntil();
void setAvailableUntil(QString* available_until);
virtual bool isSet() override; virtual bool isSet() override;
@ -211,6 +223,18 @@ private:
float image_tile_north; float image_tile_north;
bool m_image_tile_north_isSet; bool m_image_tile_north_isSet;
float image_zoom_level;
bool m_image_zoom_level_isSet;
QList<SWGMapCoordinate*>* coordinates;
bool m_coordinates_isSet;
float extruded_height;
bool m_extruded_height_isSet;
QString* available_until;
bool m_available_until_isSet;
}; };
} }

View File

@ -82,6 +82,14 @@ SWGMapItem_2::SWGMapItem_2() {
m_image_tile_east_isSet = false; m_image_tile_east_isSet = false;
image_tile_north = 0.0f; image_tile_north = 0.0f;
m_image_tile_north_isSet = false; m_image_tile_north_isSet = false;
image_zoom_level = 0.0f;
m_image_zoom_level_isSet = false;
coordinates = nullptr;
m_coordinates_isSet = false;
extruded_height = 0.0f;
m_extruded_height_isSet = false;
available_until = nullptr;
m_available_until_isSet = false;
} }
SWGMapItem_2::~SWGMapItem_2() { SWGMapItem_2::~SWGMapItem_2() {
@ -144,6 +152,14 @@ SWGMapItem_2::init() {
m_image_tile_east_isSet = false; m_image_tile_east_isSet = false;
image_tile_north = 0.0f; image_tile_north = 0.0f;
m_image_tile_north_isSet = false; m_image_tile_north_isSet = false;
image_zoom_level = 0.0f;
m_image_zoom_level_isSet = false;
coordinates = new QList<SWGMapCoordinate*>();
m_coordinates_isSet = false;
extruded_height = 0.0f;
m_extruded_height_isSet = false;
available_until = new QString("");
m_available_until_isSet = false;
} }
void void
@ -207,6 +223,18 @@ SWGMapItem_2::cleanup() {
if(coordinates != nullptr) {
auto arr = coordinates;
for(auto o: *arr) {
delete o;
}
delete coordinates;
}
if(available_until != nullptr) {
delete available_until;
}
} }
SWGMapItem_2* SWGMapItem_2*
@ -274,6 +302,14 @@ SWGMapItem_2::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&image_tile_north, pJson["imageTileNorth"], "float", ""); ::SWGSDRangel::setValue(&image_tile_north, pJson["imageTileNorth"], "float", "");
::SWGSDRangel::setValue(&image_zoom_level, pJson["imageZoomLevel"], "float", "");
::SWGSDRangel::setValue(&coordinates, pJson["coordinates"], "QList", "SWGMapCoordinate");
::SWGSDRangel::setValue(&extruded_height, pJson["extrudedHeight"], "float", "");
::SWGSDRangel::setValue(&available_until, pJson["availableUntil"], "QString", "QString");
} }
QString QString
@ -371,6 +407,18 @@ SWGMapItem_2::asJsonObject() {
if(m_image_tile_north_isSet){ if(m_image_tile_north_isSet){
obj->insert("imageTileNorth", QJsonValue(image_tile_north)); obj->insert("imageTileNorth", QJsonValue(image_tile_north));
} }
if(m_image_zoom_level_isSet){
obj->insert("imageZoomLevel", QJsonValue(image_zoom_level));
}
if(coordinates && coordinates->size() > 0){
toJsonArray((QList<void*>*)coordinates, obj, "coordinates", "SWGMapCoordinate");
}
if(m_extruded_height_isSet){
obj->insert("extrudedHeight", QJsonValue(extruded_height));
}
if(available_until != nullptr && *available_until != QString("")){
toJsonValue(QString("availableUntil"), available_until, obj, QString("QString"));
}
return obj; return obj;
} }
@ -645,6 +693,46 @@ SWGMapItem_2::setImageTileNorth(float image_tile_north) {
this->m_image_tile_north_isSet = true; this->m_image_tile_north_isSet = true;
} }
float
SWGMapItem_2::getImageZoomLevel() {
return image_zoom_level;
}
void
SWGMapItem_2::setImageZoomLevel(float image_zoom_level) {
this->image_zoom_level = image_zoom_level;
this->m_image_zoom_level_isSet = true;
}
QList<SWGMapCoordinate*>*
SWGMapItem_2::getCoordinates() {
return coordinates;
}
void
SWGMapItem_2::setCoordinates(QList<SWGMapCoordinate*>* coordinates) {
this->coordinates = coordinates;
this->m_coordinates_isSet = true;
}
float
SWGMapItem_2::getExtrudedHeight() {
return extruded_height;
}
void
SWGMapItem_2::setExtrudedHeight(float extruded_height) {
this->extruded_height = extruded_height;
this->m_extruded_height_isSet = true;
}
QString*
SWGMapItem_2::getAvailableUntil() {
return available_until;
}
void
SWGMapItem_2::setAvailableUntil(QString* available_until) {
this->available_until = available_until;
this->m_available_until_isSet = true;
}
bool bool
SWGMapItem_2::isSet(){ SWGMapItem_2::isSet(){
@ -731,6 +819,18 @@ SWGMapItem_2::isSet(){
if(m_image_tile_north_isSet){ if(m_image_tile_north_isSet){
isObjectUpdated = true; break; isObjectUpdated = true; break;
} }
if(m_image_zoom_level_isSet){
isObjectUpdated = true; break;
}
if(coordinates && (coordinates->size() > 0)){
isObjectUpdated = true; break;
}
if(m_extruded_height_isSet){
isObjectUpdated = true; break;
}
if(available_until && *available_until != QString("")){
isObjectUpdated = true; break;
}
}while(false); }while(false);
return isObjectUpdated; return isObjectUpdated;
} }

View File

@ -126,6 +126,18 @@ public:
float getImageTileNorth(); float getImageTileNorth();
void setImageTileNorth(float image_tile_north); void setImageTileNorth(float image_tile_north);
float getImageZoomLevel();
void setImageZoomLevel(float image_zoom_level);
QList<SWGMapCoordinate*>* getCoordinates();
void setCoordinates(QList<SWGMapCoordinate*>* coordinates);
float getExtrudedHeight();
void setExtrudedHeight(float extruded_height);
QString* getAvailableUntil();
void setAvailableUntil(QString* available_until);
virtual bool isSet() override; virtual bool isSet() override;
@ -211,6 +223,18 @@ private:
float image_tile_north; float image_tile_north;
bool m_image_tile_north_isSet; bool m_image_tile_north_isSet;
float image_zoom_level;
bool m_image_zoom_level_isSet;
QList<SWGMapCoordinate*>* coordinates;
bool m_coordinates_isSet;
float extruded_height;
bool m_extruded_height_isSet;
QString* available_until;
bool m_available_until_isSet;
}; };
} }