Improve satellite view and fused location telemetry
This commit is contained in:
+129
-73
@@ -15,6 +15,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
constexpr double Pi = 3.14159265358979323846;
|
||||
@@ -211,8 +212,10 @@ GlobeWidget::GlobeWidget(QWidget *parent)
|
||||
setMouseTracking(true);
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
setToolTip(QStringLiteral("Dunyayi dondurmek icin surukleyin, yakinlastirmak icin tekerlegi kullanin"));
|
||||
m_satelliteAnimationTimer.setInterval(33);
|
||||
m_satelliteAnimationTimer.setTimerType(Qt::PreciseTimer);
|
||||
// GNSS satellites move only a few screen pixels per minute. Ten frames per
|
||||
// second is visually smooth here and avoids repainting a static globe at 30 FPS.
|
||||
m_satelliteAnimationTimer.setInterval(100);
|
||||
m_satelliteAnimationTimer.setTimerType(Qt::CoarseTimer);
|
||||
connect(&m_satelliteAnimationTimer, &QTimer::timeout,
|
||||
this, &GlobeWidget::updateSatelliteAnimation);
|
||||
}
|
||||
@@ -235,8 +238,29 @@ void GlobeWidget::setSatelliteMotion(const QVector<SatellitePosition> ¤t,
|
||||
|
||||
// Continue from the currently rendered points when a fresh ephemeris arrives,
|
||||
// avoiding even a small visual jump at the update boundary.
|
||||
m_satelliteMotionStart = m_satellites.isEmpty() ? current : m_satellites;
|
||||
m_satelliteMotionTarget = next;
|
||||
const QVector<SatellitePosition> &candidateStart =
|
||||
m_satellites.isEmpty() ? current : m_satellites;
|
||||
QHash<QString, SatellitePosition> starts;
|
||||
starts.reserve(candidateStart.size());
|
||||
for (const SatellitePosition &satellite : candidateStart) {
|
||||
starts.insert(satellite.prn, satellite);
|
||||
}
|
||||
|
||||
m_satelliteMotionStart.clear();
|
||||
m_satelliteMotionTarget.clear();
|
||||
m_satelliteMotionStart.reserve(next.size());
|
||||
m_satelliteMotionTarget.reserve(next.size());
|
||||
for (const SatellitePosition &target : next) {
|
||||
const auto start = starts.constFind(target.prn);
|
||||
if (start == starts.cend()) continue;
|
||||
m_satelliteMotionStart.push_back(start.value());
|
||||
m_satelliteMotionTarget.push_back(target);
|
||||
}
|
||||
if (m_satelliteMotionStart.isEmpty()) {
|
||||
setSatellites(current);
|
||||
m_satelliteAnimationTimer.stop();
|
||||
return;
|
||||
}
|
||||
m_satelliteMotionDurationMs = durationMs;
|
||||
m_satelliteMotionClock.restart();
|
||||
m_satelliteAnimationTimer.start();
|
||||
@@ -250,18 +274,11 @@ void GlobeWidget::updateSatelliteAnimation()
|
||||
const double progress = std::clamp(
|
||||
static_cast<double>(m_satelliteMotionClock.elapsed()) / m_satelliteMotionDurationMs,
|
||||
0.0, 1.0);
|
||||
QHash<QString, SatellitePosition> targets;
|
||||
targets.reserve(m_satelliteMotionTarget.size());
|
||||
for (const SatellitePosition &satellite : m_satelliteMotionTarget) {
|
||||
targets.insert(satellite.prn, satellite);
|
||||
}
|
||||
|
||||
QVector<SatellitePosition> interpolated;
|
||||
interpolated.reserve(m_satelliteMotionStart.size());
|
||||
for (const SatellitePosition &start : m_satelliteMotionStart) {
|
||||
const auto targetIt = targets.constFind(start.prn);
|
||||
if (targetIt == targets.cend()) continue;
|
||||
const SatellitePosition &target = targetIt.value();
|
||||
for (qsizetype i = 0; i < m_satelliteMotionStart.size(); ++i) {
|
||||
const SatellitePosition &start = m_satelliteMotionStart[i];
|
||||
const SatellitePosition &target = m_satelliteMotionTarget[i];
|
||||
SatellitePosition point = start;
|
||||
point.x += (target.x - start.x) * progress;
|
||||
point.y += (target.y - start.y) * progress;
|
||||
@@ -336,6 +353,74 @@ void GlobeWidget::paintEvent(QPaintEvent *)
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
const qreal devicePixelRatio = devicePixelRatioF();
|
||||
if (m_staticLayer.isNull() || m_staticLayerSize != size()
|
||||
|| !qFuzzyCompare(m_staticLayerDevicePixelRatio, devicePixelRatio)
|
||||
|| m_staticLayerYaw != m_yaw || m_staticLayerPitch != m_pitch
|
||||
|| m_staticLayerZoom != m_zoom) {
|
||||
rebuildStaticLayer();
|
||||
}
|
||||
painter.drawPixmap(0, 0, m_staticLayer);
|
||||
|
||||
if (m_hasReceiver) drawVisibilityCone(painter);
|
||||
if (m_hasReceiver) drawReceiver(painter);
|
||||
const int visibleCount = drawSatellites(painter);
|
||||
|
||||
painter.setPen(QColor(229, 240, 249));
|
||||
QFont titleFont = painter.font();
|
||||
titleFont.setPixelSize(18);
|
||||
titleFont.setBold(true);
|
||||
painter.setFont(titleFont);
|
||||
painter.drawText(QRect(20, 16, width() - 40, 28), Qt::AlignLeft | Qt::AlignVCenter,
|
||||
QStringLiteral("GNSS YORUNGE GORUNUMU"));
|
||||
QFont smallFont = painter.font();
|
||||
smallFont.setPixelSize(11);
|
||||
smallFont.setBold(false);
|
||||
painter.setFont(smallFont);
|
||||
painter.setPen(m_statusError ? QColor(255, 177, 77) : QColor(132, 177, 204));
|
||||
painter.drawText(QRect(20, 43, width() - 40, 22), Qt::AlignLeft | Qt::AlignVCenter, m_status);
|
||||
|
||||
const int legendY = height() - 25;
|
||||
const int legendX = width() / 2 - 125;
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(QColor(42, 224, 137));
|
||||
painter.drawEllipse(QPointF(legendX, legendY - 3), 4, 4);
|
||||
painter.setPen(QColor(101, 239, 176));
|
||||
painter.drawText(legendX + 9, legendY + 1, QStringLiteral("BULUNDU"));
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(QColor(255, 79, 95));
|
||||
painter.drawEllipse(QPointF(legendX + 78, legendY - 3), 4, 4);
|
||||
painter.setPen(QColor(255, 132, 143));
|
||||
painter.drawText(legendX + 87, legendY + 1, QStringLiteral("BULUNAMADI"));
|
||||
painter.setPen(QColor(138, 183, 207));
|
||||
painter.drawText(legendX + 181, legendY + 1,
|
||||
QStringLiteral("%1/%2 PRN · ELEV≥%3°: %4")
|
||||
.arg(m_observedPrns.size()).arg(m_satellites.size())
|
||||
.arg(m_elevationMaskDegrees, 0, 'f', 1).arg(visibleCount));
|
||||
|
||||
if (!m_hasReceiver) {
|
||||
painter.setPen(QColor(219, 232, 241));
|
||||
QFont waitingFont = painter.font();
|
||||
waitingFont.setPixelSize(16);
|
||||
waitingFont.setBold(true);
|
||||
painter.setFont(waitingFont);
|
||||
painter.drawText(rect().adjusted(30, 70, -30, -50), Qt::AlignCenter,
|
||||
QStringLiteral("Ilk gecerli konum bekleniyor"));
|
||||
}
|
||||
}
|
||||
|
||||
void GlobeWidget::rebuildStaticLayer()
|
||||
{
|
||||
const qreal devicePixelRatio = devicePixelRatioF();
|
||||
const QSize pixelSize(
|
||||
std::max(1, qRound(width() * devicePixelRatio)),
|
||||
std::max(1, qRound(height() * devicePixelRatio)));
|
||||
QPixmap layer(pixelSize);
|
||||
layer.setDevicePixelRatio(devicePixelRatio);
|
||||
layer.fill(Qt::transparent);
|
||||
QPainter painter(&layer);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
QLinearGradient background(0, 0, width(), height());
|
||||
background.setColorAt(0.0, QColor(3, 10, 21));
|
||||
background.setColorAt(0.55, QColor(8, 24, 43));
|
||||
@@ -374,63 +459,23 @@ void GlobeWidget::paintEvent(QPaintEvent *)
|
||||
painter.restore();
|
||||
|
||||
for (int i = 0; i < 3; ++i) drawOrbit(painter, 55.0 + i * 1.8, i * 61.0 + 12.0);
|
||||
if (m_hasReceiver) drawVisibilityCone(painter);
|
||||
if (m_hasReceiver) drawReceiver(painter);
|
||||
drawSatellites(painter);
|
||||
painter.end();
|
||||
|
||||
painter.setPen(QColor(229, 240, 249));
|
||||
QFont titleFont = painter.font();
|
||||
titleFont.setPixelSize(18);
|
||||
titleFont.setBold(true);
|
||||
painter.setFont(titleFont);
|
||||
painter.drawText(QRect(20, 16, width() - 40, 28), Qt::AlignLeft | Qt::AlignVCenter,
|
||||
QStringLiteral("GNSS YORUNGE GORUNUMU"));
|
||||
QFont smallFont = painter.font();
|
||||
smallFont.setPixelSize(11);
|
||||
smallFont.setBold(false);
|
||||
painter.setFont(smallFont);
|
||||
painter.setPen(m_statusError ? QColor(255, 177, 77) : QColor(132, 177, 204));
|
||||
painter.drawText(QRect(20, 43, width() - 40, 22), Qt::AlignLeft | Qt::AlignVCenter, m_status);
|
||||
|
||||
const int legendY = height() - 25;
|
||||
const int legendX = width() / 2 - 125;
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(QColor(42, 224, 137));
|
||||
painter.drawEllipse(QPointF(legendX, legendY - 3), 4, 4);
|
||||
painter.setPen(QColor(101, 239, 176));
|
||||
painter.drawText(legendX + 9, legendY + 1, QStringLiteral("BULUNDU"));
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(QColor(255, 79, 95));
|
||||
painter.drawEllipse(QPointF(legendX + 78, legendY - 3), 4, 4);
|
||||
painter.setPen(QColor(255, 132, 143));
|
||||
painter.drawText(legendX + 87, legendY + 1, QStringLiteral("BULUNAMADI"));
|
||||
painter.setPen(QColor(138, 183, 207));
|
||||
int visibleCount = 0;
|
||||
for (const auto &satellite : m_satellites) {
|
||||
if (satelliteElevation(satellite) >= m_elevationMaskDegrees) ++visibleCount;
|
||||
}
|
||||
painter.drawText(legendX + 181, legendY + 1,
|
||||
QStringLiteral("%1/%2 PRN · ELEV≥%3°: %4")
|
||||
.arg(m_observedPrns.size()).arg(m_satellites.size())
|
||||
.arg(m_elevationMaskDegrees, 0, 'f', 1).arg(visibleCount));
|
||||
|
||||
if (!m_hasReceiver) {
|
||||
painter.setPen(QColor(219, 232, 241));
|
||||
QFont waitingFont = painter.font();
|
||||
waitingFont.setPixelSize(16);
|
||||
waitingFont.setBold(true);
|
||||
painter.setFont(waitingFont);
|
||||
painter.drawText(rect().adjusted(30, 70, -30, -50), Qt::AlignCenter,
|
||||
QStringLiteral("Ilk gecerli konum bekleniyor"));
|
||||
}
|
||||
m_staticLayer = std::move(layer);
|
||||
m_staticLayerSize = size();
|
||||
m_staticLayerDevicePixelRatio = devicePixelRatio;
|
||||
m_staticLayerYaw = m_yaw;
|
||||
m_staticLayerPitch = m_pitch;
|
||||
m_staticLayerZoom = m_zoom;
|
||||
}
|
||||
|
||||
void GlobeWidget::drawContinents(QPainter &painter) const
|
||||
{
|
||||
const double radius = std::min(width(), height()) * 0.255 * m_zoom;
|
||||
const double pixelRatio = std::min(devicePixelRatioF(), 1.5);
|
||||
const double pixelRatio = m_dragging ? 0.75 : std::min(devicePixelRatioF(), 1.5);
|
||||
const int textureSize = std::clamp(
|
||||
static_cast<int>(std::ceil(radius * 2.0 * pixelRatio)), 320, 900);
|
||||
static_cast<int>(std::ceil(radius * 2.0 * pixelRatio)),
|
||||
m_dragging ? 240 : 320, m_dragging ? 480 : 900);
|
||||
const QPointF center(width() * 0.5, height() * 0.52);
|
||||
const QRectF target(center.x() - radius, center.y() - radius,
|
||||
radius * 2.0, radius * 2.0);
|
||||
@@ -715,33 +760,41 @@ void GlobeWidget::drawReceiver(QPainter &painter) const
|
||||
painter.drawText(p.point + QPointF(10, -8), QStringLiteral("ALICI"));
|
||||
}
|
||||
|
||||
void GlobeWidget::drawSatellites(QPainter &painter) const
|
||||
int GlobeWidget::drawSatellites(QPainter &painter) const
|
||||
{
|
||||
if (!m_hasReceiver) return;
|
||||
if (!m_hasReceiver) return 0;
|
||||
const double orbitRadius = std::min(width(), height()) * 0.255 * m_zoom * 1.47;
|
||||
struct Item { SatellitePosition satellite; ProjectedPoint projected; };
|
||||
struct Item {
|
||||
SatellitePosition satellite;
|
||||
ProjectedPoint projected;
|
||||
double elevation = -90.0;
|
||||
};
|
||||
QVector<Item> items;
|
||||
items.reserve(m_satellites.size());
|
||||
for (const auto &satellite : m_satellites) {
|
||||
const double length = std::sqrt(satellite.x * satellite.x + satellite.y * satellite.y
|
||||
+ satellite.z * satellite.z);
|
||||
if (length <= 0.0) continue;
|
||||
items.push_back({satellite, project(satellite.x / length, satellite.y / length,
|
||||
satellite.z / length, orbitRadius)});
|
||||
satellite.z / length, orbitRadius),
|
||||
satelliteElevation(satellite)});
|
||||
}
|
||||
std::sort(items.begin(), items.end(), [](const Item &a, const Item &b) {
|
||||
return a.projected.depth < b.projected.depth;
|
||||
});
|
||||
const double earthRadius = std::min(width(), height()) * 0.255 * m_zoom;
|
||||
const Vec3 receiver = globeVector(m_receiverLatitude, m_receiverLongitude);
|
||||
const auto receiverPoint = project(receiver.x, receiver.y, receiver.z,
|
||||
earthRadius * 1.02);
|
||||
int visibleCount = 0;
|
||||
for (const Item &item : items) {
|
||||
const bool found = m_observedPrns.contains(item.satellite.prn);
|
||||
const bool inElevationMask = satelliteElevation(item.satellite) >= m_elevationMaskDegrees;
|
||||
const bool inElevationMask = item.elevation >= m_elevationMaskDegrees;
|
||||
if (inElevationMask) ++visibleCount;
|
||||
QColor color = found ? QColor(42, 224, 137) : QColor(255, 79, 95);
|
||||
if (item.projected.depth < 0.0) color.setAlpha(75);
|
||||
const double markerRadius = found ? 6.5 : 5.0;
|
||||
if (inElevationMask) {
|
||||
const double earthRadius = std::min(width(), height()) * 0.255 * m_zoom;
|
||||
const Vec3 receiver = globeVector(m_receiverLatitude, m_receiverLongitude);
|
||||
const auto receiverPoint = project(receiver.x, receiver.y, receiver.z,
|
||||
earthRadius * 1.02);
|
||||
QColor sightLine(70, 224, 231, item.projected.depth < 0.0 ? 25 : 80);
|
||||
painter.setPen(QPen(sightLine, 0.8, Qt::DashLine));
|
||||
painter.drawLine(receiverPoint.point, item.projected.point);
|
||||
@@ -757,6 +810,7 @@ void GlobeWidget::drawSatellites(QPainter &painter) const
|
||||
painter.drawText(item.projected.point + QPointF(7, -6), item.satellite.prn);
|
||||
}
|
||||
}
|
||||
return visibleCount;
|
||||
}
|
||||
|
||||
double GlobeWidget::satelliteElevation(const SatellitePosition &satellite) const
|
||||
@@ -802,7 +856,9 @@ void GlobeWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
m_dragging = false;
|
||||
m_staticLayer = QPixmap();
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user