diff --git a/CMakeLists.txt b/CMakeLists.txt index 04bd5add2..96bbc5f55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -308,6 +308,7 @@ if (BUILD_GUI) find_package(Qt5 COMPONENTS Quick) find_package(Qt5 COMPONENTS QuickWidgets) find_package(Qt5 COMPONENTS Positioning) + find_package(Qt5 COMPONENTS Location) find_package(Qt5 COMPONENTS Charts) endif() diff --git a/doc/img/Map_plugin.png b/doc/img/Map_plugin.png index 8fb44fc9a..4182ce789 100644 Binary files a/doc/img/Map_plugin.png and b/doc/img/Map_plugin.png differ diff --git a/doc/img/Map_plugin_beacon_dialog.png b/doc/img/Map_plugin_beacon_dialog.png new file mode 100644 index 000000000..b50c48fcc Binary files /dev/null and b/doc/img/Map_plugin_beacon_dialog.png differ diff --git a/doc/img/Map_plugin_beacons.png b/doc/img/Map_plugin_beacons.png new file mode 100644 index 000000000..6455b5581 Binary files /dev/null and b/doc/img/Map_plugin_beacons.png differ diff --git a/plugins/channelrx/demodadsb/CMakeLists.txt b/plugins/channelrx/demodadsb/CMakeLists.txt index 36d5ddf23..bdcceedd7 100644 --- a/plugins/channelrx/demodadsb/CMakeLists.txt +++ b/plugins/channelrx/demodadsb/CMakeLists.txt @@ -86,8 +86,3 @@ target_link_libraries(${TARGET_NAME} install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) -if(WIN32) - # Run deployqt for QtQuick etc - include(DeployQt) - windeployqt(${TARGET_NAME} ${SDRANGEL_BINARY_BIN_DIR} ${PROJECT_SOURCE_DIR}/map) -endif() diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.cpp b/plugins/channelrx/demodadsb/adsbdemodgui.cpp index abd6963c2..21df7c8b1 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodgui.cpp @@ -468,6 +468,7 @@ void ADSBDemodGUI::updatePosition(Aircraft *aircraft) swgMapItem->setName(new QString(QString("%1").arg(aircraft->m_icao, 0, 16))); swgMapItem->setLatitude(aircraft->m_latitude); swgMapItem->setLongitude(aircraft->m_longitude); + swgMapItem->setAltitude(Units::feetToMetres(aircraft->m_altitude)); swgMapItem->setImage(new QString(QString("qrc:///map/%1").arg(aircraft->getImage()))); swgMapItem->setImageRotation(aircraft->m_heading); swgMapItem->setText(new QString(aircraft->getText(true))); diff --git a/plugins/channelrx/demodadsb/csv.h b/plugins/channelrx/demodadsb/csv.h index 0a3716dc7..d59ed121d 100644 --- a/plugins/channelrx/demodadsb/csv.h +++ b/plugins/channelrx/demodadsb/csv.h @@ -22,7 +22,7 @@ #include // Extract string from CSV line, updating pp to next column -static inline char *csvNext(char **pp) +static inline char *csvNext(char **pp, char delimiter=',') { char *p = *pp; @@ -31,7 +31,7 @@ static inline char *csvNext(char **pp) char *start = p; - while ((*p != ',') && (*p != '\n')) + while ((*p != delimiter) && (*p != '\n')) p++; *p++ = '\0'; *pp = p; diff --git a/plugins/feature/CMakeLists.txt b/plugins/feature/CMakeLists.txt index 0e1e7d3da..3bd131099 100644 --- a/plugins/feature/CMakeLists.txt +++ b/plugins/feature/CMakeLists.txt @@ -4,8 +4,10 @@ if (Qt5SerialPort_FOUND) add_subdirectory(gs232controller) endif() -if (Qt5Quick_FOUND AND Qt5QuickWidgets_FOUND AND Qt5Positioning_FOUND) +if (Qt5Quick_FOUND AND Qt5QuickWidgets_FOUND AND Qt5Positioning_FOUND AND Qt5Location_FOUND) add_subdirectory(map) +endif() +if (Qt5Quick_FOUND AND Qt5QuickWidgets_FOUND AND Qt5Positioning_FOUND) add_subdirectory(vorlocalizer) endif() diff --git a/plugins/feature/aprs/aprsgui.cpp b/plugins/feature/aprs/aprsgui.cpp index 4e7326178..bd8c78fc9 100644 --- a/plugins/feature/aprs/aprsgui.cpp +++ b/plugins/feature/aprs/aprsgui.cpp @@ -354,6 +354,7 @@ bool APRSGUI::handleMessage(const Message& message) swgMapItem->setName(new QString(aprs->m_from)); swgMapItem->setLatitude(aprs->m_latitude); swgMapItem->setLongitude(aprs->m_longitude); + swgMapItem->setAltitude(aprs->m_hasAltitude ? Units::feetToMetres(aprs->m_altitudeFt) : 0); if (aprs->m_objectKilled) { swgMapItem->setImage(new QString("")); @@ -364,7 +365,7 @@ bool APRSGUI::handleMessage(const Message& message) swgMapItem->setImage(new QString(QString("qrc:///%1").arg(aprs->m_symbolImage))); swgMapItem->setText(new QString(aprs->toText())); } - swgMapItem->setImageFixedSize(0); + swgMapItem->setImageMinZoom(11); MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_aprs, swgMapItem); (*it)->push(msg); diff --git a/plugins/feature/gs232controller/gs232controllersettings.cpp b/plugins/feature/gs232controller/gs232controllersettings.cpp index 16cdec690..29e616b5d 100644 --- a/plugins/feature/gs232controller/gs232controllersettings.cpp +++ b/plugins/feature/gs232controller/gs232controllersettings.cpp @@ -25,11 +25,13 @@ const QStringList GS232ControllerSettings::m_pipeTypes = { QStringLiteral("ADSBDemod"), + QStringLiteral("Map"), QStringLiteral("StarTracker") }; const QStringList GS232ControllerSettings::m_pipeURIs = { QStringLiteral("sdrangel.channel.adsbdemod"), + QStringLiteral("sdrangel.feature.map"), QStringLiteral("sdrangel.feature.startracker") }; diff --git a/plugins/feature/gs232controller/readme.md b/plugins/feature/gs232controller/readme.md index 51bff3925..8432cc5a6 100644 --- a/plugins/feature/gs232controller/readme.md +++ b/plugins/feature/gs232controller/readme.md @@ -4,7 +4,7 @@ The GS-232 Rotator Controller feature plugin allows SDRangel to send commands to GS-232 rotators. This allows SDRangel to point antennas mounted on a rotator to a specified azimuth and elevation. -Azimuth and elevation can be set manually by a user in the GUI, via the REST API, or via another plugin, such as the ADS-B Demodulator, which can track a selected aircraft, or the Star Tracker, for radio astronomy or EME communication. +Azimuth and elevation can be set manually by a user in the GUI, via the REST API, or via another plugin, such as the Map Feature, the ADS-B Demodulator, or the Star Tracker.

Interface

