mirror of
https://github.com/f4exb/sdrangel.git
synced 2026-06-02 06:04:39 -04: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:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user