diff --git a/plugins/feature/map/CMakeLists.txt b/plugins/feature/map/CMakeLists.txt index 4c1ea9dae..21528646a 100644 --- a/plugins/feature/map/CMakeLists.txt +++ b/plugins/feature/map/CMakeLists.txt @@ -34,6 +34,7 @@ if(NOT SERVER_MODE) mapbeacondialog.cpp mapbeacondialog.ui map.qrc + icons.qrc ) set(map_HEADERS ${map_HEADERS} diff --git a/plugins/feature/map/beacon.h b/plugins/feature/map/beacon.h index f4a5be696..8fb430dfe 100644 --- a/plugins/feature/map/beacon.h +++ b/plugins/feature/map/beacon.h @@ -74,6 +74,16 @@ struct Beacon { return QString("%1 kHz").arg(m_frequency/1000.0, 0, ',', 3); } + QString getFrequencyShortText() + { + if (m_frequency > 1000000000) + return QString("%1G").arg(m_frequency/1000000000.0, 0, ',', 1); + else if (m_frequency > 1000000) + return QString("%1M").arg(std::floor(m_frequency/1000000.0), 0, ',', 0); + else + return QString("%1k").arg(std::floor(m_frequency/1000.0), 0, ',', 0); + } + // Uses ; rather than , static QList *readIARUCSV(const QString &filename) { diff --git a/plugins/feature/map/icons.qrc b/plugins/feature/map/icons.qrc new file mode 100644 index 000000000..f462b389c --- /dev/null +++ b/plugins/feature/map/icons.qrc @@ -0,0 +1,5 @@ + + + icons/groundtracks.png + + diff --git a/plugins/feature/map/icons/groundtracks.png b/plugins/feature/map/icons/groundtracks.png new file mode 100644 index 000000000..e75c45a8c Binary files /dev/null and b/plugins/feature/map/icons/groundtracks.png differ diff --git a/plugins/feature/map/map.qrc b/plugins/feature/map/map.qrc index 93f43c7db..0310f0732 100644 --- a/plugins/feature/map/map.qrc +++ b/plugins/feature/map/map.qrc @@ -1,6 +1,7 @@ map/map.qml + map/map_5_12.qml map/antenna.png diff --git a/plugins/feature/map/map/map.qml b/plugins/feature/map/map/map.qml index ba8237983..0b0816009 100644 --- a/plugins/feature/map/map/map.qml +++ b/plugins/feature/map/map/map.qml @@ -1,8 +1,8 @@ -import QtQuick 2.12 -import QtQuick.Window 2.12 -import QtQuick.Controls 2.12 -import QtLocation 5.12 -import QtPositioning 5.12 +import QtQuick 2.14 +import QtQuick.Window 2.14 +import QtQuick.Controls 2.14 +import QtLocation 5.14 +import QtPositioning 5.14 Item { id: qmlMap @@ -14,16 +14,16 @@ Item { function createMap(pluginParameters) { var parameters = new Array() for (var prop in pluginParameters) { - var parameter = Qt.createQmlObject('import QtLocation 5.6; PluginParameter{ name: "'+ prop + '"; value: "' + pluginParameters[prop]+'"}', qmlMap) + var parameter = Qt.createQmlObject('import QtLocation 5.14; PluginParameter{ name: "'+ prop + '"; value: "' + pluginParameters[prop]+'"}', qmlMap) parameters.push(parameter) } qmlMap.mapParameters = parameters var plugin if (mapParameters && mapParameters.length > 0) - plugin = Qt.createQmlObject ('import QtLocation 5.12; Plugin{ name:"' + mapProvider + '"; parameters: qmlMap.mapParameters}', qmlMap) + plugin = Qt.createQmlObject ('import QtLocation 5.14; Plugin{ name:"' + mapProvider + '"; parameters: qmlMap.mapParameters}', qmlMap) else - plugin = Qt.createQmlObject ('import QtLocation 5.12; Plugin{ name:"' + mapProvider + '"}', qmlMap) + plugin = Qt.createQmlObject ('import QtLocation 5.14; Plugin{ name:"' + mapProvider + '"}', qmlMap) if (mapPtr) { // Objects aren't destroyed immediately, so rename the old // map, so any C++ code that calls findChild("map") doesn't find @@ -66,6 +66,27 @@ Item { center: QtPositioning.coordinate(51.5, 0.125) // London zoomLevel: 10 + // Tracks first, so drawn under other items + MapItemView { + model: mapModel + delegate: groundTrack1Component + } + + MapItemView { + model: mapModel + delegate: groundTrack2Component + } + + MapItemView { + model: mapModel + delegate: predictedGroundTrack1Component + } + + MapItemView { + model: mapModel + delegate: predictedGroundTrack2Component + } + MapItemView { model: mapModel delegate: mapComponent @@ -73,12 +94,25 @@ Item { onZoomLevelChanged: { mapZoomLevel = zoomLevel + } + // The map displays MapPolyLines in the wrong place (+360 degrees) if + // they start to the left of the visible region, so we need to + // split them so they don't, each time the visible region is changed. meh. + onCenterChanged: { + mapModel.viewChanged(visibleRegion.boundingGeoRectangle().bottomLeft.longitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude); } } } + function mapRect() { + if (mapPtr) + return mapPtr.visibleRegion.boundingGeoRectangle(); + else + return null; + } + Component { id: mapComponent MapQuickItem { @@ -87,6 +121,7 @@ Item { anchorPoint.y: image.height/2 coordinate: position zoomLevel: mapZoomLevel > mapImageMinZoom ? mapZoomLevel : mapImageMinZoom + autoFadeIn: false // not in 5.12 sourceItem: Grid { id: gridItem @@ -185,4 +220,46 @@ Item { } } + Component { + id: predictedGroundTrack1Component + MapPolyline { + line.width: 2 + line.color: predictedGroundTrackColor + path: predictedGroundTrack1 + autoFadeIn: false + } + } + + // Part of the line that crosses edge of map + Component { + id: predictedGroundTrack2Component + MapPolyline { + line.width: 2 + line.color: predictedGroundTrackColor + path: predictedGroundTrack2 + autoFadeIn: false + } + } + + Component { + id: groundTrack1Component + MapPolyline { + line.width: 2 + line.color: groundTrackColor + path: groundTrack1 + autoFadeIn: false + } + } + + // Part of the line that crosses edge of map + Component { + id: groundTrack2Component + MapPolyline { + line.width: 2 + line.color: groundTrackColor + path: groundTrack2 + autoFadeIn: false + } + } + } diff --git a/plugins/feature/map/map/map_5_12.qml b/plugins/feature/map/map/map_5_12.qml new file mode 100644 index 000000000..34c97404c --- /dev/null +++ b/plugins/feature/map/map/map_5_12.qml @@ -0,0 +1,261 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 2.12 +import QtLocation 5.12 +import QtPositioning 5.12 + +Item { + id: qmlMap + property int mapZoomLevel: 11 + property string mapProvider: "osm" + property variant mapParameters + property variant mapPtr + + function createMap(pluginParameters) { + var parameters = new Array() + for (var prop in pluginParameters) { + var parameter = Qt.createQmlObject('import QtLocation 5.6; PluginParameter{ name: "'+ prop + '"; value: "' + pluginParameters[prop]+'"}', qmlMap) + parameters.push(parameter) + } + qmlMap.mapParameters = parameters + + var plugin + if (mapParameters && mapParameters.length > 0) + plugin = Qt.createQmlObject ('import QtLocation 5.12; Plugin{ name:"' + mapProvider + '"; parameters: qmlMap.mapParameters}', qmlMap) + else + plugin = Qt.createQmlObject ('import QtLocation 5.12; Plugin{ name:"' + mapProvider + '"}', qmlMap) + if (mapPtr) { + // Objects aren't destroyed immediately, so rename the old + // map, so any C++ code that calls findChild("map") doesn't find + // the old map + mapPtr.objectName = "oldMap"; + mapPtr.destroy() + } + mapPtr = actualMapComponent.createObject(page) + mapPtr.plugin = plugin; + mapPtr.forceActiveFocus() + mapPtr.objectName = "map"; + } + + function getMapTypes() { + var mapTypes = [] + if (mapPtr) { + for (var i = 0; i < mapPtr.supportedMapTypes.length; i++) { + mapTypes[i] = mapPtr.supportedMapTypes[i].name + } + } + return mapTypes + } + + function setMapType(mapTypeIndex) { + if (mapPtr) + mapPtr.activeMapType = mapPtr.supportedMapTypes[mapTypeIndex] + } + + Item { + id: page + anchors.fill: parent + } + + Component { + id: actualMapComponent + + Map { + id: map + anchors.fill: parent + center: QtPositioning.coordinate(51.5, 0.125) // London + zoomLevel: 10 + + // Tracks first, so drawn under other items + MapItemView { + model: mapModel + delegate: groundTrack1Component + } + + MapItemView { + model: mapModel + delegate: groundTrack2Component + } + + MapItemView { + model: mapModel + delegate: predictedGroundTrack1Component + } + + MapItemView { + model: mapModel + delegate: predictedGroundTrack2Component + } + + MapItemView { + model: mapModel + delegate: mapComponent + } + + onZoomLevelChanged: { + mapZoomLevel = zoomLevel + } + + // The map displays MapPolyLines in the wrong place (+360 degrees) if + // they start to the left of the visible region, so we need to + // split them so they don't, each time the visible region is changed. meh. + onCenterChanged: { + mapModel.viewChanged(visibleRegion.boundingGeoRectangle().bottomLeft.longitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude); + } + + } + } + + function mapRect() { + if (mapPtr) + return mapPtr.visibleRegion.boundingGeoRectangle(); + else + return null; + } + + Component { + id: mapComponent + MapQuickItem { + id: mapElement + anchorPoint.x: image.width/2 + anchorPoint.y: image.height/2 + coordinate: position + zoomLevel: mapZoomLevel > mapImageMinZoom ? mapZoomLevel : mapImageMinZoom + //autoFadeIn: false // not in 5.12 + + sourceItem: Grid { + id: gridItem + columns: 1 + Grid { + horizontalItemAlignment: Grid.AlignHCenter + columnSpacing: 5 + layer.enabled: true + layer.smooth: true + Image { + id: image + rotation: mapImageRotation + source: mapImage + visible: mapImageVisible + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button === Qt.LeftButton) { + selected = !selected + if (selected) { + mapModel.moveToFront(index) + } + } else if (mouse.button === Qt.RightButton) { + if (frequency > 0) { + freqMenuItem.text = "Set frequency to " + frequencyString + freqMenuItem.enabled = true + } else { + freqMenuItem.text = "No frequency available" + freqMenuItem.enabled = false + } + contextMenu.popup() + } + } + } + } + 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 + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button === Qt.LeftButton) { + selected = !selected + if (selected) { + mapModel.moveToFront(index) + } + } else if (mouse.button === Qt.RightButton) { + if (frequency > 0) { + freqMenuItem.text = "Set frequency to " + frequencyString + freqMenuItem.enabled = true + } else { + freqMenuItem.text = "No frequency available" + freqMenuItem.enabled = false + } + contextMenu.popup() + } + } + Menu { + id: contextMenu + MenuItem { + text: "Set as target" + onTriggered: target = true + } + MenuItem { + id: freqMenuItem + text: "Not set" + onTriggered: mapModel.setFrequency(frequency) + } + MenuItem { + text: "Move to front" + onTriggered: mapModel.moveToFront(index) + } + MenuItem { + text: "Move to back" + onTriggered: mapModel.moveToBack(index) + } + } + } + } + } + } + } + } + + Component { + id: predictedGroundTrack1Component + MapPolyline { + line.width: 2 + line.color: predictedGroundTrackColor + path: predictedGroundTrack1 + } + } + + // Part of the line that crosses edge of map + Component { + id: predictedGroundTrack2Component + MapPolyline { + line.width: 2 + line.color: predictedGroundTrackColor + path: predictedGroundTrack2 + } + } + + Component { + id: groundTrack1Component + MapPolyline { + line.width: 2 + line.color: groundTrackColor + path: groundTrack1 + } + } + + // Part of the line that crosses edge of map + Component { + id: groundTrack2Component + MapPolyline { + line.width: 2 + line.color: groundTrackColor + path: groundTrack2 + } + } + +} diff --git a/plugins/feature/map/mapgui.cpp b/plugins/feature/map/mapgui.cpp index ed5570cbb..2beea68c3 100644 --- a/plugins/feature/map/mapgui.cpp +++ b/plugins/feature/map/mapgui.cpp @@ -139,6 +139,42 @@ QVariant MapModel::data(const QModelIndex &index, int role) const return QVariant::fromValue(m_items[row]->m_frequency); else if (role == MapModel::frequencyStringRole) return QVariant::fromValue(m_items[row]->m_frequencyString); + else if (role == MapModel::predictedGroundTrack1Role) + { + if ((m_displayAllGroundTracks || (m_displaySelectedGroundTracks && m_selected[row])) && (m_sources & m_items[row]->m_sourceMask)) + return m_items[row]->m_predictedTrack1; + else + return QVariantList(); + } + else if (role == MapModel::predictedGroundTrack2Role) + { + if ((m_displayAllGroundTracks || (m_displaySelectedGroundTracks && m_selected[row])) && (m_sources & m_items[row]->m_sourceMask)) + return m_items[row]->m_predictedTrack2; + else + return QVariantList(); + } + else if (role == MapModel::groundTrack1Role) + { + if ((m_displayAllGroundTracks || (m_displaySelectedGroundTracks && m_selected[row])) && (m_sources & m_items[row]->m_sourceMask)) + return m_items[row]->m_takenTrack1; + else + return QVariantList(); + } + else if (role == MapModel::groundTrack2Role) + { + if ((m_displayAllGroundTracks || (m_displaySelectedGroundTracks && m_selected[row])) && (m_sources & m_items[row]->m_sourceMask)) + return m_items[row]->m_takenTrack2; + else + return QVariantList(); + } + else if (role == groundTrackColorRole) + { + return m_groundTrackColor; + } + else if (role == predictedGroundTrackColorRole) + { + return m_predictedGroundTrackColor; + } return QVariant(); } @@ -193,6 +229,7 @@ void MapModel::update(const PipeEndPoint *sourcePipe, SWGSDRangel::SWGMapItem *s { // Update the item item->update(swgMapItem); + splitTracks(item); update(item); } } @@ -235,6 +272,311 @@ void MapModel::updateTarget() } } +void MapModel::splitTracks(MapItem *item) +{ + if (item->m_takenTrackCoords.size() > 1) + splitTrack(item->m_takenTrackCoords, item->m_takenTrack, item->m_takenTrack1, item->m_takenTrack2, + item->m_takenStart1, item->m_takenStart2, item->m_takenEnd1, item->m_takenEnd2); + if (item->m_predictedTrackCoords.size() > 1) + splitTrack(item->m_predictedTrackCoords, item->m_predictedTrack, item->m_predictedTrack1, item->m_predictedTrack2, + item->m_predictedStart1, item->m_predictedStart2, item->m_predictedEnd1, item->m_predictedEnd2); +} + +void MapModel::interpolateEast(QGeoCoordinate *c1, QGeoCoordinate *c2, double x, QGeoCoordinate *ci, bool offScreen) +{ + double x1 = c1->longitude(); + double y1 = c1->latitude(); + double x2 = c2->longitude(); + double y2 = c2->latitude(); + double y; + if (x2 < x1) + x2 += 360.0; + if (x < x1) + x += 360.0; + y = interpolate(x1, y1, x2, y2, x); + if (x > 180) + x -= 360.0; + if (offScreen) + x -= 0.000000001; + else + x += 0.000000001; + ci->setLongitude(x); + ci->setLatitude(y); + ci->setAltitude(c1->altitude()); +} + +void MapModel::interpolateWest(QGeoCoordinate *c1, QGeoCoordinate *c2, double x, QGeoCoordinate *ci, bool offScreen) +{ + double x1 = c1->longitude(); + double y1 = c1->latitude(); + double x2 = c2->longitude(); + double y2 = c2->latitude(); + double y; + if (x2 > x1) + x2 -= 360.0; + if (x > x1) + x -= 360.0; + y = interpolate(x1, y1, x2, y2, x); + if (x < -180) + x += 360.0; + if (offScreen) + x += 0.000000001; + else + x -= 0.000000001; + ci->setLongitude(x); + ci->setLatitude(y); + ci->setAltitude(c1->altitude()); +} + +static bool isOnScreen(double lon, double bottomLeftLongitude, double bottomRightLongitude, double width, bool antimed) +{ + bool onScreen = false; + if (width == 360) + onScreen = true; + else if (!antimed) + onScreen = (lon > bottomLeftLongitude) && (lon <= bottomRightLongitude); + else + onScreen = (lon > bottomLeftLongitude) || (lon <= bottomRightLongitude); + return onScreen; +} + +static bool crossesAntimeridian(double prevLon, double lon) +{ + bool crosses = false; + if ((prevLon > 90) && (lon < -90)) + crosses = true; // West to East + else if ((prevLon < -90) && (lon > 90)) + crosses = true; // East to West + return crosses; +} + +static bool crossesAntimeridianEast(double prevLon, double lon) +{ + bool crosses = false; + if ((prevLon > 90) && (lon < -90)) + crosses = true; // West to East + return crosses; +} + +static bool crossesAntimeridianWest(double prevLon, double lon) +{ + bool crosses = false; + if ((prevLon < -90) && (lon > 90)) + crosses = true; // East to West + return crosses; +} + +static bool crossesEdge(double lon, double prevLon, double bottomLeftLongitude, double bottomRightLongitude, double width, bool antimed) +{ + // Determine if antimerdian is between the two points + if (!crossesAntimeridian(prevLon, lon)) + { + bool crosses = false; + if ((prevLon <= bottomRightLongitude) && (lon > bottomRightLongitude)) + crosses = true; // Crosses right edge East + else if ((prevLon >= bottomRightLongitude) && (lon < bottomRightLongitude)) + crosses = true; // Crosses right edge West + else if ((prevLon >= bottomLeftLongitude) && (lon < bottomLeftLongitude)) + crosses = true; // Crosses left edge West + else if ((prevLon <= bottomLeftLongitude) && (lon > bottomLeftLongitude)) + crosses = true; // Crosses left edge East + return crosses; + } + else + { + // Determine which point and the edge the antimerdian is between + bool prevLonToRightCrossesAnti = crossesAntimeridianEast(prevLon, bottomRightLongitude); + bool rightToLonCrossesAnti = crossesAntimeridianEast(bottomRightLongitude, lon); + bool prevLonToLeftCrossesAnti = crossesAntimeridianWest(prevLon, bottomLeftLongitude); + bool leftToLonCrossesAnti = crossesAntimeridianWest(bottomLeftLongitude, lon); + + bool crosses = false; + if ( ((prevLon > bottomRightLongitude) && prevLonToRightCrossesAnti && (lon > bottomRightLongitude)) + || ((prevLon <= bottomRightLongitude) && (lon <= bottomRightLongitude) && rightToLonCrossesAnti) + ) + crosses = true; // Crosses right edge East + else if ( ((prevLon < bottomRightLongitude) && prevLonToRightCrossesAnti && (lon < bottomRightLongitude)) + || ((prevLon >= bottomRightLongitude) && (lon >= bottomRightLongitude) && rightToLonCrossesAnti) + ) + crosses = true; // Crosses right edge West + else if ( ((prevLon < bottomLeftLongitude) && prevLonToLeftCrossesAnti && (lon < bottomLeftLongitude)) + || ((prevLon >= bottomLeftLongitude) && (lon >= bottomLeftLongitude) && leftToLonCrossesAnti) + ) + crosses = true; // Crosses left edge West + else if ( ((prevLon > bottomLeftLongitude) && prevLonToLeftCrossesAnti && (lon > bottomLeftLongitude)) + || ((prevLon <= bottomLeftLongitude) && (lon <= bottomLeftLongitude) && leftToLonCrossesAnti) + ) + crosses = true; // Crosses left edge East + return crosses; + } +} + +void MapModel::interpolate(QGeoCoordinate *c1, QGeoCoordinate *c2, double bottomLeftLongitude, double bottomRightLongitude, QGeoCoordinate* ci, bool offScreen) +{ + double x1 = c1->longitude(); + double x2 = c2->longitude(); + double crossesAnti = crossesAntimeridian(x1, x2); + double x; + + // Need to work out which edge we're interpolating too + // and whether antimeridian is in the way, as that flips x1x2 + + if (((x1 < x2) && !crossesAnti) || ((x1 > x2) && crossesAnti)) + { + x = offScreen ? bottomRightLongitude : bottomLeftLongitude; + interpolateEast(c1, c2, x, ci, offScreen); + } + else + { + x = offScreen ? bottomLeftLongitude : bottomRightLongitude; + interpolateWest(c1, c2, x, ci, offScreen); + } +} + +void MapModel::splitTrack(const QList& coords, const QVariantList& track, + QVariantList& track1, QVariantList& track2, + QGeoCoordinate& start1, QGeoCoordinate& start2, + QGeoCoordinate& end1, QGeoCoordinate& end2) +{ + /* + QStringList l; + for (int i = 0; i < track.size(); i++) + { + QGeoCoordinate c = track[i].value(); + l.append(QString("%1").arg((int)c.longitude())); + } + qDebug() << "Init T: " << l; + */ + + QQuickItem* map = m_gui->getMapItem(); + QVariant rectVariant; + QMetaObject::invokeMethod(map, "mapRect", Q_RETURN_ARG(QVariant, rectVariant)); + QGeoRectangle rect = qvariant_cast(rectVariant); + double bottomLeftLongitude = rect.bottomLeft().longitude(); + double bottomRightLongitude = rect.bottomRight().longitude(); + + int width = round(rect.width()); + bool antimed = (width == 360) || (bottomLeftLongitude > bottomRightLongitude); + + /* + qDebug() << "Anitmed visible: " << antimed; + qDebug() << "bottomLeftLongitude: " << bottomLeftLongitude; + qDebug() << "bottomRightLongitude: " << bottomRightLongitude; + */ + + track1.clear(); + track2.clear(); + + double lon, prevLon; + bool onScreen, prevOnScreen; + QList tracks({&track1, &track2}); + QList ends({&end1, &end2}); + QList starts({&start1, &start2}); + int trackIdx = 0; + for (int i = 0; i < coords.size(); i++) + { + lon = coords[i]->longitude(); + if (i == 0) + { + prevLon = lon; + prevOnScreen = true; // To avoid interpolation for first point + } + // Can be onscreen after having crossed edge from other side + // Or can be onscreen after previously having been off screen + onScreen = isOnScreen(lon, bottomLeftLongitude, bottomRightLongitude, width, antimed); + bool crossedEdge = crossesEdge(lon, prevLon, bottomLeftLongitude, bottomRightLongitude, width, antimed); + if ((onScreen && !crossedEdge) || (onScreen && !prevOnScreen)) + { + if ((i > 0) && (tracks[trackIdx]->size() == 0)) // Could also use (onScreen && !prevOnScreen)? + { + if (trackIdx >= starts.size()) + break; + // Interpolate from edge of screen + interpolate(coords[i-1], coords[i], bottomLeftLongitude, bottomRightLongitude, starts[trackIdx], false); + tracks[trackIdx]->append(QVariant::fromValue(*starts[trackIdx])); + } + tracks[trackIdx]->append(track[i]); + } + else if (tracks[trackIdx]->size() > 0) + { + // Either we've crossed to the other side, or have gone off screen + if (trackIdx >= ends.size()) + break; + // Interpolate to edge of screen + interpolate(coords[i-1], coords[i], bottomLeftLongitude, bottomRightLongitude, ends[trackIdx], true); + tracks[trackIdx]->append(QVariant::fromValue(*ends[trackIdx])); + // Start new track + trackIdx++; + if (trackIdx >= tracks.size()) + { + // This can happen with highly retrograde orbits, where trace 90% of period + // will cover more than 360 degrees - delete last point as Map + // will not be able to display it properly + tracks[trackIdx-1]->removeLast(); + break; + } + if (onScreen) + { + // Interpolate from edge of screen + interpolate(coords[i-1], coords[i], bottomLeftLongitude, bottomRightLongitude, starts[trackIdx], false); + tracks[trackIdx]->append(QVariant::fromValue(*starts[trackIdx])); + tracks[trackIdx]->append(track[i]); + } + } + prevLon = lon; + prevOnScreen = onScreen; + } + + /* + l.clear(); + for (int i = 0; i < track1.size(); i++) + { + QGeoCoordinate c = track1[i].value(); + if (!c.isValid()) + l.append("Invalid!"); + else + l.append(QString("%1").arg(c.longitude(), 0, 'f', 1)); + } + qDebug() << "T1: " << l; + + l.clear(); + for (int i = 0; i < track2.size(); i++) + { + QGeoCoordinate c = track2[i].value(); + if (!c.isValid()) + l.append("Invalid!"); + else + l.append(QString("%1").arg(c.longitude(), 0, 'f', 1)); + } + qDebug() << "T2: " << l; + */ +} + +void MapModel::viewChanged(double bottomLeftLongitude, double bottomRightLongitude) +{ + if (!isnan(bottomLeftLongitude)) + { + for (int row = 0; row < m_items.size(); row++) + { + MapItem *item = m_items[row]; + if (item->m_takenTrackCoords.size() > 1) + { + splitTrack(item->m_takenTrackCoords, item->m_takenTrack, item->m_takenTrack1, item->m_takenTrack2, + item->m_takenStart1, item->m_takenStart2, item->m_takenEnd1, item->m_takenEnd2); + QModelIndex idx = index(row); + emit dataChanged(idx, idx); + } + if (item->m_predictedTrackCoords.size() > 1) + { + splitTrack(item->m_predictedTrackCoords, item->m_predictedTrack, item->m_predictedTrack1, item->m_predictedTrack2, + item->m_predictedStart1, item->m_predictedStart2, item->m_predictedEnd1, item->m_predictedEnd2); + QModelIndex idx = index(row); + emit dataChanged(idx, idx); + } + } + } +} + MapGUI* MapGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature) { MapGUI* gui = new MapGUI(pluginAPI, featureUISet, feature); @@ -341,7 +683,12 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur ui->setupUi(this); ui->map->rootContext()->setContextProperty("mapModel", &m_mapModel); + // 5.12 doesn't display map items when fully zoomed out +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map/map_5_12.qml"))); +#else ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map/map.qml"))); +#endif setAttribute(Qt::WA_DeleteOnClose, true); setChannelWidget(false); @@ -408,7 +755,9 @@ void MapGUI::setBeacons(QList *beacons) { Beacon *beacon = i.next(); SWGSDRangel::SWGMapItem beaconMapItem; - beaconMapItem.setName(new QString(beacon->m_callsign)); + // Need to suffix frequency, as there are multiple becaons with same callsign at different locations + QString name = QString("%1-%2").arg(beacon->m_callsign).arg(beacon->getFrequencyShortText()); + beaconMapItem.setName(new QString(name)); beaconMapItem.setLatitude(beacon->m_latitude); beaconMapItem.setLongitude(beacon->m_longitude); beaconMapItem.setAltitude(beacon->m_altitude); @@ -487,8 +836,14 @@ void MapGUI::displaySettings() setWindowTitle(m_settings.m_title); blockApplySettings(true); ui->displayNames->setChecked(m_settings.m_displayNames); + ui->displaySelectedGroundTracks->setChecked(m_settings.m_displaySelectedGroundTracks); + ui->displayAllGroundTracks->setChecked(m_settings.m_displayAllGroundTracks); m_mapModel.setDisplayNames(m_settings.m_displayNames); + m_mapModel.setDisplaySelectedGroundTracks(m_settings.m_displaySelectedGroundTracks); + m_mapModel.setDisplayAllGroundTracks(m_settings.m_displayAllGroundTracks); m_mapModel.setSources(m_settings.m_sources); + m_mapModel.setGroundTrackColor(m_settings.m_groundTrackColor); + m_mapModel.setPredictedGroundTrackColor(m_settings.m_predictedGroundTrackColor); applyMapSettings(); blockApplySettings(false); } @@ -555,6 +910,18 @@ void MapGUI::on_displayNames_clicked(bool checked) m_mapModel.setDisplayNames(checked); } +void MapGUI::on_displaySelectedGroundTracks_clicked(bool checked) +{ + m_settings.m_displaySelectedGroundTracks = checked; + m_mapModel.setDisplaySelectedGroundTracks(checked); +} + +void MapGUI::on_displayAllGroundTracks_clicked(bool checked) +{ + m_settings.m_displayAllGroundTracks = checked; + m_mapModel.setDisplayAllGroundTracks(checked); +} + void MapGUI::on_find_returnPressed() { find(ui->find->text().trimmed()); @@ -655,6 +1022,8 @@ void MapGUI::on_displaySettings_clicked() applySettings(); if (dialog.m_sourcesChanged) m_mapModel.setSources(m_settings.m_sources); + m_mapModel.setGroundTrackColor(m_settings.m_groundTrackColor); + m_mapModel.setPredictedGroundTrackColor(m_settings.m_predictedGroundTrackColor); } } @@ -691,3 +1060,7 @@ QString MapGUI::getBeaconFilename() { return MapGUI::getDataDir() + "/iaru_beacons.csv"; } + +QQuickItem *MapGUI::getMapItem() { + return ui->map->rootObject(); +} diff --git a/plugins/feature/map/mapgui.h b/plugins/feature/map/mapgui.h index 5b8fcfd4a..3bc618ea3 100644 --- a/plugins/feature/map/mapgui.h +++ b/plugins/feature/map/mapgui.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "feature/featuregui.h" #include "util/messagequeue.h" @@ -41,6 +42,7 @@ namespace Ui { class MapGUI; class MapModel; +class QQuickItem; struct Beacon; // Information required about each item displayed on the map @@ -62,6 +64,8 @@ public: if (text != nullptr) m_text = *text; findFrequency(); + updateTrack(mapItem->getTrack()); + updatePredictedTrack(mapItem->getPredictedTrack()); } void update(SWGSDRangel::SWGMapItem *mapItem) @@ -76,6 +80,8 @@ public: if (text != nullptr) m_text = *text; findFrequency(); + updateTrack(mapItem->getTrack()); + updatePredictedTrack(mapItem->getPredictedTrack()); } QGeoCoordinate getCoordinates() @@ -90,6 +96,64 @@ private: void findFrequency(); + void updateTrack(QList *track) + { + if (track != nullptr) + { + qDeleteAll(m_takenTrackCoords); + m_takenTrackCoords.clear(); + m_takenTrack.clear(); + m_takenTrack1.clear(); + m_takenTrack2.clear(); + for (int i = 0; i < track->size(); i++) + { + SWGSDRangel::SWGMapCoordinate* p = track->at(i); + QGeoCoordinate *c = new QGeoCoordinate(p->getLatitude(), p->getLongitude(), p->getAltitude()); + m_takenTrackCoords.push_back(c); + m_takenTrack.push_back(QVariant::fromValue(*c)); + } + } + else + { + // Automatically create a track + if (m_takenTrackCoords.size() == 0) + { + QGeoCoordinate *c = new QGeoCoordinate(m_latitude, m_longitude, m_altitude); + m_takenTrackCoords.push_back(c); + m_takenTrack.push_back(QVariant::fromValue(*c)); + } + else + { + QGeoCoordinate *prev = m_takenTrackCoords.last(); + if ((prev->latitude() != m_latitude) || (prev->longitude() != m_longitude) || (prev->altitude() != m_altitude)) + { + QGeoCoordinate *c = new QGeoCoordinate(m_latitude, m_longitude, m_altitude); + m_takenTrackCoords.push_back(c); + m_takenTrack.push_back(QVariant::fromValue(*c)); + } + } + } + } + + void updatePredictedTrack(QList *track) + { + if (track != nullptr) + { + qDeleteAll(m_predictedTrackCoords); + m_predictedTrackCoords.clear(); + m_predictedTrack.clear(); + m_predictedTrack1.clear(); + m_predictedTrack2.clear(); + for (int i = 0; i < track->size(); i++) + { + SWGSDRangel::SWGMapCoordinate* p = track->at(i); + QGeoCoordinate *c = new QGeoCoordinate(p->getLatitude(), p->getLongitude(), p->getAltitude()); + m_predictedTrackCoords.push_back(c); + m_predictedTrack.push_back(QVariant::fromValue(*c)); + } + } + } + friend MapModel; const PipeEndPoint *m_sourcePipe; // Channel/feature that created the item quint32 m_sourceMask; // Source bitmask as per MapSettings::SOURCE_* constants @@ -103,6 +167,22 @@ private: QString m_text; double m_frequency; // Frequency to set QString m_frequencyString; + QList m_predictedTrackCoords; + QVariantList m_predictedTrack; // Line showing where the object is going + QVariantList m_predictedTrack1; + QVariantList m_predictedTrack2; + QGeoCoordinate m_predictedStart1; + QGeoCoordinate m_predictedStart2; + QGeoCoordinate m_predictedEnd1; + QGeoCoordinate m_predictedEnd2; + QList m_takenTrackCoords; + QVariantList m_takenTrack; // Line showing where the object has been + QVariantList m_takenTrack1; + QVariantList m_takenTrack2; + QGeoCoordinate m_takenStart1; + QGeoCoordinate m_takenStart2; + QGeoCoordinate m_takenEnd1; + QGeoCoordinate m_takenEnd2; }; // Model used for each item on the map @@ -123,7 +203,13 @@ public: selectedRole = Qt::UserRole + 9, targetRole = Qt::UserRole + 10, frequencyRole = Qt::UserRole + 11, - frequencyStringRole = Qt::UserRole + 12 + frequencyStringRole = Qt::UserRole + 12, + predictedGroundTrack1Role = Qt::UserRole + 13, + predictedGroundTrack2Role = Qt::UserRole + 14, + groundTrack1Role = Qt::UserRole + 15, + groundTrack2Role = Qt::UserRole + 16, + groundTrackColorRole = Qt::UserRole + 17, + predictedGroundTrackColorRole = Qt::UserRole + 18 }; MapModel(MapGUI *gui) : @@ -131,6 +217,8 @@ public: m_target(-1), m_sources(-1) { + setGroundTrackColor(0); + setPredictedGroundTrackColor(0); } Q_INVOKABLE void add(MapItem *item) @@ -275,8 +363,41 @@ public: allUpdated(); } + void setDisplaySelectedGroundTracks(bool displayGroundTracks) + { + m_displaySelectedGroundTracks = displayGroundTracks; + allUpdated(); + } + + void setDisplayAllGroundTracks(bool displayGroundTracks) + { + m_displayAllGroundTracks = displayGroundTracks; + allUpdated(); + } + + void setGroundTrackColor(quint32 color) + { + m_groundTrackColor = QVariant::fromValue(QColor::fromRgb(color)); + } + + void setPredictedGroundTrackColor(quint32 color) + { + m_predictedGroundTrackColor = QVariant::fromValue(QColor::fromRgb(color)); + } + Q_INVOKABLE void setFrequency(double frequency); + void interpolateEast(QGeoCoordinate *c1, QGeoCoordinate *c2, double x, QGeoCoordinate *ci, bool offScreen); + void interpolateWest(QGeoCoordinate *c1, QGeoCoordinate *c2, double x, QGeoCoordinate *ci, bool offScreen); + void interpolate(QGeoCoordinate *c1, QGeoCoordinate *c2, double bottomLeftLongitude, double bottomRightLongitude, QGeoCoordinate* ci, bool offScreen); + + void splitTracks(MapItem *item); + void splitTrack(const QList& coords, const QVariantList& track, + QVariantList& track1, QVariantList& track2, + QGeoCoordinate& start1, QGeoCoordinate& start2, + QGeoCoordinate& end1, QGeoCoordinate& end2); + Q_INVOKABLE void viewChanged(double bottomLeftLongitude, double bottomRightLongitude); + QHash roleNames() const { QHash roles; @@ -292,6 +413,12 @@ public: roles[targetRole] = "target"; roles[frequencyRole] = "frequency"; roles[frequencyStringRole] = "frequencyString"; + roles[predictedGroundTrack1Role] = "predictedGroundTrack1"; + roles[predictedGroundTrack2Role] = "predictedGroundTrack2"; + roles[groundTrack1Role] = "groundTrack1"; + roles[groundTrack2Role] = "groundTrack2"; + roles[groundTrackColorRole] = "groundTrackColor"; + roles[predictedGroundTrackColorRole] = "predictedGroundTrackColor"; return roles; } @@ -302,13 +429,26 @@ public: allUpdated(); } + // Linear interpolation + double interpolate(double x0, double y0, double x1, double y1, double x) + { + return (y0*(x1-x) + y1*(x-x0)) / (x1-x0); + } + private: MapGUI *m_gui; QList m_items; QList m_selected; int m_target; // Row number of current target, or -1 for none bool m_displayNames; + bool m_displaySelectedGroundTracks; + bool m_displayAllGroundTracks; quint32 m_sources; + QVariant m_groundTrackColor; + QVariant m_predictedGroundTrackColor; + + double m_bottomLeftLongitude; + double m_bottomRightLongitude; }; class MapGUI : public FeatureGUI { @@ -323,6 +463,7 @@ public: virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } AzEl *getAzEl() { return &m_azEl; } Map *getMap() { return m_map; } + QQuickItem *getMapItem(); quint32 getSourceMask(const PipeEndPoint *sourcePipe); static QString getBeaconFilename(); QList *getBeacons() { return m_beacons; } @@ -364,6 +505,8 @@ private slots: void onWidgetRolled(QWidget* widget, bool rollDown); void handleInputMessages(); void on_displayNames_clicked(bool checked=false); + void on_displayAllGroundTracks_clicked(bool checked=false); + void on_displaySelectedGroundTracks_clicked(bool checked=false); void on_find_returnPressed(); void on_maidenhead_clicked(); void on_deleteAll_clicked(); diff --git a/plugins/feature/map/mapgui.ui b/plugins/feature/map/mapgui.ui index a6a9a9ca2..05bc51f4a 100644 --- a/plugins/feature/map/mapgui.ui +++ b/plugins/feature/map/mapgui.ui @@ -6,8 +6,8 @@ 0 0 - 462 - 689 + 481 + 750 @@ -42,7 +42,7 @@ 0 0 - 461 + 471 41 @@ -82,9 +82,15 @@ + + + 0 + 0 + + - 150 + 100 0 @@ -93,19 +99,6 @@ - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -120,7 +113,7 @@ - + Maidenhead locator conversion @@ -134,7 +127,7 @@ - + Display Beacon dialog @@ -168,7 +161,57 @@ - + + + + Adobe Devanagari + + + + Display ground tracks for selected item + + + ^ + + + + :/logarithmic.png:/logarithmic.png + + + true + + + true + + + + + + + + Adobe Devanagari + + + + Display all ground tracks + + + ^ + + + + :/map/icons/groundtracks.png:/map/icons/groundtracks.png + + + true + + + true + + + + + Delete all items on the map @@ -182,7 +225,7 @@ - + Show settings dialog @@ -203,9 +246,9 @@ 0 - 100 - 461 - 581 + 60 + 471 + 681 @@ -244,7 +287,7 @@ 100 - 500 + 590 @@ -293,6 +336,7 @@ + diff --git a/plugins/feature/map/mapsettings.cpp b/plugins/feature/map/mapsettings.cpp index aa45e824e..e7bd273b1 100644 --- a/plugins/feature/map/mapsettings.cpp +++ b/plugins/feature/map/mapsettings.cpp @@ -26,13 +26,15 @@ const QStringList MapSettings::m_pipeTypes = { QStringLiteral("ADSBDemod"), QStringLiteral("APRS"), - QStringLiteral("StarTracker") + QStringLiteral("StarTracker"), + QStringLiteral("SatelliteTracker") }; const QStringList MapSettings::m_pipeURIs = { QStringLiteral("sdrangel.channel.adsbdemod"), QStringLiteral("sdrangel.feature.aprs"), - QStringLiteral("sdrangel.feature.startracker") + QStringLiteral("sdrangel.feature.startracker"), + QStringLiteral("sdrangel.feature.satellitetracker") }; // GUI combo box should match ordering in this list @@ -55,6 +57,10 @@ void MapSettings::resetToDefaults() m_mapBoxApiKey = ""; m_mapBoxStyles = ""; m_sources = -1; + m_displaySelectedGroundTracks = true; + m_displayAllGroundTracks = true; + m_groundTrackColor = QColor(150, 0, 20).rgb(); + m_predictedGroundTrackColor = QColor(225, 0, 50).rgb(); m_title = "Map"; m_rgbColor = QColor(225, 25, 99).rgb(); m_useReverseAPI = false; @@ -73,6 +79,8 @@ QByteArray MapSettings::serialize() const s.writeString(3, m_mapBoxApiKey); s.writeString(4, m_mapBoxStyles); s.writeU32(5, m_sources); + s.writeU32(6, m_groundTrackColor); + s.writeU32(7, m_predictedGroundTrackColor); s.writeString(8, m_title); s.writeU32(9, m_rgbColor); s.writeBool(10, m_useReverseAPI); @@ -80,6 +88,8 @@ QByteArray MapSettings::serialize() const s.writeU32(12, m_reverseAPIPort); s.writeU32(13, m_reverseAPIFeatureSetIndex); s.writeU32(14, m_reverseAPIFeatureIndex); + s.writeBool(15, m_displaySelectedGroundTracks); + s.writeBool(16, m_displayAllGroundTracks); return s.final(); } @@ -105,6 +115,8 @@ bool MapSettings::deserialize(const QByteArray& data) d.readString(3, &m_mapBoxApiKey, ""); d.readString(4, &m_mapBoxStyles, ""); d.readU32(5, &m_sources, -1); + d.readU32(6, &m_groundTrackColor, QColor(150, 0, 20).rgb()); + d.readU32(7, &m_predictedGroundTrackColor, QColor(225, 0, 50).rgb()); d.readString(8, &m_title, "Map"); d.readU32(9, &m_rgbColor, QColor(225, 25, 99).rgb()); d.readBool(10, &m_useReverseAPI, false); @@ -121,6 +133,8 @@ bool MapSettings::deserialize(const QByteArray& data) m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp; d.readU32(14, &utmp, 0); m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp; + d.readBool(15, &m_displaySelectedGroundTracks, true); + d.readBool(16, &m_displayAllGroundTracks, true); return true; } diff --git a/plugins/feature/map/mapsettings.h b/plugins/feature/map/mapsettings.h index ae5436b5c..8a75937fb 100644 --- a/plugins/feature/map/mapsettings.h +++ b/plugins/feature/map/mapsettings.h @@ -34,6 +34,10 @@ struct MapSettings QString m_mapBoxApiKey; QString m_mapBoxStyles; quint32 m_sources; // Bitmask of SOURCE_* + bool m_displayAllGroundTracks; + bool m_displaySelectedGroundTracks; + quint32 m_groundTrackColor; + quint32 m_predictedGroundTrackColor; QString m_title; quint32 m_rgbColor; bool m_useReverseAPI; @@ -56,8 +60,9 @@ struct MapSettings static const quint32 SOURCE_ADSB = 0x1; static const quint32 SOURCE_APRS = 0x2; static const quint32 SOURCE_STAR_TRACKER = 0x4; - static const quint32 SOURCE_BEACONS = 0x8; - static const quint32 SOURCE_STATION = 0x10; + static const quint32 SOURCE_SATELLITE_TRACKER = 0x8; + static const quint32 SOURCE_BEACONS = 0x10; + static const quint32 SOURCE_STATION = 0x20; }; #endif // INCLUDE_FEATURE_MAPSETTINGS_H_ diff --git a/plugins/feature/map/mapsettingsdialog.cpp b/plugins/feature/map/mapsettingsdialog.cpp index 1133dd380..992e3b8b1 100644 --- a/plugins/feature/map/mapsettingsdialog.cpp +++ b/plugins/feature/map/mapsettingsdialog.cpp @@ -16,12 +16,25 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include +#include #include "util/units.h" #include "mapsettingsdialog.h" #include "maplocationdialog.h" +static QString rgbToColor(quint32 rgb) +{ + QColor color = QColor::fromRgb(rgb); + return QString("%1,%2,%3").arg(color.red()).arg(color.green()).arg(color.blue()); +} + +static QString backgroundCSS(quint32 rgb) +{ + return QString("QToolButton { background:rgb(%1); }").arg(rgbToColor(rgb)); +} + MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) : QDialog(parent), m_settings(settings), @@ -33,6 +46,8 @@ MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) : ui->mapBoxStyles->setText(settings->m_mapBoxStyles); for (int i = 0; i < ui->sourceList->count(); i++) ui->sourceList->item(i)->setCheckState((m_settings->m_sources & (1 << i)) ? Qt::Checked : Qt::Unchecked); + ui->groundTrackColor->setStyleSheet(backgroundCSS(m_settings->m_groundTrackColor)); + ui->predictedGroundTrackColor->setStyleSheet(backgroundCSS(m_settings->m_predictedGroundTrackColor)); } MapSettingsDialog::~MapSettingsDialog() @@ -64,3 +79,23 @@ void MapSettingsDialog::accept() m_settings->m_sources = sources; QDialog::accept(); } + +void MapSettingsDialog::on_groundTrackColor_clicked() +{ + QColorDialog dialog(QColor::fromRgb(m_settings->m_groundTrackColor), this); + if (dialog.exec() == QDialog::Accepted) + { + m_settings->m_groundTrackColor = dialog.selectedColor().rgb(); + ui->groundTrackColor->setStyleSheet(backgroundCSS(m_settings->m_groundTrackColor)); + } +} + +void MapSettingsDialog::on_predictedGroundTrackColor_clicked() +{ + QColorDialog dialog(QColor::fromRgb(m_settings->m_predictedGroundTrackColor), this); + if (dialog.exec() == QDialog::Accepted) + { + m_settings->m_predictedGroundTrackColor = dialog.selectedColor().rgb(); + ui->predictedGroundTrackColor->setStyleSheet(backgroundCSS(m_settings->m_predictedGroundTrackColor)); + } +} diff --git a/plugins/feature/map/mapsettingsdialog.h b/plugins/feature/map/mapsettingsdialog.h index c866cc432..d3b49a9c7 100644 --- a/plugins/feature/map/mapsettingsdialog.h +++ b/plugins/feature/map/mapsettingsdialog.h @@ -34,6 +34,8 @@ public: private slots: void accept(); + void on_groundTrackColor_clicked(); + void on_predictedGroundTrackColor_clicked(); private: Ui::MapSettingsDialog* ui; diff --git a/plugins/feature/map/mapsettingsdialog.ui b/plugins/feature/map/mapsettingsdialog.ui index da40f01e6..8ac4c5eaa 100644 --- a/plugins/feature/map/mapsettingsdialog.ui +++ b/plugins/feature/map/mapsettingsdialog.ui @@ -65,6 +65,14 @@ Checked + + + Satellite Tracker + + + Checked + + Beacons @@ -76,7 +84,50 @@ - + + + Colours + + + + + + Ground tracks (predicted) + + + + + + + Ground tracks (taken) + + + + + + + Select color for predicted ground tracks + + + + + + + + + + Select color for taken ground tracks + + + + + + + + + + + Map Provider Settings diff --git a/plugins/feature/map/readme.md b/plugins/feature/map/readme.md index 1a4974e52..86efe3203 100644 --- a/plugins/feature/map/readme.md +++ b/plugins/feature/map/readme.md @@ -3,8 +3,15 @@

Introduction

The Map Feature plugin displays a world map. It can display street maps, satellite imagery as well as custom map types. -On top of this, it can plot data from other plugins, such as APRS symbols from the APRS Feature, aircraft from the ADS-B Demodulator -or the Sun, Moon and Stars from the Star Tracker. It can also display beacon locations based on the IARU Region 1 beacon database. +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, +* Satellites from the Satellite Tracker, +* The Sun, Moon and Stars from the Star Tracker, +* Beacons based on the IARU Region 1 beacon database. + +It can also create tracks showing the path aircraft and APRS objects have taken, as well as predicted paths for satellites. ![Map feature](../../../doc/img/Map_plugin_beacons.png) @@ -44,15 +51,24 @@ The beacons will then be displayed in the table and on the map. When checked, names of objects are displayed in a bubble next to each object. -

6: Delete

+

6: Display tracks for selected object

+ +When checked, displays the track (taken or predicted) for the selected object. + +

7: Display tracks for all objects

+ +When checked, displays the track (taken or predicted) for the all objects. + +

8: Delete

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

7: Display settings

+

9: Display settings

When clicked, opens the Map Display Settings dialog, which allows setting: * Which data the Map will display. +* The colour of the taken and predicted tracks. * Which Map provider will be used to source the map image. In order to display Mapbox maps, you will need to enter an API Key. A key can be obtained by registering at: http://www.mapbox.com/