1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-22 16:08:39 -05:00
sdrangel/plugins/feature/map/mapgui.cpp
Daniele Forsi b4ec43e4aa Raise the Beacon, IBP Beacons an Radio Time dialogs
Make the dialogs visible again if they are open but are hidden behind
the main window or another application's window.
2024-04-22 23:05:04 +02:00

2839 lines
111 KiB
C++

///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021-2024 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2021-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <cmath>
#include <QQmlContext>
#include <QQmlProperty>
#include <QGeoLocation>
#include <QGeoCoordinate>
#include <QGeoCodingManager>
#include <QGeoServiceProvider>
#include <QSettings>
#include <QMenu>
#include <QScreen>
#include <QSvgWidget>
#include <QTableWidgetItem>
#ifdef QT_WEBENGINE_FOUND
#include <QtWebEngineWidgets/QWebEngineView>
#include <QWebEngineSettings>
#include <QWebEngineProfile>
#endif
#include "feature/featureuiset.h"
#include "gui/basicfeaturesettingsdialog.h"
#include "gui/dialogpositioner.h"
#include "device/deviceset.h"
#include "device/deviceapi.h"
#include "dsp/devicesamplesource.h"
#include "device/deviceenumerator.h"
#include "util/units.h"
#include "util/maidenhead.h"
#include "util/morse.h"
#include "util/navtex.h"
#include "util/vlftransmitters.h"
#include "maplocationdialog.h"
#include "mapmaidenheaddialog.h"
#include "mapsettingsdialog.h"
#include "ibpbeacon.h"
#include "ui_mapgui.h"
#include "map.h"
#include "mapgui.h"
#include "SWGMapItem.h"
#include "SWGDeviceSettings.h"
#include "SWGKiwiSDRSettings.h"
#include "SWGRemoteTCPInputSettings.h"
MapGUI* MapGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
{
MapGUI* gui = new MapGUI(pluginAPI, featureUISet, feature);
return gui;
}
void MapGUI::destroy()
{
delete this;
}
void MapGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray MapGUI::serialize() const
{
return m_settings.serialize();
}
bool MapGUI::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
m_feature->setWorkspaceIndex(m_settings.m_workspaceIndex);
displaySettings();
applySettings(true);
return true;
}
else
{
resetToDefaults();
return false;
}
}
bool MapGUI::handleMessage(const Message& message)
{
if (Map::MsgConfigureMap::match(message))
{
qDebug("MapGUI::handleMessage: Map::MsgConfigureMap");
const Map::MsgConfigureMap& cfg = (Map::MsgConfigureMap&) message;
if (cfg.getForce()) {
m_settings = cfg.getSettings();
} else {
m_settings.applySettings(cfg.getSettingsKeys(), cfg.getSettings());
}
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (Map::MsgReportAvailableChannelOrFeatures::match(message))
{
Map::MsgReportAvailableChannelOrFeatures& report = (Map::MsgReportAvailableChannelOrFeatures&) message;
m_availableChannelOrFeatures = report.getItems();
return true;
}
else if (Map::MsgFind::match(message))
{
Map::MsgFind& msgFind = (Map::MsgFind&) message;
find(msgFind.getTarget());
return true;
}
else if (Map::MsgSetDateTime::match(message))
{
Map::MsgSetDateTime& msgSetDateTime = (Map::MsgSetDateTime&) message;
if (m_cesium) {
m_cesium->setDateTime(msgSetDateTime.getDateTime());
}
return true;
}
else if (MainCore::MsgMapItem::match(message))
{
MainCore::MsgMapItem& msgMapItem = (MainCore::MsgMapItem&) message;
SWGSDRangel::SWGMapItem *swgMapItem = msgMapItem.getSWGMapItem();
// TODO: Could have this in SWGMapItem so plugins can create additional groups
QString group;
for (int i = 0; i < m_availableChannelOrFeatures.size(); i++)
{
if (m_availableChannelOrFeatures[i].m_object == msgMapItem.getPipeSource())
{
for (int j = 0; j < MapSettings::m_pipeTypes.size(); j++)
{
if (m_availableChannelOrFeatures[i].m_type == MapSettings::m_pipeTypes[j]) {
group = m_availableChannelOrFeatures[i].m_type;
}
}
}
}
update(msgMapItem.getPipeSource(), swgMapItem, group);
return true;
}
return false;
}
void MapGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()))
{
if (handleMessage(*message)) {
delete message;
}
}
}
void MapGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
getRollupContents()->saveState(m_rollupState);
}
MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
FeatureGUI(parent),
ui(new Ui::MapGUI),
m_pluginAPI(pluginAPI),
m_featureUISet(featureUISet),
m_doApplySettings(true),
m_objectMapModel(this),
m_imageMapModel(this),
m_polygonMapModel(this),
m_polylineMapModel(this),
m_beacons(nullptr),
m_beaconDialog(this),
m_ibpBeaconDialog(this),
m_radioTimeDialog(this),
m_cesium(nullptr),
m_legend(nullptr),
m_nasaWidget(nullptr),
m_legendWidget(nullptr),
m_overviewWidget(nullptr),
m_descriptionWidget(nullptr)
{
m_feature = feature;
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/feature/map/readme.md";
RollupContents *rollupContents = getRollupContents();
ui->setupUi(rollupContents);
setSizePolicy(rollupContents->sizePolicy());
rollupContents->arrangeRollups();
connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
// Enable MSAA antialiasing on 2D map
// This can be much faster than using layer.smooth in the QML, when there are many items
// However, only seems to work when set to 16, and doesn't seem to be supported on all graphics cards
int multisamples = MainCore::instance()->getSettings().getMapMultisampling();
if (multisamples > 0)
{
QSurfaceFormat format;
format.setSamples(multisamples);
ui->map->setFormat(format);
}
createNASAGlobalImageryView();
connect(&m_nasaGlobalImagery, &NASAGlobalImagery::dataUpdated, this, &MapGUI::nasaGlobalImageryDataUpdated);
connect(&m_nasaGlobalImagery, &NASAGlobalImagery::metaDataUpdated, this, &MapGUI::nasaGlobalImageryMetaDataUpdated);
connect(&m_nasaGlobalImagery, &NASAGlobalImagery::legendAvailable, this, &MapGUI::nasaGlobalImageryLegendAvailable);
connect(&m_nasaGlobalImagery, &NASAGlobalImagery::htmlAvailable, this, &MapGUI::nasaGlobalImageryHTMLAvailable);
m_nasaGlobalImagery.getData();
m_nasaGlobalImagery.getMetaData();
createLayersMenu();
displayToolbar();
connect(screen(), &QScreen::orientationChanged, this, &MapGUI::orientationChanged);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
screen()->setOrientationUpdateMask(Qt::PortraitOrientation
| Qt::LandscapeOrientation
| Qt::InvertedPortraitOrientation
| Qt::InvertedLandscapeOrientation);
#endif
clearWikiMediaOSMCache();
m_rainViewer = new RainViewer();
connect(m_rainViewer, &RainViewer::pathUpdated, this, &MapGUI::pathUpdated);
m_rainViewer->getPathPeriodically();
m_mapTileServerPort = 60602;
m_mapTileServer = new MapTileServer(m_mapTileServerPort);
m_mapTileServer->setThunderforestAPIKey(thunderforestAPIKey());
m_mapTileServer->setMaptilerAPIKey(maptilerAPIKey());
m_osmPort = 0;
m_templateServer = new OSMTemplateServer(thunderforestAPIKey(), maptilerAPIKey(), m_mapTileServerPort, m_osmPort);
// Web server to serve dynamic files from QResources
m_webPort = 0;
m_webServer = new WebServer(m_webPort);
ui->map->setAttribute(Qt::WA_AcceptTouchEvents, true);
m_objectMapFilter.setSourceModel(&m_objectMapModel);
m_imageMapFilter.setSourceModel(&m_imageMapModel);
m_polygonMapFilter.setSourceModel(&m_polygonMapModel);
m_polylineMapFilter.setSourceModel(&m_polylineMapModel);
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);
connect(ui->map, &QQuickWidget::statusChanged, this, &MapGUI::statusChanged);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map/map.qml")));
#else
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map/map_6.qml")));
#endif
m_settings.m_modelURL = QString("http://127.0.0.1:%1/3d/").arg(m_webPort);
m_webServer->addPathSubstitution("3d", m_settings.m_modelDir);
m_map = reinterpret_cast<Map*>(feature);
m_map->setMessageQueueToGUI(&m_inputMessageQueue);
m_settings.setRollupState(&m_rollupState);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
connect(&m_kiwiSDRList, &KiwiSDRList::dataUpdated, this, &MapGUI::kiwiSDRUpdated);
connect(&m_spyServerList, &SpyServerList::dataUpdated, this, &MapGUI::spyServerUpdated);
#ifdef QT_WEBENGINE_FOUND
QWebEngineSettings *settings = ui->web->settings();
settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
connect(ui->web->page(), &QWebEnginePage::fullScreenRequested, this, &MapGUI::fullScreenRequested);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
connect(ui->web->page(), &QWebEnginePage::loadingChanged, this, &MapGUI::loadingChanged);
connect(ui->web, &QWebEngineView::renderProcessTerminated, this, &MapGUI::renderProcessTerminated);
#endif
QWebEngineProfile *profile = QWebEngineProfile::defaultProfile();
connect(profile, &QWebEngineProfile::downloadRequested, this, &MapGUI::downloadRequested);
#endif
// Get station position
float stationLatitude = MainCore::instance()->getSettings().getLatitude();
float stationLongitude = MainCore::instance()->getSettings().getLongitude();
float stationAltitude = MainCore::instance()->getSettings().getAltitude();
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
QQuickItem *item = ui->map->rootObject();
QObject *object = item->findChild<QObject*>("map");
if (object != nullptr)
{
QGeoCoordinate coords = object->property("center").value<QGeoCoordinate>();
coords.setLatitude(stationLatitude);
coords.setLongitude(stationLongitude);
object->setProperty("center", QVariant::fromValue(coords));
}
// Create antenna at My Position
m_antennaMapItem.setName(new QString("Station"));
m_antennaMapItem.setLatitude(stationLatitude);
m_antennaMapItem.setLongitude(stationLongitude);
m_antennaMapItem.setAltitude(stationAltitude);
m_antennaMapItem.setImage(new QString("antenna.png"));
m_antennaMapItem.setImageRotation(0);
m_antennaMapItem.setText(new QString(MainCore::instance()->getSettings().getStationName()));
m_antennaMapItem.setModel(new QString("antenna.glb"));
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.setLabel(new QString(MainCore::instance()->getSettings().getStationName()));
m_antennaMapItem.setLabelAltitudeOffset(4.5);
m_antennaMapItem.setAltitudeReference(1);
update(m_map, &m_antennaMapItem, "Station");
// Get updated when position changes
connect(&MainCore::instance()->getSettings(), &MainSettings::preferenceChanged, this, &MapGUI::preferenceChanged);
// Read beacons, if they exist
QList<Beacon *> *beacons = Beacon::readIARUCSV(MapGUI::getBeaconFilename());
if (beacons != nullptr) {
setBeacons(beacons);
}
addIBPBeacons();
addRadioTimeTransmitters();
addRadar();
addIonosonde();
addBroadcast();
addNavAids();
addAirspace();
addAirports();
addWaypoints();
addNavtex();
addVLF();
addKiwiSDR();
addSpyServer();
displaySettings();
applySettings(true);
connect(&m_objectMapModel, &ObjectMapModel::linkClicked, this, &MapGUI::linkClicked);
connect(&m_redrawMapTimer, &QTimer::timeout, this, &MapGUI::redrawMap);
m_redrawMapTimer.setSingleShot(true);
ui->map->installEventFilter(this);
makeUIConnections();
new DialogPositioner(&m_beaconDialog, true);
new DialogPositioner(&m_ibpBeaconDialog, true);
new DialogPositioner(&m_radioTimeDialog, true);
m_resizer.enableChildMouseTracking();
}
MapGUI::~MapGUI()
{
disconnect(&m_redrawMapTimer, &QTimer::timeout, this, &MapGUI::redrawMap);
m_redrawMapTimer.stop();
//m_cesium->deleteLater();
delete m_rainViewer;
delete m_cesium;
if (m_templateServer)
{
m_templateServer->close();
delete m_templateServer;
}
if (m_mapTileServer)
{
m_mapTileServer->close();
delete m_mapTileServer;
}
if (m_webServer)
{
m_webServer->close();
delete m_webServer;
}
delete m_giro;
delete ui;
}
void MapGUI::setWorkspaceIndex(int index)
{
m_settings.m_workspaceIndex = index;
m_feature->setWorkspaceIndex(index);
}
// Update a map item
void MapGUI::update(const QObject *source, SWGSDRangel::SWGMapItem *swgMapItem, const QString &group)
{
int type = swgMapItem->getType();
if (type == 0) {
m_objectMapModel.update(source, swgMapItem, group);
} else if (type == 1) {
m_imageMapModel.update(source, swgMapItem, group);
} else if (type == 2) {
m_polygonMapModel.update(source, swgMapItem, group);
} else if (type == 3) {
m_polylineMapModel.update(source, swgMapItem, group);
}
}
void MapGUI::setBeacons(QList<Beacon *> *beacons)
{
delete m_beacons;
m_beacons = beacons;
m_beaconDialog.updateTable();
// Add to Map
QListIterator<Beacon *> i(*m_beacons);
while (i.hasNext())
{
Beacon *beacon = i.next();
SWGSDRangel::SWGMapItem beaconMapItem;
// Need to suffix frequency, as there are multiple becaons with same callsign at different locations
QString name = QString("%1-%2").arg(beacon->m_callsign).arg(beacon->getFrequencyShortText());
beaconMapItem.setName(new QString(name));
beaconMapItem.setLatitude(beacon->m_latitude);
beaconMapItem.setLongitude(beacon->m_longitude);
beaconMapItem.setAltitude(beacon->m_altitude);
beaconMapItem.setImage(new QString("antenna.png"));
beaconMapItem.setImageRotation(0);
beaconMapItem.setText(new QString(beacon->getText()));
beaconMapItem.setModel(new QString("antenna.glb"));
beaconMapItem.setFixedPosition(true);
beaconMapItem.setOrientation(0);
// Just use callsign for label, so we don't have multiple labels on top of each other on 3D map
// as it makes them unreadable
beaconMapItem.setLabel(new QString(beacon->m_callsign));
beaconMapItem.setLabelAltitudeOffset(4.5);
beaconMapItem.setAltitudeReference(1);
update(m_map, &beaconMapItem, "Beacons");
}
}
void MapGUI::addIBPBeacons()
{
// Add to Map
for (const auto& beacon : IBPBeacon::m_beacons)
{
SWGSDRangel::SWGMapItem beaconMapItem;
beaconMapItem.setName(new QString(beacon.m_callsign));
beaconMapItem.setLatitude(beacon.m_latitude);
beaconMapItem.setLongitude(beacon.m_longitude);
beaconMapItem.setAltitude(0);
beaconMapItem.setImage(new QString("antenna.png"));
beaconMapItem.setImageRotation(0);
beaconMapItem.setText(new QString(beacon.getText()));
beaconMapItem.setModel(new QString("antenna.glb"));
beaconMapItem.setFixedPosition(true);
beaconMapItem.setOrientation(0);
beaconMapItem.setLabel(new QString(beacon.m_callsign));
beaconMapItem.setLabelAltitudeOffset(4.5);
beaconMapItem.setAltitudeReference(1);
update(m_map, &beaconMapItem, "Beacons");
}
}
void MapGUI::addVLF()
{
for (int i = 0; i < VLFTransmitters::m_transmitters.size(); i++)
{
SWGSDRangel::SWGMapItem vlfMapItem;
QString name = QString("%1").arg(VLFTransmitters::m_transmitters[i].m_callsign);
vlfMapItem.setName(new QString(name));
vlfMapItem.setLatitude(VLFTransmitters::m_transmitters[i].m_latitude);
vlfMapItem.setLongitude(VLFTransmitters::m_transmitters[i].m_longitude);
vlfMapItem.setAltitude(0.0);
vlfMapItem.setImage(new QString("antenna.png"));
vlfMapItem.setImageRotation(0);
QString text = QString("VLF Transmitter\nCallsign: %1\nFrequency: %2 kHz")
.arg(VLFTransmitters::m_transmitters[i].m_callsign)
.arg(VLFTransmitters::m_transmitters[i].m_frequency/1000.0);
if (VLFTransmitters::m_transmitters[i].m_power > 0) {
text.append(QString("\nPower: %1 kW").arg(VLFTransmitters::m_transmitters[i].m_power));
}
vlfMapItem.setText(new QString(text));
vlfMapItem.setModel(new QString("antenna.glb"));
vlfMapItem.setFixedPosition(true);
vlfMapItem.setOrientation(0);
vlfMapItem.setLabel(new QString(name));
vlfMapItem.setLabelAltitudeOffset(4.5);
vlfMapItem.setAltitudeReference(1);
update(m_map, &vlfMapItem, "VLF");
}
}
const QList<RadioTimeTransmitter> MapGUI::m_radioTimeTransmitters = {
{"MSF", 60000, 54.9075f, -3.27333f, 17}, // UK
{"DCF77", 77500, 50.01611111f, 9.00805556f, 50}, // Germany
{"TDF", 162000, 47.1694f, 2.2044f, 800}, // France
{"WWVB", 60000, 40.67805556f, -105.04666667f, 70}, // USA
{"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()
{
for (int i = 0; i < m_radioTimeTransmitters.size(); i++)
{
SWGSDRangel::SWGMapItem timeMapItem;
// Need to suffix frequency, as there are multiple becaons with same callsign at different locations
QString name = QString("%1").arg(m_radioTimeTransmitters[i].m_callsign);
timeMapItem.setName(new QString(name));
timeMapItem.setLatitude(m_radioTimeTransmitters[i].m_latitude);
timeMapItem.setLongitude(m_radioTimeTransmitters[i].m_longitude);
timeMapItem.setAltitude(0.0);
timeMapItem.setImage(new QString("antennatime.png"));
timeMapItem.setImageRotation(0);
QString text = QString("Radio Time Transmitter\nCallsign: %1\nFrequency: %2 kHz\nPower: %3 kW")
.arg(m_radioTimeTransmitters[i].m_callsign)
.arg(m_radioTimeTransmitters[i].m_frequency/1000.0)
.arg(m_radioTimeTransmitters[i].m_power);
timeMapItem.setText(new QString(text));
timeMapItem.setModel(new QString("antenna.glb"));
timeMapItem.setFixedPosition(true);
timeMapItem.setOrientation(0);
timeMapItem.setLabel(new QString(name));
timeMapItem.setLabelAltitudeOffset(4.5);
timeMapItem.setAltitudeReference(1);
update(m_map, &timeMapItem, "Radio Time Transmitters");
}
}
void MapGUI::addRadar()
{
SWGSDRangel::SWGMapItem radarMapItem;
radarMapItem.setName(new QString("GRAVES"));
radarMapItem.setLatitude(47.3480);
radarMapItem.setLongitude(5.5151);
radarMapItem.setAltitude(0.0);
radarMapItem.setImage(new QString("antenna.png"));
radarMapItem.setImageRotation(0);
QString text = QString("Radar\nCallsign: %1\nFrequency: %2 MHz")
.arg("GRAVES")
.arg("143.050");
radarMapItem.setText(new QString(text));
radarMapItem.setModel(new QString("antenna.glb"));
radarMapItem.setFixedPosition(true);
radarMapItem.setOrientation(0);
radarMapItem.setLabel(new QString("GRAVES"));
radarMapItem.setLabelAltitudeOffset(4.5);
radarMapItem.setAltitudeReference(1);
update(m_map, &radarMapItem, "Radar");
}
QString MapGUI::formatFrequency(qint64 frequency) const
{
QString s = QString::number(frequency);
if (s.endsWith("000000000")) {
return s.left(s.size() - 9) + " GHz";
} else if (s.endsWith("000000")) {
return s.left(s.size() - 6) + " MHz";
} else if (s.endsWith("000")) {
return s.left(s.size() - 3) + " kHz";
}
return s + " Hz";
}
void MapGUI::addKiwiSDR()
{
m_kiwiSDRList.getDataPeriodically();
}
void MapGUI::kiwiSDRUpdated(const QList<KiwiSDRList::KiwiSDR>& sdrs)
{
for (const auto& sdr : sdrs)
{
SWGSDRangel::SWGMapItem kiwiMapItem;
kiwiMapItem.setName(new QString(sdr.m_name));
kiwiMapItem.setLatitude(sdr.m_latitude);
kiwiMapItem.setLongitude(sdr.m_longitude);
kiwiMapItem.setAltitude(sdr.m_altitude);
kiwiMapItem.setImage(new QString("antennakiwi.png"));
kiwiMapItem.setImageRotation(0);
QString url = QString("sdrangel-kiwisdr://%1").arg(sdr.m_url);
QString antenna = sdr.m_antenna;
if (!sdr.m_antennaConnected) {
antenna.append(" (Not connected)");
}
QString text = QString("KiwiSDR\nName: %1\nHW: %2\nUsers: %3/%4\nFrequency: %5 - %6\nAntenna: %7\nSNR: %8 dB\nURL: %9")
.arg(sdr.m_name)
.arg(sdr.m_sdrHW)
.arg(sdr.m_users)
.arg(sdr.m_usersMax)
.arg(formatFrequency(sdr.m_lowFrequency))
.arg(formatFrequency(sdr.m_highFrequency))
.arg(antenna)
.arg(sdr.m_snr)
.arg(QString("<a href=%1 onclick=\"return parent.infoboxLink('%1')\">%2</a>").arg(url).arg(sdr.m_url))
;
kiwiMapItem.setText(new QString(text));
kiwiMapItem.setModel(new QString("antenna.glb"));
kiwiMapItem.setFixedPosition(true);
kiwiMapItem.setOrientation(0);
QString band = "HF";
if (sdr.m_highFrequency > 300000000) {
band = "UHF";
} else if (sdr.m_highFrequency > 320000000) { // Technically 30MHz, but many HF Kiwis list up to 32MHz
band = "VHF";
}
QString label = QString("Kiwi %1").arg(band);
kiwiMapItem.setLabel(new QString(label));
kiwiMapItem.setLabelAltitudeOffset(4.5);
kiwiMapItem.setAltitudeReference(1);
update(m_map, &kiwiMapItem, "KiwiSDR");
}
}
void MapGUI::addSpyServer()
{
m_spyServerList.getDataPeriodically();
}
void MapGUI::spyServerUpdated(const QList<SpyServerList::SpyServer>& sdrs)
{
for (const auto& sdr : sdrs)
{
SWGSDRangel::SWGMapItem spyServerMapItem;
QString address = QString("%1:%2").arg(sdr.m_streamingHost).arg(sdr.m_streamingPort);
spyServerMapItem.setName(new QString(address));
spyServerMapItem.setLatitude(sdr.m_latitude);
spyServerMapItem.setLongitude(sdr.m_longitude);
spyServerMapItem.setAltitude(0);
spyServerMapItem.setImage(new QString("antennaspyserver.png"));
spyServerMapItem.setImageRotation(0);
QString url = QString("sdrangel-spyserver://%1").arg(address);
QString text = QString("SpyServer\nDescription: %1\nHW: %2\nUsers: %3/%4\nFrequency: %5 - %6\nAntenna: %7\nOnline: %8\nURL: %9")
.arg(sdr.m_generalDescription)
.arg(sdr.m_deviceType)
.arg(sdr.m_currentClientCount)
.arg(sdr.m_maxClients)
.arg(formatFrequency(sdr.m_minimumFrequency))
.arg(formatFrequency(sdr.m_maximumFrequency))
.arg(sdr.m_antennaType)
.arg(sdr.m_online ? "Yes" : "No")
.arg(QString("<a href=%1 onclick=\"return parent.infoboxLink('%1')\">%2</a>").arg(url).arg(address))
;
spyServerMapItem.setText(new QString(text));
spyServerMapItem.setModel(new QString("antenna.glb"));
spyServerMapItem.setFixedPosition(true);
spyServerMapItem.setOrientation(0);
QStringList bands;
if (sdr.m_minimumFrequency < 30000000) {
bands.append("HF");
}
if ((sdr.m_minimumFrequency < 300000000) && (sdr.m_maximumFrequency > 30000000)) {
bands.append("VHF");
}
if ((sdr.m_minimumFrequency < 3000000000) && (sdr.m_maximumFrequency > 300000000)) {
bands.append("UHF");
}
if (sdr.m_maximumFrequency > 3000000000) {
bands.append("SHF");
}
QString label = QString("SpyServer %1").arg(bands.join(" "));
spyServerMapItem.setLabel(new QString(label));
spyServerMapItem.setLabelAltitudeOffset(4.5);
spyServerMapItem.setAltitudeReference(1);
update(m_map, &spyServerMapItem, "SpyServer");
}
}
// Ionosonde stations
void MapGUI::addIonosonde()
{
m_giro = GIRO::create();
if (m_giro)
{
connect(m_giro, &GIRO::indexUpdated, this, &MapGUI::giroIndexUpdated);
connect(m_giro, &GIRO::dataUpdated, this, &MapGUI::giroDataUpdated);
connect(m_giro, &GIRO::mufUpdated, this, &MapGUI::mufUpdated);
connect(m_giro, &GIRO::foF2Updated, this, &MapGUI::foF2Updated);
}
}
void MapGUI::giroIndexUpdated(const QList<GIRO::DataSet>& data)
{
(void) data;
}
void MapGUI::giroDataUpdated(const GIRO::GIROStationData& data)
{
if (!data.m_station.isEmpty())
{
IonosondeStation *station = nullptr;
// See if we already have the station in our hash
if (!m_ionosondeStations.contains(data.m_station))
{
// Create new station
station = new IonosondeStation(data);
m_ionosondeStations.insert(data.m_station, station);
}
else
{
station = m_ionosondeStations.value(data.m_station);
}
station->update(data);
// Add/update map
SWGSDRangel::SWGMapItem ionosondeStationMapItem;
ionosondeStationMapItem.setName(new QString(station->m_name));
ionosondeStationMapItem.setLatitude(station->m_latitude);
ionosondeStationMapItem.setLongitude(station->m_longitude);
ionosondeStationMapItem.setAltitude(0.0);
ionosondeStationMapItem.setImage(new QString("ionosonde.png"));
ionosondeStationMapItem.setImageRotation(0);
ionosondeStationMapItem.setText(new QString(station->m_text));
ionosondeStationMapItem.setModel(new QString("antenna.glb"));
ionosondeStationMapItem.setFixedPosition(true);
ionosondeStationMapItem.setOrientation(0);
ionosondeStationMapItem.setLabel(new QString(station->m_label));
ionosondeStationMapItem.setLabelAltitudeOffset(4.5);
ionosondeStationMapItem.setAltitudeReference(1);
ionosondeStationMapItem.setAvailableFrom(new QString(data.m_dateTime.toString(Qt::ISODateWithMs)));
ionosondeStationMapItem.setAvailableUntil(new QString(data.m_dateTime.addDays(3).toString(Qt::ISODateWithMs))); // Remove after data is too old
update(m_map, &ionosondeStationMapItem, "Ionosonde Stations");
}
}
void MapGUI::mufUpdated(const QJsonDocument& document)
{
// Could possibly try render on 2D map, but contours
// that cross anti-meridian are not drawn properly
//${Qt5Location_PRIVATE_INCLUDE_DIRS}
//#include <QtLocation/private/qgeojson_p.h>
//QVariantList list = QGeoJson::importGeoJson(document);
m_webServer->addFile("/map/map/muf.geojson", document.toJson());
if (m_cesium) {
m_cesium->showMUF(m_settings.m_displayMUF);
}
}
void MapGUI::foF2Updated(const QJsonDocument& document)
{
m_webServer->addFile("/map/map/fof2.geojson", document.toJson());
if (m_cesium) {
m_cesium->showfoF2(m_settings.m_displayfoF2);
}
}
void MapGUI::updateGIRO(const QDateTime& mapDateTime)
{
if (m_giro)
{
if (m_settings.m_displayMUF || m_settings.m_displayfoF2)
{
QString giroRunId = m_giro->getRunId(mapDateTime);
if (m_giroRunId.isEmpty() || (!giroRunId.isEmpty() && (giroRunId != m_giroRunId)))
{
m_giro->getMUF(giroRunId);
m_giro->getMUF(giroRunId);
m_giroRunId = giroRunId;
m_giroDateTime = mapDateTime;
}
}
}
}
void MapGUI::pathUpdated(const QString& radarPath, const QString& satellitePath)
{
m_radarPath = radarPath;
m_satellitePath = satellitePath;
m_mapTileServer->setRadarPath(radarPath);
m_mapTileServer->setSatellitePath(satellitePath);
if (m_settings.m_displayRain || m_settings.m_displayClouds)
{
clearOSMCache();
applyMap2DSettings(true);
}
if (m_cesium)
{
m_cesium->setLayerSettings("rain", {"path", "show"}, {radarPath, m_settings.m_displayRain});
m_cesium->setLayerSettings("clouds", {"path", "show"}, {satellitePath, m_settings.m_displayClouds});
}
}
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";
}
}
/*
static QString arrayToString(QJsonArray array)
{
QString s;
for (int i = 0; i < array.size(); i++)
{
s = s.append(array[i].toString());
s = s.append(" ");
}
return s;
}
// Code for FM list / DAB list, should they allow access
void MapGUI::addDAB()
{
QFile file("stationlist_SI.json");
if (file.open(QIODevice::ReadOnly))
{
QByteArray bytes = file.readAll();
QJsonParseError error;
QJsonDocument json = QJsonDocument::fromJson(bytes, &error);
if (!json.isNull())
{
if (json.isObject())
{
QJsonObject obj = json.object();
QJsonValue stations = obj.value("stations");
QJsonArray stationsArray = stations.toArray();
for (int i = 0; i < stationsArray.size(); i++)
{
QJsonObject station = stationsArray[i].toObject();
// "txs" contains array of transmitters
QString stationName = station.value("stationName").toString();
QJsonArray txs = station.value("txs").toArray();
QString languages = arrayToString(station.value("language").toArray());
QString format = arrayToString(station.value("format").toArray());
for (int j = 0; j < txs.size(); j++)
{
QJsonObject tx = txs[j].toObject();
QString band = tx.value("band").toString();
double lat = tx.value("latitude").toString().toDouble();
double lon = tx.value("longitude").toString().toDouble();
double alt = tx.value("haat").toString().toDouble(); // This is height above terrain - not actual height - Check "haatUnits" is m
double frequency = tx.value("frequency").toString().toDouble(); // Can be MHz or kHz for AM
double erp = tx.value("erp").toString().toDouble();
SWGSDRangel::SWGMapItem mapItem;
mapItem.setLatitude(lat);
mapItem.setLongitude(lon);
mapItem.setAltitude(alt);
mapItem.setImageRotation(0);
mapItem.setModel(new QString("antenna.glb"));
mapItem.setFixedPosition(true);
mapItem.setOrientation(0);
mapItem.setLabelAltitudeOffset(4.5);
mapItem.setAltitudeReference(1);
if (band == "DAB")
{
// Name should be unique - can we use TII code for this? can it repeat across countries?
QString name = QString("%1").arg(tx.value("tsId").toString());
mapItem.setName(new QString(name));
mapItem.setImage(new QString("antennadab.png"));
mapItem.setLabel(new QString(name));
// Need tiicode?
QString text = QString("%1 Transmitter\nStation: %2\nFrequency: %3 %4\nPower: %5 %6\nLanguage(s): %7\nType: %8\nService: %9\nEnsemble: %10")
.arg(band)
.arg(stationName)
.arg(frequency)
.arg(tx.value("frequencyUnits").toString())
.arg(erp)
.arg(tx.value("erpUnits").toString())
.arg(languages)
.arg(format)
.arg(tx.value("serviceLabel").toString())
.arg(tx.value("ensembleLabel").toString())
;
mapItem.setText(new QString(text));
update(m_map, &mapItem, "DAB");
}
else if (band == "FM")
{
// Name should be unique
QString name = QString("%1").arg(tx.value("tsId").toString());
mapItem.setName(new QString(name));
mapItem.setImage(new QString("antennafm.png"));
mapItem.setLabel(new QString(name));
QString text = QString("%1 Transmitter\nStation: %2\nFrequency: %3 %4\nPower: %5 %6\nLanguage(s): %7\nType: %8")
.arg(band)
.arg(stationName)
.arg(frequency)
.arg(tx.value("frequencyUnits").toString())
.arg(erp)
.arg(tx.value("erpUnits").toString())
.arg(languages)
.arg(format)
;
mapItem.setText(new QString(text));
update(m_map, &mapItem, "FM");
}
else if (band == "AM")
{
// Name should be unique
QString name = QString("%1").arg(tx.value("tsId").toString());
mapItem.setName(new QString(name));
mapItem.setImage(new QString("antennaam.png"));
mapItem.setLabel(new QString(name));
QString text = QString("%1 Transmitter\nStation: %2\nFrequency: %3 %4\nPower: %5 %6\nLanguage(s): %7\nType: %8")
.arg(band)
.arg(stationName)
.arg(frequency)
.arg(tx.value("frequencyUnits").toString())
.arg(erp)
.arg(tx.value("erpUnits").toString())
.arg(languages)
.arg(format)
;
mapItem.setText(new QString(text));
update(m_map, &mapItem, "AM");
}
}
}
}
else
{
qDebug() << "MapGUI::addDAB: Expecting an object in DAB json:";
}
}
else
{
qDebug() << "MapGUI::addDAB: Failed to parse DAB json: " << error.errorString();
}
}
else
{
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::addWaypoints()
{
m_waypoints = Waypoints::getWaypoints();
if (m_waypoints)
{
QHashIterator<QString, Waypoint *> i(*m_waypoints);
while (i.hasNext())
{
i.next();
const Waypoint *waypoint = i.value();
SWGSDRangel::SWGMapItem waypointMapItem;
waypointMapItem.setName(new QString(waypoint->m_name));
waypointMapItem.setLatitude(waypoint->m_latitude);
waypointMapItem.setLongitude(waypoint->m_longitude);
waypointMapItem.setAltitude(0);
waypointMapItem.setImage(new QString("waypoint.png"));
waypointMapItem.setImageRotation(0);
QStringList list;
list.append(QString("Waypoint: %1").arg(waypoint->m_name));
waypointMapItem.setText(new QString(list.join("\n")));
//waypointMapItem.setModel(new QString("waypoint.glb")); // No such model currently
waypointMapItem.setFixedPosition(true);
waypointMapItem.setOrientation(0);
waypointMapItem.setLabel(new QString(waypoint->m_name));
waypointMapItem.setLabelAltitudeOffset(4.5);
waypointMapItem.setAltitude(Units::feetToMetres(25000));
waypointMapItem.setAltitudeReference(1);
update(m_map, &waypointMapItem, "Waypoints");
}
}
}
void MapGUI::navAidsUpdated()
{
addNavAids();
}
void MapGUI::airspacesUpdated()
{
addAirspace();
}
void MapGUI::airportsUpdated()
{
addAirports();
}
void MapGUI::waypointsUpdated()
{
addWaypoints();
}
void MapGUI::addNavtex()
{
for (int i = 0; i < NavtexTransmitter::m_navtexTransmitters.size(); i++)
{
SWGSDRangel::SWGMapItem navtexMapItem;
QString name = QString("%1").arg(NavtexTransmitter::m_navtexTransmitters[i].m_station);
navtexMapItem.setName(new QString(name));
navtexMapItem.setLatitude(NavtexTransmitter::m_navtexTransmitters[i].m_latitude);
navtexMapItem.setLongitude(NavtexTransmitter::m_navtexTransmitters[i].m_longitude);
navtexMapItem.setAltitude(0.0);
navtexMapItem.setImage(new QString("antenna.png"));
navtexMapItem.setImageRotation(0);
QString text = QString("Navtex Transmitter\nStation: %1\nArea: %2")
.arg(NavtexTransmitter::m_navtexTransmitters[i].m_station)
.arg(NavtexTransmitter::m_navtexTransmitters[i].m_area);
QStringList schedules;
for (const auto& schedule : NavtexTransmitter::m_navtexTransmitters[i].m_schedules)
{
QString scheduleText = QString("\nFrequency: %1 kHz\nID: %2").arg(schedule.m_frequency / 1000).arg(schedule.m_id);
if (schedule.m_times.size() > 0)
{
QStringList times;
for (const auto& time : schedule.m_times) {
times.append(time.toString("hh:mm"));
}
scheduleText.append("\nTimes: ");
scheduleText.append(times.join(" "));
scheduleText.append(" UTC");
}
schedules.append(scheduleText);
}
text.append(schedules.join(""));
navtexMapItem.setText(new QString(text));
navtexMapItem.setModel(new QString("antenna.glb"));
navtexMapItem.setFixedPosition(true);
navtexMapItem.setOrientation(0);
navtexMapItem.setLabel(new QString(name));
navtexMapItem.setLabelAltitudeOffset(4.5);
navtexMapItem.setAltitudeReference(1);
update(m_map, &navtexMapItem, "Navtex");
}
}
void MapGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void MapGUI::nasaGlobalImageryDataUpdated(const QList<NASAGlobalImagery::DataSet>& dataSets)
{
m_nasaDataSets = dataSets;
m_nasaDataSetsHash.clear();
ui->nasaGlobalImageryIdentifier->blockSignals(true);
ui->nasaGlobalImageryIdentifier->clear();
for (const auto& dataSet : m_nasaDataSets)
{
ui->nasaGlobalImageryIdentifier->addItem(dataSet.m_identifier);
m_nasaDataSetsHash.insert(dataSet.m_identifier, dataSet);
}
ui->nasaGlobalImageryIdentifier->blockSignals(false);
ui->nasaGlobalImageryIdentifier->setCurrentIndex(ui->nasaGlobalImageryIdentifier->findText(m_settings.m_nasaGlobalImageryIdentifier));
}
void MapGUI::createNASAGlobalImageryView()
{
m_nasaWidget = new QWidget();
m_nasaWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
m_legendWidget = new QSvgWidget();
QPalette pal = QPalette();
pal.setColor(QPalette::Window, Qt::white);
m_legendWidget->setAutoFillBackground(true);
m_legendWidget->setPalette(pal);
m_nasaWidget->setAutoFillBackground(true);
m_nasaWidget->setPalette(pal);
m_descriptionWidget = new QTextEdit();
m_descriptionWidget->setReadOnly(true);
m_descriptionWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
m_overviewWidget = new QTableWidget(NASA_ROWS, 2);
m_overviewWidget->setItem(NASA_TITLE, 0, new QTableWidgetItem("Title"));
m_overviewWidget->setItem(NASA_TITLE, 1, new QTableWidgetItem(""));
m_overviewWidget->setItem(NASA_SUBTITLE, 0, new QTableWidgetItem("Subtitle"));
m_overviewWidget->setItem(NASA_SUBTITLE, 1, new QTableWidgetItem(""));
m_overviewWidget->setItem(NASA_DEFAULT_DATE, 0, new QTableWidgetItem("Default Date"));
m_overviewWidget->setItem(NASA_DEFAULT_DATE, 1, new QTableWidgetItem(""));
m_overviewWidget->setItem(NASA_START_DATE, 0, new QTableWidgetItem("Start Date"));
m_overviewWidget->setItem(NASA_START_DATE, 1, new QTableWidgetItem(""));
m_overviewWidget->setItem(NASA_END_DATE, 0, new QTableWidgetItem("End Date"));
m_overviewWidget->setItem(NASA_END_DATE, 1, new QTableWidgetItem(""));
m_overviewWidget->setItem(NASA_PERIOD, 0, new QTableWidgetItem("Period"));
m_overviewWidget->setItem(NASA_PERIOD, 1, new QTableWidgetItem(""));
m_overviewWidget->setItem(NASA_LAYER_GROUP, 0, new QTableWidgetItem("Group"));
m_overviewWidget->setItem(NASA_LAYER_GROUP, 1, new QTableWidgetItem(""));
m_overviewWidget->horizontalHeader()->setStretchLastSection(true);
m_overviewWidget->horizontalHeader()->hide();
m_overviewWidget->verticalHeader()->hide();
m_overviewWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
m_overviewWidget->setSelectionMode(QAbstractItemView::NoSelection);
QHBoxLayout *hbox = new QHBoxLayout();
hbox->addWidget(m_overviewWidget);
hbox->addWidget(m_legendWidget, 0, Qt::AlignHCenter | Qt::AlignTop);
hbox->addWidget(m_descriptionWidget);
hbox->setContentsMargins(0, 0, 0, 0);
m_nasaWidget->setLayout(hbox);
ui->splitter->addWidget(m_nasaWidget);
// Limit size of widget, otherwise the splitter makes it bigger than the maps for some unknown reason
m_nasaWidget->setMaximumHeight(m_overviewWidget->sizeHint().height());
m_nasaWidget->show();
}
void MapGUI::displayNASAMetaData()
{
if (m_nasaMetaData.m_layers.contains(m_settings.m_nasaGlobalImageryIdentifier))
{
const NASAGlobalImagery::Layer& layer = m_nasaMetaData.m_layers.value(m_settings.m_nasaGlobalImageryIdentifier);
const NASAGlobalImagery::DataSet& dataSet = m_nasaDataSetsHash.value(m_settings.m_nasaGlobalImageryIdentifier);
m_overviewWidget->item(NASA_TITLE, 1)->setText(layer.m_title);
m_overviewWidget->item(NASA_SUBTITLE, 1)->setText(layer.m_subtitle);
m_overviewWidget->item(NASA_DEFAULT_DATE, 1)->setText(dataSet.m_defaultDateTime);
m_overviewWidget->item(NASA_START_DATE, 1)->setText(layer.m_startDate.date().toString("yyyy-MM-dd"));
if (layer.m_endDate.isValid()) {
m_overviewWidget->item(NASA_END_DATE, 1)->setText(layer.m_endDate.date().toString("yyyy-MM-dd"));
} else if (layer.m_ongoing) {
m_overviewWidget->item(NASA_END_DATE, 1)->setText("Ongoing");
} else {
m_overviewWidget->item(NASA_END_DATE, 1)->setText("");
}
m_overviewWidget->item(NASA_PERIOD, 1)->setText(layer.m_layerPeriod);
m_overviewWidget->item(NASA_LAYER_GROUP, 1)->setText(layer.m_layerGroup);
}
else
{
qDebug() << "MapGUI::displayNASAMetaData: No metadata for " << m_settings.m_nasaGlobalImageryIdentifier;
m_overviewWidget->item(NASA_TITLE, 1)->setText("");
m_overviewWidget->item(NASA_SUBTITLE, 1)->setText("");
m_overviewWidget->item(NASA_DEFAULT_DATE, 1)->setText("");
m_overviewWidget->item(NASA_START_DATE, 1)->setText("");
m_overviewWidget->item(NASA_END_DATE, 1)->setText("");
m_overviewWidget->item(NASA_PERIOD, 1)->setText("");
m_overviewWidget->item(NASA_LAYER_GROUP, 1)->setText("");
}
}
void MapGUI::nasaGlobalImageryMetaDataUpdated(const NASAGlobalImagery::MetaData& metaData)
{
m_nasaMetaData = metaData;
displayNASAMetaData();
}
void MapGUI::nasaGlobalImageryLegendAvailable(const QString& url, const QByteArray& data)
{
(void) url;
if (m_legendWidget)
{
m_legendWidget->load(data);
if (m_legend && (m_legend->m_height > 0))
{
m_legendWidget->setFixedSize(m_legend->m_width, m_legend->m_height);
m_nasaWidget->updateGeometry();
}
}
}
void MapGUI::nasaGlobalImageryHTMLAvailable(const QString& url, const QByteArray& data)
{
(void) url;
if (m_descriptionWidget) {
m_descriptionWidget->setHtml(data);
}
}
QString MapGUI::osmCachePath()
{
return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/QtLocation/5.8/tiles/osm/sdrangel_map";
}
void MapGUI::clearOSMCache()
{
// Delete all cached custom tiles when user changes the URL. Is there a better way to do this?
// Now deletes all cached tiles, to remove overlays
QDir dir(osmCachePath());
if (dir.exists())
{
// FIXME: Restore this - and use custom URL for overlays!!!
//QStringList filenames = dir.entryList({"osm_100-l-8-*.png"}); // 8=custom URL
QStringList filenames = dir.entryList({"osm_100-l-*"});
for (const auto& filename : filenames)
{
QFile file(dir.filePath(filename));
if (!file.remove()) {
qDebug() << "MapGUI::clearOSMCache: Failed to remove " << file.fileName();
}
}
}
}
// Delete old cache if it might contain wikimedia OSM images before switch to using OSM directly
// as the images are different
void MapGUI::clearWikiMediaOSMCache()
{
QSettings settings;
QString cacheCleared = "sdrangel.feature.map/cacheCleared";
if (!settings.value(cacheCleared).toBool())
{
qDebug() << "MapGUI::clearWikiMediaOSMCache: Clearing cache";
QDir dir(osmCachePath());
if (dir.exists())
{
QStringList filenames = dir.entryList({"osm_100-l-1-*.png"});
for (const auto& filename : filenames)
{
QFile file(dir.filePath(filename));
if (!file.remove()) {
qDebug() << "MapGUI::clearWikiMediaOSMCache: Failed to remove " << file.fileName();
}
}
}
settings.setValue(cacheCleared, true);
}
}
void MapGUI::applyMap2DSettings(bool reloadMap)
{
ui->map->setVisible(m_settings.m_map2DEnabled);
if (m_settings.m_map2DEnabled && reloadMap)
{
float stationLatitude = MainCore::instance()->getSettings().getLatitude();
float stationLongitude = MainCore::instance()->getSettings().getLongitude();
float stationAltitude = MainCore::instance()->getSettings().getAltitude();
QQuickItem *item = ui->map->rootObject();
QObject *object = item->findChild<QObject*>("map");
QGeoCoordinate coords;
double zoom;
if (object != nullptr)
{
// Save existing position of map
coords = object->property("center").value<QGeoCoordinate>();
zoom = object->property("zoomLevel").value<double>();
}
else
{
// Center on my location when map is first opened
coords.setLatitude(stationLatitude);
coords.setLongitude(stationLongitude);
coords.setAltitude(stationAltitude);
zoom = 10.0;
}
// Update API keys in servers
m_templateServer->setThunderforestAPIKey(thunderforestAPIKey());
m_templateServer->setMaptilerAPIKey(maptilerAPIKey());
m_mapTileServer->setThunderforestAPIKey(thunderforestAPIKey());
m_mapTileServer->setMaptilerAPIKey(maptilerAPIKey());
// Create the map using the specified provider
QQmlProperty::write(item, "smoothing", MainCore::instance()->getSettings().getMapSmoothing());
QQmlProperty::write(item, "mapProvider", m_settings.m_mapProvider);
QVariantMap parameters;
if (!m_settings.m_mapBoxAPIKey.isEmpty() && m_settings.m_mapProvider == "mapbox")
{
parameters["mapbox.map_id"] = "mapbox.satellite"; // The only one that works
parameters["mapbox.access_token"] = m_settings.m_mapBoxAPIKey;
}
if (!m_settings.m_mapBoxAPIKey.isEmpty() && m_settings.m_mapProvider == "mapboxgl")
{
parameters["mapboxgl.access_token"] = m_settings.m_mapBoxAPIKey;
if (!m_settings.m_mapBoxStyles.isEmpty())
parameters["mapboxgl.mapping.additional_style_urls"] = m_settings.m_mapBoxStyles;
}
if (m_settings.m_mapProvider == "maplibregl")
{
parameters["maplibregl.settings_template"] = "maptiler"; // Or "mapbox"
parameters["maplibregl.access_token"] = m_settings.m_maptilerAPIKey;
if (!m_settings.m_mapBoxStyles.isEmpty())
parameters["maplibregl.mapping.additional_style_urls"] = m_settings.m_mapBoxStyles;
}
if (m_settings.m_mapProvider == "osm")
{
// Allow user to specify URL
if (!m_settings.m_osmURL.isEmpty()) {
parameters["osm.mapping.custom.host"] = m_settings.m_osmURL; // E.g: "http://a.tile.openstreetmap.fr/hot/"
}
// Use our repo, so we can append API key
parameters["osm.mapping.providersrepository.address"] = QString("http://127.0.0.1:%1/").arg(m_osmPort);
// Use application specific cache, as other apps may not use API key so will have different images
QString cachePath = osmCachePath();
parameters["osm.mapping.cache.directory"] = cachePath;
// On Linux, we need to create the directory
QDir dir(cachePath);
if (!dir.exists()) {
dir.mkpath(cachePath);
}
}
QVariant retVal;
if (!QMetaObject::invokeMethod(item, "createMap", Qt::DirectConnection,
Q_RETURN_ARG(QVariant, retVal),
Q_ARG(QVariant, QVariant::fromValue(parameters)),
//Q_ARG(QVariant, mapType),
Q_ARG(QVariant, QVariant::fromValue(this))))
{
qCritical() << "MapGUI::applyMap2DSettings - Failed to invoke createMap";
}
QObject *newMap = retVal.value<QObject *>();
// Restore position of map
if (newMap != nullptr)
{
if (coords.isValid())
{
newMap->setProperty("zoomLevel", QVariant::fromValue(zoom));
newMap->setProperty("center", QVariant::fromValue(coords));
}
}
else
{
qCritical() << "MapGUI::applyMap2DSettings - createMap returned a nullptr";
}
supportedMapsChanged();
}
}
void MapGUI::redrawMap()
{
// An awful workaround for https://bugreports.qt.io/browse/QTBUG-100333
// Also used in ADS-B demod
QQuickItem *item = ui->map->rootObject();
if (item)
{
QObject *object = item->findChild<QObject*>("map");
if (object)
{
double zoom = object->property("zoomLevel").value<double>();
object->setProperty("zoomLevel", QVariant::fromValue(zoom+1.0));
object->setProperty("zoomLevel", QVariant::fromValue(zoom));
}
}
}
void MapGUI::showEvent(QShowEvent *event)
{
if (!event->spontaneous())
{
// Workaround for https://bugreports.qt.io/browse/QTBUG-100333
// MapQuickItems can be in wrong position when window is first displayed
m_redrawMapTimer.start(500);
}
}
bool MapGUI::eventFilter(QObject *obj, QEvent *event)
{
if (obj == ui->map)
{
if (event->type() == QEvent::Resize)
{
// Workaround for https://bugreports.qt.io/browse/QTBUG-100333
// MapQuickItems can be in wrong position after vertical resize
QResizeEvent *resizeEvent = static_cast<QResizeEvent *>(event);
QSize oldSize = resizeEvent->oldSize();
QSize size = resizeEvent->size();
if (oldSize.height() != size.height()) {
redrawMap();
}
}
}
return FeatureGUI::eventFilter(obj, event);
}
void MapGUI::orientationChanged(Qt::ScreenOrientation orientation)
{
(void) orientation;
// Need a delay before geometry() reflects new orientation
// https://bugreports.qt.io/browse/QTBUG-109127
QTimer::singleShot(200, [this]() {
displayToolbar();
});
}
void MapGUI::displayToolbar()
{
// Replace buttons with menu when window gets narrow
bool narrow = this->screen()->availableGeometry().width() < 400;
ui->layersMenu->setVisible(narrow);
bool overlayButtons = !narrow && ((m_settings.m_mapProvider == "osm") || m_settings.m_map3DEnabled);
ui->displayRain->setVisible(overlayButtons);
ui->displayClouds->setVisible(overlayButtons);
ui->displaySeaMarks->setVisible(overlayButtons);
ui->displayRailways->setVisible(overlayButtons);
ui->displayNASAGlobalImagery->setVisible(overlayButtons);
ui->displayMUF->setVisible(!narrow && m_settings.m_map3DEnabled);
ui->displayfoF2->setVisible(!narrow && m_settings.m_map3DEnabled);
ui->save->setVisible(m_settings.m_map3DEnabled);
}
void MapGUI::setEnableOverlay()
{
bool enable = m_settings.m_displayClouds || m_settings.m_displayRain || m_settings.m_displaySeaMarks || m_settings.m_displayRailways || m_settings.m_displayNASAGlobalImagery;
m_templateServer->setEnableOverlay(enable);
}
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()
{
QQuickItem *item = ui->map->rootObject();
QObject *object = item->findChild<QObject*>("map");
// Get list of map types
ui->mapTypes->blockSignals(true);
ui->mapTypes->clear();
if (object != nullptr)
{
// Mapbox plugin only works for Satellite imagary, despite what is indicated
if (m_settings.m_mapProvider == "mapbox")
{
ui->mapTypes->addItem("Satellite");
}
else
{
QVariant mapTypesVariant;
QMetaObject::invokeMethod(item, "getMapTypes", Q_RETURN_ARG(QVariant, mapTypesVariant));
QStringList mapTypes = mapTypesVariant.value<QStringList>();
for (int i = 0; i < mapTypes.size(); i++) {
ui->mapTypes->addItem(mapTypes[i]);
}
}
}
ui->mapTypes->blockSignals(false);
// Try to select desired map, if available
if (!m_settings.m_mapType.isEmpty())
{
int index = ui->mapTypes->findText(m_settings.m_mapType);
if (index != -1) {
ui->mapTypes->setCurrentIndex(index);
}
}
}
void MapGUI::on_mapTypes_currentIndexChanged(int index)
{
if (index >= 0)
{
QVariant mapType = index;
QMetaObject::invokeMethod(ui->map->rootObject(), "setMapType", Q_ARG(QVariant, mapType));
QString currentMap = ui->mapTypes->currentText();
if (!currentMap.isEmpty())
{
m_settings.m_mapType = currentMap;
m_settingsKeys.append("mapType");
applySettings();
}
}
}
void MapGUI::applyMap3DSettings(bool reloadMap)
{
#ifdef QT_WEBENGINE_FOUND
if (m_settings.m_map3DEnabled && ((m_cesium == nullptr) || reloadMap))
{
if (m_cesium == nullptr)
{
m_cesium = new CesiumInterface(&m_settings);
connect(m_cesium, &CesiumInterface::connected, this, &MapGUI::init3DMap);
connect(m_cesium, &CesiumInterface::received, this, &MapGUI::receivedCesiumEvent);
}
m_webServer->addSubstitution("/map/map/map3d.html", "$WS_PORT$", QString::number(m_cesium->serverPort()));
m_webServer->addSubstitution("/map/map/map3d.html", "$CESIUM_ION_API_KEY$", cesiumIonAPIKey());
//ui->web->page()->profile()->clearHttpCache();
ui->web->load(QUrl(QString("http://127.0.0.1:%1/map/map/map3d.html").arg(m_webPort)));
//ui->web->load(QUrl(QString("http://webglreport.com/")));
//ui->web->load(QUrl(QString("https://sandcastle.cesium.com/")));
//ui->web->load(QUrl("chrome://gpu/"));
ui->web->show();
}
else if (!m_settings.m_map3DEnabled && (m_cesium != nullptr))
{
ui->web->setHtml("<html></html>");
m_cesium->deleteLater();
m_cesium = nullptr;
}
ui->web->setVisible(m_settings.m_map3DEnabled);
if (m_cesium && m_cesium->isConnected())
{
m_cesium->setTerrain(m_settings.m_terrain, maptilerAPIKey());
m_cesium->setBuildings(m_settings.m_buildings);
m_cesium->setSunLight(m_settings.m_sunLightEnabled);
m_cesium->setCameraReferenceFrame(m_settings.m_eciCamera);
m_cesium->setAntiAliasing(m_settings.m_antiAliasing);
m_cesium->getDateTime();
m_cesium->showMUF(m_settings.m_displayMUF);
m_cesium->showfoF2(m_settings.m_displayfoF2);
m_cesium->showLayer("rain", m_settings.m_displayRain);
m_cesium->showLayer("clouds", m_settings.m_displayClouds);
m_cesium->showLayer("seaMarks", m_settings.m_displaySeaMarks);
m_cesium->showLayer("railways", m_settings.m_displayRailways);
m_cesium->showLayer("nasaGlobalImagery", m_settings.m_displayNASAGlobalImagery);
applyNASAGlobalImagerySettings();
m_objectMapModel.allUpdated();
m_imageMapModel.allUpdated();
m_polygonMapModel.allUpdated();
m_polylineMapModel.allUpdated();
}
MapSettings::MapItemSettings *ionosondeItemSettings = getItemSettings("Ionosonde Stations");
m_giro->getIndexPeriodically((m_settings.m_displayMUF || m_settings.m_displayfoF2) ? 15 : 0);
if (ionosondeItemSettings) {
m_giro->getDataPeriodically(ionosondeItemSettings->m_enabled ? 2 : 0);
}
#else
ui->displayMUF->setVisible(false);
ui->displayfoF2->setVisible(false);
m_objectMapModel.allUpdated();
m_imageMapModel.allUpdated();
m_polygonMapModel.allUpdated();
m_polylineMapModel.allUpdated();
#endif
}
void MapGUI::statusChanged(QQuickWidget::Status status)
{
// In Qt6, it seems a page can be loaded multiple times, and this slot is too
// This causes a problem in that the map created by the call to createMap can
// be lost, so we recreate it here each time
if (status == QQuickWidget::Ready) {
applyMap2DSettings(true);
}
}
#ifdef QT_WEBENGINE_FOUND
void MapGUI::renderProcessTerminated(QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode)
{
qDebug() << "MapGUI::renderProcessTerminated: " << terminationStatus << "exitCode" << exitCode;
}
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
void MapGUI::loadingChanged(const QWebEngineLoadingInfo &loadingInfo)
{
if (loadingInfo.status() == QWebEngineLoadingInfo::LoadFailedStatus)
{
qDebug() << "MapGUI::loadingChanged: Failed to load " << loadingInfo.url().toString()
<< "errorString: " << loadingInfo.errorString() << " "
<< "errorDomain:" << loadingInfo.errorDomain()
<< "errorCode:" << loadingInfo.errorCode()
;
}
}
#endif
#endif
void MapGUI::init3DMap()
{
#ifdef QT_WEBENGINE_FOUND
qDebug() << "MapGUI::init3DMap";
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->setBuildings(m_settings.m_buildings);
m_cesium->setSunLight(m_settings.m_sunLightEnabled);
m_cesium->setCameraReferenceFrame(m_settings.m_eciCamera);
m_cesium->setAntiAliasing(m_settings.m_antiAliasing);
m_cesium->getDateTime();
m_objectMapModel.allUpdated();
m_imageMapModel.allUpdated();
m_polygonMapModel.allUpdated();
m_polylineMapModel.allUpdated();
// Set 3D view after loading initial objects
m_cesium->setHomeView(stationLatitude, stationLongitude);
m_cesium->showMUF(m_settings.m_displayMUF);
m_cesium->showfoF2(m_settings.m_displayfoF2);
m_cesium->showLayer("rain", m_settings.m_displayRain);
m_cesium->showLayer("clouds", m_settings.m_displayClouds);
m_cesium->showLayer("seaMarks", m_settings.m_displaySeaMarks);
m_cesium->showLayer("railways", m_settings.m_displayRailways);
applyNASAGlobalImagerySettings();
if (!m_radarPath.isEmpty()) {
m_cesium->setLayerSettings("rain", {"path", "show"}, {m_radarPath, m_settings.m_displayRain});
}
if (!m_satellitePath.isEmpty()) {
m_cesium->setLayerSettings("clouds", {"path", "show"}, {m_satellitePath, m_settings.m_displayClouds});
}
#endif
}
void MapGUI::displaySettings()
{
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_settings.m_title);
setTitle(m_settings.m_title);
blockApplySettings(true);
ui->displayNames->setChecked(m_settings.m_displayNames);
ui->displaySelectedGroundTracks->setChecked(m_settings.m_displaySelectedGroundTracks);
ui->displayAllGroundTracks->setChecked(m_settings.m_displayAllGroundTracks);
ui->displayRain->setChecked(m_settings.m_displayRain);
m_displayRain->setChecked(m_settings.m_displayRain);
m_mapTileServer->setDisplayRain(m_settings.m_displayRain);
ui->displayClouds->setChecked(m_settings.m_displayClouds);
m_displayClouds->setChecked(m_settings.m_displayClouds);
m_mapTileServer->setDisplayClouds(m_settings.m_displayClouds);
ui->displaySeaMarks->setChecked(m_settings.m_displaySeaMarks);
m_displaySeaMarks->setChecked(m_settings.m_displaySeaMarks);
m_mapTileServer->setDisplaySeaMarks(m_settings.m_displaySeaMarks);
ui->displayRailways->setChecked(m_settings.m_displayRailways);
m_displayRailways->setChecked(m_settings.m_displayRailways);
m_mapTileServer->setDisplayRailways(m_settings.m_displayRailways);
ui->displayNASAGlobalImagery->setChecked(m_settings.m_displayNASAGlobalImagery);
m_displayNASAGlobalImagery->setChecked(m_settings.m_displayNASAGlobalImagery);
ui->nasaGlobalImageryIdentifier->setVisible(m_settings.m_displayNASAGlobalImagery);
ui->nasaGlobalImageryOpacity->setVisible(m_settings.m_displayNASAGlobalImagery);
ui->nasaGlobalImageryOpacityText->setVisible(m_settings.m_displayNASAGlobalImagery);
ui->nasaGlobalImageryOpacity->setValue(m_settings.m_nasaGlobalImageryOpacity);
if (m_nasaWidget) {
m_nasaWidget->setVisible(m_settings.m_displayNASAGlobalImagery);
}
m_mapTileServer->setDisplayNASAGlobalImagery(m_settings.m_displayNASAGlobalImagery);
ui->displayMUF->setChecked(m_settings.m_displayMUF);
m_displayMUF->setChecked(m_settings.m_displayMUF);
ui->displayfoF2->setChecked(m_settings.m_displayfoF2);
m_displayfoF2->setChecked(m_settings.m_displayfoF2);
m_objectMapModel.setDisplayNames(m_settings.m_displayNames);
m_objectMapModel.setDisplaySelectedGroundTracks(m_settings.m_displaySelectedGroundTracks);
m_objectMapModel.setDisplayAllGroundTracks(m_settings.m_displayAllGroundTracks);
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);
setEnableOverlay();
displayToolbar();
applyMap2DSettings(true);
applyMap3DSettings(true);
getRollupContents()->restoreState(m_rollupState);
blockApplySettings(false);
}
void MapGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
{
BasicFeatureSettingsDialog dialog(this);
dialog.setTitle(m_settings.m_title);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIFeatureSetIndex(m_settings.m_reverseAPIFeatureSetIndex);
dialog.setReverseAPIFeatureIndex(m_settings.m_reverseAPIFeatureIndex);
dialog.setDefaultTitle(m_displayedName);
dialog.move(p);
new DialogPositioner(&dialog, false);
dialog.exec();
m_settings.m_title = dialog.getTitle();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIFeatureSetIndex = dialog.getReverseAPIFeatureSetIndex();
m_settings.m_reverseAPIFeatureIndex = dialog.getReverseAPIFeatureIndex();
setTitle(m_settings.m_title);
setTitleColor(m_settings.m_rgbColor);
m_settingsKeys.append("title");
m_settingsKeys.append("rgbColor");
m_settingsKeys.append("useReverseAPI");
m_settingsKeys.append("reverseAPIAddress");
m_settingsKeys.append("reverseAPIPort");
m_settingsKeys.append("reverseAPIFeatureSetIndex");
m_settingsKeys.append("reverseAPIFeatureIndex");
applySettings();
}
resetContextMenuType();
}
void MapGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
Map::MsgConfigureMap* message = Map::MsgConfigureMap::create(m_settings, m_settingsKeys, force);
m_map->getInputMessageQueue()->push(message);
}
m_settingsKeys.clear();
}
void MapGUI::on_maidenhead_clicked()
{
MapMaidenheadDialog dialog;
new DialogPositioner(&dialog, true);
dialog.exec();
}
void MapGUI::on_displayNames_clicked(bool checked)
{
m_settings.m_displayNames = checked;
m_objectMapModel.setDisplayNames(checked);
}
void MapGUI::on_displaySelectedGroundTracks_clicked(bool checked)
{
m_settings.m_displaySelectedGroundTracks = checked;
m_objectMapModel.setDisplaySelectedGroundTracks(checked);
}
void MapGUI::on_displayAllGroundTracks_clicked(bool checked)
{
m_settings.m_displayAllGroundTracks = checked;
m_objectMapModel.setDisplayAllGroundTracks(checked);
}
void MapGUI::on_displayRain_clicked(bool checked)
{
if (this->sender() != ui->displayRain) {
ui->displayRain->setChecked(checked);
}
if (this->sender() != m_displayRain) {
m_displayRain->setChecked(checked);
}
m_settings.m_displayRain = checked;
m_mapTileServer->setDisplayRain(m_settings.m_displayRain);
setEnableOverlay();
clearOSMCache();
applyMap2DSettings(true);
if (m_cesium) {
m_cesium->showLayer("rain", m_settings.m_displayRain);
}
}
void MapGUI::on_displayClouds_clicked(bool checked)
{
if (this->sender() != ui->displayClouds) {
ui->displayClouds->setChecked(checked);
}
if (this->sender() != m_displayClouds) {
m_displayClouds->setChecked(checked);
}
m_settings.m_displayClouds = checked;
m_mapTileServer->setDisplayClouds(m_settings.m_displayClouds);
setEnableOverlay();
clearOSMCache();
applyMap2DSettings(true);
if (m_cesium) {
m_cesium->showLayer("clouds", m_settings.m_displayClouds);
}
}
void MapGUI::on_displaySeaMarks_clicked(bool checked)
{
if (this->sender() != ui->displaySeaMarks) {
ui->displaySeaMarks->setChecked(checked);
}
if (this->sender() != m_displaySeaMarks) {
m_displaySeaMarks->setChecked(checked);
}
m_settings.m_displaySeaMarks = checked;
m_mapTileServer->setDisplaySeaMarks(m_settings.m_displaySeaMarks);
setEnableOverlay();
clearOSMCache();
applyMap2DSettings(true);
if (m_cesium) {
m_cesium->showLayer("seaMarks", m_settings.m_displaySeaMarks);
}
}
void MapGUI::on_displayRailways_clicked(bool checked)
{
if (this->sender() != ui->displayRailways) {
ui->displayRailways->setChecked(checked);
}
if (this->sender() != m_displayRailways) {
m_displayRailways->setChecked(checked);
}
m_settings.m_displayRailways = checked;
m_mapTileServer->setDisplayRailways(m_settings.m_displayRailways);
setEnableOverlay();
clearOSMCache();
applyMap2DSettings(true);
if (m_cesium) {
m_cesium->showLayer("railways", m_settings.m_displayRailways);
}
}
void MapGUI::on_displayNASAGlobalImagery_clicked(bool checked)
{
if (this->sender() != ui->displayNASAGlobalImagery) {
ui->displayNASAGlobalImagery->setChecked(checked);
}
if (this->sender() != m_displayNASAGlobalImagery) {
m_displayNASAGlobalImagery->setChecked(checked);
}
m_settings.m_displayNASAGlobalImagery = checked;
ui->nasaGlobalImageryIdentifier->setVisible(m_settings.m_displayNASAGlobalImagery);
ui->nasaGlobalImageryOpacity->setVisible(m_settings.m_displayNASAGlobalImagery);
ui->nasaGlobalImageryOpacityText->setVisible(m_settings.m_displayNASAGlobalImagery);
if (m_nasaWidget) {
m_nasaWidget->setVisible(m_settings.m_displayNASAGlobalImagery);
}
m_mapTileServer->setDisplayNASAGlobalImagery(m_settings.m_displayNASAGlobalImagery);
setEnableOverlay();
clearOSMCache();
applyMap2DSettings(true);
if (m_cesium) {
m_cesium->showLayer("NASAGlobalImagery", m_settings.m_displayNASAGlobalImagery);
}
}
void MapGUI::on_nasaGlobalImageryIdentifier_currentIndexChanged(int index)
{
if ((index >= 0) && (index < m_nasaDataSets.size()))
{
m_settings.m_nasaGlobalImageryIdentifier = m_nasaDataSets[index].m_identifier;
// MODIS_Terra_Aerosol/default/2014-04-09/GoogleMapsCompatible_Level6
QString date = "default"; // FIXME: Get from 3D map
QString path = QString("%1/default/%2/%3").arg(m_settings.m_nasaGlobalImageryIdentifier).arg(date).arg(m_nasaDataSets[index].m_tileMatrixSet);
m_mapTileServer->setNASAGlobalImageryPath(path);
QString format = m_nasaDataSets[index].m_format;
if (format == "image/jpeg") {
m_mapTileServer->setNASAGlobalImageryFormat("jpeg");
} else {
m_mapTileServer->setNASAGlobalImageryFormat("png");
}
setEnableOverlay();
clearOSMCache();
applyMap2DSettings(true);
applyNASAGlobalImagerySettings();
}
}
void MapGUI::applyNASAGlobalImagerySettings()
{
int index = ui->nasaGlobalImageryIdentifier->currentIndex();
// Update 3D map
if (m_cesium)
{
if ((index >= 0) && (index < m_nasaDataSets.size()))
{
QString format = m_nasaDataSets[index].m_format;
QString extension = (format == "image/jpeg") ? "jpg" : "png";
// Does cesium want epsg3857 or epsg4326
//QString url = QString("https://gibs.earthdata.nasa.gov/wmts/epsg4326/best/%1/default/{Time}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.%2").arg(m_settings.m_nasaGlobalImageryIdentifier).arg(extension);
//QString url = QString("https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/%1/default/default/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.%2").arg(m_settings.m_nasaGlobalImageryIdentifier).arg(extension);
QString url = QString("https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/%1/default/{Time}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.%2").arg(m_settings.m_nasaGlobalImageryIdentifier).arg(extension);
QString show = m_settings.m_displayNASAGlobalImagery ? "true" : "false";
m_cesium->setLayerSettings("NASAGlobalImagery",
{"url", "tileMatrixSet", "format", "show", "opacity", "dates"},
{url, m_nasaDataSets[index].m_tileMatrixSet, format, m_settings.m_displayNASAGlobalImagery, m_settings.m_nasaGlobalImageryOpacity, m_nasaDataSets[index].m_dates});
}
}
// Update NASA table / legend / description
if ((index >= 0) && (m_nasaDataSets[index].m_legends.size() > 0))
{
m_legend = &m_nasaDataSets[index].m_legends[0];
m_nasaGlobalImagery.downloadLegend(*m_legend);
m_descriptionWidget->setHtml("");
if (m_nasaMetaData.m_layers.contains(m_settings.m_nasaGlobalImageryIdentifier))
{
QString url = m_nasaMetaData.m_layers.value(m_settings.m_nasaGlobalImageryIdentifier).m_descriptionURL;
m_nasaGlobalImagery.downloadHTML(url);
}
displayNASAMetaData();
m_nasaWidget->setVisible(m_settings.m_displayNASAGlobalImagery);
}
else
{
if (m_nasaWidget) {
m_nasaWidget->setVisible(false);
}
}
}
void MapGUI::on_nasaGlobalImageryOpacity_valueChanged(int value)
{
m_settings.m_nasaGlobalImageryOpacity = value;
ui->nasaGlobalImageryOpacityText->setText(QString("%1%").arg(m_settings.m_nasaGlobalImageryOpacity));
if (m_cesium)
{
m_cesium->setLayerSettings("NASAGlobalImagery",
{"opacity"},
{m_settings.m_nasaGlobalImageryOpacity}
);
}
}
void MapGUI::on_displayMUF_clicked(bool checked)
{
if (this->sender() != ui->displayMUF) {
ui->displayMUF->setChecked(checked);
}
if (this->sender() != m_displayMUF) {
m_displayMUF->setChecked(checked);
}
m_settings.m_displayMUF = checked;
// Only call show if disabling, so we don't get two updates
// (as getMUFPeriodically results in a call to showMUF when the data is available)
m_giro->getIndexPeriodically((m_settings.m_displayMUF || m_settings.m_displayfoF2) ? 15 : 0);
if (m_cesium && !m_settings.m_displayMUF) {
m_cesium->showMUF(m_settings.m_displayMUF);
}
}
void MapGUI::on_displayfoF2_clicked(bool checked)
{
if (this->sender() != ui->displayfoF2) {
ui->displayfoF2->setChecked(checked);
}
if (this->sender() != m_displayfoF2) {
m_displayfoF2->setChecked(checked);
}
m_settings.m_displayfoF2 = checked;
m_giro->getIndexPeriodically((m_settings.m_displayMUF || m_settings.m_displayfoF2) ? 15 : 0);
if (m_cesium && !m_settings.m_displayfoF2) {
m_cesium->showfoF2(m_settings.m_displayfoF2);
}
}
void MapGUI::createLayersMenu()
{
QMenu *menu = new QMenu();
m_displayRain = menu->addAction("Weather radar");
m_displayRain->setCheckable(true);
m_displayRain->setToolTip("Display weather radar (rain/snow)");
connect(m_displayRain, &QAction::triggered, this, &MapGUI::on_displayRain_clicked);
m_displayClouds = menu->addAction("Satellite IR");
m_displayClouds->setCheckable(true);
m_displayClouds->setToolTip("Display satellite infra-red (clouds)");
connect(m_displayClouds, &QAction::triggered, this, &MapGUI::on_displayClouds_clicked);
m_displaySeaMarks = menu->addAction("Sea marks");
m_displaySeaMarks->setCheckable(true);
m_displaySeaMarks->setToolTip("Display sea marks");
//QIcon seaMarksIcon;
//seaMarksIcon.addPixmap(QPixmap("://map/icons/anchor.png"), QIcon::Normal, QIcon::On);
//displaySeaMarks->setIcon(seaMarksIcon);
connect(m_displaySeaMarks, &QAction::triggered, this, &MapGUI::on_displaySeaMarks_clicked);
m_displayRailways = menu->addAction("Railways");
m_displayRailways->setCheckable(true);
m_displayRailways->setToolTip("Display railways");
connect(m_displayRailways, &QAction::triggered, this, &MapGUI::on_displayRailways_clicked);
m_displayNASAGlobalImagery = menu->addAction("NASA Global Imagery");
m_displayNASAGlobalImagery->setCheckable(true);
m_displayNASAGlobalImagery->setToolTip("Display NASA Global Imagery");
connect(m_displayNASAGlobalImagery, &QAction::triggered, this, &MapGUI::on_displayNASAGlobalImagery_clicked);
m_displayMUF = menu->addAction("MUF");
m_displayMUF->setCheckable(true);
m_displayMUF->setToolTip("Display Maximum Usable Frequency contours");
connect(m_displayMUF, &QAction::triggered, this, &MapGUI::on_displayMUF_clicked);
m_displayfoF2 = menu->addAction("foF2");
m_displayfoF2->setCheckable(true);
m_displayfoF2->setToolTip("Display F2 layer critical frequency contours");
connect(m_displayfoF2, &QAction::triggered, this, &MapGUI::on_displayfoF2_clicked);
ui->layersMenu->setMenu(menu);
}
void MapGUI::on_layersMenu_clicked()
{
}
void MapGUI::on_find_returnPressed()
{
find(ui->find->text().trimmed());
}
void MapGUI::geoReply()
{
QGeoCodeReply *pQGeoCode = dynamic_cast<QGeoCodeReply*>(sender());
if ((pQGeoCode != nullptr) && (pQGeoCode->error() == QGeoCodeReply::NoError))
{
QList<QGeoLocation> qGeoLocs = pQGeoCode->locations();
QQuickItem *item = ui->map->rootObject();
QObject *map = item->findChild<QObject*>("map");
if (qGeoLocs.size() == 1)
{
// Only one result, so centre map on that
QGeoCoordinate coord = qGeoLocs.at(0).coordinate();
map->setProperty("center", QVariant::fromValue(coord));
if (m_cesium) {
m_cesium->setView(coord.latitude(), coord.longitude());
}
}
else if (qGeoLocs.size() == 0)
{
qDebug() << "MapGUI::geoReply: No location found for address";
QApplication::beep();
}
else
{
// Show dialog allowing user to select from the results
MapLocationDialog dialog(qGeoLocs);
new DialogPositioner(&dialog, true);
if (dialog.exec() == QDialog::Accepted)
{
QGeoCoordinate coord = dialog.m_selectedLocation.coordinate();
map->setProperty("center", QVariant::fromValue(coord));
if (m_cesium) {
m_cesium->setView(coord.latitude(), coord.longitude());
}
}
}
}
else
{
qWarning() << "MapGUI::geoReply: GeoCode error: " << pQGeoCode->error();
}
pQGeoCode->deleteLater();
}
// Free keys, so no point in stealing them :)
QString MapGUI::thunderforestAPIKey() const
{
return m_settings.m_thunderforestAPIKey.isEmpty() ? "3e1f614f78a345459931ba3c898e975e" : m_settings.m_thunderforestAPIKey;
}
QString MapGUI::maptilerAPIKey() const
{
return m_settings.m_maptilerAPIKey.isEmpty() ? "q2RVNAe3eFKCH4XsrE3r" : m_settings.m_maptilerAPIKey;
}
QString MapGUI::cesiumIonAPIKey() const
{
return m_settings.m_cesiumIonAPIKey.isEmpty()
? "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyNTcxMDA2OC0yNTIzLTQxMGYtYTNiMS1iM2I3MDFhNWVlMDYiLCJpZCI6ODEyMDUsImlhdCI6MTY0MzY2OTIzOX0.A7NchU4LzaNsuAUpsrA9ZwekOJfMoNcja-8XeRdRoIg"
: m_settings.m_cesiumIonAPIKey;
}
void MapGUI::find(const QString& target)
{
if (!target.isEmpty())
{
QQuickItem *item = ui->map->rootObject();
QObject *map = item->findChild<QObject*>("map");
if (map != nullptr)
{
// Search as:
// latitude and longitude
// Maidenhead locator
// object name
// address
float latitude, longitude;
if (Units::stringToLatitudeAndLongitude(target, latitude, longitude))
{
map->setProperty("center", QVariant::fromValue(QGeoCoordinate(latitude, longitude)));
if (m_cesium) {
m_cesium->setView(latitude, longitude);
}
}
else if (Maidenhead::fromMaidenhead(target, latitude, longitude))
{
map->setProperty("center", QVariant::fromValue(QGeoCoordinate(latitude, longitude)));
if (m_cesium) {
m_cesium->setView(latitude, longitude);
}
}
else
{
ObjectMapItem *mapItem = (ObjectMapItem *)m_objectMapModel.findMapItem(target);
if (mapItem != nullptr)
{
map->setProperty("center", QVariant::fromValue(mapItem->getCoordinates()));
if (m_cesium) {
m_cesium->track(target);
}
m_objectMapModel.moveToFront(m_objectMapModel.findMapItemIndex(target).row());
return;
}
PolylineMapItem *polylineMapItem = (PolylineMapItem *)m_polylineMapModel.findMapItem(target);
if (polylineMapItem != nullptr)
{
map->setProperty("center", QVariant::fromValue(polylineMapItem->getCoordinates()));
if (m_cesium) {
m_cesium->track(target);
}
//m_polylineMapModel.moveToFront(m_polylineMapModel.findMapItemIndex(target).row());
return;
}
PolygonMapItem *polygonMapItem = (PolygonMapItem *)m_polylineMapModel.findMapItem(target);
if (polygonMapItem != nullptr)
{
map->setProperty("center", QVariant::fromValue(polygonMapItem->getCoordinates()));
if (m_cesium) {
m_cesium->track(target);
}
//m_polylineMapModel.moveToFront(m_polylineMapModel.findMapItemIndex(target).row());
return;
}
// Search as an address
QGeoServiceProvider* geoSrv = new QGeoServiceProvider("osm");
if (geoSrv != nullptr)
{
QLocale qLocaleC(QLocale::C, QLocale::AnyCountry);
geoSrv->setLocale(qLocaleC);
QGeoCodeReply *pQGeoCode = geoSrv->geocodingManager()->geocode(target);
if (pQGeoCode) {
QObject::connect(pQGeoCode, &QGeoCodeReply::finished, this, &MapGUI::geoReply);
} else {
qDebug() << "MapGUI::find: GeoCoding failed";
}
}
else
{
qDebug() << "MapGUI::find: osm not available";
}
}
}
}
}
void MapGUI::track3D(const QString& target)
{
if (m_cesium) {
m_cesium->track(target);
}
}
void MapGUI::on_save_clicked()
{
if (m_cesium)
{
m_fileDialog.setAcceptMode(QFileDialog::AcceptSave);
m_fileDialog.setNameFilter("*.kml *.kmz");
if (m_fileDialog.exec())
{
QStringList fileNames = m_fileDialog.selectedFiles();
if (fileNames.size() > 0) {
m_cesium->save(fileNames[0], getDataDir());
}
}
}
}
void MapGUI::on_deleteAll_clicked()
{
m_objectMapModel.removeAll();
m_imageMapModel.removeAll();
m_polygonMapModel.removeAll();
m_polylineMapModel.removeAll();
if (m_cesium)
{
m_cesium->removeAllCZMLEntities();
m_cesium->removeAllImages();
}
}
void MapGUI::on_displaySettings_clicked()
{
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);
if (dialog.exec() == QDialog::Accepted)
{
if (dialog.m_osmURLChanged) {
clearOSMCache();
}
displayToolbar();
applyMap2DSettings(dialog.m_map2DSettingsChanged);
applyMap3DSettings(dialog.m_map3DSettingsChanged);
m_settingsKeys.append(dialog.m_settingsKeysChanged);
applySettings();
m_objectMapModel.allUpdated();
m_imageMapModel.allUpdated();
m_polygonMapModel.allUpdated();
m_polylineMapModel.allUpdated();
}
}
void MapGUI::on_beacons_clicked()
{
m_beaconDialog.show();
m_beaconDialog.raise();
}
void MapGUI::on_ibpBeacons_clicked()
{
m_ibpBeaconDialog.show();
m_ibpBeaconDialog.raise();
}
void MapGUI::on_radiotime_clicked()
{
m_radioTimeDialog.updateTable();
m_radioTimeDialog.show();
m_radioTimeDialog.raise();
}
QString MapGUI::getDataDir()
{
// Get directory to store app data in (aircraft & airport databases and user-definable icons)
QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
// First dir is writable
return locations[0];
}
QString MapGUI::getBeaconFilename()
{
return MapGUI::getDataDir() + "/iaru_beacons.csv";
}
QQuickItem *MapGUI::getMapItem() {
return ui->map->rootObject();
}
void MapGUI::receivedCesiumEvent(const QJsonObject &obj)
{
if (obj.contains("event"))
{
QString event = obj.value("event").toString();
if (event == "selected")
{
if (obj.contains("id")) {
m_objectMapModel.setSelected3D(obj.value("id").toString());
} else {
m_objectMapModel.setSelected3D("");
}
}
else if (event == "tracking")
{
if (obj.contains("id")) {
//m_objectMapModel.setTarget(obj.value("id").toString());
} else {
//m_objectMapModel.setTarget("");
}
}
else if (event == "clock")
{
if (m_map)
{
QDateTime mapDateTime = QDateTime::fromString(obj.value("currentTime").toString(), Qt::ISODateWithMs);
QDateTime systemDateTime = QDateTime::fromString(obj.value("systemTime").toString(), Qt::ISODateWithMs);
double multiplier = obj.value("multiplier").toDouble();
bool canAnimate = obj.value("canAnimate").toBool();
bool shouldAnimate = obj.value("shouldAnimate").toBool();
m_map->setMapDateTime(mapDateTime, systemDateTime, canAnimate && shouldAnimate ? multiplier : 0.0);
updateGIRO(mapDateTime);
}
}
else if (event == "link")
{
QString url = obj.value("url").toString();
linkClicked(url);
}
}
else
{
qDebug() << "MapGUI::receivedCesiumEvent - Unexpected event: " << obj;
}
}
// Handle link clicked in 2D Map Text box or 3D map infobox.
void MapGUI::linkClicked(const QString& url)
{
if (url.startsWith("sdrangel-kiwisdr://"))
{
QString kiwiURL = url.mid(19);
openKiwiSDR(kiwiURL);
}
else if (url.startsWith("sdrangel-spyserver://"))
{
QString spyServerURL = url.mid(21);
openSpyServer(spyServerURL);
}
}
// Open a KiwiSDR RX device
void MapGUI::openKiwiSDR(const QString& url)
{
// Create DeviceSet
MainCore *mainCore = MainCore::instance();
unsigned int deviceSetIndex = mainCore->getDeviceSets().size();
MainCore::MsgAddDeviceSet *msg = MainCore::MsgAddDeviceSet::create(0);
mainCore->getMainMessageQueue()->push(msg);
// Switch to KiwiSDR
int nbSamplingDevices = DeviceEnumerator::instance()->getNbRxSamplingDevices();
bool found = false;
QString hwType = "KiwiSDR";
for (int i = 0; i < nbSamplingDevices; i++)
{
const PluginInterface::SamplingDevice *samplingDevice;
samplingDevice = DeviceEnumerator::instance()->getRxSamplingDevice(i);
if (!hwType.isEmpty() && (hwType != samplingDevice->hardwareId)) {
continue;
}
int direction = 0;
MainCore::MsgSetDevice *msg = MainCore::MsgSetDevice::create(deviceSetIndex, i, direction);
mainCore->getMainMessageQueue()->push(msg);
found = true;
break;
}
if (!found)
{
qCritical() << "MapGUI::openKiwiSDR: Failed to find KiwiSDR";
return;
}
// Wait until device is created - is there a better way?
DeviceSet *deviceSet = nullptr;
do
{
QTime dieTime = QTime::currentTime().addMSecs(100);
while (QTime::currentTime() < dieTime) {
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}
if (mainCore->getDeviceSets().size() > deviceSetIndex)
{
deviceSet = mainCore->getDeviceSets()[deviceSetIndex];
}
}
while (!deviceSet);
// Move to same workspace
//getWorkspaceIndex();
// Set address setting
QStringList deviceSettingsKeys = {"serverAddress"};
SWGSDRangel::SWGDeviceSettings response;
response.init();
SWGSDRangel::SWGKiwiSDRSettings *deviceSettings = response.getKiwiSdrSettings();
deviceSettings->setServerAddress(new QString(url));
QString errorMessage;
deviceSet->m_deviceAPI->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, errorMessage);
}
// Open a RemoteTCPInput device to use for SpyServer
void MapGUI::openSpyServer(const QString& url)
{
// Create DeviceSet
MainCore *mainCore = MainCore::instance();
unsigned int deviceSetIndex = mainCore->getDeviceSets().size();
MainCore::MsgAddDeviceSet *msg = MainCore::MsgAddDeviceSet::create(0);
mainCore->getMainMessageQueue()->push(msg);
// Switch to RemoteTCPInput
int nbSamplingDevices = DeviceEnumerator::instance()->getNbRxSamplingDevices();
bool found = false;
QString hwType = "RemoteTCPInput";
for (int i = 0; i < nbSamplingDevices; i++)
{
const PluginInterface::SamplingDevice *samplingDevice;
samplingDevice = DeviceEnumerator::instance()->getRxSamplingDevice(i);
if (!hwType.isEmpty() && (hwType != samplingDevice->hardwareId)) {
continue;
}
int direction = 0;
MainCore::MsgSetDevice *msg = MainCore::MsgSetDevice::create(deviceSetIndex, i, direction);
mainCore->getMainMessageQueue()->push(msg);
found = true;
break;
}
if (!found)
{
qCritical() << "MapGUI::openSpyServer: Failed to find RemoteTCPInput";
return;
}
// Wait until device is created - is there a better way?
DeviceSet *deviceSet = nullptr;
do
{
QTime dieTime = QTime::currentTime().addMSecs(100);
while (QTime::currentTime() < dieTime) {
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}
if (mainCore->getDeviceSets().size() > deviceSetIndex)
{
deviceSet = mainCore->getDeviceSets()[deviceSetIndex];
}
}
while (!deviceSet);
// Move to same workspace
//getWorkspaceIndex();
// Set address/port setting
QStringList address = url.split(":");
QStringList deviceSettingsKeys = {"dataAddress", "dataPort", "protocol"};
SWGSDRangel::SWGDeviceSettings response;
response.init();
SWGSDRangel::SWGRemoteTCPInputSettings *deviceSettings = response.getRemoteTcpInputSettings();
deviceSettings->setDataAddress(new QString(address[0]));
deviceSettings->setDataPort(address[1].toInt());
deviceSettings->setProtocol(new QString("Spy Server"));
QString errorMessage;
deviceSet->m_deviceAPI->getSampleSource()->webapiSettingsPutPatch(false, deviceSettingsKeys, response, errorMessage);
}
#ifdef QT_WEBENGINE_FOUND
void MapGUI::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest)
{
fullScreenRequest.accept();
if (fullScreenRequest.toggleOn())
{
ui->web->setParent(nullptr);
ui->web->showFullScreen();
}
else
{
ui->splitter->addWidget(ui->web);
}
}
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
void MapGUI::downloadRequested(QWebEngineDownloadRequest *download)
{
download->accept();
}
#else
void MapGUI::downloadRequested(QWebEngineDownloadItem *download)
{
download->accept();
}
#endif
#endif
void MapGUI::preferenceChanged(int elementType)
{
Preferences::ElementType pref = (Preferences::ElementType)elementType;
if ((pref == Preferences::Latitude) || (pref == Preferences::Longitude) || (pref == Preferences::Altitude))
{
float stationLatitude = MainCore::instance()->getSettings().getLatitude();
float stationLongitude = MainCore::instance()->getSettings().getLongitude();
float stationAltitude = MainCore::instance()->getSettings().getAltitude();
QGeoCoordinate stationPosition(stationLatitude, stationLongitude, stationAltitude);
QGeoCoordinate previousPosition(m_azEl.getLocationSpherical().m_latitude, m_azEl.getLocationSpherical().m_longitude, m_azEl.getLocationSpherical().m_altitude);
if (stationPosition != previousPosition)
{
// Update position of station
m_azEl.setLocation(stationLatitude, stationLongitude, stationAltitude);
m_antennaMapItem.setLatitude(stationLatitude);
m_antennaMapItem.setLongitude(stationLongitude);
m_antennaMapItem.setAltitude(stationAltitude);
delete m_antennaMapItem.getPositionDateTime();
m_antennaMapItem.setPositionDateTime(new QString(QDateTime::currentDateTime().toString(Qt::ISODateWithMs)));
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;
}
}
}
}
else if (pref == Preferences::StationName)
{
// Update station name
m_antennaMapItem.setLabel(new QString(MainCore::instance()->getSettings().getStationName()));
m_antennaMapItem.setText(new QString(MainCore::instance()->getSettings().getStationName()));
update(m_map, &m_antennaMapItem, "Station");
}
else if (pref == Preferences::MapSmoothing)
{
QQuickItem *item = ui->map->rootObject();
QQmlProperty::write(item, "smoothing", MainCore::instance()->getSettings().getMapSmoothing());
}
}
void MapGUI::makeUIConnections()
{
QObject::connect(ui->displayNames, &ButtonSwitch::clicked, this, &MapGUI::on_displayNames_clicked);
QObject::connect(ui->displayAllGroundTracks, &ButtonSwitch::clicked, this, &MapGUI::on_displayAllGroundTracks_clicked);
QObject::connect(ui->displaySelectedGroundTracks, &ButtonSwitch::clicked, this, &MapGUI::on_displaySelectedGroundTracks_clicked);
QObject::connect(ui->displayRain, &ButtonSwitch::clicked, this, &MapGUI::on_displayRain_clicked);
QObject::connect(ui->displayClouds, &ButtonSwitch::clicked, this, &MapGUI::on_displayClouds_clicked);
QObject::connect(ui->displaySeaMarks, &ButtonSwitch::clicked, this, &MapGUI::on_displaySeaMarks_clicked);
QObject::connect(ui->displayRailways, &ButtonSwitch::clicked, this, &MapGUI::on_displayRailways_clicked);
QObject::connect(ui->displayNASAGlobalImagery, &ButtonSwitch::clicked, this, &MapGUI::on_displayNASAGlobalImagery_clicked);
QObject::connect(ui->nasaGlobalImageryIdentifier, qOverload<int>(&QComboBox::currentIndexChanged), this, &MapGUI::on_nasaGlobalImageryIdentifier_currentIndexChanged);
QObject::connect(ui->nasaGlobalImageryOpacity, qOverload<int>(&QDial::valueChanged), this, &MapGUI::on_nasaGlobalImageryOpacity_valueChanged);
QObject::connect(ui->displayMUF, &ButtonSwitch::clicked, this, &MapGUI::on_displayMUF_clicked);
QObject::connect(ui->displayfoF2, &ButtonSwitch::clicked, this, &MapGUI::on_displayfoF2_clicked);
QObject::connect(ui->find, &QLineEdit::returnPressed, this, &MapGUI::on_find_returnPressed);
QObject::connect(ui->maidenhead, &QToolButton::clicked, this, &MapGUI::on_maidenhead_clicked);
QObject::connect(ui->save, &QToolButton::clicked, this, &MapGUI::on_save_clicked);
QObject::connect(ui->deleteAll, &QToolButton::clicked, this, &MapGUI::on_deleteAll_clicked);
QObject::connect(ui->displaySettings, &QToolButton::clicked, this, &MapGUI::on_displaySettings_clicked);
QObject::connect(ui->mapTypes, qOverload<int>(&QComboBox::currentIndexChanged), this, &MapGUI::on_mapTypes_currentIndexChanged);
QObject::connect(ui->beacons, &QToolButton::clicked, this, &MapGUI::on_beacons_clicked);
QObject::connect(ui->ibpBeacons, &QToolButton::clicked, this, &MapGUI::on_ibpBeacons_clicked);
QObject::connect(ui->radiotime, &QToolButton::clicked, this, &MapGUI::on_radiotime_clicked);
}