1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2025-05-30 05:52:24 -04:00

Add better Map API key support

This commit is contained in:
Jon Beniston 2021-11-23 16:44:07 +00:00
parent a41d0319dc
commit c0fdb670ab
17 changed files with 558 additions and 69 deletions

View File

@ -5,6 +5,7 @@ set(map_SOURCES
mapsettings.cpp mapsettings.cpp
mapplugin.cpp mapplugin.cpp
mapwebapiadapter.cpp mapwebapiadapter.cpp
osmtemplateserver.cpp
) )
set(map_HEADERS set(map_HEADERS
@ -13,6 +14,7 @@ set(map_HEADERS
mapplugin.h mapplugin.h
mapreport.h mapreport.h
mapwebapiadapter.h mapwebapiadapter.h
osmtemplateserver.h
beacon.h beacon.h
) )

View File

@ -4,5 +4,8 @@
<file>map/map_5_12.qml</file> <file>map/map_5_12.qml</file>
<file>map/antenna.png</file> <file>map/antenna.png</file>
<file>map/antennatime.png</file> <file>map/antennatime.png</file>
<file>map/antennadab.png</file>
<file>map/antennafm.png</file>
<file>map/antennaam.png</file>
</qresource> </qresource>
</RCC> </RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -8,33 +8,28 @@ Item {
id: qmlMap id: qmlMap
property int mapZoomLevel: 11 property int mapZoomLevel: 11
property string mapProvider: "osm" property string mapProvider: "osm"
property variant mapParameters
property variant mapPtr property variant mapPtr
property variant guiPtr
function createMap(pluginParameters) { function createMap(pluginParameters, gui) {
var parameters = new Array() guiPtr = gui
var paramString = ""
for (var prop in pluginParameters) { for (var prop in pluginParameters) {
var parameter = Qt.createQmlObject('import QtLocation 5.14; PluginParameter{ name: "'+ prop + '"; value: "' + pluginParameters[prop]+'"}', qmlMap) var parameter = 'PluginParameter { name: "' + prop + '"; value: "' + pluginParameters[prop] + '"}'
parameters.push(parameter) paramString = paramString + parameter
} }
qmlMap.mapParameters = parameters var pluginString = 'import QtLocation 5.14; Plugin{ name:"' + mapProvider + '"; ' + paramString + '}'
var plugin = Qt.createQmlObject (pluginString, qmlMap)
var plugin
if (mapParameters && mapParameters.length > 0)
plugin = Qt.createQmlObject ('import QtLocation 5.14; Plugin{ name:"' + mapProvider + '"; parameters: qmlMap.mapParameters}', qmlMap)
else
plugin = Qt.createQmlObject ('import QtLocation 5.14; Plugin{ name:"' + mapProvider + '"}', qmlMap)
if (mapPtr) { if (mapPtr) {
// Objects aren't destroyed immediately, so rename the old // Objects aren't destroyed immediately, so don't call findChild("map")
// map, so any C++ code that calls findChild("map") doesn't find
// the old map
mapPtr.objectName = "oldMap";
mapPtr.destroy() mapPtr.destroy()
mapPtr = null
} }
mapPtr = actualMapComponent.createObject(page) mapPtr = actualMapComponent.createObject(page)
mapPtr.plugin = plugin; mapPtr.plugin = plugin;
mapPtr.forceActiveFocus() mapPtr.forceActiveFocus()
mapPtr.objectName = "map"; return mapPtr
} }
function getMapTypes() { function getMapTypes() {
@ -48,9 +43,12 @@ Item {
} }
function setMapType(mapTypeIndex) { function setMapType(mapTypeIndex) {
if (mapPtr) if (mapPtr && (mapTypeIndex < mapPtr.supportedMapTypes.length)) {
if (mapPtr.supportedMapTypes[mapTypeIndex] !== undefined) {
mapPtr.activeMapType = mapPtr.supportedMapTypes[mapTypeIndex] mapPtr.activeMapType = mapPtr.supportedMapTypes[mapTypeIndex]
} }
}
}
Item { Item {
id: page id: page
@ -62,6 +60,7 @@ Item {
Map { Map {
id: map id: map
objectName: "map"
anchors.fill: parent anchors.fill: parent
center: QtPositioning.coordinate(51.5, 0.125) // London center: QtPositioning.coordinate(51.5, 0.125) // London
zoomLevel: 10 zoomLevel: 10
@ -103,6 +102,10 @@ Item {
mapModel.viewChanged(visibleRegion.boundingGeoRectangle().bottomLeft.longitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude); mapModel.viewChanged(visibleRegion.boundingGeoRectangle().bottomLeft.longitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude);
} }
onSupportedMapTypesChanged : {
guiPtr.supportedMapsChanged()
}
} }
} }

View File

@ -8,33 +8,28 @@ Item {
id: qmlMap id: qmlMap
property int mapZoomLevel: 11 property int mapZoomLevel: 11
property string mapProvider: "osm" property string mapProvider: "osm"
property variant mapParameters
property variant mapPtr property variant mapPtr
property variant guiPtr
function createMap(pluginParameters) { function createMap(pluginParameters, gui) {
var parameters = new Array() guiPtr = gui
var paramString = ""
for (var prop in pluginParameters) { for (var prop in pluginParameters) {
var parameter = Qt.createQmlObject('import QtLocation 5.6; PluginParameter{ name: "'+ prop + '"; value: "' + pluginParameters[prop]+'"}', qmlMap) var parameter = 'PluginParameter { name: "' + prop + '"; value: "' + pluginParameters[prop] + '"}'
parameters.push(parameter) paramString = paramString + parameter
} }
qmlMap.mapParameters = parameters var pluginString = 'import QtLocation 5.12; Plugin{ name:"' + mapProvider + '"; ' + paramString + '}'
var plugin = Qt.createQmlObject (pluginString, qmlMap)
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) { if (mapPtr) {
// Objects aren't destroyed immediately, so rename the old // Objects aren't destroyed immediately, so don't call findChild("map")
// map, so any C++ code that calls findChild("map") doesn't find
// the old map
mapPtr.objectName = "oldMap";
mapPtr.destroy() mapPtr.destroy()
mapPtr = null
} }
mapPtr = actualMapComponent.createObject(page) mapPtr = actualMapComponent.createObject(page)
mapPtr.plugin = plugin; mapPtr.plugin = plugin;
mapPtr.forceActiveFocus() mapPtr.forceActiveFocus()
mapPtr.objectName = "map"; return mapPtr
} }
function getMapTypes() { function getMapTypes() {
@ -48,9 +43,12 @@ Item {
} }
function setMapType(mapTypeIndex) { function setMapType(mapTypeIndex) {
if (mapPtr) if (mapPtr && (mapTypeIndex < mapPtr.supportedMapTypes.length)) {
if (mapPtr.supportedMapTypes[mapTypeIndex] !== undefined) {
mapPtr.activeMapType = mapPtr.supportedMapTypes[mapTypeIndex] mapPtr.activeMapType = mapPtr.supportedMapTypes[mapTypeIndex]
} }
}
}
Item { Item {
id: page id: page
@ -62,6 +60,7 @@ Item {
Map { Map {
id: map id: map
objectName: "map"
anchors.fill: parent anchors.fill: parent
center: QtPositioning.coordinate(51.5, 0.125) // London center: QtPositioning.coordinate(51.5, 0.125) // London
zoomLevel: 10 zoomLevel: 10
@ -103,6 +102,10 @@ Item {
mapModel.viewChanged(visibleRegion.boundingGeoRectangle().bottomLeft.longitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude); mapModel.viewChanged(visibleRegion.boundingGeoRectangle().bottomLeft.longitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude);
} }
onSupportedMapTypesChanged : {
guiPtr.supportedMapsChanged()
}
} }
} }

View File

@ -669,6 +669,9 @@ void MapGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{ {
(void) widget; (void) widget;
(void) rollDown; (void) rollDown;
m_settings.m_rollupState = saveState();
applySettings();
} }
MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) : MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
@ -684,6 +687,12 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
{ {
ui->setupUi(this); ui->setupUi(this);
quint16 port = 0; // Pick a free port
// Free keys, so no point in stealing them :)
QString tfKey = m_settings.m_thunderforestAPIKey.isEmpty() ? "3e1f614f78a345459931ba3c898e975e" : m_settings.m_thunderforestAPIKey;
QString mtKey = m_settings.m_maptilerAPIKey.isEmpty() ? "q2RVNAe3eFKCH4XsrE3r" : m_settings.m_maptilerAPIKey;
m_templateServer = new OSMTemplateServer(tfKey, mtKey, m_osmPort);
ui->map->rootContext()->setContextProperty("mapModel", &m_mapModel); ui->map->rootContext()->setContextProperty("mapModel", &m_mapModel);
// 5.12 doesn't display map items when fully zoomed out // 5.12 doesn't display map items when fully zoomed out
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
@ -745,6 +754,11 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
MapGUI::~MapGUI() MapGUI::~MapGUI()
{ {
if (m_templateServer)
{
m_templateServer->close();
delete m_templateServer;
}
delete ui; delete ui;
} }
@ -804,42 +818,252 @@ void MapGUI::addRadioTimeTransmitters()
} }
} }
static QString arrayToString(QJsonArray array)
{
QString s;
for (int i = 0; i < array.size(); i++)
{
s = s.append(array[i].toString());
s = s.append(" ");
}
return s;
}
// Coming soon
void MapGUI::addDAB()
{
QFile file("stationlist_SI.json");
if (file.open(QIODevice::ReadOnly))
{
QByteArray bytes = file.readAll();
QJsonParseError error;
QJsonDocument json = QJsonDocument::fromJson(bytes, &error);
if (!json.isNull())
{
if (json.isObject())
{
QJsonObject obj = json.object();
QJsonValue stations = obj.value("stations");
QJsonArray stationsArray = stations.toArray();
for (int i = 0; i < stationsArray.size(); i++)
{
QJsonObject station = stationsArray[i].toObject();
// "txs" contains array of transmitters
QString stationName = station.value("stationName").toString();
QJsonArray txs = station.value("txs").toArray();
QString languages = arrayToString(station.value("language").toArray());
QString format = arrayToString(station.value("format").toArray());
for (int j = 0; j < txs.size(); j++)
{
QJsonObject tx = txs[j].toObject();
QString band = tx.value("band").toString();
double lat = tx.value("latitude").toString().toDouble();
double lon = tx.value("longitude").toString().toDouble();
double alt = tx.value("haat").toString().toDouble(); // This is height above terrain - not actual height - Check "haatUnits" is m
double frequency = tx.value("frequency").toString().toDouble(); // Can be MHz or kHz for AM
double erp = tx.value("erp").toString().toDouble();
SWGSDRangel::SWGMapItem mapItem;
mapItem.setLatitude(lat);
mapItem.setLongitude(lon);
mapItem.setAltitude(alt);
mapItem.setImageRotation(0);
mapItem.setImageMinZoom(8);
if (band == "DAB")
{
// Name should be unique - can we use TII code for this? can it repeat across countries?
QString name = QString("%1").arg(tx.value("tsId").toString());
mapItem.setName(new QString(name));
mapItem.setImage(new QString("antennadab.png"));
// Need tiicode?
QString text = QString("%1 Transmitter\nStation: %2\nFrequency: %3 %4\nPower: %5 %6\nLanguage(s): %7\nType: %8\nService: %9\nEnsemble: %10")
.arg(band)
.arg(stationName)
.arg(frequency)
.arg(tx.value("frequencyUnits").toString())
.arg(erp)
.arg(tx.value("erpUnits").toString())
.arg(languages)
.arg(format)
.arg(tx.value("serviceLabel").toString())
.arg(tx.value("ensembleLabel").toString())
;
mapItem.setText(new QString(text));
m_mapModel.update(m_map, &mapItem, MapSettings::SOURCE_DAB);
}
else if (band == "FM")
{
// Name should be unique
QString name = QString("%1").arg(tx.value("tsId").toString());
mapItem.setName(new QString(name));
mapItem.setImage(new QString("antennafm.png"));
QString text = QString("%1 Transmitter\nStation: %2\nFrequency: %3 %4\nPower: %5 %6\nLanguage(s): %7\nType: %8")
.arg(band)
.arg(stationName)
.arg(frequency)
.arg(tx.value("frequencyUnits").toString())
.arg(erp)
.arg(tx.value("erpUnits").toString())
.arg(languages)
.arg(format)
;
mapItem.setText(new QString(text));
m_mapModel.update(m_map, &mapItem, MapSettings::SOURCE_FM);
}
else if (band == "AM")
{
// Name should be unique
QString name = QString("%1").arg(tx.value("tsId").toString());
mapItem.setName(new QString(name));
mapItem.setImage(new QString("antennaam.png"));
QString text = QString("%1 Transmitter\nStation: %2\nFrequency: %3 %4\nPower: %5 %6\nLanguage(s): %7\nType: %8")
.arg(band)
.arg(stationName)
.arg(frequency)
.arg(tx.value("frequencyUnits").toString())
.arg(erp)
.arg(tx.value("erpUnits").toString())
.arg(languages)
.arg(format)
;
mapItem.setText(new QString(text));
m_mapModel.update(m_map, &mapItem, MapSettings::SOURCE_AM);
}
}
}
}
else
{
qDebug() << "MapGUI::addDAB: Expecting an object in DAB json:";
}
}
else
{
qDebug() << "MapGUI::addDAB: Failed to parse DAB json: " << error.errorString();
}
}
else
{
qDebug() << "MapGUI::addDAB: Failed to open DAB json";
}
}
void MapGUI::blockApplySettings(bool block) void MapGUI::blockApplySettings(bool block)
{ {
m_doApplySettings = !block; m_doApplySettings = !block;
} }
QString MapGUI::osmCachePath()
{
return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/QtLocation/5.8/tiles/osm/sdrangel_map";
}
void MapGUI::clearOSMCache()
{
// Delete all cached custom tiles when user changes the URL. Is there a better way to do this?
QDir dir(osmCachePath());
if (dir.exists())
{
QStringList filenames = dir.entryList({"osm_100-l-8-*.png"});
for (const auto& filename : filenames)
{
QFile file(dir.filePath(filename));
if (!file.remove()) {
qDebug() << "MapGUI::clearOSMCache: Failed to remove " << file;
}
}
}
}
void MapGUI::applyMapSettings() void MapGUI::applyMapSettings()
{ {
float stationLatitude = MainCore::instance()->getSettings().getLatitude();
float stationLongitude = MainCore::instance()->getSettings().getLongitude();
float stationAltitude = MainCore::instance()->getSettings().getAltitude();
QQuickItem *item = ui->map->rootObject(); QQuickItem *item = ui->map->rootObject();
// Save existing position of map
QObject *object = item->findChild<QObject*>("map"); QObject *object = item->findChild<QObject*>("map");
QGeoCoordinate coords; QGeoCoordinate coords;
double zoom;
if (object != nullptr) if (object != nullptr)
{
// Save existing position of map
coords = object->property("center").value<QGeoCoordinate>(); coords = object->property("center").value<QGeoCoordinate>();
zoom = object->property("zoomLevel").value<double>();
}
else
{
// Center on my location when map is first opened
coords.setLatitude(stationLatitude);
coords.setLongitude(stationLongitude);
coords.setAltitude(stationAltitude);
zoom = 10.0;
}
// Create the map using the specified provider // Create the map using the specified provider
QQmlProperty::write(item, "mapProvider", m_settings.m_mapProvider); QQmlProperty::write(item, "mapProvider", m_settings.m_mapProvider);
QVariantMap parameters; QVariantMap parameters;
if (!m_settings.m_mapBoxApiKey.isEmpty() && m_settings.m_mapProvider == "mapbox") if (!m_settings.m_mapBoxAPIKey.isEmpty() && m_settings.m_mapProvider == "mapbox")
{ {
parameters["mapbox.map_id"] = "mapbox.satellite"; // The only one that works parameters["mapbox.map_id"] = "mapbox.satellite"; // The only one that works
parameters["mapbox.access_token"] = m_settings.m_mapBoxApiKey; parameters["mapbox.access_token"] = m_settings.m_mapBoxAPIKey;
} }
if (!m_settings.m_mapBoxApiKey.isEmpty() && m_settings.m_mapProvider == "mapboxgl") if (!m_settings.m_mapBoxAPIKey.isEmpty() && m_settings.m_mapProvider == "mapboxgl")
{ {
parameters["mapboxgl.access_token"] = m_settings.m_mapBoxApiKey; parameters["mapboxgl.access_token"] = m_settings.m_mapBoxAPIKey;
if (!m_settings.m_mapBoxStyles.isEmpty()) if (!m_settings.m_mapBoxStyles.isEmpty())
parameters["mapboxgl.mapping.additional_style_urls"] = m_settings.m_mapBoxStyles; parameters["mapboxgl.mapping.additional_style_urls"] = m_settings.m_mapBoxStyles;
} }
//QQmlProperty::write(item, "mapParameters", parameters); if (m_settings.m_mapProvider == "osm")
QMetaObject::invokeMethod(item, "createMap", Q_ARG(QVariant, QVariant::fromValue(parameters))); {
// Allow user to specify URL
if (!m_settings.m_osmURL.isEmpty()) {
parameters["osm.mapping.custom.host"] = m_settings.m_osmURL; // E.g: "http://a.tile.openstreetmap.fr/hot/"
}
// Use our repo, so we can append API key
parameters["osm.mapping.providersrepository.address"] = QString("http://127.0.0.1:%1/").arg(m_osmPort);
// Use application specific cache, as other apps may not use API key so will have different images
QString cachePath = osmCachePath();
parameters["osm.mapping.cache.directory"] = cachePath;
// On Linux, we need to create the directory
QDir dir(cachePath);
if (!dir.exists()) {
dir.mkpath(cachePath);
}
}
QVariant retVal;
if (!QMetaObject::invokeMethod(item, "createMap", Qt::DirectConnection,
Q_RETURN_ARG(QVariant, retVal),
Q_ARG(QVariant, QVariant::fromValue(parameters)),
//Q_ARG(QVariant, mapType),
Q_ARG(QVariant, QVariant::fromValue(this))))
{
qCritical() << "MapGUI::applyMapSettings - Failed to invoke createMap";
}
QObject *newMap = retVal.value<QObject *>();
// Restore position of map // Restore position of map
object = item->findChild<QObject*>("map"); if (newMap != nullptr)
if ((object != nullptr) && coords.isValid()) {
object->setProperty("center", QVariant::fromValue(coords)); if (coords.isValid())
{
newMap->setProperty("zoomLevel", QVariant::fromValue(zoom));
newMap->setProperty("center", QVariant::fromValue(coords));
}
}
else
{
qCritical() << "MapGUI::applyMapSettings - createMap returned a nullptr";
}
supportedMapsChanged();
}
void MapGUI::supportedMapsChanged()
{
QQuickItem *item = ui->map->rootObject();
QObject *object = item->findChild<QObject*>("map");
// Get list of map types // Get list of map types
ui->mapTypes->clear(); ui->mapTypes->clear();
@ -847,17 +1071,20 @@ void MapGUI::applyMapSettings()
{ {
// Mapbox plugin only works for Satellite imagary, despite what is indicated // Mapbox plugin only works for Satellite imagary, despite what is indicated
if (m_settings.m_mapProvider == "mapbox") if (m_settings.m_mapProvider == "mapbox")
{
ui->mapTypes->addItem("Satellite"); ui->mapTypes->addItem("Satellite");
}
else else
{ {
QVariant mapTypesVariant; QVariant mapTypesVariant;
QMetaObject::invokeMethod(item, "getMapTypes", Q_RETURN_ARG(QVariant, mapTypesVariant)); QMetaObject::invokeMethod(item, "getMapTypes", Q_RETURN_ARG(QVariant, mapTypesVariant));
QStringList mapTypes = mapTypesVariant.value<QStringList>(); QStringList mapTypes = mapTypesVariant.value<QStringList>();
for (int i = 0; i < mapTypes.size(); i++) for (int i = 0; i < mapTypes.size(); i++) {
ui->mapTypes->addItem(mapTypes[i]); ui->mapTypes->addItem(mapTypes[i]);
} }
} }
} }
}
void MapGUI::on_mapTypes_currentIndexChanged(int index) void MapGUI::on_mapTypes_currentIndexChanged(int index)
{ {
@ -880,6 +1107,7 @@ void MapGUI::displaySettings()
m_mapModel.setGroundTrackColor(m_settings.m_groundTrackColor); m_mapModel.setGroundTrackColor(m_settings.m_groundTrackColor);
m_mapModel.setPredictedGroundTrackColor(m_settings.m_predictedGroundTrackColor); m_mapModel.setPredictedGroundTrackColor(m_settings.m_predictedGroundTrackColor);
applyMapSettings(); applyMapSettings();
restoreState(m_settings.m_rollupState);
blockApplySettings(false); blockApplySettings(false);
} }
@ -1052,11 +1280,16 @@ void MapGUI::on_displaySettings_clicked()
MapSettingsDialog dialog(&m_settings); MapSettingsDialog dialog(&m_settings);
if (dialog.exec() == QDialog::Accepted) if (dialog.exec() == QDialog::Accepted)
{ {
if (dialog.m_mapSettingsChanged) if (dialog.m_osmURLChanged) {
clearOSMCache();
}
if (dialog.m_mapSettingsChanged) {
applyMapSettings(); applyMapSettings();
}
applySettings(); applySettings();
if (dialog.m_sourcesChanged) if (dialog.m_sourcesChanged) {
m_mapModel.setSources(m_settings.m_sources); m_mapModel.setSources(m_settings.m_sources);
}
m_mapModel.setGroundTrackColor(m_settings.m_groundTrackColor); m_mapModel.setGroundTrackColor(m_settings.m_groundTrackColor);
m_mapModel.setPredictedGroundTrackColor(m_settings.m_predictedGroundTrackColor); m_mapModel.setPredictedGroundTrackColor(m_settings.m_predictedGroundTrackColor);
} }

View File

@ -32,6 +32,7 @@
#include "SWGMapItem.h" #include "SWGMapItem.h"
#include "mapbeacondialog.h" #include "mapbeacondialog.h"
#include "mapradiotimedialog.h" #include "mapradiotimedialog.h"
#include "osmtemplateserver.h"
class PluginAPI; class PluginAPI;
class FeatureUISet; class FeatureUISet;
@ -482,7 +483,9 @@ public:
void setBeacons(QList<Beacon *> *beacons); void setBeacons(QList<Beacon *> *beacons);
QList<RadioTimeTransmitter> getRadioTimeTransmitters() { return m_radioTimeTransmitters; } QList<RadioTimeTransmitter> getRadioTimeTransmitters() { return m_radioTimeTransmitters; }
void addRadioTimeTransmitters(); void addRadioTimeTransmitters();
void addDAB();
void find(const QString& target); void find(const QString& target);
Q_INVOKABLE void supportedMapsChanged();
private: private:
Ui::MapGUI* ui; Ui::MapGUI* ui;
@ -499,6 +502,8 @@ private:
QList<Beacon *> *m_beacons; QList<Beacon *> *m_beacons;
MapBeaconDialog m_beaconDialog; MapBeaconDialog m_beaconDialog;
MapRadioTimeDialog m_radioTimeDialog; MapRadioTimeDialog m_radioTimeDialog;
quint16 m_osmPort;
OSMTemplateServer *m_templateServer;
explicit MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr); explicit MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
virtual ~MapGUI(); virtual ~MapGUI();
@ -506,6 +511,8 @@ private:
void blockApplySettings(bool block); void blockApplySettings(bool block);
void applySettings(bool force = false); void applySettings(bool force = false);
void applyMapSettings(); void applyMapSettings();
QString osmCachePath();
void clearOSMCache();
void displaySettings(); void displaySettings();
bool handleMessage(const Message& message); bool handleMessage(const Message& message);
void geoReply(); void geoReply();

