1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-17 05:41:56 -05:00

Map updates.

Add support for different map types (street/satellite) and different map
providers.
Support finding real world addresses on the map.
Add Maidenhead locator converter.
Add Beacons.
Allow data sources to be selected by a user.
Add context menu to allow setting an object as a target, setting center
frequency and adjusting display order.
This commit is contained in:
Jon Beniston 2021-01-22 14:54:22 +00:00
parent c8d07396d2
commit 446749cbbb
41 changed files with 2469 additions and 266 deletions

View File

@ -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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 545 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 KiB

View File

@ -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()

View File

@ -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)));

View File

@ -22,7 +22,7 @@
#include <QHash>
// 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;

View File

@ -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()

View File

@ -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);

View File

@ -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")
};

View File

@ -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.
<h2>Interface</h2>

View File

@ -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()

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_BEACON_H
#define INCLUDE_BEACON_H
#include <stdio.h>
#include <string.h>
#include <QString>
#include <QList>
#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<Beacon *> *readIARUCSV(const QString &filename)
{
int cnt = 0;
QList<Beacon *> *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<Beacon *>();
// 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

View File

@ -1,7 +1,6 @@
<RCC>
<qresource prefix="/map/">
<file>map/map.qml</file>
<file>map/MapStation.qml</file>
<file>map/antenna.png</file>
</qresource>
</RCC>

View File

@ -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
}
}
}
}
}

View File

@ -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)
}
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "mapbeacondialog.h"
#include <QDebug>
#include <QUrl>
#include <QMessageBox>
#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<Beacon *> *beacons = m_gui->getBeacons();
if (beacons != nullptr)
{
ui->beacons->setRowCount(beacons->size());
QListIterator<Beacon *> 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<Beacon *> *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);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_MAPBEACONDIALOG_H
#define INCLUDE_FEATURE_MAPBEACONDIALOG_H
#include "ui_mapbeacondialog.h"
#include <QProgressDialog>
#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

View File

