diff --git a/plugins/feature/map/czml.cpp b/plugins/feature/map/czml.cpp index de7874222..bc0ecc019 100644 --- a/plugins/feature/map/czml.cpp +++ b/plugins/feature/map/czml.cpp @@ -77,6 +77,7 @@ QJsonObject CZML::update(PolygonMapItem *mapItem) if ( !mapItem->m_itemSettings->m_enabled || !mapItem->m_itemSettings->m_display3DTrack || filter(mapItem) + || mapItem->m_deleted ) { // Delete obj completely (including any history) @@ -147,6 +148,15 @@ QJsonObject CZML::update(PolygonMapItem *mapItem) polygon.insert("extrudedHeight", mapItem->m_extrudedHeight); } + // We need to have a position, otherwise viewer entity tracking doesn't seem to work + QJsonArray coords { + mapItem->m_longitude, mapItem->m_latitude, mapItem->m_altitude + }; + QJsonObject position { + {"cartographicDegrees", coords}, + }; + obj.insert("position", position); + obj.insert("polygon", polygon); obj.insert("description", mapItem->m_label); @@ -165,6 +175,7 @@ QJsonObject CZML::update(PolylineMapItem *mapItem) if ( !mapItem->m_itemSettings->m_enabled || !mapItem->m_itemSettings->m_display3DTrack || filter(mapItem) + || mapItem->m_deleted ) { // Delete obj completely (including any history) @@ -214,6 +225,15 @@ QJsonObject CZML::update(PolylineMapItem *mapItem) polyline.insert("altitudeReference", m_heightReferences[mapItem->m_altitudeReference]); // Custom code in map3d.html } + // We need to have a position, otherwise viewer entity tracking doesn't seem to work + QJsonArray coords { + mapItem->m_longitude, mapItem->m_latitude, mapItem->m_altitude + }; + QJsonObject position { + {"cartographicDegrees", coords}, + }; + obj.insert("position", position); + obj.insert("polyline", polyline); obj.insert("description", mapItem->m_label); diff --git a/plugins/feature/map/map/map.qml b/plugins/feature/map/map/map.qml index 193b2d95a..5d807c985 100644 --- a/plugins/feature/map/map/map.qml +++ b/plugins/feature/map/map/map.qml @@ -194,6 +194,7 @@ Item { Text { id: polygonText text: label + textFormat: TextEdit.RichText } } } @@ -226,6 +227,7 @@ Item { Text { id: polylineText text: label + textFormat: TextEdit.RichText } } } diff --git a/plugins/feature/map/mapgui.cpp b/plugins/feature/map/mapgui.cpp index 47285af89..e9ccfb9cb 100644 --- a/plugins/feature/map/mapgui.cpp +++ b/plugins/feature/map/mapgui.cpp @@ -309,6 +309,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur addAirspace(); addAirports(); addNavtex(); + addVLF(); displaySettings(); applySettings(true); @@ -419,6 +420,53 @@ void MapGUI::addIBPBeacons() } } +// https://sidstation.loudet.org/stations-list-en.xhtml +// https://core.ac.uk/download/pdf/224769021.pdf -- Table 1 +// GQD/GQZ callsigns: https://groups.io/g/VLF/message/19212?p=%2C%2C%2C20%2C0%2C0%2C0%3A%3Arecentpostdate%2Fsticky%2C%2C19.6%2C20%2C2%2C0%2C38924431 +const QList MapGUI::m_vlfTransmitters = { + // Other signals possibly seen: 13800, 19000 + {"VTX2", 17000, 8.387015, 77.752762}, // South Vijayanarayanam, India + {"GQD", 19580, 54.911643, -3.278456, 100}, // Anthorn, UK, Often referred to as GBZ + {"NWC", 19800, -21.816325, 114.16546, 1000}, // Exmouth, Aus + {"ICV", 20270, 40.922946, 9.731881, 50}, // Isola di Tavolara, Italy (Can be distorted on 3D map if terrain used) + {"FTA", 20900, 48.544632, 2.579429, 50}, // Sainte-Assise, France (Satellite imagary obfuscated) + {"NPM", 21400, 21.420166, -158.151140, 600}, // Pearl Harbour, Lualuahei, USA (Not seen?) + {"HWU", 21750, 46.713129, 1.245248, 200}, // Rosnay, France + {"GQZ", 22100, 54.731799, -2.883033, 100}, // Skelton, UK (GVT in paper) + {"DHO38", 23400, 53.078900, 7.615000, 300}, // Rhauderfehn, Germany - Off air 7-8 UTC - Not seen on air! + {"NAA", 24000, 44.644506, -67.284565, 1000}, // Cutler, Maine, USA + {"TFK/NRK", 37500, 63.850365, -22.466773, 100}, // Grindavik, Iceland + {"SRC/SHR", 38000, 57.120328, 16.153083, -1}, // Ruda, Sweden +}; + +void MapGUI::addVLF() +{ + for (int i = 0; i < m_vlfTransmitters.size(); i++) + { + SWGSDRangel::SWGMapItem vlfMapItem; + // Need to suffix frequency, as there are multiple becaons with same callsign at different locations + QString name = QString("%1").arg(m_vlfTransmitters[i].m_callsign); + vlfMapItem.setName(new QString(name)); + vlfMapItem.setLatitude(m_vlfTransmitters[i].m_latitude); + vlfMapItem.setLongitude(m_vlfTransmitters[i].m_longitude); + vlfMapItem.setAltitude(0.0); + vlfMapItem.setImage(new QString("antenna.png")); + vlfMapItem.setImageRotation(0); + QString text = QString("VLF Transmitter\nCallsign: %1\nFrequency: %2 kHz") + .arg(m_vlfTransmitters[i].m_callsign) + .arg(m_vlfTransmitters[i].m_frequency/1000.0); + vlfMapItem.setText(new QString(text)); + vlfMapItem.setModel(new QString("antenna.glb")); + vlfMapItem.setFixedPosition(true); + vlfMapItem.setOrientation(0); + vlfMapItem.setLabel(new QString(name)); + vlfMapItem.setLabelAltitudeOffset(4.5); + vlfMapItem.setAltitudeReference(1); + update(m_map, &vlfMapItem, "VLF"); + } +} + + const QList MapGUI::m_radioTimeTransmitters = { {"MSF", 60000, 54.9075f, -3.27333f, 17}, // UK {"DCF77", 77500, 50.01611111f, 9.00805556f, 50}, // Germany @@ -1575,8 +1623,7 @@ void MapGUI::find(const QString& target) } else { - // FIXME: Support polygon/polyline - ObjectMapItem *mapItem = m_objectMapModel.findMapItem(target); + ObjectMapItem *mapItem = (ObjectMapItem *)m_objectMapModel.findMapItem(target); if (mapItem != nullptr) { map->setProperty("center", QVariant::fromValue(mapItem->getCoordinates())); @@ -1584,25 +1631,47 @@ void MapGUI::find(const QString& target) m_cesium->track(target); } m_objectMapModel.moveToFront(m_objectMapModel.findMapItemIndex(target).row()); + return; + } + + PolylineMapItem *polylineMapItem = (PolylineMapItem *)m_polylineMapModel.findMapItem(target); + if (polylineMapItem != nullptr) + { + map->setProperty("center", QVariant::fromValue(polylineMapItem->getCoordinates())); + if (m_cesium) { + m_cesium->track(target); + } + //m_polylineMapModel.moveToFront(m_polylineMapModel.findMapItemIndex(target).row()); + return; + } + + PolygonMapItem *polygonMapItem = (PolygonMapItem *)m_polylineMapModel.findMapItem(target); + if (polygonMapItem != nullptr) + { + map->setProperty("center", QVariant::fromValue(polygonMapItem->getCoordinates())); + if (m_cesium) { + m_cesium->track(target); + } + //m_polylineMapModel.moveToFront(m_polylineMapModel.findMapItemIndex(target).row()); + return; + } + + // Search as an address + QGeoServiceProvider* geoSrv = new QGeoServiceProvider("osm"); + if (geoSrv != nullptr) + { + QLocale qLocaleC(QLocale::C, QLocale::AnyCountry); + geoSrv->setLocale(qLocaleC); + QGeoCodeReply *pQGeoCode = geoSrv->geocodingManager()->geocode(target); + if (pQGeoCode) { + QObject::connect(pQGeoCode, &QGeoCodeReply::finished, this, &MapGUI::geoReply); + } else { + qDebug() << "MapGUI::find: GeoCoding failed"; + } } else { - QGeoServiceProvider* geoSrv = new QGeoServiceProvider("osm"); - if (geoSrv != nullptr) - { - QLocale qLocaleC(QLocale::C, QLocale::AnyCountry); - geoSrv->setLocale(qLocaleC); - QGeoCodeReply *pQGeoCode = geoSrv->geocodingManager()->geocode(target); - if (pQGeoCode) { - QObject::connect(pQGeoCode, &QGeoCodeReply::finished, this, &MapGUI::geoReply); - } else { - qDebug() << "MapGUI::find: GeoCoding failed"; - } - } - else - { - qDebug() << "MapGUI::find: osm not available"; - } + qDebug() << "MapGUI::find: osm not available"; } } } diff --git a/plugins/feature/map/mapitem.cpp b/plugins/feature/map/mapitem.cpp index 5a3c27af2..ba96454cb 100644 --- a/plugins/feature/map/mapitem.cpp +++ b/plugins/feature/map/mapitem.cpp @@ -39,6 +39,14 @@ void MapItem::update(SWGSDRangel::SWGMapItem *mapItem) m_altitude = mapItem->getAltitude(); } +QGeoCoordinate MapItem::getCoordinates() +{ + QGeoCoordinate coords; + coords.setLatitude(m_latitude); + coords.setLongitude(m_longitude); + return coords; +} + void ObjectMapItem::update(SWGSDRangel::SWGMapItem *mapItem) { MapItem::update(mapItem); @@ -116,6 +124,7 @@ void PolygonMapItem::update(SWGSDRangel::SWGMapItem *mapItem) m_colorValid = mapItem->getColorValid(); m_color = mapItem->getColor(); m_altitudeReference = mapItem->getAltitudeReference(); + m_deleted = *mapItem->getImage() == ""; qDeleteAll(m_points); m_points.clear(); @@ -151,6 +160,7 @@ void PolylineMapItem::update(SWGSDRangel::SWGMapItem *mapItem) m_colorValid = mapItem->getColorValid(); m_color = mapItem->getColor(); m_altitudeReference = mapItem->getAltitudeReference(); + m_deleted = *mapItem->getImage() == ""; qDeleteAll(m_points); m_points.clear(); @@ -180,14 +190,6 @@ void PolylineMapItem::update(SWGSDRangel::SWGMapItem *mapItem) m_bounds = QGeoRectangle(QGeoCoordinate(latMax, lonMin), QGeoCoordinate(latMin, lonMax)); } -QGeoCoordinate ObjectMapItem::getCoordinates() -{ - QGeoCoordinate coords; - coords.setLatitude(m_latitude); - coords.setLongitude(m_longitude); - return coords; -} - void ObjectMapItem::findFrequency() { // Look for a frequency in the text for this object diff --git a/plugins/feature/map/mapitem.h b/plugins/feature/map/mapitem.h index 02b47e55e..8a733bbfc 100644 --- a/plugins/feature/map/mapitem.h +++ b/plugins/feature/map/mapitem.h @@ -41,6 +41,7 @@ public: MapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem); virtual void update(SWGSDRangel::SWGMapItem *mapItem); + QGeoCoordinate getCoordinates(); protected: @@ -74,7 +75,6 @@ public: update(mapItem); } void update(SWGSDRangel::SWGMapItem *mapItem) override; - QGeoCoordinate getCoordinates(); protected: void findFrequency(); @@ -143,6 +143,7 @@ protected: bool m_colorValid; QRgb m_color; int m_altitudeReference; + bool m_deleted; }; class PolylineMapItem : public MapItem { @@ -165,6 +166,7 @@ protected: bool m_colorValid; QRgb m_color; int m_altitudeReference; + bool m_deleted; }; class ImageMapItem : public MapItem { diff --git a/plugins/feature/map/mapmodel.cpp b/plugins/feature/map/mapmodel.cpp index 7b110604d..0fd548074 100644 --- a/plugins/feature/map/mapmodel.cpp +++ b/plugins/feature/map/mapmodel.cpp @@ -87,12 +87,11 @@ void MapModel::update(const QObject *sourcePipe, SWGSDRangel::SWGMapItem *swgMap QString image = *swgMapItem->getImage(); if (image.isEmpty()) { - // Delete the item + // Delete the item from 2D map remove(item); - // Need to call update, for it to be removed in 3D map - // Item is set to not be available from this point in time - // It will still be available if time is set in the past + // Delete from 3D map item->update(swgMapItem); + update3D(item); } else { @@ -178,6 +177,36 @@ MapItem *MapModel::findMapItem(const QObject *source, const QString& name) return nullptr; } +// FIXME: This should potentially return a list, as we have have multiple items with the same name +// from different sources +MapItem *MapModel::findMapItem(const QString& name) +{ + QListIterator i(m_items); + while (i.hasNext()) + { + MapItem *item = i.next(); + if (!item->m_name.compare(name, Qt::CaseInsensitive)) { + return item; + } + } + return nullptr; +} + +QModelIndex MapModel::findMapItemIndex(const QString& name) +{ + int idx = 0; + QListIterator i(m_items); + while (i.hasNext()) + { + MapItem *item = i.next(); + if (item->m_name == name) { + return index(idx); + } + idx++; + } + return index(-1); +} + QHash MapModel::roleNames() const { QHash roles; @@ -589,36 +618,6 @@ Q_INVOKABLE void ObjectMapModel::moveToBack(int oldRow) } } -// FIXME: This should potentially return a list, as we have have multiple items with the same name -// from different sources -ObjectMapItem *ObjectMapModel::findMapItem(const QString& name) -{ - QListIterator i(m_items); - while (i.hasNext()) - { - MapItem *item = i.next(); - if (!item->m_name.compare(name, Qt::CaseInsensitive)) { - return (ObjectMapItem *)item; - } - } - return nullptr; -} - -QModelIndex ObjectMapModel::findMapItemIndex(const QString& name) -{ - int idx = 0; - QListIterator i(m_items); - while (i.hasNext()) - { - MapItem *item = i.next(); - if (item->m_name == name) { - return index(idx); - } - idx++; - } - return index(-1); -} - QVariant ObjectMapModel::data(const QModelIndex &index, int role) const { int row = index.row(); diff --git a/plugins/feature/map/mapsettings.cpp b/plugins/feature/map/mapsettings.cpp index de8b90f92..1848fdc7b 100644 --- a/plugins/feature/map/mapsettings.cpp +++ b/plugins/feature/map/mapsettings.cpp @@ -31,6 +31,7 @@ const QStringList MapSettings::m_pipeTypes = { QStringLiteral("AIS"), QStringLiteral("APRS"), QStringLiteral("APTDemod"), + QStringLiteral("DSCDemod"), QStringLiteral("FT8Demod"), QStringLiteral("HeatMap"), QStringLiteral("ILSDemod"), @@ -46,6 +47,7 @@ const QStringList MapSettings::m_pipeURIs = { QStringLiteral("sdrangel.feature.ais"), QStringLiteral("sdrangel.feature.aprs"), QStringLiteral("sdrangel.channel.aptdemod"), + QStringLiteral("sdrangel.channel.dscdemod"), QStringLiteral("sdrangel.channel.ft8demod"), QStringLiteral("sdrangel.channel.heatmap"), QStringLiteral("sdrangel.channel.ilsdemod"), @@ -79,6 +81,7 @@ MapSettings::MapSettings() : MapItemSettings *aprsSettings = new MapItemSettings("APRS", true, QColor(255, 255, 0), true, false, 11); aprsSettings->m_extrapolate = 0; m_itemSettings.insert("APRS", aprsSettings); + m_itemSettings.insert("DSCDemod", new MapItemSettings("DSCDemod", true, QColor(181, 230, 29), true, true, 3)); m_itemSettings.insert("StarTracker", new MapItemSettings("StarTracker", true, QColor(230, 230, 230), true, true, 3)); m_itemSettings.insert("SatelliteTracker", new MapItemSettings("SatelliteTracker", true, QColor(0, 0, 255), true, false, 0, modelMinPixelSize)); m_itemSettings.insert("Beacons", new MapItemSettings("Beacons", true, QColor(255, 0, 0), false, true, 8)); @@ -87,6 +90,7 @@ MapSettings::MapSettings() : m_itemSettings.insert("Radar", new MapItemSettings("Radar", true, QColor(255, 0, 0), false, true, 8)); m_itemSettings.insert("FT8Demod", new MapItemSettings("FT8Demod", true, QColor(0, 192, 255), true, true, 8)); m_itemSettings.insert("HeatMap", new MapItemSettings("HeatMap", true, QColor(102, 40, 220), true, true, 11)); + m_itemSettings.insert("VLF", new MapItemSettings("VLF", false, QColor(255, 0, 0), false, true, 8)); m_itemSettings.insert("AM", new MapItemSettings("AM", false, QColor(255, 0, 0), false, true, 10)); MapItemSettings *fmSettings = new MapItemSettings("FM", false, QColor(255, 0, 0), false, true, 12); diff --git a/plugins/feature/map/readme.md b/plugins/feature/map/readme.md index 71cfdd26d..584223cc8 100644 --- a/plugins/feature/map/readme.md +++ b/plugins/feature/map/readme.md @@ -15,6 +15,7 @@ On top of this, it can plot data from other plugins, such as: * RF Heat Maps from the Heap Map channel, * Radials and estimated position from the VOR localizer feature, * ILS course line and glide path from the ILS Demodulator. +* DSC geographic call areas. As well as internet data sources: @@ -25,6 +26,7 @@ As well as internet data sources: * GRAVES radar, * Ionosonde station data, * Navtex transmitters. +* VLF transmitters. It can also create tracks showing the path aircraft, ships and APRS objects have taken, as well as predicted paths for satellites.