diff --git a/plugins/feature/map/CMakeLists.txt b/plugins/feature/map/CMakeLists.txt index b5c87a2a2..4c1ea9dae 100644 --- a/plugins/feature/map/CMakeLists.txt +++ b/plugins/feature/map/CMakeLists.txt @@ -13,6 +13,7 @@ set(map_HEADERS mapplugin.h mapreport.h mapwebapiadapter.h + beacon.h ) include_directories( @@ -24,15 +25,27 @@ if(NOT SERVER_MODE) ${map_SOURCES} mapgui.cpp mapgui.ui + maplocationdialog.cpp + maplocationdialog.ui + mapmaidenheaddialog.cpp + mapmaidenheaddialog.ui + mapsettingsdialog.cpp + mapsettingsdialog.ui + mapbeacondialog.cpp + mapbeacondialog.ui map.qrc ) set(map_HEADERS ${map_HEADERS} mapgui.h + maplocationdialog.h + mapmaidenheaddialog.h + mapsettingsdialog.h + mapbeacondialog.h ) set(TARGET_NAME map) - set(TARGET_LIB "Qt5::Widgets" Qt5::Quick Qt5::QuickWidgets Qt5::Positioning) + set(TARGET_LIB "Qt5::Widgets" Qt5::Quick Qt5::QuickWidgets Qt5::Positioning Qt5::Location) set(TARGET_LIB_GUI "sdrgui") set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) else() @@ -54,3 +67,9 @@ target_link_libraries(${TARGET_NAME} ) install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) + +if(WIN32) + # Run deployqt for QtQuick etc + include(DeployQt) + windeployqt(${TARGET_NAME} ${SDRANGEL_BINARY_BIN_DIR} ${PROJECT_SOURCE_DIR}/map) +endif() diff --git a/plugins/feature/map/beacon.h b/plugins/feature/map/beacon.h new file mode 100644 index 000000000..fba066e18 --- /dev/null +++ b/plugins/feature/map/beacon.h @@ -0,0 +1,238 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_BEACON_H +#define INCLUDE_BEACON_H + +#include +#include + +#include +#include + +#include "util/units.h" +#include "util/maidenhead.h" +#include "../../channelrx/demodadsb/csv.h" + +#define IARU_BEACONS_URL "https://iaru-r1-c5-beacons.org/wp-content/uploads/beacons.csv" + +struct Beacon { + + QString m_callsign; + quint64 m_frequency; // In Hz + QString m_locator; + float m_latitude; + float m_longitude; + float m_altitude; // In metres above sea-level + QString m_power; // In Watts - sometimes a string with extra infos + QString m_polarization; // H or V + QString m_pattern; // Omni or 30deg etc + QString m_key; // F1A, F1B + QString m_mgm; // Machine mode + + QString getText() + { + QStringList list; + list.append("Beacon"); + list.append(QString("Callsign: %1").arg(m_callsign)); + list.append(QString("Frequency: %1").arg(getFrequencyText())); + if (!m_power.isEmpty()) + list.append(QString("Power: %1 Watts ERP").arg(m_power)); + if (!m_polarization.isEmpty()) + list.append(QString("Polarization: %1").arg(m_polarization)); + if (!m_pattern.isEmpty()) + list.append(QString("Pattern: %1").arg(m_pattern)); + if (!m_key.isEmpty()) + list.append(QString("Key: %1").arg(m_key)); + if (!m_mgm.isEmpty()) + list.append(QString("MGM: %1").arg(m_mgm)); + list.append(QString("Locator: %1").arg(m_locator)); + return list.join("\n"); + } + + QString getFrequencyText() + { + if (m_frequency > 1000000000) + return QString("%1 GHz").arg(m_frequency/1000000000.0, 0, ',', 6); + else if (m_frequency > 1000000) + return QString("%1 MHz").arg(m_frequency/1000000.0, 0, ',', 3); + else + return QString("%1 kHz").arg(m_frequency/1000.0, 0, ',', 3); + } + + // Uses ; rather than , + static QList *readIARUCSV(const QString &filename) + { + int cnt = 0; + QList *beacons = nullptr; + + // Column numbers used for the data as of 2021/1/20 + int callsignCol = 0; + int qrgCol = 1; + int locatorCol = 2; + int heightCol = 5; + int patternCol = 7; + int polarizationCol = 9; + int powerCol = 10; + int keyCol = 11; + int mgmCol = 12; + + FILE *file; + QByteArray utfFilename = filename.toUtf8(); + if ((file = fopen(utfFilename.constData(), "r")) != nullptr) + { + char row[2048]; + + if (fgets(row, sizeof(row), file)) + { + beacons = new QList(); + + // Read header + int idx = 0; + char *p = strtok(row, ";"); + while (p != nullptr) + { + if (!strcmp(p, "Callsign")) + callsignCol = idx; + else if (!strcmp(p, "QRG")) + qrgCol = idx; + else if (!strcmp(p, "Locator")) + locatorCol = idx; + else if (!strcmp(p, "Hight ASL") || !strcmp(p, "Height ASL")) + heightCol = idx; + else if (!strcmp(p, "Pattern")) + patternCol = idx; + else if (!strcmp(p, "H/V")) + polarizationCol = idx; + else if (!strcmp(p, "Power")) + powerCol = idx; + else if (!strcmp(p, "Keying")) + keyCol = idx; + else if (!strcmp(p, "MGM")) + mgmCol = idx; + p = strtok(nullptr, ","); + idx++; + } + // Read data + while (fgets(row, sizeof(row), file)) + { + int id = 0; + char *callsign = nullptr; + size_t callsignLen = 0; + char *frequencyString = nullptr; + quint64 frequency; + char *locator = nullptr; + int height = 0; + char *heightString = nullptr; + char *pattern = nullptr; + char *polarization = nullptr; + char *power = nullptr; + char *key = nullptr; + char *mgm = nullptr; + + char *q = row; + idx = 0; + while ((p = csvNext(&q, ';')) != nullptr) + { + // Read strings, stripping quotes + if ((idx == callsignCol) && (p[0] == '\"')) + { + callsign = p+1; + callsignLen = strlen(callsign)-1; + callsign[callsignLen] = '\0'; + } + else if ((idx == qrgCol) && (p[0] == '\"')) + { + frequencyString = p+1; + frequencyString[strlen(frequencyString)-1] = '\0'; + frequency = QString(frequencyString).toLongLong(); + } + else if ((idx == locatorCol) && (p[0] == '\"')) + { + locator = p+1; + locator[strlen(locator)-1] = '\0'; + } + else if ((idx == heightCol) && (p[0] == '\"')) + { + heightString = p+1; + heightString[strlen(heightString)-1] = '\0'; + height = atoi(heightString); + } + else if ((idx == patternCol) && (p[0] == '\"')) + { + pattern = p+1; + pattern[strlen(pattern)-1] = '\0'; + } + else if ((idx == polarizationCol) && (p[0] == '\"')) + { + polarization = p+1; + polarization[strlen(polarization)-1] = '\0'; + } + else if ((idx == powerCol) && (p[0] == '\"')) + { + power = p+1; + power[strlen(power)-1] = '\0'; + } + else if ((idx == keyCol) && (p[0] == '\"')) + { + key = p+1; + key[strlen(key)-1] = '\0'; + } + else if ((idx == mgmCol) && (p[0] == '\"')) + { + mgm = p+1; + mgm[strlen(mgm)-1] = '\0'; + } + idx++; + } + float latitude, longitude; + if (callsign && frequency && locator && Maidenhead::fromMaidenhead(locator, latitude, longitude)) + { + Beacon *beacon = new Beacon(); + beacon->m_callsign = callsign; + beacon->m_frequency = frequency * 1000; // kHz to Hz + beacon->m_locator = locator; + beacon->m_latitude = latitude; + beacon->m_longitude = longitude; + beacon->m_altitude = height; + if (!QString("omni").compare(pattern, Qt::CaseInsensitive)) + beacon->m_pattern = "Omni"; // Eliminate usage of mixed case + else + beacon->m_pattern = pattern; + beacon->m_polarization = polarization; + beacon->m_power = power; + beacon->m_key = key; + beacon->m_mgm = mgm; + + beacons->append(beacon); + cnt++; + } + } + } + fclose(file); + } + else + qDebug() << "Beacon::readIARUCSV: Failed to open " << filename; + + qDebug() << "Beacon::readIARUCSV: Read " << cnt << " beacons"; + + return beacons; + } + +}; + +#endif // INCLUDE_BEACON_H diff --git a/plugins/feature/map/map.qrc b/plugins/feature/map/map.qrc index 4299b3178..93f43c7db 100644 --- a/plugins/feature/map/map.qrc +++ b/plugins/feature/map/map.qrc @@ -1,7 +1,6 @@ map/map.qml - map/MapStation.qml map/antenna.png diff --git a/plugins/feature/map/map/MapStation.qml b/plugins/feature/map/map/MapStation.qml deleted file mode 100644 index a69346e46..000000000 --- a/plugins/feature/map/map/MapStation.qml +++ /dev/null @@ -1,40 +0,0 @@ -import QtQuick 2.12 -import QtLocation 5.12 -import QtPositioning 5.12 - -MapQuickItem { - id: station - property string stationName // Name of the station, E.g. Home - - coordinate: QtPositioning.coordinate(51.5, 0.125) // Location of the antenna (QTH) - London - zoomLevel: 11 - - anchorPoint.x: image.width/2 - anchorPoint.y: image.height/2 - - sourceItem: Grid { - columns: 1 - Grid { - horizontalItemAlignment: Grid.AlignHCenter - layer.enabled: true - layer.smooth: true - Image { - id: image - source: "antenna.png" - } - Rectangle { - id: bubble - color: "lightblue" - border.width: 1 - width: text.width * 1.3 - height: text.height * 1.3 - radius: 5 - Text { - id: text - anchors.centerIn: parent - text: stationName - } - } - } - } -} diff --git a/plugins/feature/map/map/map.qml b/plugins/feature/map/map/map.qml index 54d78d096..ba8237983 100644 --- a/plugins/feature/map/map/map.qml +++ b/plugins/feature/map/map/map.qml @@ -1,48 +1,82 @@ import QtQuick 2.12 import QtQuick.Window 2.12 +import QtQuick.Controls 2.12 import QtLocation 5.12 import QtPositioning 5.12 Item { id: qmlMap property int mapZoomLevel: 11 + property string mapProvider: "osm" + property variant mapParameters + property variant mapPtr - Plugin { - id: mapPlugin - name: "osm" + function createMap(pluginParameters) { + var parameters = new Array() + for (var prop in pluginParameters) { + var parameter = Qt.createQmlObject('import QtLocation 5.6; PluginParameter{ name: "'+ prop + '"; value: "' + pluginParameters[prop]+'"}', qmlMap) + parameters.push(parameter) + } + qmlMap.mapParameters = parameters + + var plugin + if (mapParameters && mapParameters.length > 0) + plugin = Qt.createQmlObject ('import QtLocation 5.12; Plugin{ name:"' + mapProvider + '"; parameters: qmlMap.mapParameters}', qmlMap) + else + plugin = Qt.createQmlObject ('import QtLocation 5.12; Plugin{ name:"' + mapProvider + '"}', qmlMap) + if (mapPtr) { + // Objects aren't destroyed immediately, so rename the old + // map, so any C++ code that calls findChild("map") doesn't find + // the old map + mapPtr.objectName = "oldMap"; + mapPtr.destroy() + } + mapPtr = actualMapComponent.createObject(page) + mapPtr.plugin = plugin; + mapPtr.forceActiveFocus() + mapPtr.objectName = "map"; } - Map { - id: map - objectName: "map" - anchors.fill: parent - plugin: mapPlugin - center: QtPositioning.coordinate(51.5, 0.125) // London - zoomLevel: 10 - - - MapItemView { - model: mapModel - delegate: mapComponent - } - - MapStation { - id: station - objectName: "station" - stationName: "Home" - coordinate: QtPositioning.coordinate(51.5, 0.125) - } - - onZoomLevelChanged: { - if (zoomLevel > 11) { - station.zoomLevel = zoomLevel - mapZoomLevel = zoomLevel - } else { - station.zoomLevel = 11 - mapZoomLevel = 11 + function getMapTypes() { + var mapTypes = [] + if (mapPtr) { + for (var i = 0; i < mapPtr.supportedMapTypes.length; i++) { + mapTypes[i] = mapPtr.supportedMapTypes[i].name } } + return mapTypes + } + function setMapType(mapTypeIndex) { + if (mapPtr) + mapPtr.activeMapType = mapPtr.supportedMapTypes[mapTypeIndex] + } + + Item { + id: page + anchors.fill: parent + } + + Component { + id: actualMapComponent + + Map { + id: map + anchors.fill: parent + center: QtPositioning.coordinate(51.5, 0.125) // London + zoomLevel: 10 + + MapItemView { + model: mapModel + delegate: mapComponent + } + + onZoomLevelChanged: { + mapZoomLevel = zoomLevel + + } + + } } Component { @@ -52,9 +86,10 @@ Item { anchorPoint.x: image.width/2 anchorPoint.y: image.height/2 coordinate: position - zoomLevel: mapImageFixedSize ? zoomLevel : mapZoomLevel + zoomLevel: mapZoomLevel > mapImageMinZoom ? mapZoomLevel : mapImageMinZoom sourceItem: Grid { + id: gridItem columns: 1 Grid { horizontalItemAlignment: Grid.AlignHCenter @@ -65,11 +100,27 @@ Item { id: image rotation: mapImageRotation source: mapImage + visible: mapImageVisible MouseArea { anchors.fill: parent hoverEnabled: true - onClicked: (mouse) => { - selected = !selected + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button === Qt.LeftButton) { + selected = !selected + if (selected) { + mapModel.moveToFront(index) + } + } else if (mouse.button === Qt.RightButton) { + if (frequency > 0) { + freqMenuItem.text = "Set frequency to " + frequencyString + freqMenuItem.enabled = true + } else { + freqMenuItem.text = "No frequency available" + freqMenuItem.enabled = false + } + contextMenu.popup() + } } } } @@ -89,8 +140,43 @@ Item { MouseArea { anchors.fill: parent hoverEnabled: true - onClicked: (mouse) => { - selected = !selected + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button === Qt.LeftButton) { + selected = !selected + if (selected) { + mapModel.moveToFront(index) + } + } else if (mouse.button === Qt.RightButton) { + if (frequency > 0) { + freqMenuItem.text = "Set frequency to " + frequencyString + freqMenuItem.enabled = true + } else { + freqMenuItem.text = "No frequency available" + freqMenuItem.enabled = false + } + contextMenu.popup() + } + } + Menu { + id: contextMenu + MenuItem { + text: "Set as target" + onTriggered: target = true + } + MenuItem { + id: freqMenuItem + text: "Not set" + onTriggered: mapModel.setFrequency(frequency) + } + MenuItem { + text: "Move to front" + onTriggered: mapModel.moveToFront(index) + } + MenuItem { + text: "Move to back" + onTriggered: mapModel.moveToBack(index) + } } } } diff --git a/plugins/feature/map/mapbeacondialog.cpp b/plugins/feature/map/mapbeacondialog.cpp new file mode 100644 index 000000000..dce8a3337 --- /dev/null +++ b/plugins/feature/map/mapbeacondialog.cpp @@ -0,0 +1,221 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "mapbeacondialog.h" + +#include +#include +#include + +#include "channel/channelwebapiutils.h" +#include "mapgui.h" + +MapBeaconDialog::MapBeaconDialog(MapGUI *gui, QWidget* parent) : + QDialog(parent), + m_gui(gui), + ui(new Ui::MapBeaconDialog), + m_progressDialog(nullptr) +{ + ui->setupUi(this); + connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &MapBeaconDialog::downloadFinished); +} + +MapBeaconDialog::~MapBeaconDialog() +{ + delete ui; +} + +void MapBeaconDialog::updateTable() +{ + AzEl azEl = *m_gui->getAzEl(); + ui->beacons->setSortingEnabled(false); + ui->beacons->setRowCount(0); + QList *beacons = m_gui->getBeacons(); + if (beacons != nullptr) + { + ui->beacons->setRowCount(beacons->size()); + QListIterator i(*beacons); + int row = 0; + while (i.hasNext()) + { + Beacon *beacon = i.next(); + ui->beacons->setItem(row, BEACON_COL_CALLSIGN, new QTableWidgetItem(beacon->m_callsign)); + QTableWidgetItem *freq = new QTableWidgetItem(); + freq->setText(beacon->getFrequencyText()); + freq->setData(Qt::UserRole, beacon->m_frequency); + ui->beacons->setItem(row, BEACON_COL_FREQUENCY, freq); + ui->beacons->setItem(row, BEACON_COL_LOCATION, new QTableWidgetItem(beacon->m_locator)); + ui->beacons->setItem(row, BEACON_COL_POWER, new QTableWidgetItem(beacon->m_power)); + ui->beacons->setItem(row, BEACON_COL_POLARIZATION, new QTableWidgetItem(beacon->m_polarization)); + ui->beacons->setItem(row, BEACON_COL_PATTERN, new QTableWidgetItem(beacon->m_pattern)); + ui->beacons->setItem(row, BEACON_COL_KEY, new QTableWidgetItem(beacon->m_key)); + ui->beacons->setItem(row, BEACON_COL_MGM, new QTableWidgetItem(beacon->m_mgm)); + azEl.setTarget(beacon->m_latitude, beacon->m_longitude, beacon->m_altitude); + azEl.calculate(); + ui->beacons->setItem(row, BEACON_COL_AZIMUTH, new QTableWidgetItem(QString("%1").arg(round(azEl.getAzimuth())))); + ui->beacons->setItem(row, BEACON_COL_ELEVATION, new QTableWidgetItem(QString("%1").arg(round(azEl.getElevation())))); + int km = round(azEl.getDistance()/1000); + QTableWidgetItem *dist = new QTableWidgetItem(); + dist->setData(Qt::DisplayRole, km); + ui->beacons->setItem(row, BEACON_COL_DISTANCE, dist); + row++; + } + } + ui->beacons->setSortingEnabled(true); + ui->beacons->resizeColumnsToContents(); +} + +qint64 MapBeaconDialog::fileAgeInDays(QString filename) +{ + QFile file(filename); + if (file.exists()) + { + QDateTime modified = file.fileTime(QFileDevice::FileModificationTime); + if (modified.isValid()) + return modified.daysTo(QDateTime::currentDateTime()); + else + return -1; + } + return -1; +} + +bool MapBeaconDialog::confirmDownload(QString filename) +{ + qint64 age = fileAgeInDays(filename); + if ((age == -1) || (age > 100)) + return true; + else + { + QMessageBox::StandardButton reply; + if (age == 0) + reply = QMessageBox::question(this, "Confirm download", "This file was last downloaded today. Are you sure you wish to redownload it?", QMessageBox::Yes|QMessageBox::No); + else if (age == 1) + reply = QMessageBox::question(this, "Confirm download", "This file was last downloaded yesterday. Are you sure you wish to redownload it?", QMessageBox::Yes|QMessageBox::No); + else + reply = QMessageBox::question(this, "Confirm download", QString("This file was last downloaded %1 days ago. Are you sure you wish to redownload this file?").arg(age), QMessageBox::Yes|QMessageBox::No); + return reply == QMessageBox::Yes; + } +} + +void MapBeaconDialog::updateDownloadProgress(qint64 bytesRead, qint64 totalBytes) +{ + m_progressDialog->setMaximum(totalBytes); + m_progressDialog->setValue(bytesRead); +} + +void MapBeaconDialog::accept() +{ + QDialog::accept(); +} + +void MapBeaconDialog::on_downloadIARU_clicked() +{ + if (m_progressDialog == nullptr) + { + QString beaconFile = MapGUI::getBeaconFilename(); + if (confirmDownload(beaconFile)) + { + // Download IARU beacons database to disk + QUrl dbURL(QString(IARU_BEACONS_URL)); + m_progressDialog = new QProgressDialog(this); + m_progressDialog->setAttribute(Qt::WA_DeleteOnClose); + m_progressDialog->setCancelButton(nullptr); + m_progressDialog->setMinimumDuration(500); + m_progressDialog->setLabelText(QString("Downloading %1.").arg(IARU_BEACONS_URL)); + QNetworkReply *reply = m_dlm.download(dbURL, beaconFile); + connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updateDownloadProgress(qint64,qint64))); + } + } +} + +void MapBeaconDialog::downloadFinished(const QString& filename, bool success) +{ + if (success) + { + if (filename == MapGUI::getBeaconFilename()) + { + QList *beacons = Beacon::readIARUCSV(filename); + if (beacons != nullptr) + m_gui->setBeacons(beacons); + m_progressDialog->close(); + m_progressDialog = nullptr; + } + else + { + qDebug() << "MapBeaconDialog::downloadFinished: Unexpected filename: " << filename; + m_progressDialog->close(); + m_progressDialog = nullptr; + } + } + else + { + qDebug() << "MapBeaconDialog::downloadFinished: Failed: " << filename; + m_progressDialog->close(); + m_progressDialog = nullptr; + QMessageBox::warning(this, "Download failed", QString("Failed to download %1").arg(filename)); + } +} + +void MapBeaconDialog::on_beacons_cellDoubleClicked(int row, int column) +{ + if ((column == BEACON_COL_CALLSIGN) || (column == BEACON_COL_LOCATION)) + { + // Find beacon on map + QString location = ui->beacons->item(row, column)->text(); + m_gui->find(location); + } + else if (column == BEACON_COL_FREQUENCY) + { + // Tune to beacon freq + ChannelWebAPIUtils::setCenterFrequency(0, ui->beacons->item(row, column)->data(Qt::UserRole).toDouble()); + } +} + +void MapBeaconDialog::on_filter_currentIndexChanged(int index) +{ + for (int row = 0; row < ui->beacons->rowCount(); row++) + { + bool hidden = false; + QTableWidgetItem *item = ui->beacons->item(row, BEACON_COL_FREQUENCY); + qint64 freq = item->data(Qt::UserRole).toLongLong(); + qint64 band = freq/1000000; + switch (index) + { + case 0: // All + break; + case 1: + hidden = band != 50; + break; + case 2: + hidden = band != 70; + break; + case 3: + hidden = band != 144; + break; + case 4: + hidden = band != 432; + break; + case 5: + hidden = band != 1296; + break; + case 6: + hidden = band <= 1296; + break; + } + ui->beacons->setRowHidden(row, hidden); + } +} diff --git a/plugins/feature/map/mapbeacondialog.h b/plugins/feature/map/mapbeacondialog.h new file mode 100644 index 000000000..94dc137d3 --- /dev/null +++ b/plugins/feature/map/mapbeacondialog.h @@ -0,0 +1,71 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FEATURE_MAPBEACONDIALOG_H +#define INCLUDE_FEATURE_MAPBEACONDIALOG_H + +#include "ui_mapbeacondialog.h" + +#include + +#include "util/httpdownloadmanager.h" +#include "beacon.h" + +class MapGUI; + +class MapBeaconDialog : public QDialog { + Q_OBJECT + +public: + explicit MapBeaconDialog(MapGUI *gui, QWidget* parent = 0); + ~MapBeaconDialog(); + void updateTable(); + +private: + qint64 fileAgeInDays(QString filename); + bool confirmDownload(QString filename); + void downloadFinished(const QString& filename, bool success); + +private slots: + void accept(); + void on_downloadIARU_clicked(); + void updateDownloadProgress(qint64 bytesRead, qint64 totalBytes); + void on_beacons_cellDoubleClicked(int row, int column); + void on_filter_currentIndexChanged(int index); + +private: + MapGUI *m_gui; + Ui::MapBeaconDialog* ui; + HttpDownloadManager m_dlm; + QProgressDialog *m_progressDialog; + + enum BeaconCol { + BEACON_COL_CALLSIGN, + BEACON_COL_FREQUENCY, + BEACON_COL_LOCATION, + BEACON_COL_POWER, + BEACON_COL_POLARIZATION, + BEACON_COL_PATTERN, + BEACON_COL_KEY, + BEACON_COL_MGM, + BEACON_COL_AZIMUTH, + BEACON_COL_ELEVATION, + BEACON_COL_DISTANCE + }; +}; + +#endif // INCLUDE_FEATURE_MAPBEACONDIALOG_H diff --git a/plugins/feature/map/mapbeacondialog.ui b/plugins/feature/map/mapbeacondialog.ui new file mode 100644 index 000000000..4b749b039 --- /dev/null +++ b/plugins/feature/map/mapbeacondialog.ui @@ -0,0 +1,226 @@ + + + MapBeaconDialog + + + + 0 + 0 + 1027 + 349 + + + + + Liberation Sans + 9 + + + + Beacons + + + + + + + 0 + + + + + + + Show + + + + + + + + 100 + 0 + + + + + All + + + + + 50MHz (6m) + + + + + 70MHz (4m) + + + + + 144MHz (2m) + + + + + 432MHz (70cm) + + + + + 1.296GHz (23cm) + + + + + >2.3GHz (<13cm) + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Download IARU Beacon list + + + + + + + :/antenna.png:/antenna.png + + + + + + + + + + + + + Callsign + + + + + Frequency + + + + + Location + + + + + Power + + + + + Polarization + + + + + Pattern + + + + + Key + + + + + MGM + + + + + Azimuth + + + + + Elevation + + + + + Distance (km) + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + + + buttonBox + accepted() + MapBeaconDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MapBeaconDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/plugins/feature/map/mapgui.cpp b/plugins/feature/map/mapgui.cpp index bd854e44f..4fe23a0d9 100644 --- a/plugins/feature/map/mapgui.cpp +++ b/plugins/feature/map/mapgui.cpp @@ -20,21 +20,54 @@ #include #include #include +#include #include #include #include +#include +#include +#include #include "feature/featureuiset.h" #include "gui/basicfeaturesettingsdialog.h" +#include "channel/channelwebapiutils.h" #include "mainwindow.h" #include "device/deviceuiset.h" #include "util/units.h" #include "util/maidenhead.h" +#include "maplocationdialog.h" +#include "mapmaidenheaddialog.h" +#include "mapsettingsdialog.h" #include "ui_mapgui.h" #include "map.h" #include "mapgui.h" #include "SWGMapItem.h" +#include "SWGTargetAzimuthElevation.h" + +void MapItem::findFrequency() +{ + // Look for a frequency in the text for this object + QRegExp re("(([0-9]+(\\.[0-9]+)?) *([kMG])?Hz)"); + if (re.indexIn(m_text) != -1) + { + QStringList capture = re.capturedTexts(); + m_frequency = capture[2].toDouble(); + if (capture.length() == 5) + { + QChar unit = capture[4][0]; + if (unit == 'k') + m_frequency *= 1000.0; + else if (unit == 'M') + m_frequency *= 1000000.0; + else if (unit == 'G') + m_frequency *= 1000000000.0; + } + m_frequencyString = capture[0]; + } + else + m_frequency = 0.0; +} QVariant MapModel::data(const QModelIndex &index, int role) const { @@ -53,14 +86,27 @@ QVariant MapModel::data(const QModelIndex &index, int role) const else if (role == MapModel::mapTextRole) { // Create the text to go in the bubble next to the image - if (m_selected[row]) + if (row == m_target) + { + AzEl *azEl = m_gui->getAzEl(); + QString text = QString("%1\nAz: %2 El: %3") + .arg(m_selected[row] ? m_items[row]->m_text : m_items[row]->m_name) + .arg(std::round(azEl->getAzimuth())) + .arg(std::round(azEl->getElevation())); + return QVariant::fromValue(text); + } + else if (m_selected[row]) return QVariant::fromValue(m_items[row]->m_text); else return QVariant::fromValue(m_items[row]->m_name); } else if (role == MapModel::mapTextVisibleRole) { - return QVariant::fromValue(m_selected[row] || m_displayNames); + return QVariant::fromValue((m_selected[row] || m_displayNames) && (m_sources & m_items[row]->m_sourceMask)); + } + else if (role == MapModel::mapImageVisibleRole) + { + return QVariant::fromValue((m_sources & m_items[row]->m_sourceMask) != 0); } else if (role == MapModel::mapImageRole) { @@ -72,10 +118,10 @@ QVariant MapModel::data(const QModelIndex &index, int role) const // Angle to rotate image by return QVariant::fromValue(m_items[row]->m_imageRotation); } - else if (role == MapModel::mapImageFixedSizeRole) + else if (role == MapModel::mapImageMinZoomRole) { - // Whether image changes size with zoom level - return QVariant::fromValue(m_items[row]->m_imageFixedSize); + // Minimum zoom level + return QVariant::fromValue(m_items[row]->m_imageMinZoom); } else if (role == MapModel::bubbleColourRole) { @@ -87,23 +133,108 @@ QVariant MapModel::data(const QModelIndex &index, int role) const } else if (role == MapModel::selectedRole) return QVariant::fromValue(m_selected[row]); + else if (role == MapModel::targetRole) + return QVariant::fromValue(m_target == row); + else if (role == MapModel::frequencyRole) + return QVariant::fromValue(m_items[row]->m_frequency); + else if (role == MapModel::frequencyStringRole) + return QVariant::fromValue(m_items[row]->m_frequencyString); return QVariant(); } -bool MapModel::setData(const QModelIndex &index, const QVariant& value, int role) +bool MapModel::setData(const QModelIndex &idx, const QVariant& value, int role) { - int row = index.row(); + int row = idx.row(); if ((row < 0) || (row >= m_items.count())) return false; if (role == MapModel::selectedRole) { m_selected[row] = value.toBool(); - emit dataChanged(index, index); + emit dataChanged(idx, idx); + return true; + } + else if (role == MapModel::targetRole) + { + if (m_target >= 0) + { + // Update text bubble for old target + QModelIndex oldIdx = index(m_target); + m_target = -1; + emit dataChanged(oldIdx, oldIdx); + } + m_target = row; + updateTarget(); + emit dataChanged(idx, idx); return true; } return true; } +void MapModel::setFrequency(double frequency) +{ + // Set as centre frequency + ChannelWebAPIUtils::setCenterFrequency(0, frequency); +} + +void MapModel::update(const PipeEndPoint *sourcePipe, SWGSDRangel::SWGMapItem *swgMapItem, quint32 sourceMask) +{ + QString name = *swgMapItem->getName(); + // Add, update or delete and item + MapItem *item = findMapItem(sourcePipe, name); + if (item != nullptr) + { + QString image = *swgMapItem->getImage(); + if (image.isEmpty()) + { + // Delete the item + remove(item); + } + else + { + // Update the item + item->update(swgMapItem); + update(item); + } + } + else + { + // Make sure not a duplicate request to delete + QString image = *swgMapItem->getImage(); + if (!image.isEmpty()) + { + if (!sourceMask) + sourceMask = m_gui->getSourceMask(sourcePipe); + // Add new item + add(new MapItem(sourcePipe, sourceMask, swgMapItem)); + } + } +} + +void MapModel::updateTarget() +{ + // Calculate range, azimuth and elevation to object from station + AzEl *azEl = m_gui->getAzEl(); + azEl->setTarget(m_items[m_target]->m_latitude, m_items[m_target]->m_longitude, m_items[m_target]->m_altitude); + azEl->calculate(); + + // Send to Rotator Controllers + MessagePipes& messagePipes = MainCore::instance()->getMessagePipes(); + QList *mapMessageQueues = messagePipes.getMessageQueues(m_gui->getMap(), "target"); + if (mapMessageQueues) + { + QList::iterator it = mapMessageQueues->begin(); + + for (; it != mapMessageQueues->end(); ++it) + { + SWGSDRangel::SWGTargetAzimuthElevation *swgTarget = new SWGSDRangel::SWGTargetAzimuthElevation(); + swgTarget->setName(new QString(m_items[m_target]->m_name)); + swgTarget->setAzimuth(azEl->getAzimuth()); + swgTarget->setElevation(azEl->getElevation()); + (*it)->push(MainCore::MsgTargetAzimuthElevation::create(m_gui->getMap(), swgTarget)); + } + } +} + MapGUI* MapGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature) { MapGUI* gui = new MapGUI(pluginAPI, featureUISet, feature); @@ -159,7 +290,6 @@ bool MapGUI::handleMessage(const Message& message) { PipeEndPoint::MsgReportPipes& report = (PipeEndPoint::MsgReportPipes&) message; m_availablePipes = report.getAvailablePipes(); - updatePipeList(); return true; } @@ -204,7 +334,9 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur m_pluginAPI(pluginAPI), m_featureUISet(featureUISet), m_doApplySettings(true), - m_mapModel(this) + m_mapModel(this), + m_beacons(nullptr), + m_beaconDialog(this) { ui->setupUi(this); @@ -219,38 +351,45 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur m_featureUISet->addRollupWidget(this); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + + displaySettings(); + applySettings(true); + // 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); // Centre map at My Position QQuickItem *item = ui->map->rootObject(); QObject *object = item->findChild("map"); - if(object != NULL) + if (object != nullptr) { QGeoCoordinate coords = object->property("center").value(); coords.setLatitude(stationLatitude); coords.setLongitude(stationLongitude); object->setProperty("center", QVariant::fromValue(coords)); } - // Move antenna icon to My Position to start with - QObject *stationObject = item->findChild("station"); - if(stationObject != NULL) - { - QGeoCoordinate coords = stationObject->property("coordinate").value(); - coords.setLatitude(stationLatitude); - coords.setLongitude(stationLongitude); - coords.setAltitude(stationAltitude); - stationObject->setProperty("coordinate", QVariant::fromValue(coords)); - stationObject->setProperty("stationName", QVariant::fromValue(MainCore::instance()->getSettings().getStationName())); - } - connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); - connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + // Create antenna at My Position + SWGSDRangel::SWGMapItem antennaMapItem; + antennaMapItem.setName(new QString(MainCore::instance()->getSettings().getStationName())); + antennaMapItem.setLatitude(stationLatitude); + antennaMapItem.setLongitude(stationLongitude); + antennaMapItem.setAltitude(stationAltitude); + antennaMapItem.setImage(new QString("antenna.png")); + antennaMapItem.setImageRotation(0); + antennaMapItem.setImageMinZoom(11); + antennaMapItem.setText(new QString(MainCore::instance()->getSettings().getStationName())); + m_mapModel.update(m_map, &antennaMapItem, MapSettings::SOURCE_STATION); - displaySettings(); - applySettings(true); + // Read beacons, if they exist + QList *beacons = Beacon::readIARUCSV(MapGUI::getBeaconFilename()); + if (beacons != nullptr) + setBeacons(beacons); } MapGUI::~MapGUI() @@ -258,11 +397,90 @@ MapGUI::~MapGUI() delete ui; } +void MapGUI::setBeacons(QList *beacons) +{ + delete m_beacons; + m_beacons = beacons; + m_beaconDialog.updateTable(); + // Add to Map + QListIterator i(*m_beacons); + while (i.hasNext()) + { + Beacon *beacon = i.next(); + SWGSDRangel::SWGMapItem beaconMapItem; + beaconMapItem.setName(new QString(beacon->m_callsign)); + 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.setImageMinZoom(8); + beaconMapItem.setText(new QString(beacon->getText())); + m_mapModel.update(m_map, &beaconMapItem, MapSettings::SOURCE_BEACONS); + } +} + void MapGUI::blockApplySettings(bool block) { m_doApplySettings = !block; } +void MapGUI::applyMapSettings() +{ + QQuickItem *item = ui->map->rootObject(); + + // Save existing position of map + QObject *object = item->findChild("map"); + QGeoCoordinate coords; + if (object != nullptr) + coords = object->property("center").value(); + + // Create the map using the specified provider + 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; + } + //QQmlProperty::write(item, "mapParameters", parameters); + QMetaObject::invokeMethod(item, "createMap", Q_ARG(QVariant, QVariant::fromValue(parameters))); + + // Restore position of map + object = item->findChild("map"); + if ((object != nullptr) && coords.isValid()) + object->setProperty("center", QVariant::fromValue(coords)); + + // Get list of map types + 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(); + for (int i = 0; i < mapTypes.size(); i++) + ui->mapTypes->addItem(mapTypes[i]); + } + } +} + +void MapGUI::on_mapTypes_currentIndexChanged(int index) +{ + QVariant mapType = index; + QMetaObject::invokeMethod(ui->map->rootObject(), "setMapType", Q_ARG(QVariant, mapType)); +} + void MapGUI::displaySettings() { setTitleColor(m_settings.m_rgbColor); @@ -270,24 +488,11 @@ void MapGUI::displaySettings() blockApplySettings(true); ui->displayNames->setChecked(m_settings.m_displayNames); m_mapModel.setDisplayNames(m_settings.m_displayNames); + m_mapModel.setSources(m_settings.m_sources); + applyMapSettings(); blockApplySettings(false); } -void MapGUI::updatePipeList() -{ - ui->pipes->blockSignals(true); - ui->pipes->clear(); - - QList::const_iterator it = m_availablePipes.begin(); - - for (int i = 0; it != m_availablePipes.end(); ++it, i++) - { - ui->pipes->addItem(it->getName()); - } - - ui->pipes->blockSignals(false); -} - void MapGUI::leaveEvent(QEvent*) { } @@ -338,6 +543,12 @@ void MapGUI::applySettings(bool force) } } +void MapGUI::on_maidenhead_clicked(bool checked) +{ + MapMaidenheadDialog dialog; + dialog.exec(); +} + void MapGUI::on_displayNames_clicked(bool checked) { m_settings.m_displayNames = checked; @@ -349,14 +560,51 @@ void MapGUI::on_find_returnPressed() find(ui->find->text().trimmed()); } +void MapGUI::geoReply() +{ + QGeoCodeReply *pQGeoCode = dynamic_cast(sender()); + + if ((pQGeoCode != nullptr) && (pQGeoCode->error() == QGeoCodeReply::NoError)) + { + QList qGeoLocs = pQGeoCode->locations(); + QQuickItem *item = ui->map->rootObject(); + QObject *map = item->findChild("map"); + if (qGeoLocs.size() == 1) + { + // Only one result, so centre map on that + map->setProperty("center", QVariant::fromValue(qGeoLocs.at(0).coordinate())); + } + 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); + if (dialog.exec() == QDialog::Accepted) + map->setProperty("center", QVariant::fromValue(dialog.m_selectedLocation.coordinate())); + } + } + else + qWarning() << "MapGUI::geoReply: GeoCode error: " << pQGeoCode->error(); + pQGeoCode->deleteLater(); +} + void MapGUI::find(const QString& target) { if (!target.isEmpty()) { QQuickItem *item = ui->map->rootObject(); QObject *map = item->findChild("map"); - if (map != NULL) + if (map != nullptr) { + // Search as: + // latitude and longitude + // Maidenhead locator + // object name + // address float latitude, longitude; if (Units::stringToLatitudeAndLongitude(target, latitude, longitude)) { @@ -371,6 +619,22 @@ void MapGUI::find(const QString& target) MapItem *mapItem = m_mapModel.findMapItem(target); if (mapItem != nullptr) map->setProperty("center", QVariant::fromValue(mapItem->getCoordinates())); + else + { + 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"; + } } } } @@ -380,3 +644,50 @@ void MapGUI::on_deleteAll_clicked() { m_mapModel.removeAll(); } + +void MapGUI::on_displaySettings_clicked() +{ + MapSettingsDialog dialog(&m_settings); + if (dialog.exec() == QDialog::Accepted) + { + if (dialog.m_mapSettingsChanged) + applyMapSettings(); + applySettings(); + if (dialog.m_sourcesChanged) + m_mapModel.setSources(m_settings.m_sources); + } +} + +void MapGUI::on_beacons_clicked() +{ + m_beaconDialog.show(); +} + +quint32 MapGUI::getSourceMask(const PipeEndPoint *sourcePipe) +{ + for (int i = 0; i < m_availablePipes.size(); i++) + { + if (m_availablePipes[i].m_source == sourcePipe) + { + for (int j = 0; j < MapSettings::m_pipeTypes.size(); j++) + { + if (m_availablePipes[i].m_id == MapSettings::m_pipeTypes[j]) + return 1 << j; + } + } + } + return 0; +} + +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"; +} diff --git a/plugins/feature/map/mapgui.h b/plugins/feature/map/mapgui.h index c78c8e88b..935540e97 100644 --- a/plugins/feature/map/mapgui.h +++ b/plugins/feature/map/mapgui.h @@ -25,9 +25,11 @@ #include "feature/featuregui.h" #include "util/messagequeue.h" +#include "util/azel.h" #include "pipes/pipeendpoint.h" #include "mapsettings.h" #include "SWGMapItem.h" +#include "mapbeacondialog.h" class PluginAPI; class FeatureUISet; @@ -39,31 +41,41 @@ namespace Ui { class MapGUI; class MapModel; +struct Beacon; // Information required about each item displayed on the map class MapItem { public: - MapItem(const PipeEndPoint *source, SWGSDRangel::SWGMapItem *mapItem) + MapItem(const PipeEndPoint *sourcePipe, quint32 sourceMask, SWGSDRangel::SWGMapItem *mapItem) { - m_source = source; + m_sourcePipe = sourcePipe; + m_sourceMask = sourceMask; m_name = *mapItem->getName(); m_latitude = mapItem->getLatitude(); m_longitude = mapItem->getLongitude(); + m_altitude = mapItem->getAltitude(); m_image = *mapItem->getImage(); m_imageRotation = mapItem->getImageRotation(); - m_imageFixedSize = mapItem->getImageFixedSize() == 1; - m_text = *mapItem->getText(); + m_imageMinZoom = mapItem->getImageMinZoom(); + QString *text = mapItem->getText(); + if (text != nullptr) + m_text = *text; + findFrequency(); } void update(SWGSDRangel::SWGMapItem *mapItem) { m_latitude = mapItem->getLatitude(); m_longitude = mapItem->getLongitude(); + m_altitude = mapItem->getAltitude(); m_image = *mapItem->getImage(); m_imageRotation = mapItem->getImageRotation(); - m_imageFixedSize = mapItem->getImageFixedSize() == 1; - m_text = *mapItem->getText(); + m_imageMinZoom = mapItem->getImageMinZoom(); + QString *text = mapItem->getText(); + if (text != nullptr) + m_text = *text; + findFrequency(); } QGeoCoordinate getCoordinates() @@ -75,15 +87,22 @@ public: } private: + + void findFrequency(); + friend MapModel; - const PipeEndPoint *m_source; // Channel/feature that created the item + const PipeEndPoint *m_sourcePipe; // Channel/feature that created the item + quint32 m_sourceMask; // Source bitmask as per MapSettings::SOURCE_* constants QString m_name; float m_latitude; float m_longitude; + float m_altitude; // In metres QString m_image; int m_imageRotation; - bool m_imageFixedSize; // Keep image same size when map is zoomed + int m_imageMinZoom; QString m_text; + double m_frequency; // Frequency to set + QString m_frequencyString; }; // Model used for each item on the map @@ -96,15 +115,21 @@ public: positionRole = Qt::UserRole + 1, mapTextRole = Qt::UserRole + 2, mapTextVisibleRole = Qt::UserRole + 3, - mapImageRole = Qt::UserRole + 4, - mapImageRotationRole = Qt::UserRole + 5, - mapImageFixedSizeRole = Qt::UserRole + 6, - bubbleColourRole = Qt::UserRole + 7, - selectedRole = Qt::UserRole + 8 + mapImageVisibleRole = Qt::UserRole + 4, + mapImageRole = Qt::UserRole + 5, + mapImageRotationRole = Qt::UserRole + 6, + mapImageMinZoomRole = Qt::UserRole + 7, + bubbleColourRole = Qt::UserRole + 8, + selectedRole = Qt::UserRole + 9, + targetRole = Qt::UserRole + 10, + frequencyRole = Qt::UserRole + 11, + frequencyStringRole = Qt::UserRole + 12 }; MapModel(MapGUI *gui) : - m_gui(gui) + m_gui(gui), + m_target(-1), + m_sources(-1) { } @@ -116,37 +141,9 @@ public: endInsertRows(); } - void update(const PipeEndPoint *source, SWGSDRangel::SWGMapItem *swgMapItem) - { - QString name = *swgMapItem->getName(); - // Add, update or delete and item - MapItem *item = findMapItem(source, name); - if (item != nullptr) - { - QString image = *swgMapItem->getImage(); - if (image.isEmpty()) - { - // Delete the item - remove(item); - } - else - { - // Update the item - item->update(swgMapItem); - update(item); - } - } - else - { - // Make sure not a duplicate request to delete - QString image = *swgMapItem->getImage(); - if (!image.isEmpty()) - { - // Add new item - add(new MapItem(source, swgMapItem)); - } - } - } + void update(const PipeEndPoint *source, SWGSDRangel::SWGMapItem *swgMapItem, quint32 sourceMask=0); + + void updateTarget(); void update(MapItem *item) { @@ -155,6 +152,8 @@ public: { QModelIndex idx = index(row); emit dataChanged(idx, idx); + if (row == m_target) + updateTarget(); } } @@ -166,10 +165,52 @@ public: beginRemoveRows(QModelIndex(), row, row); m_items.removeAt(row); m_selected.removeAt(row); + if (row == m_target) + m_target = -1; endRemoveRows(); } } + Q_INVOKABLE void moveToFront(int oldRow) + { + // Last item in list is drawn on top, so remove than add to end of list + if (oldRow < m_items.size() - 1) + { + bool wasTarget = m_target == oldRow; + MapItem *item = m_items[oldRow]; + bool wasSelected = m_selected[oldRow]; + remove(item); + add(item); + int newRow = m_items.size() - 1; + if (wasTarget) + m_target = newRow; + m_selected[newRow] = wasSelected; + QModelIndex idx = index(newRow); + emit dataChanged(idx, idx); + } + } + + Q_INVOKABLE void moveToBack(int oldRow) + { + // First item in list is drawn first, so remove item then add to front of list + if ((oldRow < m_items.size()) && (oldRow > 0)) + { + bool wasTarget = m_target == oldRow; + int newRow = 0; + // See: https://forum.qt.io/topic/122991/changing-the-order-mapquickitems-are-drawn-on-a-map + //QModelIndex parent; + //beginMoveRows(parent, oldRow, oldRow, parent, newRow); + beginResetModel(); + m_items.move(oldRow, newRow); + m_selected.move(oldRow, newRow); + if (wasTarget) + m_target = newRow; + //endMoveRows(); + endResetModel(); + //emit dataChanged(index(oldRow), index(newRow)); + } + } + MapItem *findMapItem(const PipeEndPoint *source, const QString& name) { // FIXME: Should consider adding a QHash for this @@ -177,7 +218,7 @@ public: while (i.hasNext()) { MapItem *item = i.next(); - if ((item->m_name == name) && (item->m_source == source)) + if ((item->m_name == name) && (item->m_sourcePipe == source)) return item; } return nullptr; @@ -234,25 +275,40 @@ public: allUpdated(); } + Q_INVOKABLE void setFrequency(double frequency); + QHash roleNames() const { QHash roles; roles[positionRole] = "position"; roles[mapTextRole] = "mapText"; roles[mapTextVisibleRole] = "mapTextVisible"; + roles[mapImageVisibleRole] = "mapImageVisible"; roles[mapImageRole] = "mapImage"; roles[mapImageRotationRole] = "mapImageRotation"; - roles[mapImageFixedSizeRole] = "mapImageFixedSize"; + roles[mapImageMinZoomRole] = "mapImageMinZoom"; roles[bubbleColourRole] = "bubbleColour"; roles[selectedRole] = "selected"; + roles[targetRole] = "target"; + roles[frequencyRole] = "frequency"; + roles[frequencyStringRole] = "frequencyString"; return roles; } + // Set the sources of data we should display + void setSources(quint32 sources) + { + m_sources = sources; + allUpdated(); + } + private: MapGUI *m_gui; QList m_items; QList m_selected; + int m_target; // Row number of current target, or -1 for none bool m_displayNames; + quint32 m_sources; }; class MapGUI : public FeatureGUI { @@ -265,6 +321,13 @@ public: QByteArray serialize() const; bool deserialize(const QByteArray& data); virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + AzEl *getAzEl() { return &m_azEl; } + Map *getMap() { return m_map; } + quint32 getSourceMask(const PipeEndPoint *sourcePipe); + static QString getBeaconFilename(); + QList *getBeacons() { return m_beacons; } + void setBeacons(QList *beacons); + void find(const QString& target); private: Ui::MapGUI* ui; @@ -277,27 +340,36 @@ private: Map* m_map; MessageQueue m_inputMessageQueue; MapModel m_mapModel; + AzEl m_azEl; // Position of station + QList *m_beacons; + MapBeaconDialog m_beaconDialog; explicit MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr); virtual ~MapGUI(); void blockApplySettings(bool block); void applySettings(bool force = false); + void applyMapSettings(); void displaySettings(); - void updatePipeList(); bool handleMessage(const Message& message); - void find(const QString& target); + void geoReply(); void leaveEvent(QEvent*); void enterEvent(QEvent*); + static QString getDataDir(); + private slots: void onMenuDialogCalled(const QPoint &p); void onWidgetRolled(QWidget* widget, bool rollDown); void handleInputMessages(); void on_displayNames_clicked(bool checked=false); void on_find_returnPressed(); + void on_maidenhead_clicked(bool checked=false); void on_deleteAll_clicked(); + void on_displaySettings_clicked(); + void on_mapTypes_currentIndexChanged(int index); + void on_beacons_clicked(); }; diff --git a/plugins/feature/map/mapgui.ui b/plugins/feature/map/mapgui.ui index 95ba4709f..a6a9a9ca2 100644 --- a/plugins/feature/map/mapgui.ui +++ b/plugins/feature/map/mapgui.ui @@ -11,7 +11,7 @@ - + 0 0 @@ -73,26 +73,6 @@ - - - - Sources - - - - - - - - 150 - 0 - - - - Data source channels and features - - - @@ -102,8 +82,14 @@ + + + 150 + 0 + + - Enter name of object to find, latitude and longitude or Maidenhead locator + Enter name of object to find, latitude and longitude, Maidenhead locator or an address @@ -120,6 +106,47 @@ + + + + + 120 + 0 + + + + Select type of map to display + + + + + + + Maidenhead locator conversion + + + + + + + :/mem.png:/mem.png + + + + + + + Display Beacon dialog + + + + + + + :/antenna.png:/antenna.png + + + @@ -154,6 +181,20 @@ + + + + Show settings dialog + + + + + + + :/listing.png:/listing.png + + + @@ -241,6 +282,13 @@ + find + mapTypes + maidenhead + beacons + displayNames + deleteAll + displaySettings map diff --git a/plugins/feature/map/maplocationdialog.cpp b/plugins/feature/map/maplocationdialog.cpp new file mode 100644 index 000000000..840aa716e --- /dev/null +++ b/plugins/feature/map/maplocationdialog.cpp @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "maplocationdialog.h" +#include +#include + +MapLocationDialog::MapLocationDialog(const QList& locations, + QWidget* parent) : + QDialog(parent), + ui(new Ui::MapLocationDialog) +{ + ui->setupUi(this); + for (const QGeoLocation& location : locations) + { + QGeoAddress address = location.address(); + ui->locations->addItem(address.text()); + } + ui->locations->setCurrentRow(0); + m_locations = &locations; +} + +MapLocationDialog::~MapLocationDialog() +{ + delete ui; +} + +void MapLocationDialog::accept() +{ + int row = ui->locations->currentRow(); + m_selectedLocation = m_locations->at(row); + QDialog::accept(); +} diff --git a/plugins/feature/map/maplocationdialog.h b/plugins/feature/map/maplocationdialog.h new file mode 100644 index 000000000..b63a8c837 --- /dev/null +++ b/plugins/feature/map/maplocationdialog.h @@ -0,0 +1,43 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FEATURE_MAPLOCATIONDIALOG_H +#define INCLUDE_FEATURE_MAPLOCATIONDIALOG_H + +#include "ui_maplocationdialog.h" + +#include +#include + +class MapLocationDialog : public QDialog { + Q_OBJECT + +public: + explicit MapLocationDialog(const QList& locations, QWidget* parent = 0); + ~MapLocationDialog(); + + const QList *m_locations; + QGeoLocation m_selectedLocation; + +private slots: + void accept(); + +private: + Ui::MapLocationDialog* ui; +}; + +#endif // INCLUDE_FEATURE_MAPLOCATIONDIALOG_H diff --git a/plugins/feature/map/maplocationdialog.ui b/plugins/feature/map/maplocationdialog.ui new file mode 100644 index 000000000..5c22371d5 --- /dev/null +++ b/plugins/feature/map/maplocationdialog.ui @@ -0,0 +1,93 @@ + + + MapLocationDialog + + + + 0 + 0 + 565 + 349 + + + + + Liberation Sans + 9 + + + + Select a Location + + + + + + + + + Select a location: + + + + + + + Select a location + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + locations + + + + + buttonBox + accepted() + MapLocationDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MapLocationDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/plugins/feature/map/mapmaidenheaddialog.cpp b/plugins/feature/map/mapmaidenheaddialog.cpp new file mode 100644 index 000000000..18f87ccaa --- /dev/null +++ b/plugins/feature/map/mapmaidenheaddialog.cpp @@ -0,0 +1,144 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "util/units.h" +#include "util/maidenhead.h" + +#include "mapmaidenheaddialog.h" +#include "maplocationdialog.h" + +MapMaidenheadDialog::MapMaidenheadDialog(QWidget* parent) : + QDialog(parent), + ui(new Ui::MapMaidenheadDialog) +{ + ui->setupUi(this); +} + +MapMaidenheadDialog::~MapMaidenheadDialog() +{ + delete ui; +} + +void MapMaidenheadDialog::on_address_returnPressed() +{ + QString address = ui->address->text().trimmed(); + if (!address.isEmpty()) + { + ui->latAndLong->setText(""); + ui->error->setText(""); + QGeoServiceProvider* geoSrv = new QGeoServiceProvider("osm"); + if (geoSrv != nullptr) + { + QLocale qLocaleC(QLocale::C, QLocale::AnyCountry); + geoSrv->setLocale(qLocaleC); + QGeoCodeReply *pQGeoCode = geoSrv->geocodingManager()->geocode(address); + if (pQGeoCode) + QObject::connect(pQGeoCode, &QGeoCodeReply::finished, this, &MapMaidenheadDialog::geoReply); + else + ui->error->setText("GeoCoding failed"); + } + else + ui->error->setText("OpenStreetMap Location services not available"); + } +} + +void MapMaidenheadDialog::on_latAndLong_returnPressed() +{ + float latitude, longitude; + QString coords = ui->latAndLong->text(); + + if (Units::stringToLatitudeAndLongitude(coords, latitude, longitude)) + { + ui->error->setText(""); + ui->maidenhead->setText(Maidenhead::toMaidenhead(latitude, longitude)); + } + else + { + ui->error->setText("Not a valid latitude and longitude"); + ui->maidenhead->setText(""); + QApplication::beep(); + } + ui->address->setText(""); +} + +void MapMaidenheadDialog::on_maidenhead_returnPressed() +{ + float latitude, longitude; + QString locator = ui->maidenhead->text(); + + if (Maidenhead::fromMaidenhead(locator, latitude, longitude)) + { + ui->error->setText(""); + ui->latAndLong->setText(QString("%1,%2").arg(latitude).arg(longitude)); + } + else + { + ui->error->setText("Not a valid Maidenhead locator"); + ui->latAndLong->setText(""); + QApplication::beep(); + } + ui->address->setText(""); +} + +void MapMaidenheadDialog::geoReply() +{ + QGeoCodeReply *pQGeoCode = dynamic_cast(sender()); + + if ((pQGeoCode != nullptr) && (pQGeoCode->error() == QGeoCodeReply::NoError)) + { + QList qGeoLocs = pQGeoCode->locations(); + if (qGeoLocs.size() == 0) + { + ui->error->setText("No location found for address"); + QApplication::beep(); + } + else + { + if (qGeoLocs.size() == 1) + { + QGeoCoordinate c = qGeoLocs.at(0).coordinate(); + ui->latAndLong->setText(QString("%1,%2").arg(c.latitude()).arg(c.longitude())); + ui->maidenhead->setText(Maidenhead::toMaidenhead(c.latitude(), c.longitude())); + } + else + { + // Show dialog allowing user to select from the results + MapLocationDialog dialog(qGeoLocs, this); + if (dialog.exec() == QDialog::Accepted) + { + QGeoCoordinate c = dialog.m_selectedLocation.coordinate(); + ui->latAndLong->setText(QString("%1,%2").arg(c.latitude()).arg(c.longitude())); + ui->maidenhead->setText(Maidenhead::toMaidenhead(c.latitude(), c.longitude())); + } + } + } + } + else + ui->error->setText(QString("GeoCode error: %1").arg(pQGeoCode->error())); + pQGeoCode->deleteLater(); +} + +// Use a custom close button, so pressing enter doesn't close the dialog +void MapMaidenheadDialog::on_close_clicked() +{ + QDialog::accept(); +} diff --git a/plugins/feature/map/mapmaidenheaddialog.h b/plugins/feature/map/mapmaidenheaddialog.h new file mode 100644 index 000000000..e4c75562b --- /dev/null +++ b/plugins/feature/map/mapmaidenheaddialog.h @@ -0,0 +1,42 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FEATURE_MAPMAIDENHEADDIALOG_H +#define INCLUDE_FEATURE_MAPMAIDENHEADDIALOG_H + +#include "ui_mapmaidenheaddialog.h" + +class MapMaidenheadDialog : public QDialog { + Q_OBJECT + +public: + explicit MapMaidenheadDialog(QWidget* parent = 0); + ~MapMaidenheadDialog(); + + void geoReply(); + +private slots: + void on_address_returnPressed(); + void on_latAndLong_returnPressed(); + void on_maidenhead_returnPressed(); + void on_close_clicked(); + +private: + Ui::MapMaidenheadDialog* ui; +}; + +#endif // INCLUDE_FEATURE_MAPMAIDENHEADDIALOG_H diff --git a/plugins/feature/map/mapmaidenheaddialog.ui b/plugins/feature/map/mapmaidenheaddialog.ui new file mode 100644 index 000000000..5e6922aa9 --- /dev/null +++ b/plugins/feature/map/mapmaidenheaddialog.ui @@ -0,0 +1,145 @@ + + + MapMaidenheadDialog + + + + 0 + 0 + 565 + 194 + + + + + Liberation Sans + 9 + + + + Maidenhead Locator Converter + + + Location conversion + + + + + + + + + Enter a location to convert and press enter: + + + + + + + + + Address + + + + + + + Enter an address to convert to latitude/longitude and a Maidenhead locator + + + + + + + Latitude and longitude + + + + + + + Maidenhead locator + + + + + + + Enter a Maidenhead locator to convert to latitude and longitude + + + + + + + Qt::StrongFocus + + + Enter latitude and longitude to convert to a Maidenhead locator + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::NoFocus + + + Close + + + + + + + + + + + + address + latAndLong + maidenhead + close + + + + diff --git a/plugins/feature/map/mapsettings.cpp b/plugins/feature/map/mapsettings.cpp index 5f88c43ea..aa45e824e 100644 --- a/plugins/feature/map/mapsettings.cpp +++ b/plugins/feature/map/mapsettings.cpp @@ -35,6 +35,14 @@ const QStringList MapSettings::m_pipeURIs = { QStringLiteral("sdrangel.feature.startracker") }; +// GUI combo box should match ordering in this list +const QStringList MapSettings::m_mapProviders = { + QStringLiteral("osm"), + QStringLiteral("esri"), + QStringLiteral("mapbox"), + QStringLiteral("mapboxgl") +}; + MapSettings::MapSettings() { resetToDefaults(); @@ -43,6 +51,10 @@ MapSettings::MapSettings() void MapSettings::resetToDefaults() { m_displayNames = true; + m_mapProvider = "osm"; + m_mapBoxApiKey = ""; + m_mapBoxStyles = ""; + m_sources = -1; m_title = "Map"; m_rgbColor = QColor(225, 25, 99).rgb(); m_useReverseAPI = false; @@ -57,6 +69,10 @@ QByteArray MapSettings::serialize() const SimpleSerializer s(1); s.writeBool(1, m_displayNames); + s.writeString(2, m_mapProvider); + s.writeString(3, m_mapBoxApiKey); + s.writeString(4, m_mapBoxStyles); + s.writeU32(5, m_sources); s.writeString(8, m_title); s.writeU32(9, m_rgbColor); s.writeBool(10, m_useReverseAPI); @@ -85,6 +101,10 @@ bool MapSettings::deserialize(const QByteArray& data) QString strtmp; d.readBool(1, &m_displayNames, true); + d.readString(2, &m_mapProvider, "osm"); + d.readString(3, &m_mapBoxApiKey, ""); + d.readString(4, &m_mapBoxStyles, ""); + d.readU32(5, &m_sources, -1); d.readString(8, &m_title, "Map"); d.readU32(9, &m_rgbColor, QColor(225, 25, 99).rgb()); d.readBool(10, &m_useReverseAPI, false); diff --git a/plugins/feature/map/mapsettings.h b/plugins/feature/map/mapsettings.h index 2550b81ef..ae5436b5c 100644 --- a/plugins/feature/map/mapsettings.h +++ b/plugins/feature/map/mapsettings.h @@ -29,20 +29,11 @@ class PipeEndPoint; struct MapSettings { - struct AvailablePipe - { - enum {RX, TX, Feature} m_type; - int m_setIndex; - int m_index; - PipeEndPoint *m_source; - QString m_id; - - AvailablePipe() = default; - AvailablePipe(const AvailablePipe&) = default; - AvailablePipe& operator=(const AvailablePipe&) = default; - }; - bool m_displayNames; + QString m_mapProvider; + QString m_mapBoxApiKey; + QString m_mapBoxStyles; + quint32 m_sources; // Bitmask of SOURCE_* QString m_title; quint32 m_rgbColor; bool m_useReverseAPI; @@ -58,6 +49,15 @@ struct MapSettings static const QStringList m_pipeTypes; static const QStringList m_pipeURIs; + + static const QStringList m_mapProviders; + + // The first few should match the order in m_pipeTypes for MapGUI::getSourceMask to work + static const quint32 SOURCE_ADSB = 0x1; + static const quint32 SOURCE_APRS = 0x2; + static const quint32 SOURCE_STAR_TRACKER = 0x4; + static const quint32 SOURCE_BEACONS = 0x8; + static const quint32 SOURCE_STATION = 0x10; }; #endif // INCLUDE_FEATURE_MAPSETTINGS_H_ diff --git a/plugins/feature/map/mapsettingsdialog.cpp b/plugins/feature/map/mapsettingsdialog.cpp new file mode 100644 index 000000000..1133dd380 --- /dev/null +++ b/plugins/feature/map/mapsettingsdialog.cpp @@ -0,0 +1,66 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "util/units.h" + +#include "mapsettingsdialog.h" +#include "maplocationdialog.h" + +MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) : + QDialog(parent), + m_settings(settings), + ui(new Ui::MapSettingsDialog) +{ + ui->setupUi(this); + ui->mapProvider->setCurrentIndex(MapSettings::m_mapProviders.indexOf(settings->m_mapProvider)); + ui->mapBoxApiKey->setText(settings->m_mapBoxApiKey); + ui->mapBoxStyles->setText(settings->m_mapBoxStyles); + for (int i = 0; i < ui->sourceList->count(); i++) + ui->sourceList->item(i)->setCheckState((m_settings->m_sources & (1 << i)) ? Qt::Checked : Qt::Unchecked); +} + +MapSettingsDialog::~MapSettingsDialog() +{ + delete ui; +} + +void MapSettingsDialog::accept() +{ + QString mapProvider = MapSettings::m_mapProviders[ui->mapProvider->currentIndex()]; + QString mapBoxApiKey = ui->mapBoxApiKey->text(); + QString mapBoxStyles = ui->mapBoxStyles->text(); + if ((mapProvider != m_settings->m_mapProvider) + || (mapBoxApiKey != m_settings->m_mapBoxApiKey) + || (mapBoxStyles != m_settings->m_mapBoxStyles)) + { + m_settings->m_mapProvider = mapProvider; + m_settings->m_mapBoxApiKey = mapBoxApiKey; + m_settings->m_mapBoxStyles = mapBoxStyles; + m_mapSettingsChanged = true; + } + else + m_mapSettingsChanged = false; + m_settings->m_sources = 0; + quint32 sources = MapSettings::SOURCE_STATION; + for (int i = 0; i < ui->sourceList->count(); i++) + sources |= (ui->sourceList->item(i)->checkState() == Qt::Checked) << i; + m_sourcesChanged = sources != m_settings->m_sources; + m_settings->m_sources = sources; + QDialog::accept(); +} diff --git a/plugins/feature/map/mapsettingsdialog.h b/plugins/feature/map/mapsettingsdialog.h new file mode 100644 index 000000000..c866cc432 --- /dev/null +++ b/plugins/feature/map/mapsettingsdialog.h @@ -0,0 +1,42 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FEATURE_MAPSETTINGSDIALOG_H +#define INCLUDE_FEATURE_MAPSETTINGSDIALOG_H + +#include "ui_mapsettingsdialog.h" +#include "mapsettings.h" + +class MapSettingsDialog : public QDialog { + Q_OBJECT + +public: + explicit MapSettingsDialog(MapSettings *settings, QWidget* parent = 0); + ~MapSettingsDialog(); + + MapSettings *m_settings; + bool m_mapSettingsChanged; + bool m_sourcesChanged; + +private slots: + void accept(); + +private: + Ui::MapSettingsDialog* ui; +}; + +#endif // INCLUDE_FEATURE_MAPSETTINGSDIALOG_H diff --git a/plugins/feature/map/mapsettingsdialog.ui b/plugins/feature/map/mapsettingsdialog.ui new file mode 100644 index 000000000..da40f01e6 --- /dev/null +++ b/plugins/feature/map/mapsettingsdialog.ui @@ -0,0 +1,199 @@ + + + MapSettingsDialog + + + + 0 + 0 + 436 + 491 + + + + + Liberation Sans + 9 + + + + Select a Location + + + + + + + 0 + + + + + Select data to display: + + + + + + + QAbstractItemView::MultiSelection + + + false + + + + ADS-B + + + Checked + + + + + APRS + + + Checked + + + + + Star Tracker + + + Checked + + + + + Beacons + + + Checked + + + + + + + + Map Provider Settings + + + + + + Map provider + + + + + + + Select map provider + + + + OpenStreetMap + + + + + ESRI + + + + + Mapbox + + + + + MapboxGL + + + + + + + + Mapbox API Key + + + + + + + Enter a Mapbox API key in order to use Mapbox maps + + + + + + + MapboxGL Styles + + + + + + + Comma separated list of MapBox styles + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + MapSettingsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MapSettingsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/plugins/feature/map/readme.md b/plugins/feature/map/readme.md index c34497525..fe37c5e78 100644 --- a/plugins/feature/map/readme.md +++ b/plugins/feature/map/readme.md @@ -2,39 +2,79 @@