@ -0,0 +1,226 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MapBeaconDialog</class>
<widget class="QDialog" name="MapBeaconDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1027</width>
<height>349</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Beacons</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="topMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="filterLabel">
<property name="text">
<string>Show</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="filter">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<item>
<property name="text">
<string>All</string>
</property>
</item>
<item>
<property name="text">
<string>50MHz (6m)</string>
</property>
</item>
<item>
<property name="text">
<string>70MHz (4m)</string>
</property>
</item>
<item>
<property name="text">
<string>144MHz (2m)</string>
</property>
</item>
<item>
<property name="text">
<string>432MHz (70cm)</string>
</property>
</item>
<item>
<property name="text">
<string>1.296GHz (23cm)</string>
</property>
</item>
<item>
<property name="text">
<string>&gt;2.3GHz (&lt;13cm)</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="downloadIARU">
<property name="toolTip">
<string>Download IARU Beacon list</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/antenna.png</normaloff>:/antenna.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="beacons">
<property name="toolTip">
<string/>
</property>
<column>
<property name="text">
<string>Callsign</string>
</property>
</column>
<column>
<property name="text">
<string>Frequency</string>
</property>
</column>
<column>
<property name="text">
<string>Location</string>
</property>
</column>
<column>
<property name="text">
<string>Power</string>
</property>
</column>
<column>
<property name="text">
<string>Polarization</string>
</property>
</column>
<column>
<property name="text">
<string>Pattern</string>
</property>
</column>
<column>
<property name="text">
<string>Key</string>
</property>
</column>
<column>
<property name="text">
<string>MGM</string>
</property>
</column>
<column>
<property name="text">
<string>Azimuth</string>
</property>
</column>
<column>
<property name="text">
<string>Elevation</string>
</property>
</column>
<column>
<property name="text">
<string>Distance (km)</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>MapBeaconDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>MapBeaconDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -20,21 +20,54 @@
#include <QMessageBox>
#include <QLineEdit>
#include <QQmlContext>
#include <QQmlProperty>
#include <QQuickItem>
#include <QGeoLocation>
#include <QGeoCoordinate>
#include <QGeoCodingManager>
#include <QGeoServiceProvider>
#include <QRegExp>
#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<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_gui->getMap(), "target");
if (mapMessageQueues)
{
QList<MessageQueue*>::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<QObject*>("map");
if(object != NULL)
if (object != nullptr)
{
QGeoCoordinate coords = object->property("center").value<QGeoCoordinate>();
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<QObject*>("station");
if(stationObject != NULL)
{
QGeoCoordinate coords = stationObject->property("coordinate").value<QGeoCoordinate>();
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<Beacon *> *beacons = Beacon::readIARUCSV(MapGUI::getBeaconFilename());
if (beacons != nullptr)
setBeacons(beacons);
}
MapGUI::~MapGUI()
@ -258,11 +397,90 @@ MapGUI::~MapGUI()
delete ui;
}
void MapGUI::setBeacons(QList<Beacon *> *beacons)
{
delete m_beacons;
m_beacons = beacons;
m_beaconDialog.updateTable();
// Add to Map
QListIterator<Beacon *> i(*m_beacons);
while (i.hasNext())
{
Beacon *beacon = i.next();
SWGSDRangel::SWGMapItem beaconMapItem;
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<QObject*>("map");
QGeoCoordinate coords;
if (object != nullptr)
coords = object->property("center").value<QGeoCoordinate>();
// 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<QObject*>("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<QStringList>();
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<PipeEndPoint::AvailablePipeSource>::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<QGeoCodeReply*>(sender());
if ((pQGeoCode != nullptr) && (pQGeoCode->error() == QGeoCodeReply::NoError))
{
QList<QGeoLocation> qGeoLocs = pQGeoCode->locations();
QQuickItem *item = ui->map->rootObject();
QObject *map = item->findChild<QObject*>("map");
if (qGeoLocs.size() == 1)
{
// Only one result, so centre map on that
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<QObject*>("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";
}

View File

@ -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<int, QByteArray> roleNames() const
{
QHash<int, QByteArray> 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<MapItem *> m_items;
QList<bool> 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<Beacon *> *getBeacons() { return m_beacons; }
void setBeacons(QList<Beacon *> *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<Beacon *> *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();
};

View File

@ -11,7 +11,7 @@
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -73,26 +73,6 @@
</property>
<item>
<layout class="QHBoxLayout" name="buttonLayout">
<item>
<widget class="QLabel" name="pipesLabel">
<property name="text">
<string>Sources</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="pipes">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Data source channels and features</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="findLabel">
<property name="text">
@ -102,8 +82,14 @@
</item>
<item>
<widget class="QLineEdit" name="find">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Enter name of object to find, latitude and longitude or Maidenhead locator</string>
<string>Enter name of object to find, latitude and longitude, Maidenhead locator or an address</string>
</property>
</widget>
</item>
@ -120,6 +106,47 @@
</property>
</spacer>
</item>
<item>
<widget class="QComboBox" name="mapTypes">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Select type of map to display</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="maidenhead">
<property name="toolTip">
<string>Maidenhead locator conversion</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/mem.png</normaloff>:/mem.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="beacons">
<property name="toolTip">
<string>Display Beacon dialog</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/antenna.png</normaloff>:/antenna.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="displayNames">
<property name="toolTip">
@ -154,6 +181,20 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="displaySettings">
<property name="toolTip">
<string>Show settings dialog</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/listing.png</normaloff>:/listing.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@ -241,6 +282,13 @@
</customwidget>
</customwidgets>
<tabstops>
<tabstop>find</tabstop>
<tabstop>mapTypes</tabstop>
<tabstop>maidenhead</tabstop>
<tabstop>beacons</tabstop>
<tabstop>displayNames</tabstop>
<tabstop>deleteAll</tabstop>
<tabstop>displaySettings</tabstop>
<tabstop>map</tabstop>
</tabstops>
<resources>

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "maplocationdialog.h"
#include <QDebug>
#include <QGeoAddress>
MapLocationDialog::MapLocationDialog(const QList<QGeoLocation>& 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();
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_MAPLOCATIONDIALOG_H
#define INCLUDE_FEATURE_MAPLOCATIONDIALOG_H
#include "ui_maplocationdialog.h"
#include <QList>
#include <QGeoLocation>
class MapLocationDialog : public QDialog {
Q_OBJECT
public:
explicit MapLocationDialog(const QList<QGeoLocation>& locations, QWidget* parent = 0);
~MapLocationDialog();
const QList<QGeoLocation> *m_locations;
QGeoLocation m_selectedLocation;
private slots:
void accept();
private:
Ui::MapLocationDialog* ui;
};
#endif // INCLUDE_FEATURE_MAPLOCATIONDIALOG_H

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MapLocationDialog</class>
<widget class="QDialog" name="MapLocationDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>565</width>
<height>349</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Select a Location</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="locationsLabel">
<property name="text">
<string>Select a location:</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="locations">
<property name="toolTip">
<string>Select a location</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>locations</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>MapLocationDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>MapLocationDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QGeoCoordinate>
#include <QGeoCodingManager>
#include <QGeoServiceProvider>
#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<QGeoCodeReply*>(sender());
if ((pQGeoCode != nullptr) && (pQGeoCode->error() == QGeoCodeReply::NoError))
{
QList<QGeoLocation> 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();
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#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

View File

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MapMaidenheadDialog</class>
<widget class="QDialog" name="MapMaidenheadDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>565</width>
<height>194</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Maidenhead Locator Converter</string>
</property>
<property name="toolTip">
<string>Location conversion</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="infoLabel">
<property name="text">
<string>Enter a location to convert and press enter:</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="addressLabel">
<property name="text">
<string>Address</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="address">
<property name="toolTip">
<string>Enter an address to convert to latitude/longitude and a Maidenhead locator</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="latAndLongLabel">
<property name="text">
<string>Latitude and longitude</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="maidenheadLabel">
<property name="text">
<string>Maidenhead locator</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="maidenhead">
<property name="toolTip">
<string>Enter a Maidenhead locator to convert to latitude and longitude</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="latAndLong">
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Enter latitude and longitude to convert to a Maidenhead locator</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="error">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="close">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>address</tabstop>
<tabstop>latAndLong</tabstop>
<tabstop>maidenhead</tabstop>
<tabstop>close</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@ -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);

View File

@ -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_

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#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();
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#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

View File

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MapSettingsDialog</class>
<widget class="QDialog" name="MapSettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>436</width>
<height>491</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Select a Location</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="locationsLabel">
<property name="text">
<string>Select data to display:</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="sourceList">
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
<property name="sortingEnabled">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>ADS-B</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>APRS</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>Star Tracker</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>Beacons</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Map Provider Settings</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="mapProviderLabel">
<property name="text">
<string>Map provider</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="mapProvider">
<property name="toolTip">
<string>Select map provider</string>
</property>
<item>
<property name="text">
<string>OpenStreetMap</string>
</property>
</item>
<item>
<property name="text">
<string>ESRI</string>
</property>
</item>
<item>
<property name="text">
<string>Mapbox</string>
</property>
</item>
<item>
<property name="text">
<string>MapboxGL</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="mapBoxApiKeyLabel">
<property name="text">
<string>Mapbox API Key</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="mapBoxApiKey">
<property name="toolTip">
<string>Enter a Mapbox API key in order to use Mapbox maps</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="mapBoxStylesLabel">
<property name="text">
<string>MapboxGL Styles</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="mapBoxStyles">
<property name="toolTip">
<string>Comma separated list of MapBox styles</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>MapSettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>MapSettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -2,39 +2,79 @@
<h2>Introduction</h2>
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)
<h2>Interface</h2>
![Map feature plugin GUI](../../../doc/img/Map_plugin.png)
<h3>1: Source Channels</h3>
This displays the list of channels the Map is displaying data from.
<h3>2: Find</h3>
<h3>1: Find</h3>
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)
<h3>3: Display Names</h3>
<h3>2: Map Type</h3>
Allows you to select a map type. The available types will depend upon the Map provider
selected under Display Settings (7).
<h3>3: Maidenhead locator conversion</h3>
When checked, opens the Maidenhead locator converter dialog, which allows conversion between addresses, latitude and longitude and Maidenhead locators.
<h3>4: Display Beacon dialog</h3>
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)
<h3>5: Display Names</h3>
When checked, names of objects are displayed in a bubble next to each object.
<h3>4: Delete</h3>
<h3>6: Delete</h3>
When clicked, all items will be deleted from the map.
<h3>7: Display settings</h3>
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.
<h3>Map</h3>
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.
<h2>Attribution</h2>
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/
<h2>API</h2>

View File

@ -391,7 +391,7 @@ void StarTrackerWorker::sendToMap(QList<MessageQueue*> *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);

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}

View File

@ -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;
};
}

View File

@ -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;
}

View File

@ -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;
};
}