View File

@ -56,7 +56,10 @@ void MapSettings::resetToDefaults()
{ {
m_displayNames = true; m_displayNames = true;
m_mapProvider = "osm"; m_mapProvider = "osm";
m_mapBoxApiKey = ""; m_thunderforestAPIKey = "";
m_maptilerAPIKey = "";
m_mapBoxAPIKey = "";
m_osmURL = "";
m_mapBoxStyles = ""; m_mapBoxStyles = "";
m_sources = -1; m_sources = -1;
m_displaySelectedGroundTracks = true; m_displaySelectedGroundTracks = true;
@ -78,7 +81,7 @@ QByteArray MapSettings::serialize() const
s.writeBool(1, m_displayNames); s.writeBool(1, m_displayNames);
s.writeString(2, m_mapProvider); s.writeString(2, m_mapProvider);
s.writeString(3, m_mapBoxApiKey); s.writeString(3, m_mapBoxAPIKey);
s.writeString(4, m_mapBoxStyles); s.writeString(4, m_mapBoxStyles);
s.writeU32(5, m_sources); s.writeU32(5, m_sources);
s.writeU32(6, m_groundTrackColor); s.writeU32(6, m_groundTrackColor);
@ -92,6 +95,10 @@ QByteArray MapSettings::serialize() const
s.writeU32(14, m_reverseAPIFeatureIndex); s.writeU32(14, m_reverseAPIFeatureIndex);
s.writeBool(15, m_displaySelectedGroundTracks); s.writeBool(15, m_displaySelectedGroundTracks);
s.writeBool(16, m_displayAllGroundTracks); s.writeBool(16, m_displayAllGroundTracks);
s.writeString(17, m_thunderforestAPIKey);
s.writeString(18, m_maptilerAPIKey);
s.writeBlob(19, m_rollupState);
s.writeString(20, m_osmURL);
return s.final(); return s.final();
} }
@ -114,7 +121,7 @@ bool MapSettings::deserialize(const QByteArray& data)
d.readBool(1, &m_displayNames, true); d.readBool(1, &m_displayNames, true);
d.readString(2, &m_mapProvider, "osm"); d.readString(2, &m_mapProvider, "osm");
d.readString(3, &m_mapBoxApiKey, ""); d.readString(3, &m_mapBoxAPIKey, "");
d.readString(4, &m_mapBoxStyles, ""); d.readString(4, &m_mapBoxStyles, "");
d.readU32(5, &m_sources, -1); d.readU32(5, &m_sources, -1);
d.readU32(6, &m_groundTrackColor, QColor(150, 0, 20).rgb()); d.readU32(6, &m_groundTrackColor, QColor(150, 0, 20).rgb());
@ -138,6 +145,11 @@ bool MapSettings::deserialize(const QByteArray& data)
d.readBool(15, &m_displaySelectedGroundTracks, true); d.readBool(15, &m_displaySelectedGroundTracks, true);
d.readBool(16, &m_displayAllGroundTracks, true); d.readBool(16, &m_displayAllGroundTracks, true);
d.readString(17, &m_thunderforestAPIKey, "");
d.readString(18, &m_maptilerAPIKey, "");
d.readBlob(19, &m_rollupState);
d.readString(20, &m_osmURL, "");
return true; return true;
} }
else else

