mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-17 13:51:47 -05:00
Add Map feature for displaying the combined items from other plugins
This commit is contained in:
parent
c74ec2c426
commit
bbe75aab6f
@ -5,6 +5,7 @@ if (Qt5SerialPort_FOUND)
|
||||
endif()
|
||||
|
||||
if (Qt5Quick_FOUND AND Qt5QuickWidgets_FOUND AND Qt5Positioning_FOUND)
|
||||
add_subdirectory(map)
|
||||
add_subdirectory(vorlocalizer)
|
||||
endif()
|
||||
|
||||
|
56
plugins/feature/map/CMakeLists.txt
Normal file
56
plugins/feature/map/CMakeLists.txt
Normal file
@ -0,0 +1,56 @@
|
||||
project(map)
|
||||
|
||||
set(map_SOURCES
|
||||
map.cpp
|
||||
mapsettings.cpp
|
||||
mapplugin.cpp
|
||||
mapwebapiadapter.cpp
|
||||
)
|
||||
|
||||
set(map_HEADERS
|
||||
map.h
|
||||
mapsettings.h
|
||||
mapplugin.h
|
||||
mapreport.h
|
||||
mapwebapiadapter.h
|
||||
)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
|
||||
)
|
||||
|
||||
if(NOT SERVER_MODE)
|
||||
set(map_SOURCES
|
||||
${map_SOURCES}
|
||||
mapgui.cpp
|
||||
mapgui.ui
|
||||
map.qrc
|
||||
)
|
||||
set(map_HEADERS
|
||||
${map_HEADERS}
|
||||
mapgui.h
|
||||
)
|
||||
|
||||
set(TARGET_NAME map)
|
||||
set(TARGET_LIB "Qt5::Widgets" Qt5::Quick Qt5::QuickWidgets Qt5::Positioning)
|
||||
set(TARGET_LIB_GUI "sdrgui")
|
||||
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
|
||||
else()
|
||||
set(TARGET_NAME mapsrv)
|
||||
set(TARGET_LIB "")
|
||||
set(TARGET_LIB_GUI "")
|
||||
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
|
||||
endif()
|
||||
|
||||
add_library(${TARGET_NAME} SHARED
|
||||
${map_SOURCES}
|
||||
)
|
||||
|
||||
target_link_libraries(${TARGET_NAME}
|
||||
Qt5::Core
|
||||
${TARGET_LIB}
|
||||
sdrbase
|
||||
${TARGET_LIB_GUI}
|
||||
)
|
||||
|
||||
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
|
345
plugins/feature/map/map.cpp
Normal file
345
plugins/feature/map/map.cpp
Normal file
@ -0,0 +1,345 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// 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 <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
|
||||
#include "SWGFeatureSettings.h"
|
||||
#include "SWGFeatureReport.h"
|
||||
#include "SWGFeatureActions.h"
|
||||
#include "SWGDeviceState.h"
|
||||
|
||||
#include "dsp/dspengine.h"
|
||||
|
||||
#include "device/deviceset.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "feature/featureset.h"
|
||||
#include "maincore.h"
|
||||
#include "map.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(Map::MsgConfigureMap, Message)
|
||||
MESSAGE_CLASS_DEFINITION(Map::MsgFind, Message)
|
||||
|
||||
const char* const Map::m_featureIdURI = "sdrangel.feature.map";
|
||||
const char* const Map::m_featureId = "Map";
|
||||
|
||||
Map::Map(WebAPIAdapterInterface *webAPIAdapterInterface) :
|
||||
Feature(m_featureIdURI, webAPIAdapterInterface)
|
||||
{
|
||||
qDebug("Map::Map: webAPIAdapterInterface: %p", webAPIAdapterInterface);
|
||||
setObjectName(m_featureId);
|
||||
m_state = StIdle;
|
||||
m_errorMessage = "Map error";
|
||||
connect(&m_updatePipesTimer, SIGNAL(timeout()), this, SLOT(updatePipes()));
|
||||
m_updatePipesTimer.start(1000);
|
||||
}
|
||||
|
||||
Map::~Map()
|
||||
{
|
||||
}
|
||||
|
||||
bool Map::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (MsgConfigureMap::match(cmd))
|
||||
{
|
||||
MsgConfigureMap& cfg = (MsgConfigureMap&) cmd;
|
||||
qDebug() << "Map::handleMessage: MsgConfigureMap";
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MainCore::MsgMapItem::match(cmd))
|
||||
{
|
||||
MainCore::MsgMapItem& msgMapItem = (MainCore::MsgMapItem&) cmd;
|
||||
MainCore::MsgMapItem *copy = new MainCore::MsgMapItem(msgMapItem);
|
||||
getMessageQueueToGUI()->push(copy);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Map::updatePipes()
|
||||
{
|
||||
QList<AvailablePipeSource> availablePipes = updateAvailablePipeSources("mapitems", MapSettings::m_pipeTypes, MapSettings::m_pipeURIs, this);
|
||||
|
||||
if (availablePipes != m_availablePipes)
|
||||
{
|
||||
m_availablePipes = availablePipes;
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MsgReportPipes *msgToGUI = MsgReportPipes::create();
|
||||
QList<AvailablePipeSource>& msgAvailablePipes = msgToGUI->getAvailablePipes();
|
||||
msgAvailablePipes.append(availablePipes);
|
||||
getMessageQueueToGUI()->push(msgToGUI);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray Map::serialize() const
|
||||
{
|
||||
return m_settings.serialize();
|
||||
}
|
||||
|
||||
bool Map::deserialize(const QByteArray& data)
|
||||
{
|
||||
if (m_settings.deserialize(data))
|
||||
{
|
||||
MsgConfigureMap *msg = MsgConfigureMap::create(m_settings, true);
|
||||
m_inputMessageQueue.push(msg);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings.resetToDefaults();
|
||||
MsgConfigureMap *msg = MsgConfigureMap::create(m_settings, true);
|
||||
m_inputMessageQueue.push(msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Map::applySettings(const MapSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "Map::applySettings:"
|
||||
<< " m_displayNames: " << settings.m_displayNames
|
||||
<< " m_title: " << settings.m_title
|
||||
<< " m_rgbColor: " << settings.m_rgbColor
|
||||
<< " m_useReverseAPI: " << settings.m_useReverseAPI
|
||||
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
|
||||
<< " m_reverseAPIPort: " << settings.m_reverseAPIPort
|
||||
<< " m_reverseAPIFeatureSetIndex: " << settings.m_reverseAPIFeatureSetIndex
|
||||
<< " m_reverseAPIFeatureIndex: " << settings.m_reverseAPIFeatureIndex
|
||||
<< " force: " << force;
|
||||
|
||||
QList<QString> reverseAPIKeys;
|
||||
|
||||
if ((m_settings.m_displayNames != settings.m_displayNames) || force) {
|
||||
reverseAPIKeys.append("displayNames");
|
||||
}
|
||||
if ((m_settings.m_title != settings.m_title) || force) {
|
||||
reverseAPIKeys.append("title");
|
||||
}
|
||||
if ((m_settings.m_rgbColor != settings.m_rgbColor) || force) {
|
||||
reverseAPIKeys.append("rgbColor");
|
||||
}
|
||||
|
||||
if (settings.m_useReverseAPI)
|
||||
{
|
||||
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
|
||||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
|
||||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
|
||||
(m_settings.m_reverseAPIFeatureSetIndex != settings.m_reverseAPIFeatureSetIndex) ||
|
||||
(m_settings.m_reverseAPIFeatureIndex != settings.m_reverseAPIFeatureIndex);
|
||||
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
int Map::webapiRun(bool run,
|
||||
SWGSDRangel::SWGDeviceState& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
getFeatureStateStr(*response.getState());
|
||||
return 202;
|
||||
}
|
||||
|
||||
int Map::webapiSettingsGet(
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
response.setMapSettings(new SWGSDRangel::SWGMapSettings());
|
||||
response.getMapSettings()->init();
|
||||
webapiFormatFeatureSettings(response, m_settings);
|
||||
return 200;
|
||||
}
|
||||
|
||||
int Map::webapiSettingsPutPatch(
|
||||
bool force,
|
||||
const QStringList& featureSettingsKeys,
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
MapSettings settings = m_settings;
|
||||
webapiUpdateFeatureSettings(settings, featureSettingsKeys, response);
|
||||
|
||||
MsgConfigureMap *msg = MsgConfigureMap::create(settings, force);
|
||||
m_inputMessageQueue.push(msg);
|
||||
|
||||
if (m_guiMessageQueue) // forward to GUI if any
|
||||
{
|
||||
MsgConfigureMap *msgToGUI = MsgConfigureMap::create(settings, force);
|
||||
m_guiMessageQueue->push(msgToGUI);
|
||||
}
|
||||
|
||||
webapiFormatFeatureSettings(response, settings);
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
int Map::webapiActionsPost(
|
||||
const QStringList& featureActionsKeys,
|
||||
SWGSDRangel::SWGFeatureActions& query,
|
||||
QString& errorMessage)
|
||||
{
|
||||
SWGSDRangel::SWGMapActions *swgMapActions = query.getMapActions();
|
||||
|
||||
if (swgMapActions)
|
||||
{
|
||||
if (featureActionsKeys.contains("find"))
|
||||
{
|
||||
QString id = *swgMapActions->getFind();
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
getMessageQueueToGUI()->push(MsgFind::create(id));
|
||||
}
|
||||
|
||||
return 202;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = "Missing MapActions in query";
|
||||
return 400;
|
||||
}
|
||||
}
|
||||
|
||||
void Map::webapiFormatFeatureSettings(
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
const MapSettings& settings)
|
||||
{
|
||||
response.getMapSettings()->setDisplayNames(settings.m_displayNames ? 1 : 0);
|
||||
|
||||
if (response.getMapSettings()->getTitle()) {
|
||||
*response.getMapSettings()->getTitle() = settings.m_title;
|
||||
} else {
|
||||
response.getMapSettings()->setTitle(new QString(settings.m_title));
|
||||
}
|
||||
|
||||
response.getMapSettings()->setRgbColor(settings.m_rgbColor);
|
||||
response.getMapSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
|
||||
|
||||
if (response.getMapSettings()->getReverseApiAddress()) {
|
||||
*response.getMapSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
|
||||
} else {
|
||||
response.getMapSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
|
||||
}
|
||||
|
||||
response.getMapSettings()->setReverseApiPort(settings.m_reverseAPIPort);
|
||||
response.getMapSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIFeatureSetIndex);
|
||||
response.getMapSettings()->setReverseApiChannelIndex(settings.m_reverseAPIFeatureIndex);
|
||||
}
|
||||
|
||||
void Map::webapiUpdateFeatureSettings(
|
||||
MapSettings& settings,
|
||||
const QStringList& featureSettingsKeys,
|
||||
SWGSDRangel::SWGFeatureSettings& response)
|
||||
{
|
||||
if (featureSettingsKeys.contains("displayNames")) {
|
||||
settings.m_displayNames = response.getMapSettings()->getDisplayNames();
|
||||
}
|
||||
if (featureSettingsKeys.contains("title")) {
|
||||
settings.m_title = *response.getMapSettings()->getTitle();
|
||||
}
|
||||
if (featureSettingsKeys.contains("rgbColor")) {
|
||||
settings.m_rgbColor = response.getMapSettings()->getRgbColor();
|
||||
}
|
||||
if (featureSettingsKeys.contains("useReverseAPI")) {
|
||||
settings.m_useReverseAPI = response.getMapSettings()->getUseReverseApi() != 0;
|
||||
}
|
||||
if (featureSettingsKeys.contains("reverseAPIAddress")) {
|
||||
settings.m_reverseAPIAddress = *response.getMapSettings()->getReverseApiAddress();
|
||||
}
|
||||
if (featureSettingsKeys.contains("reverseAPIPort")) {
|
||||
settings.m_reverseAPIPort = response.getMapSettings()->getReverseApiPort();
|
||||
}
|
||||
if (featureSettingsKeys.contains("reverseAPIDeviceIndex")) {
|
||||
settings.m_reverseAPIFeatureSetIndex = response.getMapSettings()->getReverseApiDeviceIndex();
|
||||
}
|
||||
if (featureSettingsKeys.contains("reverseAPIChannelIndex")) {
|
||||
settings.m_reverseAPIFeatureIndex = response.getMapSettings()->getReverseApiChannelIndex();
|
||||
}
|
||||
}
|
||||
|
||||
void Map::webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const MapSettings& settings, bool force)
|
||||
{
|
||||
SWGSDRangel::SWGFeatureSettings *swgFeatureSettings = new SWGSDRangel::SWGFeatureSettings();
|
||||
// swgFeatureSettings->setOriginatorFeatureIndex(getIndexInDeviceSet());
|
||||
// swgFeatureSettings->setOriginatorFeatureSetIndex(getDeviceSetIndex());
|
||||
swgFeatureSettings->setFeatureType(new QString("Map"));
|
||||
swgFeatureSettings->setMapSettings(new SWGSDRangel::SWGMapSettings());
|
||||
SWGSDRangel::SWGMapSettings *swgMapSettings = swgFeatureSettings->getMapSettings();
|
||||
|
||||
// transfer data that has been modified. When force is on transfer all data except reverse API data
|
||||
|
||||
if (featureSettingsKeys.contains("displayNames") || force) {
|
||||
swgMapSettings->setDisplayNames(settings.m_displayNames);
|
||||
}
|
||||
if (featureSettingsKeys.contains("title") || force) {
|
||||
swgMapSettings->setTitle(new QString(settings.m_title));
|
||||
}
|
||||
if (featureSettingsKeys.contains("rgbColor") || force) {
|
||||
swgMapSettings->setRgbColor(settings.m_rgbColor);
|
||||
}
|
||||
|
||||
QString channelSettingsURL = QString("http://%1:%2/sdrangel/featureset/%3/feature/%4/settings")
|
||||
.arg(settings.m_reverseAPIAddress)
|
||||
.arg(settings.m_reverseAPIPort)
|
||||
.arg(settings.m_reverseAPIFeatureSetIndex)
|
||||
.arg(settings.m_reverseAPIFeatureIndex);
|
||||
m_networkRequest.setUrl(QUrl(channelSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer = new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgFeatureSettings->asJson().toUtf8());
|
||||
buffer->seek(0);
|
||||
|
||||
// Always use PATCH to avoid passing reverse API settings
|
||||
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
buffer->setParent(reply);
|
||||
|
||||
delete swgFeatureSettings;
|
||||
}
|
||||
|
||||
void Map::networkManagerFinished(QNetworkReply *reply)
|
||||
{
|
||||
QNetworkReply::NetworkError replyError = reply->error();
|
||||
|
||||
if (replyError)
|
||||
{
|
||||
qWarning() << "Map::networkManagerFinished:"
|
||||
<< " error(" << (int) replyError
|
||||
<< "): " << replyError
|
||||
<< ": " << reply->errorString();
|
||||
}
|
||||
else
|
||||
{
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("Map::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
}
|
144
plugins/feature/map/map.h
Normal file
144
plugins/feature/map/map.h
Normal file
@ -0,0 +1,144 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// 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_MAP_H_
|
||||
#define INCLUDE_FEATURE_MAP_H_
|
||||
|
||||
#include <QThread>
|
||||
#include <QHash>
|
||||
#include <QNetworkRequest>
|
||||
#include <QTimer>
|
||||
|
||||
#include "feature/feature.h"
|
||||
#include "util/message.h"
|
||||
|
||||
#include "mapsettings.h"
|
||||
|
||||
class WebAPIAdapterInterface;
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
namespace SWGSDRangel {
|
||||
class SWGDeviceState;
|
||||
}
|
||||
|
||||
class Map : public Feature
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureMap : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const MapSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureMap* create(const MapSettings& settings, bool force) {
|
||||
return new MsgConfigureMap(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
MapSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureMap(const MapSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgFind : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
QString getTarget() const { return m_target; }
|
||||
|
||||
static MsgFind* create(const QString& target) {
|
||||
return new MsgFind(target);
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_target;
|
||||
|
||||
MsgFind(const QString& target) :
|
||||
Message(),
|
||||
m_target(target)
|
||||
{}
|
||||
};
|
||||
|
||||
Map(WebAPIAdapterInterface *webAPIAdapterInterface);
|
||||
virtual ~Map();
|
||||
virtual void destroy() { delete this; }
|
||||
virtual bool handleMessage(const Message& cmd);
|
||||
|
||||
virtual void getIdentifier(QString& id) const { id = objectName(); }
|
||||
virtual void getTitle(QString& title) const { title = m_settings.m_title; }
|
||||
|
||||
virtual QByteArray serialize() const;
|
||||
virtual bool deserialize(const QByteArray& data);
|
||||
|
||||
virtual int webapiRun(bool run,
|
||||
SWGSDRangel::SWGDeviceState& response,
|
||||
QString& errorMessage);
|
||||
|
||||
virtual int webapiSettingsGet(
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
QString& errorMessage);
|
||||
|
||||
virtual int webapiSettingsPutPatch(
|
||||
bool force,
|
||||
const QStringList& featureSettingsKeys,
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
QString& errorMessage);
|
||||
|
||||
virtual int webapiActionsPost(
|
||||
const QStringList& featureActionsKeys,
|
||||
SWGSDRangel::SWGFeatureActions& query,
|
||||
QString& errorMessage);
|
||||
|
||||
static void webapiFormatFeatureSettings(
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
const MapSettings& settings);
|
||||
|
||||
static void webapiUpdateFeatureSettings(
|
||||
MapSettings& settings,
|
||||
const QStringList& featureSettingsKeys,
|
||||
SWGSDRangel::SWGFeatureSettings& response);
|
||||
|
||||
static const char* const m_featureIdURI;
|
||||
static const char* const m_featureId;
|
||||
|
||||
private:
|
||||
QThread m_thread;
|
||||
MapSettings m_settings;
|
||||
QList<AvailablePipeSource> m_availablePipes;
|
||||
QTimer m_updatePipesTimer;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
void applySettings(const MapSettings& settings, bool force = false);
|
||||
void webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const MapSettings& settings, bool force);
|
||||
|
||||
private slots:
|
||||
void updatePipes();
|
||||
void networkManagerFinished(QNetworkReply *reply);
|
||||
};
|
||||
|
||||
#endif // INCLUDE_FEATURE_MAP_H_
|
7
plugins/feature/map/map.qrc
Normal file
7
plugins/feature/map/map.qrc
Normal file
@ -0,0 +1,7 @@
|
||||
<RCC>
|
||||
<qresource prefix="/map/">
|
||||
<file>map/map.qml</file>
|
||||
<file>map/MapStation.qml</file>
|
||||
<file>map/antenna.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
40
plugins/feature/map/map/MapStation.qml
Normal file
40
plugins/feature/map/map/MapStation.qml
Normal file
@ -0,0 +1,40 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
BIN
plugins/feature/map/map/antenna.png
Normal file
BIN
plugins/feature/map/map/antenna.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
102
plugins/feature/map/map/map.qml
Normal file
102
plugins/feature/map/map/map.qml
Normal file
@ -0,0 +1,102 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.12
|
||||
import QtLocation 5.12
|
||||
import QtPositioning 5.12
|
||||
|
||||
Item {
|
||||
id: qmlMap
|
||||
property int mapZoomLevel: 11
|
||||
|
||||
Plugin {
|
||||
id: mapPlugin
|
||||
name: "osm"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: mapComponent
|
||||
MapQuickItem {
|
||||
id: mapElement
|
||||
anchorPoint.x: image.width/2
|
||||
anchorPoint.y: image.height/2
|
||||
coordinate: position
|
||||
zoomLevel: mapImageFixedSize ? zoomLevel : mapZoomLevel
|
||||
|
||||
sourceItem: Grid {
|
||||
columns: 1
|
||||
Grid {
|
||||
horizontalItemAlignment: Grid.AlignHCenter
|
||||
columnSpacing: 5
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
Image {
|
||||
id: image
|
||||
rotation: mapImageRotation
|
||||
source: mapImage
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: (mouse) => {
|
||||
selected = !selected
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
id: bubble
|
||||
color: bubbleColour
|
||||
border.width: 1
|
||||
width: text.width + 5
|
||||
height: text.height + 5
|
||||
radius: 5
|
||||
visible: mapTextVisible
|
||||
Text {
|
||||
id: text
|
||||
anchors.centerIn: parent
|
||||
text: mapText
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: (mouse) => {
|
||||
selected = !selected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
382
plugins/feature/map/mapgui.cpp
Normal file
382
plugins/feature/map/mapgui.cpp
Normal file
@ -0,0 +1,382 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cmath>
|
||||
#include <QMessageBox>
|
||||
#include <QLineEdit>
|
||||
#include <QQmlContext>
|
||||
#include <QQuickItem>
|
||||
#include <QGeoLocation>
|
||||
#include <QGeoCoordinate>
|
||||
|
||||
#include "feature/featureuiset.h"
|
||||
#include "gui/basicfeaturesettingsdialog.h"
|
||||
#include "mainwindow.h"
|
||||
#include "device/deviceuiset.h"
|
||||
#include "util/units.h"
|
||||
#include "util/maidenhead.h"
|
||||
|
||||
#include "ui_mapgui.h"
|
||||
#include "map.h"
|
||||
#include "mapgui.h"
|
||||
#include "SWGMapItem.h"
|
||||
|
||||
QVariant MapModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
int row = index.row();
|
||||
|
||||
if ((row < 0) || (row >= m_items.count()))
|
||||
return QVariant();
|
||||
if (role == MapModel::positionRole)
|
||||
{
|
||||
// Coordinates to display the item at
|
||||
QGeoCoordinate coords;
|
||||
coords.setLatitude(m_items[row]->m_latitude);
|
||||
coords.setLongitude(m_items[row]->m_longitude);
|
||||
return QVariant::fromValue(coords);
|
||||
}
|
||||
else if (role == MapModel::mapTextRole)
|
||||
{
|
||||
// Create the text to go in the bubble next to the image
|
||||
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);
|
||||
}
|
||||
else if (role == MapModel::mapImageRole)
|
||||
{
|
||||
// Set an image to use
|
||||
return QVariant::fromValue(m_items[row]->m_image);
|
||||
}
|
||||
else if (role == MapModel::mapImageRotationRole)
|
||||
{
|
||||
// Angle to rotate image by
|
||||
return QVariant::fromValue(m_items[row]->m_imageRotation);
|
||||
}
|
||||
else if (role == MapModel::mapImageFixedSizeRole)
|
||||
{
|
||||
// Whether image changes size with zoom level
|
||||
return QVariant::fromValue(m_items[row]->m_imageFixedSize);
|
||||
}
|
||||
else if (role == MapModel::bubbleColourRole)
|
||||
{
|
||||
// Select a background colour for the text bubble next to the item
|
||||
if (m_selected[row])
|
||||
return QVariant::fromValue(QColor("lightgreen"));
|
||||
else
|
||||
return QVariant::fromValue(QColor("lightblue"));
|
||||
}
|
||||
else if (role == MapModel::selectedRole)
|
||||
return QVariant::fromValue(m_selected[row]);
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool MapModel::setData(const QModelIndex &index, const QVariant& value, int role)
|
||||
{
|
||||
int row = index.row();
|
||||
if ((row < 0) || (row >= m_items.count()))
|
||||
return false;
|
||||
if (role == MapModel::selectedRole)
|
||||
{
|
||||
m_selected[row] = value.toBool();
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MapGUI* MapGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
|
||||
{
|
||||
MapGUI* gui = new MapGUI(pluginAPI, featureUISet, feature);
|
||||
return gui;
|
||||
}
|
||||
|
||||
void MapGUI::destroy()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
void MapGUI::resetToDefaults()
|
||||
{
|
||||
m_settings.resetToDefaults();
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
}
|
||||
|
||||
QByteArray MapGUI::serialize() const
|
||||
{
|
||||
return m_settings.serialize();
|
||||
}
|
||||
|
||||
bool MapGUI::deserialize(const QByteArray& data)
|
||||
{
|
||||
if (m_settings.deserialize(data))
|
||||
{
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool MapGUI::handleMessage(const Message& message)
|
||||
{
|
||||
if (Map::MsgConfigureMap::match(message))
|
||||
{
|
||||
qDebug("MapGUI::handleMessage: Map::MsgConfigureMap");
|
||||
const Map::MsgConfigureMap& cfg = (Map::MsgConfigureMap&) message;
|
||||
m_settings = cfg.getSettings();
|
||||
blockApplySettings(true);
|
||||
displaySettings();
|
||||
blockApplySettings(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (PipeEndPoint::MsgReportPipes::match(message))
|
||||
{
|
||||
PipeEndPoint::MsgReportPipes& report = (PipeEndPoint::MsgReportPipes&) message;
|
||||
m_availablePipes = report.getAvailablePipes();
|
||||
updatePipeList();
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (Map::MsgFind::match(message))
|
||||
{
|
||||
Map::MsgFind& msgFind = (Map::MsgFind&) message;
|
||||
find(msgFind.getTarget());
|
||||
return true;
|
||||
}
|
||||
else if (MainCore::MsgMapItem::match(message))
|
||||
{
|
||||
MainCore::MsgMapItem& msgMapItem = (MainCore::MsgMapItem&) message;
|
||||
SWGSDRangel::SWGMapItem *swgMapItem = msgMapItem.getSWGMapItem();
|
||||
m_mapModel.update(msgMapItem.getPipeSource(), swgMapItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MapGUI::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = getInputMessageQueue()->pop()))
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MapGUI::onWidgetRolled(QWidget* widget, bool rollDown)
|
||||
{
|
||||
(void) widget;
|
||||
(void) rollDown;
|
||||
}
|
||||
|
||||
MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
|
||||
FeatureGUI(parent),
|
||||
ui(new Ui::MapGUI),
|
||||
m_pluginAPI(pluginAPI),
|
||||
m_featureUISet(featureUISet),
|
||||
m_doApplySettings(true),
|
||||
m_mapModel(this)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->map->rootContext()->setContextProperty("mapModel", &m_mapModel);
|
||||
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map/map.qml")));
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setChannelWidget(false);
|
||||
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
|
||||
m_map = reinterpret_cast<Map*>(feature);
|
||||
m_map->setMessageQueueToGUI(&m_inputMessageQueue);
|
||||
|
||||
m_featureUISet->addRollupWidget(this);
|
||||
|
||||
// Get station position
|
||||
float stationLatitude = MainCore::instance()->getSettings().getLatitude();
|
||||
float stationLongitude = MainCore::instance()->getSettings().getLongitude();
|
||||
float stationAltitude = MainCore::instance()->getSettings().getAltitude();
|
||||
|
||||
// Centre map at My Position
|
||||
QQuickItem *item = ui->map->rootObject();
|
||||
QObject *object = item->findChild<QObject*>("map");
|
||||
if(object != NULL)
|
||||
{
|
||||
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()));
|
||||
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
}
|
||||
|
||||
MapGUI::~MapGUI()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MapGUI::blockApplySettings(bool block)
|
||||
{
|
||||
m_doApplySettings = !block;
|
||||
}
|
||||
|
||||
void MapGUI::displaySettings()
|
||||
{
|
||||
setTitleColor(m_settings.m_rgbColor);
|
||||
setWindowTitle(m_settings.m_title);
|
||||
blockApplySettings(true);
|
||||
ui->displayNames->setChecked(m_settings.m_displayNames);
|
||||
m_mapModel.setDisplayNames(m_settings.m_displayNames);
|
||||
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*)
|
||||
{
|
||||
}
|
||||
|
||||
void MapGUI::enterEvent(QEvent*)
|
||||
{
|
||||
}
|
||||
|
||||
void MapGUI::onMenuDialogCalled(const QPoint &p)
|
||||
{
|
||||
if (m_contextMenuType == ContextMenuChannelSettings)
|
||||
{
|
||||
BasicFeatureSettingsDialog dialog(this);
|
||||
dialog.setTitle(m_settings.m_title);
|
||||
dialog.setColor(m_settings.m_rgbColor);
|
||||
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
|
||||
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
|
||||
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
|
||||
dialog.setReverseAPIFeatureSetIndex(m_settings.m_reverseAPIFeatureSetIndex);
|
||||
dialog.setReverseAPIFeatureIndex(m_settings.m_reverseAPIFeatureIndex);
|
||||
|
||||
dialog.move(p);
|
||||
dialog.exec();
|
||||
|
||||
m_settings.m_rgbColor = dialog.getColor().rgb();
|
||||
m_settings.m_title = dialog.getTitle();
|
||||
m_settings.m_useReverseAPI = dialog.useReverseAPI();
|
||||
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
|
||||
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
|
||||
m_settings.m_reverseAPIFeatureSetIndex = dialog.getReverseAPIFeatureSetIndex();
|
||||
m_settings.m_reverseAPIFeatureIndex = dialog.getReverseAPIFeatureIndex();
|
||||
|
||||
setWindowTitle(m_settings.m_title);
|
||||
setTitleColor(m_settings.m_rgbColor);
|
||||
|
||||
applySettings();
|
||||
}
|
||||
|
||||
resetContextMenuType();
|
||||
}
|
||||
|
||||
void MapGUI::applySettings(bool force)
|
||||
{
|
||||
if (m_doApplySettings)
|
||||
{
|
||||
Map::MsgConfigureMap* message = Map::MsgConfigureMap::create(m_settings, force);
|
||||
m_map->getInputMessageQueue()->push(message);
|
||||
}
|
||||
}
|
||||
|
||||
void MapGUI::on_displayNames_clicked(bool checked)
|
||||
{
|
||||
m_settings.m_displayNames = checked;
|
||||
m_mapModel.setDisplayNames(checked);
|
||||
}
|
||||
|
||||
void MapGUI::on_find_returnPressed()
|
||||
{
|
||||
find(ui->find->text().trimmed());
|
||||
}
|
||||
|
||||
void MapGUI::find(const QString& target)
|
||||
{
|
||||
if (!target.isEmpty())
|
||||
{
|
||||
QQuickItem *item = ui->map->rootObject();
|
||||
QObject *map = item->findChild<QObject*>("map");
|
||||
if (map != NULL)
|
||||
{
|
||||
float latitude, longitude;
|
||||
if (Units::stringToLatitudeAndLongitude(target, latitude, longitude))
|
||||
{
|
||||
map->setProperty("center", QVariant::fromValue(QGeoCoordinate(latitude, longitude)));
|
||||
}
|
||||
else if (Maidenhead::fromMaidenhead(target, latitude, longitude))
|
||||
{
|
||||
map->setProperty("center", QVariant::fromValue(QGeoCoordinate(latitude, longitude)));
|
||||
}
|
||||
else
|
||||
{
|
||||
MapItem *mapItem = m_mapModel.findMapItem(target);
|
||||
if (mapItem != nullptr)
|
||||
map->setProperty("center", QVariant::fromValue(mapItem->getCoordinates()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MapGUI::on_deleteAll_clicked()
|
||||
{
|
||||
m_mapModel.removeAll();
|
||||
}
|
304
plugins/feature/map/mapgui.h
Normal file
304
plugins/feature/map/mapgui.h
Normal file
@ -0,0 +1,304 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// 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_MAPGUI_H_
|
||||
#define INCLUDE_FEATURE_MAPGUI_H_
|
||||
|
||||
#include <QTimer>
|
||||
#include <QAbstractListModel>
|
||||
#include <QGeoCoordinate>
|
||||
|
||||
#include "feature/featuregui.h"
|
||||
#include "util/messagequeue.h"
|
||||
#include "pipes/pipeendpoint.h"
|
||||
#include "mapsettings.h"
|
||||
#include "SWGMapItem.h"
|
||||
|
||||
class PluginAPI;
|
||||
class FeatureUISet;
|
||||
class Map;
|
||||
|
||||
namespace Ui {
|
||||
class MapGUI;
|
||||
}
|
||||
|
||||
class MapGUI;
|
||||
class MapModel;
|
||||
|
||||
// Information required about each item displayed on the map
|
||||
class MapItem {
|
||||
|
||||
public:
|
||||
MapItem(const PipeEndPoint *source, SWGSDRangel::SWGMapItem *mapItem)
|
||||
{
|
||||
m_source = source;
|
||||
m_name = *mapItem->getName();
|
||||
m_latitude = mapItem->getLatitude();
|
||||
m_longitude = mapItem->getLongitude();
|
||||
m_image = *mapItem->getImage();
|
||||
m_imageRotation = mapItem->getImageRotation();
|
||||
m_imageFixedSize = mapItem->getImageFixedSize() == 1;
|
||||
m_text = *mapItem->getText();
|
||||
}
|
||||
|
||||
void update(SWGSDRangel::SWGMapItem *mapItem)
|
||||
{
|
||||
m_latitude = mapItem->getLatitude();
|
||||
m_longitude = mapItem->getLongitude();
|
||||
m_image = *mapItem->getImage();
|
||||
m_imageRotation = mapItem->getImageRotation();
|
||||
m_imageFixedSize = mapItem->getImageFixedSize() == 1;
|
||||
m_text = *mapItem->getText();
|
||||
}
|
||||
|
||||
QGeoCoordinate getCoordinates()
|
||||
{
|
||||
QGeoCoordinate coords;
|
||||
coords.setLatitude(m_latitude);
|
||||
coords.setLongitude(m_longitude);
|
||||
return coords;
|
||||
}
|
||||
|
||||
private:
|
||||
friend MapModel;
|
||||
const PipeEndPoint *m_source; // Channel/feature that created the item
|
||||
QString m_name;
|
||||
float m_latitude;
|
||||
float m_longitude;
|
||||
QString m_image;
|
||||
int m_imageRotation;
|
||||
bool m_imageFixedSize; // Keep image same size when map is zoomed
|
||||
QString m_text;
|
||||
};
|
||||
|
||||
// Model used for each item on the map
|
||||
class MapModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using QAbstractListModel::QAbstractListModel;
|
||||
enum MarkerRoles {
|
||||
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
|
||||
};
|
||||
|
||||
MapModel(MapGUI *gui) :
|
||||
m_gui(gui)
|
||||
{
|
||||
}
|
||||
|
||||
Q_INVOKABLE void add(MapItem *item)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||
m_items.append(item);
|
||||
m_selected.append(false);
|
||||
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(MapItem *item)
|
||||
{
|
||||
int row = m_items.indexOf(item);
|
||||
if (row >= 0)
|
||||
{
|
||||
QModelIndex idx = index(row);
|
||||
emit dataChanged(idx, idx);
|
||||
}
|
||||
}
|
||||
|
||||
void remove(MapItem *item)
|
||||
{
|
||||
int row = m_items.indexOf(item);
|
||||
if (row >= 0)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
m_items.removeAt(row);
|
||||
m_selected.removeAt(row);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
MapItem *findMapItem(const PipeEndPoint *source, const QString& name)
|
||||
{
|
||||
// FIXME: Should consider adding a QHash for this
|
||||
QListIterator<MapItem *> i(m_items);
|
||||
while (i.hasNext())
|
||||
{
|
||||
MapItem *item = i.next();
|
||||
if ((item->m_name == name) && (item->m_source == source))
|
||||
return item;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MapItem *findMapItem(const QString& name)
|
||||
{
|
||||
QListIterator<MapItem *> i(m_items);
|
||||
while (i.hasNext())
|
||||
{
|
||||
MapItem *item = i.next();
|
||||
if (item->m_name == name)
|
||||
return item;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_items.count();
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override;
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override
|
||||
{
|
||||
(void) index;
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
|
||||
}
|
||||
|
||||
void allUpdated()
|
||||
{
|
||||
for (int i = 0; i < m_items.count(); i++)
|
||||
{
|
||||
QModelIndex idx = index(i);
|
||||
emit dataChanged(idx, idx);
|
||||
}
|
||||
}
|
||||
|
||||
void removeAll()
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), 0, m_items.count());
|
||||
m_items.clear();
|
||||
m_selected.clear();
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void setDisplayNames(bool displayNames)
|
||||
{
|
||||
m_displayNames = displayNames;
|
||||
allUpdated();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[positionRole] = "position";
|
||||
roles[mapTextRole] = "mapText";
|
||||
roles[mapTextVisibleRole] = "mapTextVisible";
|
||||
roles[mapImageRole] = "mapImage";
|
||||
roles[mapImageRotationRole] = "mapImageRotation";
|
||||
roles[mapImageFixedSizeRole] = "mapImageFixedSize";
|
||||
roles[bubbleColourRole] = "bubbleColour";
|
||||
roles[selectedRole] = "selected";
|
||||
return roles;
|
||||
}
|
||||
|
||||
private:
|
||||
MapGUI *m_gui;
|
||||
QList<MapItem *> m_items;
|
||||
QList<bool> m_selected;
|
||||
bool m_displayNames;
|
||||
};
|
||||
|
||||
class MapGUI : public FeatureGUI {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static MapGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature);
|
||||
virtual void destroy();
|
||||
|
||||
void resetToDefaults();
|
||||
QByteArray serialize() const;
|
||||
bool deserialize(const QByteArray& data);
|
||||
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
|
||||
|
||||
private:
|
||||
Ui::MapGUI* ui;
|
||||
PluginAPI* m_pluginAPI;
|
||||
FeatureUISet* m_featureUISet;
|
||||
MapSettings m_settings;
|
||||
bool m_doApplySettings;
|
||||
QList<PipeEndPoint::AvailablePipeSource> m_availablePipes;
|
||||
|
||||
Map* m_map;
|
||||
MessageQueue m_inputMessageQueue;
|
||||
MapModel m_mapModel;
|
||||
|
||||
explicit MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
|
||||
virtual ~MapGUI();
|
||||
|
||||
void blockApplySettings(bool block);
|
||||
void applySettings(bool force = false);
|
||||
void displaySettings();
|
||||
void updatePipeList();
|
||||
bool handleMessage(const Message& message);
|
||||
void find(const QString& target);
|
||||
|
||||
void leaveEvent(QEvent*);
|
||||
void enterEvent(QEvent*);
|
||||
|
||||
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_deleteAll_clicked();
|
||||
};
|
||||
|
||||
|
||||
#endif // INCLUDE_FEATURE_MAPGUI_H_
|
250
plugins/feature/map/mapgui.ui
Normal file
250
plugins/feature/map/mapgui.ui
Normal file
@ -0,0 +1,250 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MapGUI</class>
|
||||
<widget class="RollupWidget" name="MapGUI">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>462</width>
|
||||
<height>689</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>462</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Liberation Sans</family>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Map</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Map</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="settingsContainer" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>461</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>350</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</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">
|
||||
<string>Find</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="find">
|
||||
<property name="toolTip">
|
||||
<string>Enter name of object to find, latitude and longitude or Maidenhead locator</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<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="ButtonSwitch" name="displayNames">
|
||||
<property name="toolTip">
|
||||
<string>Display names</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/info.png</normaloff>:/info.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="deleteAll">
|
||||
<property name="toolTip">
|
||||
<string>Delete all items on the map</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/bin.png</normaloff>:/bin.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mapContainer" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>100</y>
|
||||
<width>461</width>
|
||||
<height>581</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Map</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayoutMap" stretch="0">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QQuickWidget" name="map">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Map</string>
|
||||
</property>
|
||||
<property name="resizeMode">
|
||||
<enum>QQuickWidget::SizeRootObjectToView</enum>
|
||||
</property>
|
||||
<property name="source">
|
||||
<url>
|
||||
<string/>
|
||||
</url>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QQuickWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header location="global">QtQuickWidgets/QQuickWidget</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>RollupWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/rollupwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>map</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../../sdrgui/resources/res.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
80
plugins/feature/map/mapplugin.cpp
Normal file
80
plugins/feature/map/mapplugin.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// 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 <QtPlugin>
|
||||
#include "plugin/pluginapi.h"
|
||||
|
||||
#ifndef SERVER_MODE
|
||||
#include "mapgui.h"
|
||||
#endif
|
||||
#include "map.h"
|
||||
#include "mapplugin.h"
|
||||
#include "mapwebapiadapter.h"
|
||||
|
||||
const PluginDescriptor MapPlugin::m_pluginDescriptor = {
|
||||
Map::m_featureId,
|
||||
QStringLiteral("Map"),
|
||||
QStringLiteral("6.4.0"),
|
||||
QStringLiteral("(c) Jon Beniston, M7RCE"),
|
||||
QStringLiteral("https://github.com/f4exb/sdrangel"),
|
||||
true,
|
||||
QStringLiteral("https://github.com/f4exb/sdrangel")
|
||||
};
|
||||
|
||||
MapPlugin::MapPlugin(QObject* parent) :
|
||||
QObject(parent),
|
||||
m_pluginAPI(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
const PluginDescriptor& MapPlugin::getPluginDescriptor() const
|
||||
{
|
||||
return m_pluginDescriptor;
|
||||
}
|
||||
|
||||
void MapPlugin::initPlugin(PluginAPI* pluginAPI)
|
||||
{
|
||||
m_pluginAPI = pluginAPI;
|
||||
|
||||
m_pluginAPI->registerFeature(Map::m_featureIdURI, Map::m_featureId, this);
|
||||
}
|
||||
|
||||
#ifdef SERVER_MODE
|
||||
FeatureGUI* MapPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
|
||||
{
|
||||
(void) featureUISet;
|
||||
(void) feature;
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
FeatureGUI* MapPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
|
||||
{
|
||||
return MapGUI::create(m_pluginAPI, featureUISet, feature);
|
||||
}
|
||||
#endif
|
||||
|
||||
Feature* MapPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const
|
||||
{
|
||||
return new Map(webAPIAdapterInterface);
|
||||
}
|
||||
|
||||
FeatureWebAPIAdapter* MapPlugin::createFeatureWebAPIAdapter() const
|
||||
{
|
||||
return new MapWebAPIAdapter();
|
||||
}
|
49
plugins/feature/map/mapplugin.h
Normal file
49
plugins/feature/map/mapplugin.h
Normal file
@ -0,0 +1,49 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// 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_MAPPLUGIN_H
|
||||
#define INCLUDE_FEATURE_MAPPLUGIN_H
|
||||
|
||||
#include <QObject>
|
||||
#include "plugin/plugininterface.h"
|
||||
|
||||
class FeatureGUI;
|
||||
class WebAPIAdapterInterface;
|
||||
|
||||
class MapPlugin : public QObject, PluginInterface {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(PluginInterface)
|
||||
Q_PLUGIN_METADATA(IID "sdrangel.feature.map")
|
||||
|
||||
public:
|
||||
explicit MapPlugin(QObject* parent = nullptr);
|
||||
|
||||
const PluginDescriptor& getPluginDescriptor() const;
|
||||
void initPlugin(PluginAPI* pluginAPI);
|
||||
|
||||
virtual FeatureGUI* createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const;
|
||||
virtual Feature* createFeature(WebAPIAdapterInterface *webAPIAdapterInterface) const;
|
||||
virtual FeatureWebAPIAdapter* createFeatureWebAPIAdapter() const;
|
||||
|
||||
private:
|
||||
static const PluginDescriptor m_pluginDescriptor;
|
||||
|
||||
PluginAPI* m_pluginAPI;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_FEATURE_MAPPLUGIN_H
|
112
plugins/feature/map/mapsettings.cpp
Normal file
112
plugins/feature/map/mapsettings.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// 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 <QColor>
|
||||
|
||||
#include "util/simpleserializer.h"
|
||||
#include "settings/serializable.h"
|
||||
|
||||
#include "mapsettings.h"
|
||||
|
||||
const QStringList MapSettings::m_pipeTypes = {
|
||||
QStringLiteral("ADSBDemod"),
|
||||
QStringLiteral("APRS"),
|
||||
QStringLiteral("StarTracker")
|
||||
};
|
||||
|
||||
const QStringList MapSettings::m_pipeURIs = {
|
||||
QStringLiteral("sdrangel.channel.adsbdemod"),
|
||||
QStringLiteral("sdrangel.feature.aprs"),
|
||||
QStringLiteral("sdrangel.feature.startracker")
|
||||
};
|
||||
|
||||
MapSettings::MapSettings()
|
||||
{
|
||||
resetToDefaults();
|
||||
}
|
||||
|
||||
void MapSettings::resetToDefaults()
|
||||
{
|
||||
m_displayNames = true;
|
||||
m_title = "Map";
|
||||
m_rgbColor = QColor(225, 25, 99).rgb();
|
||||
m_useReverseAPI = false;
|
||||
m_reverseAPIAddress = "127.0.0.1";
|
||||
m_reverseAPIPort = 8888;
|
||||
m_reverseAPIFeatureSetIndex = 0;
|
||||
m_reverseAPIFeatureIndex = 0;
|
||||
}
|
||||
|
||||
QByteArray MapSettings::serialize() const
|
||||
{
|
||||
SimpleSerializer s(1);
|
||||
|
||||
s.writeBool(1, m_displayNames);
|
||||
s.writeString(8, m_title);
|
||||
s.writeU32(9, m_rgbColor);
|
||||
s.writeBool(10, m_useReverseAPI);
|
||||
s.writeString(11, m_reverseAPIAddress);
|
||||
s.writeU32(12, m_reverseAPIPort);
|
||||
s.writeU32(13, m_reverseAPIFeatureSetIndex);
|
||||
s.writeU32(14, m_reverseAPIFeatureIndex);
|
||||
|
||||
return s.final();
|
||||
}
|
||||
|
||||
bool MapSettings::deserialize(const QByteArray& data)
|
||||
{
|
||||
SimpleDeserializer d(data);
|
||||
|
||||
if (!d.isValid())
|
||||
{
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (d.getVersion() == 1)
|
||||
{
|
||||
QByteArray bytetmp;
|
||||
uint32_t utmp;
|
||||
QString strtmp;
|
||||
|
||||
d.readBool(1, &m_displayNames, true);
|
||||
d.readString(8, &m_title, "Map");
|
||||
d.readU32(9, &m_rgbColor, QColor(225, 25, 99).rgb());
|
||||
d.readBool(10, &m_useReverseAPI, false);
|
||||
d.readString(11, &m_reverseAPIAddress, "127.0.0.1");
|
||||
d.readU32(12, &utmp, 0);
|
||||
|
||||
if ((utmp > 1023) && (utmp < 65535)) {
|
||||
m_reverseAPIPort = utmp;
|
||||
} else {
|
||||
m_reverseAPIPort = 8888;
|
||||
}
|
||||
|
||||
d.readU32(13, &utmp, 0);
|
||||
m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp;
|
||||
d.readU32(14, &utmp, 0);
|
||||
m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
}
|
63
plugins/feature/map/mapsettings.h
Normal file
63
plugins/feature/map/mapsettings.h
Normal file
@ -0,0 +1,63 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// 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_MAPSETTINGS_H_
|
||||
#define INCLUDE_FEATURE_MAPSETTINGS_H_
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "util/message.h"
|
||||
|
||||
class Serializable;
|
||||
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_title;
|
||||
quint32 m_rgbColor;
|
||||
bool m_useReverseAPI;
|
||||
QString m_reverseAPIAddress;
|
||||
uint16_t m_reverseAPIPort;
|
||||
uint16_t m_reverseAPIFeatureSetIndex;
|
||||
uint16_t m_reverseAPIFeatureIndex;
|
||||
|
||||
MapSettings();
|
||||
void resetToDefaults();
|
||||
QByteArray serialize() const;
|
||||
bool deserialize(const QByteArray& data);
|
||||
|
||||
static const QStringList m_pipeTypes;
|
||||
static const QStringList m_pipeURIs;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_FEATURE_MAPSETTINGS_H_
|
52
plugins/feature/map/mapwebapiadapter.cpp
Normal file
52
plugins/feature/map/mapwebapiadapter.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
|
||||
// //
|
||||
// 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 "SWGFeatureSettings.h"
|
||||
#include "map.h"
|
||||
#include "mapwebapiadapter.h"
|
||||
|
||||
MapWebAPIAdapter::MapWebAPIAdapter()
|
||||
{}
|
||||
|
||||
MapWebAPIAdapter::~MapWebAPIAdapter()
|
||||
{}
|
||||
|
||||
int MapWebAPIAdapter::webapiSettingsGet(
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
response.setSimplePttSettings(new SWGSDRangel::SWGSimplePTTSettings());
|
||||
response.getSimplePttSettings()->init();
|
||||
Map::webapiFormatFeatureSettings(response, m_settings);
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
int MapWebAPIAdapter::webapiSettingsPutPatch(
|
||||
bool force,
|
||||
const QStringList& featureSettingsKeys,
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) force; // no action
|
||||
(void) errorMessage;
|
||||
Map::webapiUpdateFeatureSettings(m_settings, featureSettingsKeys, response);
|
||||
|
||||
return 200;
|
||||
}
|
50
plugins/feature/map/mapwebapiadapter.h
Normal file
50
plugins/feature/map/mapwebapiadapter.h
Normal file
@ -0,0 +1,50 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
|
||||
// //
|
||||
// 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_MAP_WEBAPIADAPTER_H
|
||||
#define INCLUDE_MAP_WEBAPIADAPTER_H
|
||||
|
||||
#include "feature/featurewebapiadapter.h"
|
||||
#include "mapsettings.h"
|
||||
|
||||
/**
|
||||
* Standalone API adapter only for the settings
|
||||
*/
|
||||
class MapWebAPIAdapter : public FeatureWebAPIAdapter {
|
||||
public:
|
||||
MapWebAPIAdapter();
|
||||
virtual ~MapWebAPIAdapter();
|
||||
|
||||
virtual QByteArray serialize() const { return m_settings.serialize(); }
|
||||
virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); }
|
||||
|
||||
virtual int webapiSettingsGet(
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
QString& errorMessage);
|
||||
|
||||
virtual int webapiSettingsPutPatch(
|
||||
bool force,
|
||||
const QStringList& featureSettingsKeys,
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
QString& errorMessage);
|
||||
|
||||
private:
|
||||
MapSettings m_settings;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_MAP_WEBAPIADAPTER_H
|
43
plugins/feature/map/readme.md
Normal file
43
plugins/feature/map/readme.md
Normal file
@ -0,0 +1,43 @@
|
||||
<h1>Map Feature Plugin</h1>
|
||||
|
||||
<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.
|
||||
|
||||
<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>
|
||||
|
||||
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).
|
||||
|
||||
<h3>3: Display Names<h3>
|
||||
|
||||
When checked, names of objects are displayed in a bubble next to each object.
|
||||
|
||||
<h3>Map</h3>
|
||||
|
||||
The map displays objects reported by other SDRangel channels and features.
|
||||
|
||||
* 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.
|
||||
* 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.
|
||||
|
||||
<h2>API</h2>
|
||||
|
||||
Full details of the API can be found in the Swagger documentation. Here is a quick example of how to centre the map on an object from the command line:
|
||||
|
||||
curl -X POST "http://127.0.0.1:8091/sdrangel/featureset/0/feature/0/actions" -d '{"featureType": "Map", "MapActions": { "find": "M7RCE" }}'
|
||||
|
||||
And to centre the map at a particular latitude and longitude:
|
||||
|
||||
curl -X POST "http://127.0.0.1:8091/sdrangel/featureset/0/feature/0/actions" -d '{"featureType": "Map", "MapActions": { "find": "51.2 0.0" }}'
|
101
sdrbase/feature/featurewebapiutils.cpp
Normal file
101
sdrbase/feature/featurewebapiutils.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "SWGFeatureActions.h"
|
||||
#include "SWGMapActions.h"
|
||||
|
||||
#include "maincore.h"
|
||||
#include "feature/featureset.h"
|
||||
#include "feature/feature.h"
|
||||
#include "featurewebapiutils.h"
|
||||
|
||||
// Find the specified target on the map
|
||||
bool FeatureWebAPIUtils::mapFind(const QString& target, int featureSetIndex, int featureIndex)
|
||||
{
|
||||
Feature *feature = FeatureWebAPIUtils::getFeature(featureSetIndex, featureIndex, "sdrangel.feature.map");
|
||||
if (feature != nullptr)
|
||||
{
|
||||
QString errorMessage;
|
||||
QStringList featureActionKeys = {"find"};
|
||||
SWGSDRangel::SWGFeatureActions query;
|
||||
SWGSDRangel::SWGMapActions *mapActions = new SWGSDRangel::SWGMapActions();
|
||||
|
||||
mapActions->setFind(new QString(target));
|
||||
query.setMapActions(mapActions);
|
||||
|
||||
int httpRC = feature->webapiActionsPost(featureActionKeys, query, errorMessage);
|
||||
if (httpRC/100 != 2)
|
||||
{
|
||||
qWarning("FeatureWebAPIUtils::mapFind: error %d: %s", httpRC, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning("FeatureWebAPIUtils::mapFind: no Map feature");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get first feature with the given URI
|
||||
Feature* FeatureWebAPIUtils::getFeature(int featureSetIndex, int featureIndex, const QString& uri)
|
||||
{
|
||||
FeatureSet *featureSet;
|
||||
Feature *feature;
|
||||
std::vector<FeatureSet*>& featureSets = MainCore::instance()->getFeatureeSets();
|
||||
|
||||
if (featureSetIndex != -1)
|
||||
{
|
||||
// Find feature with specific index
|
||||
if (featureSetIndex < featureSets.size())
|
||||
{
|
||||
featureSet = featureSets[featureSetIndex];
|
||||
if (featureIndex < featureSet->getNumberOfFeatures())
|
||||
{
|
||||
feature = featureSet->getFeatureAt(featureIndex);
|
||||
if (uri.isEmpty() || feature->getURI() == uri)
|
||||
return feature;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find first feature matching URI
|
||||
for (std::vector<FeatureSet*>::const_iterator it = featureSets.begin(); it != featureSets.end(); ++it, featureIndex++)
|
||||
{
|
||||
for (int fi = 0; fi < (*it)->getNumberOfFeatures(); fi++)
|
||||
{
|
||||
feature = (*it)->getFeatureAt(fi);
|
||||
if (feature->getURI() == uri)
|
||||
{
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
32
sdrbase/feature/featurewebapiutils.h
Normal file
32
sdrbase/feature/featurewebapiutils.h
Normal file
@ -0,0 +1,32 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 SDRBASE_FEATURE_FEATUREWEBAPIUTILS_H_
|
||||
#define SDRBASE_FEATURE_FEATUREWEBAPIUTILS_H_
|
||||
|
||||
#include "export.h"
|
||||
|
||||
class Feature;
|
||||
|
||||
class SDRBASE_API FeatureWebAPIUtils
|
||||
{
|
||||
public:
|
||||
static bool mapFind(const QString& target, int featureSetIndex=-1, int featureIndex=-1);
|
||||
static Feature *getFeature(int featureSetIndex, int featureIndex, const QString& uri);
|
||||
};
|
||||
|
||||
#endif // SDRBASE_FEATURE_FEATUREWEBAPIUTILS_H_
|
81
sdrbase/util/maidenhead.cpp
Normal file
81
sdrbase/util/maidenhead.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// Maidenhead locator utilities //
|
||||
// //
|
||||
// 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 <QRegExp>
|
||||
|
||||
#include "maidenhead.h"
|
||||
|
||||
// See: https://en.wikipedia.org/wiki/Maidenhead_Locator_System
|
||||
|
||||
QString Maidenhead::toMaidenhead(float latitude, float longitude)
|
||||
{
|
||||
longitude += 180.0;
|
||||
latitude += 90.0;
|
||||
int lon1 = floor(longitude/20);
|
||||
longitude -= lon1*20;
|
||||
int lat1 = floor(latitude/10);
|
||||
latitude -= lat1*10;
|
||||
int lon2 = floor(longitude/2);
|
||||
longitude -= lon2*2;
|
||||
int lat2 = floor(latitude/1);
|
||||
latitude -= lat2;
|
||||
int lon3 = round(longitude*12);
|
||||
int lat3 = round(latitude*24);
|
||||
return QString("%1%2%3%4%5%6").arg(QChar(lon1+'A')).arg(QChar(lat1+'A'))
|
||||
.arg(QChar(lon2+'0')).arg(QChar(lat2+'0'))
|
||||
.arg(QChar(lon3+'A')).arg(QChar(lat3+'A'));
|
||||
}
|
||||
|
||||
bool Maidenhead::fromMaidenhead(const QString& maidenhead, float& latitude, float& longitude)
|
||||
{
|
||||
if (Maidenhead::isMaidenhead(maidenhead))
|
||||
{
|
||||
int lon1 = maidenhead[0].toUpper().toLatin1() - 'A';
|
||||
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 lon4 = 0;
|
||||
int lat4 = 0;
|
||||
if (maidenhead.length() == 8)
|
||||
{
|
||||
lon4 = maidenhead[6].toLatin1() - '0';
|
||||
lat4 = maidenhead[7].toLatin1() - '0';
|
||||
}
|
||||
longitude = lon1 * 20 + lon2 * 2 + lon3 * 2.0/24.0 + lon4 * 2.0/24.0/10; // 20=360/18
|
||||
latitude = lat1 * 10 + lat2 * 1 + lat3 * 1.0/24.0 + lat4 * 1.0/24.0/10; // 10=180/18
|
||||
|
||||
longitude -= 180.0;
|
||||
latitude -= 90.0;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Maidenhead::isMaidenhead(const QString& maidenhead)
|
||||
{
|
||||
int length = maidenhead.length();
|
||||
if ((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])?");
|
||||
return re.exactMatch(maidenhead);
|
||||
}
|
37
sdrbase/util/maidenhead.h
Normal file
37
sdrbase/util/maidenhead.h
Normal file
@ -0,0 +1,37 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// Maidenhead locator utilities //
|
||||
// //
|
||||
// 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_MAIDENHEAD_H
|
||||
#define INCLUDE_MAIDENHEAD_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
class SDRBASE_API Maidenhead {
|
||||
|
||||
public:
|
||||
|
||||
static QString toMaidenhead(float latitude, float longitude);
|
||||
static bool fromMaidenhead(const QString& maidenhead, float& latitude, float& longitude);
|
||||
static bool isMaidenhead(const QString& maidenhead);
|
||||
|
||||
};
|
||||
|
||||
#endif // INCLUDE_MAIDENHEAD_H
|
Loading…
Reference in New Issue
Block a user