Improve satellite view and fused location telemetry

This commit is contained in:
2026-07-23 23:19:52 +03:00
parent 049f6301e8
commit 24b08ee518
12 changed files with 486 additions and 188 deletions
+129 -73
View File
@@ -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> &current,
// 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();
}
}