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

Map updates.

Add support for taken and predicted ground tracks.
Support multiple beacons with same callsign at different locations.
Use separate QML for Qt 5.14, as 5.12 doesn't support autoFadeIn, needed
to view satellites at min zoom.
This commit is contained in:
Jon Beniston 2021-02-26 20:30:59 +00:00
parent cd504da84e
commit d381568437
16 changed files with 1082 additions and 44 deletions

View File

@ -34,6 +34,7 @@ if(NOT SERVER_MODE)
mapbeacondialog.cpp
mapbeacondialog.ui
map.qrc
icons.qrc
)
set(map_HEADERS
${map_HEADERS}

View File

@ -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<Beacon *> *readIARUCSV(const QString &filename)
{

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/map/">
<file>icons/groundtracks.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

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

View File

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

View File

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

View File

@ -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 x1<x2 to x1>x2
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<QGeoCoordinate *>& 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<QGeoCoordinate>();
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<QGeoRectangle>(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<QVariantList *> tracks({&track1, &track2});
QList<QGeoCoordinate *> ends({&end1, &end2});
QList<QGeoCoordinate *> 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<QGeoCoordinate>();
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<QGeoCoordinate>();
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<Beacon *> *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();
}

View File

@ -22,6 +22,7 @@
#include <QTimer>
#include <QAbstractListModel>
#include <QGeoCoordinate>
#include <QGeoRectangle>
#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<SWGSDRangel::SWGMapCoordinate *> *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<SWGSDRangel::SWGMapCoordinate *> *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<QGeoCoordinate *> 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<QGeoCoordinate *> 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<QGeoCoordinate *>& 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<int, QByteArray> roleNames() const
{
QHash<int, QByteArray> 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<MapItem *> m_items;
QList<bool> 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<Beacon *> *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();

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>462</width>
<height>689</height>
<width>481</width>
<height>750</height>
</rect>
</property>
<property name="sizePolicy">
@ -42,7 +42,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>461</width>
<width>471</width>
<height>41</height>
</rect>
</property>
@ -82,9 +82,15 @@
</item>
<item>
<widget class="QLineEdit" name="find">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<width>100</width>
<height>0</height>
</size>
</property>
@ -93,19 +99,6 @@
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QComboBox" name="mapTypes">
<property name="minimumSize">
@ -120,7 +113,7 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="maidenhead">
<widget class="QToolButton" name="maidenhead">
<property name="toolTip">
<string>Maidenhead locator conversion</string>
</property>
@ -134,7 +127,7 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="beacons">
<widget class="QToolButton" name="beacons">
<property name="toolTip">
<string>Display Beacon dialog</string>
</property>
@ -168,7 +161,57 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteAll">
<widget class="ButtonSwitch" name="displaySelectedGroundTracks">
<property name="font">
<font>
<family>Adobe Devanagari</family>
</font>
</property>
<property name="toolTip">
<string>Display ground tracks for selected item</string>
</property>
<property name="text">
<string>^</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/logarithmic.png</normaloff>:/logarithmic.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="displayAllGroundTracks">
<property name="font">
<font>
<family>Adobe Devanagari</family>
</font>
</property>
<property name="toolTip">
<string>Display all ground tracks</string>
</property>
<property name="text">
<string>^</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/map/icons/groundtracks.png</normaloff>:/map/icons/groundtracks.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="deleteAll">
<property name="toolTip">
<string>Delete all items on the map</string>
</property>
@ -182,7 +225,7 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="displaySettings">
<widget class="QToolButton" name="displaySettings">
<property name="toolTip">
<string>Show settings dialog</string>
</property>
@ -203,9 +246,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>100</y>
<width>461</width>
<height>581</height>
<y>60</y>
<width>471</width>
<height>681</height>
</rect>
</property>
<property name="sizePolicy">
@ -244,7 +287,7 @@
<property name="minimumSize">
<size>
<width>100</width>
<height>500</height>
<height>590</height>
</size>
</property>
<property name="toolTip">
@ -293,6 +336,7 @@
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
<include location="icons.qrc"/>
</resources>
<connections/>
</ui>

View File

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

View File

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

View File

@ -16,12 +16,25 @@
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QColorDialog>
#include <QColor>
#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));
}
}

View File

@ -34,6 +34,8 @@ public:
private slots:
void accept();
void on_groundTrackColor_clicked();
void on_predictedGroundTrackColor_clicked();
private:
Ui::MapSettingsDialog* ui;

View File

@ -65,6 +65,14 @@
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>Satellite Tracker</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>Beacons</string>
@ -76,7 +84,50 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<widget class="QGroupBox" name="colorsGroupBox">
<property name="title">
<string>Colours</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QLabel" name="predictedGroundTrackColorLabel">
<property name="text">
<string>Ground tracks (predicted)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="groundTrackColorLabel">
<property name="text">
<string>Ground tracks (taken)</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QToolButton" name="predictedGroundTrackColor">
<property name="toolTip">
<string>Select color for predicted ground tracks</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QToolButton" name="groundTrackColor">
<property name="toolTip">
<string>Select color for taken ground tracks</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="mapProvidersGroupBox">
<property name="title">
<string>Map Provider Settings</string>
</property>

View File

@ -3,8 +3,15 @@
<h2>Introduction</h2>
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.
<h3>6: Delete</h3>
<h3>6: Display tracks for selected object</h3>
When checked, displays the track (taken or predicted) for the selected object.
<h3>7: Display tracks for all objects</h3>
When checked, displays the track (taken or predicted) for the all objects.
<h3>8: Delete</h3>
When clicked, all items will be deleted from the map.
<h3>7: Display settings</h3>
<h3>9: Display settings</h3>
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/