Introduction

-The Map Feature plugin displays a world map. On top of this, it can plot data from other plugins, such as APRS symbols from the APRS Feature, aircraft from the ADS-B Demodulator or the Sun, Moon and Stars from the Star Tracker. +The Map Feature plugin displays a world map. It can display street maps, satellite imagery as well as custom map types. +On top of this, it can plot data from other plugins, such as APRS symbols from the APRS Feature, aircraft from the ADS-B Demodulator +or the Sun, Moon and Stars from the Star Tracker. It can also display beacon locations based on the IARU Region 1 beacon database. + +![Map feature](../../../doc/img/Map_plugin_beacons.png)

Interface

![Map feature plugin GUI](../../../doc/img/Map_plugin.png) -

1: Source Channels

- -This displays the list of channels the Map is displaying data from. - -

2: Find

+

1: Find

To centre the map on an object or location, enter: * An object name. * Latitude and longitude. This can be in decimal degrees (E.g: -23.666413, -46.573550) or degrees, minutes and seconds (E.g: 50°40'46.461"N 95°48'26.533"W or 33d51m54.5148sS 151d12m35.6400sE). * A Maidenhead locator (E.g: IO86av). +* An address (E.g: St Katharine's & Wapping, London EC3N 4AB) -

3: Display Names

+

2: Map Type

+ +Allows you to select a map type. The available types will depend upon the Map provider +selected under Display Settings (7). + +

3: Maidenhead locator conversion

+ +When checked, opens the Maidenhead locator converter dialog, which allows conversion between addresses, latitude and longitude and Maidenhead locators. + +

4: Display Beacon dialog

+ +When clicked, opens the Beacon dialog. Initially, no beacons will be listed. To download the IARU Region 1 beacon list, click the download button in the top right. +The beacons will then be displayed in the table and on the map. + +* Double clicking in a cell in the beacon table in the Callsign or Location columns, will centre the map on that beacon. +* Double clicking on the Frequency column will set the Device center frequency. + +![Beacon dialog](../../../doc/img/Map_plugin_beacon_dialog.png) + +

5: Display Names

When checked, names of objects are displayed in a bubble next to each object. -

4: Delete

+

6: Delete

When clicked, all items will be deleted from the map. +

7: Display settings

+ +When clicked, opens the Map Display Settings dialog, which allows setting: + +* Which data the Map will display. +* Which Map provider will be used to source the map image. + +In order to display Mapbox maps, you will need to enter an API Key. A key can be obtained by registering at: http://www.mapbox.com/ +Note that it is not currently possible to support entering an API Key for Open Street Maps, in order to remove the watermarks. +

Map

-The map displays objects reported by other SDRangel channels and features. +The map displays objects reported by other SDRangel channels and features, as well as beacon locations. -* The antenna location is placed according to My Position set under the Preferences > My Position menu. The position is only updated when the Map plugin is first opened. +* The "Home" antenna location is placed according to My Position set under the Preferences > My Position menu. The position is only updated when the Map plugin is first opened. * To pan around the map, click the left mouse button and drag. To zoom in or out, use the mouse scroll wheel. -* Clicking on an object in the map will display a text bubble with additional information about the object. +* Single clicking on an object in the map will display a text bubble with additional information about the object. +* Right clicking on a object will open a context menu, which allows: + * To set an object as the target. The target object with have its azimuth and elevation displayed in the text bubble and sent to the Rotator Controller feature. + * Setting the center frequency to the first frequency found in the text bubble for the object. + * Changing the order in which the objects are drawn, which can be help to cycle through multiple objects that are at the same position. + +

Attribution

+ +IARU Region 1 beacon list used with permission from: https://iaru-r1-c5-beacons.org/ To add or update a beacon, see: https://iaru-r1-c5-beacons.org/index.php/beacon-update/ + +Mapping and geolocation services are by Open Street Map: https://www.openstreetmap.org/ esri: https://www.esri.com/ and Mapbox: https://www.mapbox.com/

API

diff --git a/plugins/feature/startracker/startrackerworker.cpp b/plugins/feature/startracker/startrackerworker.cpp index 268a2a3fb..631cdf5a7 100644 --- a/plugins/feature/startracker/startrackerworker.cpp +++ b/plugins/feature/startracker/startrackerworker.cpp @@ -391,7 +391,7 @@ void StarTrackerWorker::sendToMap(QList *mapMessageQueues, QStrin swgMapItem->setImage(new QString(image)); swgMapItem->setImageRotation(rotation); swgMapItem->setText(new QString(text)); - swgMapItem->setImageFixedSize(1); + swgMapItem->setImageMinZoom(3); MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_starTracker, swgMapItem); (*it)->push(msg); diff --git a/sdrbase/util/maidenhead.cpp b/sdrbase/util/maidenhead.cpp index 7cdda6957..6b52bfdd5 100644 --- a/sdrbase/util/maidenhead.cpp +++ b/sdrbase/util/maidenhead.cpp @@ -52,10 +52,15 @@ bool Maidenhead::fromMaidenhead(const QString& maidenhead, float& latitude, floa int lat1 = maidenhead[1].toUpper().toLatin1() - 'A'; int lon2 = maidenhead[2].toLatin1() - '0'; int lat2 = maidenhead[3].toLatin1() - '0'; - int lon3 = maidenhead[4].toUpper().toLatin1() - 'A'; - int lat3 = maidenhead[5].toUpper().toLatin1() - 'A'; + int lon3 = 0; + int lat3 = 0; int lon4 = 0; int lat4 = 0; + if (maidenhead.length() >= 6) + { + lon3 = maidenhead[4].toUpper().toLatin1() - 'A'; + lat3 = maidenhead[5].toUpper().toLatin1() - 'A'; + } if (maidenhead.length() == 8) { lon4 = maidenhead[6].toLatin1() - '0'; @@ -76,8 +81,8 @@ bool Maidenhead::fromMaidenhead(const QString& maidenhead, float& latitude, floa bool Maidenhead::isMaidenhead(const QString& maidenhead) { int length = maidenhead.length(); - if ((length != 6) && (length != 8)) + if ((length != 4) && (length != 6) && (length != 8)) return false; - QRegExp re("[A-Ra-r][A-Ra-r][0-9][0-9][A-Xa-x][A-Xa-x]([0-9][0-9])?"); + QRegExp re("[A-Ra-r][A-Ra-r][0-9][0-9]([A-Xa-x][A-Xa-x]([0-9][0-9])?)?"); return re.exactMatch(maidenhead); } diff --git a/swagger/sdrangel/api/swagger/include/Map.yaml b/swagger/sdrangel/api/swagger/include/Map.yaml index ce71eaa72..19ff43c16 100644 --- a/swagger/sdrangel/api/swagger/include/Map.yaml +++ b/swagger/sdrangel/api/swagger/include/Map.yaml @@ -41,9 +41,11 @@ MapItem: imageRotation: description: "Angle to rotate the image by" type: integer - imageFixedSize: - description: "Keep the image the same size, regardless of map zoom level (1 for yes, 0 for no)" + default: 0 + imageMinZoom: + description: "Minimim zoom value" type: integer + default: 11 text: descrption: "Text to draw on the map when item is selected" type: string @@ -55,3 +57,7 @@ MapItem: description: "Longitude in decimal degrees, positive to the east" type: number format: float + altitude: + description: "Altitude / height above sea level in metres" + type: number + format: float diff --git a/swagger/sdrangel/code/qt5/client/SWGMapItem.cpp b/swagger/sdrangel/code/qt5/client/SWGMapItem.cpp index f390c6d01..901774d61 100644 --- a/swagger/sdrangel/code/qt5/client/SWGMapItem.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGMapItem.cpp @@ -34,14 +34,16 @@ SWGMapItem::SWGMapItem() { m_image_isSet = false; image_rotation = 0; m_image_rotation_isSet = false; - image_fixed_size = 0; - m_image_fixed_size_isSet = false; + image_min_zoom = 0; + m_image_min_zoom_isSet = false; text = nullptr; m_text_isSet = false; latitude = 0.0f; m_latitude_isSet = false; longitude = 0.0f; m_longitude_isSet = false; + altitude = 0.0f; + m_altitude_isSet = false; } SWGMapItem::~SWGMapItem() { @@ -56,14 +58,16 @@ SWGMapItem::init() { m_image_isSet = false; image_rotation = 0; m_image_rotation_isSet = false; - image_fixed_size = 0; - m_image_fixed_size_isSet = false; + image_min_zoom = 0; + m_image_min_zoom_isSet = false; text = new QString(""); m_text_isSet = false; latitude = 0.0f; m_latitude_isSet = false; longitude = 0.0f; m_longitude_isSet = false; + altitude = 0.0f; + m_altitude_isSet = false; } void @@ -81,6 +85,7 @@ SWGMapItem::cleanup() { } + } SWGMapItem* @@ -100,7 +105,7 @@ SWGMapItem::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&image_rotation, pJson["imageRotation"], "qint32", ""); - ::SWGSDRangel::setValue(&image_fixed_size, pJson["imageFixedSize"], "qint32", ""); + ::SWGSDRangel::setValue(&image_min_zoom, pJson["imageMinZoom"], "qint32", ""); ::SWGSDRangel::setValue(&text, pJson["text"], "QString", "QString"); @@ -108,6 +113,8 @@ SWGMapItem::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&longitude, pJson["longitude"], "float", ""); + ::SWGSDRangel::setValue(&altitude, pJson["altitude"], "float", ""); + } QString @@ -133,8 +140,8 @@ SWGMapItem::asJsonObject() { if(m_image_rotation_isSet){ obj->insert("imageRotation", QJsonValue(image_rotation)); } - if(m_image_fixed_size_isSet){ - obj->insert("imageFixedSize", QJsonValue(image_fixed_size)); + if(m_image_min_zoom_isSet){ + obj->insert("imageMinZoom", QJsonValue(image_min_zoom)); } if(text != nullptr && *text != QString("")){ toJsonValue(QString("text"), text, obj, QString("QString")); @@ -145,6 +152,9 @@ SWGMapItem::asJsonObject() { if(m_longitude_isSet){ obj->insert("longitude", QJsonValue(longitude)); } + if(m_altitude_isSet){ + obj->insert("altitude", QJsonValue(altitude)); + } return obj; } @@ -180,13 +190,13 @@ SWGMapItem::setImageRotation(qint32 image_rotation) { } qint32 -SWGMapItem::getImageFixedSize() { - return image_fixed_size; +SWGMapItem::getImageMinZoom() { + return image_min_zoom; } void -SWGMapItem::setImageFixedSize(qint32 image_fixed_size) { - this->image_fixed_size = image_fixed_size; - this->m_image_fixed_size_isSet = true; +SWGMapItem::setImageMinZoom(qint32 image_min_zoom) { + this->image_min_zoom = image_min_zoom; + this->m_image_min_zoom_isSet = true; } QString* @@ -219,6 +229,16 @@ SWGMapItem::setLongitude(float longitude) { this->m_longitude_isSet = true; } +float +SWGMapItem::getAltitude() { + return altitude; +} +void +SWGMapItem::setAltitude(float altitude) { + this->altitude = altitude; + this->m_altitude_isSet = true; +} + bool SWGMapItem::isSet(){ @@ -233,7 +253,7 @@ SWGMapItem::isSet(){ if(m_image_rotation_isSet){ isObjectUpdated = true; break; } - if(m_image_fixed_size_isSet){ + if(m_image_min_zoom_isSet){ isObjectUpdated = true; break; } if(text && *text != QString("")){ @@ -245,6 +265,9 @@ SWGMapItem::isSet(){ if(m_longitude_isSet){ isObjectUpdated = true; break; } + if(m_altitude_isSet){ + isObjectUpdated = true; break; + } }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGMapItem.h b/swagger/sdrangel/code/qt5/client/SWGMapItem.h index f9c16eec3..998f8eadc 100644 --- a/swagger/sdrangel/code/qt5/client/SWGMapItem.h +++ b/swagger/sdrangel/code/qt5/client/SWGMapItem.h @@ -51,8 +51,8 @@ public: qint32 getImageRotation(); void setImageRotation(qint32 image_rotation); - qint32 getImageFixedSize(); - void setImageFixedSize(qint32 image_fixed_size); + qint32 getImageMinZoom(); + void setImageMinZoom(qint32 image_min_zoom); QString* getText(); void setText(QString* text); @@ -63,6 +63,9 @@ public: float getLongitude(); void setLongitude(float longitude); + float getAltitude(); + void setAltitude(float altitude); + virtual bool isSet() override; @@ -76,8 +79,8 @@ private: qint32 image_rotation; bool m_image_rotation_isSet; - qint32 image_fixed_size; - bool m_image_fixed_size_isSet; + qint32 image_min_zoom; + bool m_image_min_zoom_isSet; QString* text; bool m_text_isSet; @@ -88,6 +91,9 @@ private: float longitude; bool m_longitude_isSet; + float altitude; + bool m_altitude_isSet; + }; } diff --git a/swagger/sdrangel/code/qt5/client/SWGMapItem_2.cpp b/swagger/sdrangel/code/qt5/client/SWGMapItem_2.cpp index d788fee5a..7b4f0c1b8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGMapItem_2.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGMapItem_2.cpp @@ -34,14 +34,16 @@ SWGMapItem_2::SWGMapItem_2() { m_image_isSet = false; image_rotation = 0; m_image_rotation_isSet = false; - image_fixed_size = 0; - m_image_fixed_size_isSet = false; + image_min_zoom = 0; + m_image_min_zoom_isSet = false; text = nullptr; m_text_isSet = false; latitude = 0.0f; m_latitude_isSet = false; longitude = 0.0f; m_longitude_isSet = false; + altitude = 0.0f; + m_altitude_isSet = false; } SWGMapItem_2::~SWGMapItem_2() { @@ -56,14 +58,16 @@ SWGMapItem_2::init() { m_image_isSet = false; image_rotation = 0; m_image_rotation_isSet = false; - image_fixed_size = 0; - m_image_fixed_size_isSet = false; + image_min_zoom = 0; + m_image_min_zoom_isSet = false; text = new QString(""); m_text_isSet = false; latitude = 0.0f; m_latitude_isSet = false; longitude = 0.0f; m_longitude_isSet = false; + altitude = 0.0f; + m_altitude_isSet = false; } void @@ -81,6 +85,7 @@ SWGMapItem_2::cleanup() { } + } SWGMapItem_2* @@ -100,7 +105,7 @@ SWGMapItem_2::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&image_rotation, pJson["imageRotation"], "qint32", ""); - ::SWGSDRangel::setValue(&image_fixed_size, pJson["imageFixedSize"], "qint32", ""); + ::SWGSDRangel::setValue(&image_min_zoom, pJson["imageMinZoom"], "qint32", ""); ::SWGSDRangel::setValue(&text, pJson["text"], "QString", "QString"); @@ -108,6 +113,8 @@ SWGMapItem_2::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&longitude, pJson["longitude"], "float", ""); + ::SWGSDRangel::setValue(&altitude, pJson["altitude"], "float", ""); + } QString @@ -133,8 +140,8 @@ SWGMapItem_2::asJsonObject() { if(m_image_rotation_isSet){ obj->insert("imageRotation", QJsonValue(image_rotation)); } - if(m_image_fixed_size_isSet){ - obj->insert("imageFixedSize", QJsonValue(image_fixed_size)); + if(m_image_min_zoom_isSet){ + obj->insert("imageMinZoom", QJsonValue(image_min_zoom)); } if(text != nullptr && *text != QString("")){ toJsonValue(QString("text"), text, obj, QString("QString")); @@ -145,6 +152,9 @@ SWGMapItem_2::asJsonObject() { if(m_longitude_isSet){ obj->insert("longitude", QJsonValue(longitude)); } + if(m_altitude_isSet){ + obj->insert("altitude", QJsonValue(altitude)); + } return obj; } @@ -180,13 +190,13 @@ SWGMapItem_2::setImageRotation(qint32 image_rotation) { } qint32 -SWGMapItem_2::getImageFixedSize() { - return image_fixed_size; +SWGMapItem_2::getImageMinZoom() { + return image_min_zoom; } void -SWGMapItem_2::setImageFixedSize(qint32 image_fixed_size) { - this->image_fixed_size = image_fixed_size; - this->m_image_fixed_size_isSet = true; +SWGMapItem_2::setImageMinZoom(qint32 image_min_zoom) { + this->image_min_zoom = image_min_zoom; + this->m_image_min_zoom_isSet = true; } QString* @@ -219,6 +229,16 @@ SWGMapItem_2::setLongitude(float longitude) { this->m_longitude_isSet = true; } +float +SWGMapItem_2::getAltitude() { + return altitude; +} +void +SWGMapItem_2::setAltitude(float altitude) { + this->altitude = altitude; + this->m_altitude_isSet = true; +} + bool SWGMapItem_2::isSet(){ @@ -233,7 +253,7 @@ SWGMapItem_2::isSet(){ if(m_image_rotation_isSet){ isObjectUpdated = true; break; } - if(m_image_fixed_size_isSet){ + if(m_image_min_zoom_isSet){ isObjectUpdated = true; break; } if(text && *text != QString("")){ @@ -245,6 +265,9 @@ SWGMapItem_2::isSet(){ if(m_longitude_isSet){ isObjectUpdated = true; break; } + if(m_altitude_isSet){ + isObjectUpdated = true; break; + } }while(false); return isObjectUpdated; } diff --git a/swagger/sdrangel/code/qt5/client/SWGMapItem_2.h b/swagger/sdrangel/code/qt5/client/SWGMapItem_2.h index 67414fe28..f1b159afd 100644 --- a/swagger/sdrangel/code/qt5/client/SWGMapItem_2.h +++ b/swagger/sdrangel/code/qt5/client/SWGMapItem_2.h @@ -51,8 +51,8 @@ public: qint32 getImageRotation(); void setImageRotation(qint32 image_rotation); - qint32 getImageFixedSize(); - void setImageFixedSize(qint32 image_fixed_size); + qint32 getImageMinZoom(); + void setImageMinZoom(qint32 image_min_zoom); QString* getText(); void setText(QString* text); @@ -63,6 +63,9 @@ public: float getLongitude(); void setLongitude(float longitude); + float getAltitude(); + void setAltitude(float altitude); + virtual bool isSet() override; @@ -76,8 +79,8 @@ private: qint32 image_rotation; bool m_image_rotation_isSet; - qint32 image_fixed_size; - bool m_image_fixed_size_isSet; + qint32 image_min_zoom; + bool m_image_min_zoom_isSet; QString* text; bool m_text_isSet; @@ -88,6 +91,9 @@ private: float longitude; bool m_longitude_isSet; + float altitude; + bool m_altitude_isSet; + }; }