View File

@ -31,7 +31,10 @@ struct MapSettings
{ {
bool m_displayNames; bool m_displayNames;
QString m_mapProvider; QString m_mapProvider;
QString m_mapBoxApiKey; QString m_thunderforestAPIKey;
QString m_maptilerAPIKey;
QString m_mapBoxAPIKey;
QString m_osmURL;
QString m_mapBoxStyles; QString m_mapBoxStyles;
quint32 m_sources; // Bitmask of SOURCE_* quint32 m_sources; // Bitmask of SOURCE_*
bool m_displayAllGroundTracks; bool m_displayAllGroundTracks;
@ -45,6 +48,7 @@ struct MapSettings
uint16_t m_reverseAPIPort; uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIFeatureSetIndex; uint16_t m_reverseAPIFeatureSetIndex;
uint16_t m_reverseAPIFeatureIndex; uint16_t m_reverseAPIFeatureIndex;
QByteArray m_rollupState;
MapSettings(); MapSettings();
void resetToDefaults(); void resetToDefaults();
@ -64,7 +68,10 @@ struct MapSettings
static const quint32 SOURCE_SATELLITE_TRACKER = 0x10; static const quint32 SOURCE_SATELLITE_TRACKER = 0x10;
static const quint32 SOURCE_BEACONS = 0x20; static const quint32 SOURCE_BEACONS = 0x20;
static const quint32 SOURCE_RADIO_TIME = 0x40; static const quint32 SOURCE_RADIO_TIME = 0x40;
static const quint32 SOURCE_STATION = 0x80; static const quint32 SOURCE_AM = 0x80;
static const quint32 SOURCE_FM = 0x100;
static const quint32 SOURCE_DAB = 0x200;
static const quint32 SOURCE_STATION = 0x400; // Antenna at "My Position"
}; };
#endif // INCLUDE_FEATURE_MAPSETTINGS_H_ #endif // INCLUDE_FEATURE_MAPSETTINGS_H_

