Initial commit: add Location Plotter
This commit is contained in:
@@ -0,0 +1,623 @@
|
||||
#include "MapWidget.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QLabel>
|
||||
#include <QLineF>
|
||||
#include <QMouseEvent>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QResizeEvent>
|
||||
#include <QStandardPaths>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QWheelEvent>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int TileSize = 256;
|
||||
constexpr int MinZoom = 2;
|
||||
constexpr int MaxZoom = 19;
|
||||
constexpr int MaxMemoryTileCount = 512;
|
||||
constexpr double Pi = 3.14159265358979323846;
|
||||
constexpr double WebMercatorRadiusMeters = 6378137.0;
|
||||
constexpr double MaxMercatorLatitude = 85.05112878;
|
||||
|
||||
QString tileKey(int zoom, int tileX, int tileY)
|
||||
{
|
||||
return QStringLiteral("%1/%2/%3").arg(zoom).arg(tileX).arg(tileY);
|
||||
}
|
||||
|
||||
double clampLatitude(double latitude)
|
||||
{
|
||||
return std::clamp(latitude, -MaxMercatorLatitude, MaxMercatorLatitude);
|
||||
}
|
||||
|
||||
int positiveModulo(int value, int modulo)
|
||||
{
|
||||
const int result = value % modulo;
|
||||
return result < 0 ? result + modulo : result;
|
||||
}
|
||||
|
||||
double metersPerPixel(double latitude, int zoom)
|
||||
{
|
||||
const double scale = static_cast<double>(TileSize) * static_cast<double>(1 << zoom);
|
||||
const double latitudeScale = std::cos(latitude * Pi / 180.0);
|
||||
return latitudeScale * 2.0 * Pi * WebMercatorRadiusMeters / scale;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MapWidget::MapWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_network(new QNetworkAccessManager(this))
|
||||
{
|
||||
setMinimumSize(640, 420);
|
||||
setMouseTracking(true);
|
||||
|
||||
const QString cacheRoot = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
|
||||
if (!cacheRoot.isEmpty()) {
|
||||
auto *diskCache = new QNetworkDiskCache(m_network);
|
||||
const QString cacheDirectory = cacheRoot + QStringLiteral("/map-tiles");
|
||||
QDir().mkpath(cacheDirectory);
|
||||
diskCache->setCacheDirectory(cacheDirectory);
|
||||
diskCache->setMaximumCacheSize(100 * 1024 * 1024);
|
||||
m_network->setCache(diskCache);
|
||||
}
|
||||
|
||||
m_attributionLabel = new QLabel(this);
|
||||
m_attributionLabel->setText(
|
||||
QStringLiteral("<a style=\"color:#174b7a;text-decoration:none\" "
|
||||
"href=\"https://www.openstreetmap.org/copyright\">")
|
||||
+ QString::fromUtf8("\xC2\xA9 OpenStreetMap contributors")
|
||||
+ QStringLiteral("</a>"));
|
||||
m_attributionLabel->setOpenExternalLinks(true);
|
||||
m_attributionLabel->setStyleSheet(QStringLiteral(
|
||||
"QLabel { background: rgba(255, 255, 255, 220); color: #26313d; "
|
||||
"border: 1px solid rgba(120, 130, 140, 90); padding: 3px 6px; }"));
|
||||
m_attributionLabel->adjustSize();
|
||||
}
|
||||
|
||||
void MapWidget::setPoints(const QVector<TelemetryPoint> &points)
|
||||
{
|
||||
const bool wasEmpty = m_points.isEmpty();
|
||||
m_points = points;
|
||||
|
||||
if (!m_points.isEmpty() && m_autoFollow) {
|
||||
const TelemetryPoint &last = m_points.last();
|
||||
m_centerLat = last.latitude;
|
||||
m_centerLon = last.longitude;
|
||||
if (wasEmpty) {
|
||||
m_zoom = MaxZoom;
|
||||
}
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void MapWidget::clearPoints()
|
||||
{
|
||||
m_points.clear();
|
||||
m_hasAverageMarker = false;
|
||||
update();
|
||||
}
|
||||
|
||||
void MapWidget::setAverageMarker(double latitude, double longitude, double radiusMeters)
|
||||
{
|
||||
m_hasAverageMarker = true;
|
||||
m_averageLat = latitude;
|
||||
m_averageLon = longitude;
|
||||
m_averageRadiusMeters = std::max(0.0, radiusMeters);
|
||||
update();
|
||||
}
|
||||
|
||||
void MapWidget::clearAverageMarker()
|
||||
{
|
||||
if (!m_hasAverageMarker) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_hasAverageMarker = false;
|
||||
update();
|
||||
}
|
||||
|
||||
void MapWidget::setCepMarker(double latitude, double longitude,
|
||||
const std::optional<double> &radiusMeters)
|
||||
{
|
||||
m_hasCepMarker = true;
|
||||
m_cepLatitude = latitude;
|
||||
m_cepLongitude = longitude;
|
||||
if (m_points.isEmpty()) {
|
||||
m_centerLat = latitude;
|
||||
m_centerLon = longitude;
|
||||
m_zoom = MaxZoom;
|
||||
}
|
||||
if (radiusMeters.has_value()) {
|
||||
m_cepRadiusMeters = std::max(0.0, *radiusMeters);
|
||||
} else {
|
||||
m_cepRadiusMeters.reset();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void MapWidget::clearCepMarker()
|
||||
{
|
||||
if (!m_hasCepMarker) {
|
||||
return;
|
||||
}
|
||||
m_hasCepMarker = false;
|
||||
m_cepRadiusMeters.reset();
|
||||
update();
|
||||
}
|
||||
|
||||
void MapWidget::setAutoFollow(bool enabled)
|
||||
{
|
||||
if (m_autoFollow == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_autoFollow = enabled;
|
||||
if (m_autoFollow && !m_points.isEmpty()) {
|
||||
const TelemetryPoint &last = m_points.last();
|
||||
m_centerLat = last.latitude;
|
||||
m_centerLon = last.longitude;
|
||||
update();
|
||||
}
|
||||
emit autoFollowChanged(m_autoFollow);
|
||||
}
|
||||
|
||||
void MapWidget::setAverageOverlayEnabled(bool enabled)
|
||||
{
|
||||
if (m_showAverageOverlay == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_showAverageOverlay = enabled;
|
||||
update();
|
||||
}
|
||||
|
||||
void MapWidget::setTelemetryOverlayEnabled(bool enabled)
|
||||
{
|
||||
if (m_showTelemetryOverlay == enabled) {
|
||||
return;
|
||||
}
|
||||
m_showTelemetryOverlay = enabled;
|
||||
update();
|
||||
}
|
||||
|
||||
void MapWidget::setRecentPointsEnabled(bool enabled)
|
||||
{
|
||||
if (m_showRecentPoints == enabled) {
|
||||
return;
|
||||
}
|
||||
m_showRecentPoints = enabled;
|
||||
update();
|
||||
}
|
||||
|
||||
void MapWidget::zoomIn()
|
||||
{
|
||||
setZoomKeepingPoint(m_zoom + 1, QPointF(width() / 2.0, height() / 2.0));
|
||||
}
|
||||
|
||||
void MapWidget::zoomOut()
|
||||
{
|
||||
setZoomKeepingPoint(m_zoom - 1, QPointF(width() / 2.0, height() / 2.0));
|
||||
}
|
||||
|
||||
void MapWidget::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.fillRect(rect(), QColor(219, 225, 220));
|
||||
drawTiles(painter);
|
||||
drawTelemetry(painter);
|
||||
|
||||
if (m_points.isEmpty()) {
|
||||
drawEmptyState(painter);
|
||||
}
|
||||
}
|
||||
|
||||
void MapWidget::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
const int delta = event->angleDelta().y();
|
||||
if (delta == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
disableAutoFollowFromUserAction();
|
||||
setZoomKeepingPoint(m_zoom + (delta > 0 ? 1 : -1), event->position());
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void MapWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::RightButton) {
|
||||
const QPointF coordinate = screenToCoordinate(event->position());
|
||||
double longitude = std::fmod(coordinate.y() + 180.0, 360.0);
|
||||
if (longitude < 0.0) {
|
||||
longitude += 360.0;
|
||||
}
|
||||
longitude -= 180.0;
|
||||
emit referenceLocationSelected(coordinate.x(), longitude);
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
disableAutoFollowFromUserAction();
|
||||
m_dragging = true;
|
||||
m_lastMousePos = event->pos();
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
void MapWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!m_dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QPoint delta = event->pos() - m_lastMousePos;
|
||||
m_lastMousePos = event->pos();
|
||||
|
||||
const QPointF centerWorld = coordinateToWorld(m_centerLat, m_centerLon, m_zoom);
|
||||
const QPointF newCenterWorld = centerWorld - QPointF(delta);
|
||||
const QPointF coordinate = worldToCoordinate(newCenterWorld, m_zoom);
|
||||
m_centerLat = coordinate.x();
|
||||
m_centerLon = coordinate.y();
|
||||
update();
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void MapWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton && m_dragging) {
|
||||
m_dragging = false;
|
||||
unsetCursor();
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
void MapWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
if (m_attributionLabel != nullptr) {
|
||||
m_attributionLabel->adjustSize();
|
||||
m_attributionLabel->move(width() - m_attributionLabel->width() - 8,
|
||||
height() - m_attributionLabel->height() - 8);
|
||||
m_attributionLabel->raise();
|
||||
}
|
||||
}
|
||||
|
||||
QPointF MapWidget::coordinateToWorld(double latitude, double longitude, int zoom) const
|
||||
{
|
||||
latitude = clampLatitude(latitude);
|
||||
const double sinLat = std::sin(latitude * Pi / 180.0);
|
||||
const double scale = static_cast<double>(TileSize) * static_cast<double>(1 << zoom);
|
||||
|
||||
const double x = (longitude + 180.0) / 360.0 * scale;
|
||||
const double y = (0.5 - std::log((1.0 + sinLat) / (1.0 - sinLat)) / (4.0 * Pi)) * scale;
|
||||
return QPointF(x, y);
|
||||
}
|
||||
|
||||
QPointF MapWidget::worldToCoordinate(const QPointF &world, int zoom) const
|
||||
{
|
||||
const double scale = static_cast<double>(TileSize) * static_cast<double>(1 << zoom);
|
||||
const double longitude = world.x() / scale * 360.0 - 180.0;
|
||||
const double n = Pi - 2.0 * Pi * world.y() / scale;
|
||||
const double latitude = 180.0 / Pi * std::atan(0.5 * (std::exp(n) - std::exp(-n)));
|
||||
return QPointF(clampLatitude(latitude), longitude);
|
||||
}
|
||||
|
||||
QPointF MapWidget::coordinateToScreen(double latitude, double longitude) const
|
||||
{
|
||||
const QPointF world = coordinateToWorld(latitude, longitude, m_zoom);
|
||||
const QPointF centerWorld = coordinateToWorld(m_centerLat, m_centerLon, m_zoom);
|
||||
const double worldWidth = static_cast<double>(TileSize) * static_cast<double>(1 << m_zoom);
|
||||
double deltaX = world.x() - centerWorld.x();
|
||||
if (deltaX > worldWidth / 2.0) {
|
||||
deltaX -= worldWidth;
|
||||
} else if (deltaX < -worldWidth / 2.0) {
|
||||
deltaX += worldWidth;
|
||||
}
|
||||
return QPointF(width() / 2.0 + deltaX, height() / 2.0 + world.y() - centerWorld.y());
|
||||
}
|
||||
|
||||
QPointF MapWidget::screenToCoordinate(const QPointF &screen) const
|
||||
{
|
||||
return worldToCoordinate(topLeftWorld() + screen, m_zoom);
|
||||
}
|
||||
|
||||
QPointF MapWidget::topLeftWorld() const
|
||||
{
|
||||
return coordinateToWorld(m_centerLat, m_centerLon, m_zoom)
|
||||
- QPointF(width() / 2.0, height() / 2.0);
|
||||
}
|
||||
|
||||
void MapWidget::drawTiles(QPainter &painter)
|
||||
{
|
||||
const int tileCount = 1 << m_zoom;
|
||||
const QPointF topLeft = topLeftWorld();
|
||||
const int firstTileX = static_cast<int>(std::floor(topLeft.x() / TileSize));
|
||||
const int lastTileX = static_cast<int>(std::floor((topLeft.x() + width()) / TileSize));
|
||||
const int firstTileY = static_cast<int>(std::floor(topLeft.y() / TileSize));
|
||||
const int lastTileY = static_cast<int>(std::floor((topLeft.y() + height()) / TileSize));
|
||||
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform, false);
|
||||
|
||||
for (int tileY = firstTileY; tileY <= lastTileY; ++tileY) {
|
||||
if (tileY < 0 || tileY >= tileCount) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int tileX = firstTileX; tileX <= lastTileX; ++tileX) {
|
||||
const int wrappedTileX = positiveModulo(tileX, tileCount);
|
||||
const QString key = tileKey(m_zoom, wrappedTileX, tileY);
|
||||
const QRectF target(
|
||||
tileX * TileSize - topLeft.x(),
|
||||
tileY * TileSize - topLeft.y(),
|
||||
TileSize,
|
||||
TileSize);
|
||||
|
||||
const auto tileIt = m_tileCache.constFind(key);
|
||||
if (tileIt != m_tileCache.constEnd()) {
|
||||
painter.drawPixmap(target.toRect(), *tileIt);
|
||||
} else {
|
||||
painter.fillRect(target, QColor(230, 234, 230));
|
||||
painter.setPen(QColor(198, 205, 200));
|
||||
painter.drawRect(target);
|
||||
requestTile(wrappedTileX, tileY, m_zoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MapWidget::drawTelemetry(QPainter &painter)
|
||||
{
|
||||
if (m_points.isEmpty() || !m_showTelemetryOverlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
drawCepOverlay(painter);
|
||||
|
||||
QPainterPath path;
|
||||
for (int i = 0; i < m_points.size(); ++i) {
|
||||
const QPointF screen = coordinateToScreen(m_points[i].latitude, m_points[i].longitude);
|
||||
if (i == 0) {
|
||||
path.moveTo(screen);
|
||||
} else {
|
||||
path.lineTo(screen);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_showRecentPoints && m_points.size() > 1) {
|
||||
QPen pathPen(QColor(210, 105, 30), 3.0);
|
||||
pathPen.setCapStyle(Qt::RoundCap);
|
||||
pathPen.setJoinStyle(Qt::RoundJoin);
|
||||
painter.setPen(pathPen);
|
||||
painter.drawPath(path);
|
||||
}
|
||||
|
||||
drawAverageOverlay(painter);
|
||||
drawCepMarker(painter);
|
||||
|
||||
const bool drawNumbers = m_points.size() <= 150;
|
||||
const int firstPoint = m_showRecentPoints ? 0 : m_points.size() - 1;
|
||||
for (int i = firstPoint; i < m_points.size(); ++i) {
|
||||
const bool isLast = i == m_points.size() - 1;
|
||||
const QPointF screen = coordinateToScreen(m_points[i].latitude, m_points[i].longitude);
|
||||
const double radius = isLast ? 8.0 : 5.0;
|
||||
|
||||
painter.setPen(QPen(Qt::white, isLast ? 3.0 : 2.0));
|
||||
painter.setBrush(isLast ? QColor(31, 150, 82) : QColor(37, 99, 235));
|
||||
painter.drawEllipse(screen, radius, radius);
|
||||
|
||||
if (drawNumbers) {
|
||||
painter.setPen(Qt::white);
|
||||
painter.setFont(QFont(painter.font().family(), 8, QFont::Bold));
|
||||
painter.drawText(QRectF(screen.x() - 13, screen.y() - 9, 26, 18), Qt::AlignCenter,
|
||||
QString::number(i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
const TelemetryPoint &last = m_points.last();
|
||||
const QPointF lastScreen = coordinateToScreen(last.latitude, last.longitude);
|
||||
QStringList lines;
|
||||
const QString latitudeText = last.latitudeText.isEmpty()
|
||||
? QString::number(last.latitude, 'f', 15) : last.latitudeText;
|
||||
const QString longitudeText = last.longitudeText.isEmpty()
|
||||
? QString::number(last.longitude, 'f', 15) : last.longitudeText;
|
||||
lines << QStringLiteral("Lat: %1").arg(latitudeText);
|
||||
lines << QStringLiteral("Long: %1").arg(longitudeText);
|
||||
if (last.hheight.has_value()) {
|
||||
lines << QStringLiteral("HHeight: %1").arg(*last.hheight, 0, 'f', 2);
|
||||
}
|
||||
if (last.satellites.has_value()) {
|
||||
lines << QStringLiteral("Aktif uydu: %1").arg(*last.satellites);
|
||||
}
|
||||
if (m_cepRadiusMeters.has_value()) {
|
||||
lines << QStringLiteral("CEP50: %1 m").arg(*m_cepRadiusMeters, 0, 'f', 2);
|
||||
}
|
||||
|
||||
painter.setFont(QFont(painter.font().family(), 10));
|
||||
const QString label = lines.join('\n');
|
||||
const QRect textRect = painter.fontMetrics().boundingRect(QRect(0, 0, 300, 150),
|
||||
Qt::TextWordWrap, label);
|
||||
QRectF bubble(lastScreen + QPointF(14, -textRect.height() - 12),
|
||||
QSizeF(textRect.width() + 18, textRect.height() + 14));
|
||||
if (bubble.right() > width() - 8) {
|
||||
bubble.moveRight(width() - 8);
|
||||
}
|
||||
if (bubble.top() < 8) {
|
||||
bubble.moveTop(8);
|
||||
}
|
||||
|
||||
painter.setPen(QPen(QColor(50, 58, 65, 180), 1.0));
|
||||
painter.setBrush(QColor(255, 255, 255, 232));
|
||||
painter.drawRoundedRect(bubble, 6, 6);
|
||||
painter.setPen(QColor(25, 32, 38));
|
||||
painter.drawText(bubble.adjusted(9, 7, -9, -7), Qt::AlignLeft | Qt::AlignVCenter, label);
|
||||
}
|
||||
|
||||
void MapWidget::drawCepOverlay(QPainter &painter)
|
||||
{
|
||||
if (!m_hasCepMarker || !m_cepRadiusMeters.has_value() || *m_cepRadiusMeters <= 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QPointF screen = coordinateToScreen(m_cepLatitude, m_cepLongitude);
|
||||
const double resolution = metersPerPixel(m_cepLatitude, m_zoom);
|
||||
if (!std::isfinite(resolution) || resolution <= 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const double radiusPixels = std::min(*m_cepRadiusMeters / resolution, 2500.0);
|
||||
if (radiusPixels < 1.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
painter.setPen(QPen(QColor(8, 111, 104, 210), 2.0));
|
||||
painter.setBrush(QColor(13, 148, 136, 42));
|
||||
painter.drawEllipse(screen, radiusPixels, radiusPixels);
|
||||
}
|
||||
|
||||
void MapWidget::drawCepMarker(QPainter &painter)
|
||||
{
|
||||
if (!m_hasCepMarker) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QPointF screen = coordinateToScreen(m_cepLatitude, m_cepLongitude);
|
||||
painter.setPen(QPen(QColor(8, 111, 104), 2.5));
|
||||
painter.setBrush(QColor(255, 255, 255, 230));
|
||||
painter.drawEllipse(screen, 7.0, 7.0);
|
||||
painter.drawLine(QPointF(screen.x() - 11.0, screen.y()),
|
||||
QPointF(screen.x() + 11.0, screen.y()));
|
||||
painter.drawLine(QPointF(screen.x(), screen.y() - 11.0),
|
||||
QPointF(screen.x(), screen.y() + 11.0));
|
||||
|
||||
const bool overlapsLast = !m_points.isEmpty()
|
||||
&& QLineF(screen, coordinateToScreen(m_points.last().latitude,
|
||||
m_points.last().longitude)).length() < 30.0;
|
||||
if (!overlapsLast) {
|
||||
painter.setFont(QFont(painter.font().family(), 9, QFont::Bold));
|
||||
painter.setPen(QColor(20, 83, 78));
|
||||
painter.drawText(QRectF(screen + QPointF(12.0, -10.0), QSizeF(110.0, 20.0)),
|
||||
Qt::AlignLeft | Qt::AlignVCenter, QStringLiteral("Bilinen konum"));
|
||||
}
|
||||
}
|
||||
|
||||
void MapWidget::drawAverageOverlay(QPainter &painter)
|
||||
{
|
||||
if (!m_showAverageOverlay || !m_hasAverageMarker) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QPointF screen = coordinateToScreen(m_averageLat, m_averageLon);
|
||||
const double resolution = metersPerPixel(m_averageLat, m_zoom);
|
||||
if (std::isfinite(resolution) && resolution > 0.0 && m_averageRadiusMeters > 0.0) {
|
||||
const double radiusPixels = std::min(m_averageRadiusMeters / resolution, 2500.0);
|
||||
if (radiusPixels >= 2.0) {
|
||||
painter.setPen(QPen(QColor(124, 58, 237, 150), 2.0, Qt::DashLine));
|
||||
painter.setBrush(QColor(124, 58, 237, 30));
|
||||
painter.drawEllipse(screen, radiusPixels, radiusPixels);
|
||||
}
|
||||
}
|
||||
|
||||
painter.setPen(QPen(Qt::white, 3.0));
|
||||
painter.setBrush(QColor(124, 58, 237));
|
||||
painter.drawEllipse(screen, 9.0, 9.0);
|
||||
|
||||
painter.setPen(QPen(QColor(25, 32, 38), 2.0));
|
||||
painter.drawLine(QPointF(screen.x() - 12.0, screen.y()), QPointF(screen.x() + 12.0, screen.y()));
|
||||
painter.drawLine(QPointF(screen.x(), screen.y() - 12.0), QPointF(screen.x(), screen.y() + 12.0));
|
||||
|
||||
const bool overlapsLast = !m_points.isEmpty()
|
||||
&& QLineF(screen, coordinateToScreen(m_points.last().latitude,
|
||||
m_points.last().longitude)).length() < 30.0;
|
||||
if (!overlapsLast) {
|
||||
painter.setFont(QFont(painter.font().family(), 9, QFont::Bold));
|
||||
const QRectF labelRect(screen + QPointF(12.0, 8.0), QSizeF(72.0, 20.0));
|
||||
painter.setPen(QColor(25, 32, 38));
|
||||
painter.drawText(labelRect, Qt::AlignLeft | Qt::AlignVCenter, QStringLiteral("Ortalama"));
|
||||
}
|
||||
}
|
||||
|
||||
void MapWidget::drawEmptyState(QPainter &painter)
|
||||
{
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.setPen(QColor(83, 96, 110));
|
||||
painter.setFont(QFont(painter.font().family(), 13));
|
||||
painter.drawText(rect(), Qt::AlignCenter,
|
||||
QStringLiteral("Seri porttan lat / long / hheight bekleniyor"));
|
||||
}
|
||||
|
||||
void MapWidget::requestTile(int tileX, int tileY, int zoom)
|
||||
{
|
||||
const QString key = tileKey(zoom, tileX, tileY);
|
||||
if (m_tileCache.contains(key) || m_pendingTiles.contains(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_pendingTiles.insert(key);
|
||||
const QUrl url(QStringLiteral("https://tile.openstreetmap.org/%1/%2/%3.png")
|
||||
.arg(zoom)
|
||||
.arg(tileX)
|
||||
.arg(tileY));
|
||||
QNetworkRequest request(url);
|
||||
request.setRawHeader("User-Agent", "RocketLocationPlotter/0.2 (Qt desktop application)");
|
||||
QNetworkReply *reply = m_network->get(request);
|
||||
reply->setProperty("tileKey", key);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QString key = reply->property("tileKey").toString();
|
||||
m_pendingTiles.remove(key);
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QPixmap pixmap;
|
||||
if (pixmap.loadFromData(reply->readAll())) {
|
||||
if (!m_tileCache.contains(key) && m_tileCache.size() >= MaxMemoryTileCount) {
|
||||
m_tileCache.erase(m_tileCache.begin());
|
||||
}
|
||||
m_tileCache.insert(key, pixmap);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void MapWidget::setZoomKeepingPoint(int newZoom, const QPointF &screenAnchor)
|
||||
{
|
||||
newZoom = std::clamp(newZoom, MinZoom, MaxZoom);
|
||||
if (newZoom == m_zoom) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QPointF coordinateAtAnchor = screenToCoordinate(screenAnchor);
|
||||
m_zoom = newZoom;
|
||||
const QPointF anchorWorld = coordinateToWorld(coordinateAtAnchor.x(), coordinateAtAnchor.y(), m_zoom);
|
||||
const QPointF newCenterWorld = anchorWorld - (screenAnchor - QPointF(width() / 2.0, height() / 2.0));
|
||||
const QPointF newCenter = worldToCoordinate(newCenterWorld, m_zoom);
|
||||
m_centerLat = newCenter.x();
|
||||
m_centerLon = newCenter.y();
|
||||
update();
|
||||
}
|
||||
|
||||
void MapWidget::disableAutoFollowFromUserAction()
|
||||
{
|
||||
if (!m_autoFollow) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_autoFollow = false;
|
||||
emit autoFollowChanged(false);
|
||||
}
|
||||
Reference in New Issue
Block a user