View File

@ -42,10 +42,14 @@ MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) :
{ {
ui->setupUi(this); ui->setupUi(this);
ui->mapProvider->setCurrentIndex(MapSettings::m_mapProviders.indexOf(settings->m_mapProvider)); ui->mapProvider->setCurrentIndex(MapSettings::m_mapProviders.indexOf(settings->m_mapProvider));
ui->mapBoxApiKey->setText(settings->m_mapBoxApiKey); ui->thunderforestAPIKey->setText(settings->m_thunderforestAPIKey);
ui->maptilerAPIKey->setText(settings->m_maptilerAPIKey);
ui->mapBoxAPIKey->setText(settings->m_mapBoxAPIKey);
ui->osmURL->setText(settings->m_osmURL);
ui->mapBoxStyles->setText(settings->m_mapBoxStyles); ui->mapBoxStyles->setText(settings->m_mapBoxStyles);
for (int i = 0; i < ui->sourceList->count(); i++) for (int i = 0; i < ui->sourceList->count(); i++) {
ui->sourceList->item(i)->setCheckState((m_settings->m_sources & (1 << i)) ? Qt::Checked : Qt::Unchecked); ui->sourceList->item(i)->setCheckState((m_settings->m_sources & (1 << i)) ? Qt::Checked : Qt::Unchecked);
}
ui->groundTrackColor->setStyleSheet(backgroundCSS(m_settings->m_groundTrackColor)); ui->groundTrackColor->setStyleSheet(backgroundCSS(m_settings->m_groundTrackColor));
ui->predictedGroundTrackColor->setStyleSheet(backgroundCSS(m_settings->m_predictedGroundTrackColor)); ui->predictedGroundTrackColor->setStyleSheet(backgroundCSS(m_settings->m_predictedGroundTrackColor));
} }
@ -58,23 +62,36 @@ MapSettingsDialog::~MapSettingsDialog()
void MapSettingsDialog::accept() void MapSettingsDialog::accept()
{ {
QString mapProvider = MapSettings::m_mapProviders[ui->mapProvider->currentIndex()]; QString mapProvider = MapSettings::m_mapProviders[ui->mapProvider->currentIndex()];
QString mapBoxApiKey = ui->mapBoxApiKey->text(); QString mapBoxAPIKey = ui->mapBoxAPIKey->text();
QString osmURL = ui->osmURL->text();
QString mapBoxStyles = ui->mapBoxStyles->text(); QString mapBoxStyles = ui->mapBoxStyles->text();
QString thunderforestAPIKey = ui->thunderforestAPIKey->text();
QString maptilerAPIKey = ui->maptilerAPIKey->text();
m_osmURLChanged = osmURL != m_settings->m_osmURL;
if ((mapProvider != m_settings->m_mapProvider) if ((mapProvider != m_settings->m_mapProvider)
|| (mapBoxApiKey != m_settings->m_mapBoxApiKey) || (thunderforestAPIKey != m_settings->m_thunderforestAPIKey)
|| (mapBoxStyles != m_settings->m_mapBoxStyles)) || (maptilerAPIKey != m_settings->m_maptilerAPIKey)
|| (mapBoxAPIKey != m_settings->m_mapBoxAPIKey)
|| (mapBoxStyles != m_settings->m_mapBoxStyles)
|| (osmURL != m_settings->m_osmURL))
{ {
m_settings->m_mapProvider = mapProvider; m_settings->m_mapProvider = mapProvider;
m_settings->m_mapBoxApiKey = mapBoxApiKey; m_settings->m_thunderforestAPIKey = thunderforestAPIKey;
m_settings->m_maptilerAPIKey = maptilerAPIKey;
m_settings->m_mapBoxAPIKey = mapBoxAPIKey;
m_settings->m_osmURL = osmURL;
m_settings->m_mapBoxStyles = mapBoxStyles; m_settings->m_mapBoxStyles = mapBoxStyles;
m_mapSettingsChanged = true; m_mapSettingsChanged = true;
} }
else else
{
m_mapSettingsChanged = false; m_mapSettingsChanged = false;
}
m_settings->m_sources = 0; m_settings->m_sources = 0;
quint32 sources = MapSettings::SOURCE_STATION; quint32 sources = MapSettings::SOURCE_STATION;
for (int i = 0; i < ui->sourceList->count(); i++) for (int i = 0; i < ui->sourceList->count(); i++) {
sources |= (ui->sourceList->item(i)->checkState() == Qt::Checked) << i; sources |= (ui->sourceList->item(i)->checkState() == Qt::Checked) << i;
}
m_sourcesChanged = sources != m_settings->m_sources; m_sourcesChanged = sources != m_settings->m_sources;
m_settings->m_sources = sources; m_settings->m_sources = sources;
QDialog::accept(); QDialog::accept();

View File

@ -30,6 +30,7 @@ public:
MapSettings *m_settings; MapSettings *m_settings;
bool m_mapSettingsChanged; bool m_mapSettingsChanged;
bool m_osmURLChanged;
bool m_sourcesChanged; bool m_sourcesChanged;
private slots: private slots:

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>436</width> <width>436</width>
<height>491</height> <height>520</height>
</rect> </rect>
</property> </property>
<property name="font"> <property name="font">
@ -182,34 +182,76 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="3" column="0">
<widget class="QLabel" name="mapBoxApiKeyLabel"> <widget class="QLabel" name="mapBoxAPIKeyLabel">
<property name="text"> <property name="text">
<string>Mapbox API Key</string> <string>Mapbox API Key</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="3" column="1">
<widget class="QLineEdit" name="mapBoxApiKey"> <widget class="QLineEdit" name="mapBoxAPIKey">
<property name="toolTip"> <property name="toolTip">
<string>Enter a Mapbox API key in order to use Mapbox maps</string> <string>Enter a Mapbox API key in order to use Mapbox maps: https://www.mapbox.com/</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="5" column="0">
<widget class="QLabel" name="mapBoxStylesLabel"> <widget class="QLabel" name="mapBoxStylesLabel">
<property name="text"> <property name="text">
<string>MapboxGL Styles</string> <string>MapboxGL Styles</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="5" column="1">
<widget class="QLineEdit" name="mapBoxStyles"> <widget class="QLineEdit" name="mapBoxStyles">
<property name="toolTip"> <property name="toolTip">
<string>Comma separated list of MapBox styles</string> <string>Comma separated list of MapBox styles</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0">
<widget class="QLabel" name="thunderforestAPIKeyLabel">
<property name="text">
<string>Thunderforest API Key</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="maptilerAPIKeyLabel">
<property name="text">
<string>Maptiler API Key</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="thunderforestAPIKey">
<property name="toolTip">
<string>Enter a Thunderforest API key in order to use non-watermarked Thunderforest maps: https://www.thunderforest.com/</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="maptilerAPIKey">
<property name="toolTip">
<string>Enter a Maptiler API key in order to use Maptiler maps: https://www.maptiler.com/</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="osmURLLabel">
<property name="text">
<string>OSM Custom URL</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="osmURL">
<property name="toolTip">
<string>URL of custom map for use with OpenStreetMap provider</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -0,0 +1,18 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "osmtemplateserver.h"

View File

@ -0,0 +1,137 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_OSMTEMPLATE_SERVER_H_
#define INCLUDE_OSMTEMPLATE_SERVER_H_
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
class OSMTemplateServer : public QTcpServer
{
Q_OBJECT
private:
QString m_thunderforestAPIKey;
QString m_maptilerAPIKey;
public:
// port - port to listen on / is listening on. Use 0 for any free port.
OSMTemplateServer(const QString &thunderforestAPIKey, const QString &maptilerAPIKey, quint16 &port, QObject* parent = 0) :
QTcpServer(parent),
m_thunderforestAPIKey(thunderforestAPIKey),
m_maptilerAPIKey(maptilerAPIKey)
{
listen(QHostAddress::Any, port);
port = serverPort();
}
void incomingConnection(qintptr socket) override
{
QTcpSocket* s = new QTcpSocket(this);
connect(s, SIGNAL(readyRead()), this, SLOT(readClient()));
connect(s, SIGNAL(disconnected()), this, SLOT(discardClient()));
s->setSocketDescriptor(socket);
//addPendingConnection(socket);
}
private slots:
void readClient()
{
QStringList map({"/cycle", "/cycle-hires", "/hiking", "/hiking-hires", "/night-transit", "/night-transit-hires", "/terrain", "/terrain-hires", "/transit", "/transit-hires"});
QStringList mapId({"thf-cycle", "thf-cycle-hires", "thf-hike", "thf-hike-hires", "thf-nighttransit", "thf-nighttransit-hires", "thf-landsc", "thf-landsc-hires", "thf-transit", "thf-transit-hires"});
QStringList mapUrl({"cycle", "cycle", "outdoors", "outdoors", "transport-dark", "transport-dark", "landscape", "landscape", "transport", "transport"});
QTcpSocket* socket = (QTcpSocket*)sender();
if (socket->canReadLine())
{
QString line = socket->readLine();
qDebug() << "HTTP Request: " << line;
QStringList tokens = QString(line).split(QRegExp("[ \r\n][ \r\n]*"));
if (tokens[0] == "GET")
{
bool hires = tokens[1].contains("hires");
QString hiresURL = hires ? "@2x" : "";
QString xml;
if ((tokens[1] == "/street") || (tokens[1] == "/street-hires"))
{
xml = QString("\
{\
\"UrlTemplate\" : \"https://maps.wikimedia.org/osm-intl/%z/%x/%y%1.png\",\
\"ImageFormat\" : \"png\",\
\"QImageFormat\" : \"Indexed8\",\
\"ID\" : \"wmf-intl-%2x\",\
\"MaximumZoomLevel\" : 18,\
\"MapCopyRight\" : \"<a href='https://wikimediafoundation.org/wiki/Terms_of_Use'>WikiMedia Foundation</a>\",\
\"DataCopyRight\" : \"<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors\"\
}").arg(hiresURL).arg(hires ? 1 : 2);
}
else if (tokens[1] == "/satellite")
{
xml = QString("\
{\
\"Enabled\" : true,\
\"UrlTemplate\" : \"https://api.maptiler.com/tiles/satellite/%z/%x/%y%1.jpg?key=%2\",\
\"ImageFormat\" : \"jpg\",\
\"QImageFormat\" : \"RGB888\",\
\"ID\" : \"usgs-l7\",\
\"MaximumZoomLevel\" : 20,\
\"MapCopyRight\" : \"<a href='http://maptiler.com/'>Maptiler</a>\",\
\"DataCopyRight\" : \"<a href='http://maptiler.com'>Maptiler</a>\"\
}").arg(hiresURL).arg(m_maptilerAPIKey);
}
else
{
int idx = map.indexOf(tokens[1]);
if (idx != -1)
{
xml = QString("\
{\
\"UrlTemplate\" : \"http://a.tile.thunderforest.com/%1/%z/%x/%y%4.png?apikey=%2\",\
\"ImageFormat\" : \"png\",\
\"QImageFormat\" : \"Indexed8\",\
\"ID\" : \"%3\",\
\"MaximumZoomLevel\" : 20,\
\"MapCopyRight\" : \"<a href='http://www.thunderforest.com/'>Thunderforest</a>\",\
\"DataCopyRight\" : \"<a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors\"\
}").arg(mapUrl[idx]).arg(m_thunderforestAPIKey).arg(mapId[idx]).arg(hiresURL);
}
}
QTextStream os(socket);
os.setAutoDetectUnicode(true);
os << "HTTP/1.0 200 Ok\r\n"
"Content-Type: text/html; charset=\"utf-8\"\r\n"
"\r\n"
<< xml << "\n";
socket->close();
if (socket->state() == QTcpSocket::UnconnectedState) {
delete socket;
}
}
}
}
void discardClient()
{
QTcpSocket* socket = (QTcpSocket*)sender();
socket->deleteLater();
}
};
#endif

View File

@ -89,6 +89,10 @@ Free API keys are available by signing up for an accounts with:
* [Maptiler](https://www.maptiler.com/) * [Maptiler](https://www.maptiler.com/)
* [Mapbox](https://www.mapbox.com/) * [Mapbox](https://www.mapbox.com/)
If API keys are not specified, a default key will be used, but this may not work if too many users use it.
When OpenStreetMap is used as the provider, a custom map URL can be entered. For example, http://a.tile.openstreetmap.fr/hot/ or http://1.basemaps.cartocdn.com/light_nolabels/
<h3>Map</h3> <h3>Map</h3>
The map displays objects reported by other SDRangel channels and features, as well as beacon locations. The map displays objects reported by other SDRangel channels and features, as well as beacon locations.