Add polished macOS location plotter experience
This commit is contained in:
+82
-13
@@ -4,6 +4,7 @@
|
||||
#include <QLabel>
|
||||
#include <QLineF>
|
||||
#include <QMouseEvent>
|
||||
#include <QNativeGestureEvent>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QNetworkRequest>
|
||||
@@ -58,7 +59,7 @@ MapWidget::MapWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_network(new QNetworkAccessManager(this))
|
||||
{
|
||||
setMinimumSize(640, 420);
|
||||
setMinimumSize(480, 320);
|
||||
setMouseTracking(true);
|
||||
|
||||
const QString cacheRoot = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
|
||||
@@ -115,23 +116,55 @@ void MapWidget::fitToPoints()
|
||||
}
|
||||
double minimumLatitude = m_points.first().latitude;
|
||||
double maximumLatitude = minimumLatitude;
|
||||
double minimumLongitude = m_points.first().longitude;
|
||||
double maximumLongitude = minimumLongitude;
|
||||
QVector<double> longitudes;
|
||||
longitudes.reserve(m_points.size());
|
||||
for (const TelemetryPoint &point : m_points) {
|
||||
minimumLatitude = std::min(minimumLatitude, point.latitude);
|
||||
maximumLatitude = std::max(maximumLatitude, point.latitude);
|
||||
minimumLongitude = std::min(minimumLongitude, point.longitude);
|
||||
maximumLongitude = std::max(maximumLongitude, point.longitude);
|
||||
double normalizedLongitude = std::fmod(point.longitude + 360.0, 360.0);
|
||||
if (normalizedLongitude < 0.0) {
|
||||
normalizedLongitude += 360.0;
|
||||
}
|
||||
longitudes.push_back(normalizedLongitude);
|
||||
}
|
||||
std::sort(longitudes.begin(), longitudes.end());
|
||||
|
||||
// Fit the shortest circular longitude interval. A plain min/max makes
|
||||
// points around +180/-180 look almost a full world apart.
|
||||
double arcStart = longitudes.first();
|
||||
double longitudeSpan = 0.0;
|
||||
if (longitudes.size() > 1) {
|
||||
double largestGap = -1.0;
|
||||
int largestGapIndex = 0;
|
||||
for (int i = 0; i < longitudes.size(); ++i) {
|
||||
const double current = longitudes[i];
|
||||
const double next = i + 1 < longitudes.size()
|
||||
? longitudes[i + 1] : longitudes.first() + 360.0;
|
||||
const double gap = next - current;
|
||||
if (gap > largestGap) {
|
||||
largestGap = gap;
|
||||
largestGapIndex = i;
|
||||
}
|
||||
}
|
||||
arcStart = largestGapIndex + 1 < longitudes.size()
|
||||
? longitudes[largestGapIndex + 1] : longitudes.first();
|
||||
longitudeSpan = 360.0 - largestGap;
|
||||
}
|
||||
m_centerLat = 0.5 * (minimumLatitude + maximumLatitude);
|
||||
m_centerLon = 0.5 * (minimumLongitude + maximumLongitude);
|
||||
m_centerLon = std::fmod(arcStart + longitudeSpan * 0.5 + 180.0, 360.0);
|
||||
if (m_centerLon < 0.0) {
|
||||
m_centerLon += 360.0;
|
||||
}
|
||||
m_centerLon -= 180.0;
|
||||
const double availableWidth = std::max(100.0, width() * 0.82);
|
||||
const double availableHeight = std::max(100.0, height() * 0.82);
|
||||
for (int zoom = MaxZoom; zoom >= MinZoom; --zoom) {
|
||||
const QPointF topLeft = coordinateToWorld(maximumLatitude, minimumLongitude, zoom);
|
||||
const QPointF bottomRight = coordinateToWorld(minimumLatitude, maximumLongitude, zoom);
|
||||
if (std::fabs(bottomRight.x() - topLeft.x()) <= availableWidth
|
||||
&& std::fabs(bottomRight.y() - topLeft.y()) <= availableHeight) {
|
||||
const double worldWidth = static_cast<double>(TileSize) * static_cast<double>(1 << zoom);
|
||||
const double longitudeWidth = longitudeSpan / 360.0 * worldWidth;
|
||||
const double top = coordinateToWorld(maximumLatitude, m_centerLon, zoom).y();
|
||||
const double bottom = coordinateToWorld(minimumLatitude, m_centerLon, zoom).y();
|
||||
if (longitudeWidth <= availableWidth
|
||||
&& std::fabs(bottom - top) <= availableHeight) {
|
||||
m_zoom = zoom;
|
||||
break;
|
||||
}
|
||||
@@ -260,16 +293,52 @@ void MapWidget::paintEvent(QPaintEvent *)
|
||||
|
||||
void MapWidget::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
const int delta = event->angleDelta().y();
|
||||
if (delta == 0) {
|
||||
const QPoint pixelDelta = event->pixelDelta();
|
||||
const QPoint angleDelta = event->angleDelta();
|
||||
const double delta = !pixelDelta.isNull()
|
||||
? pixelDelta.y() / 36.0
|
||||
: angleDelta.y() / 120.0;
|
||||
if (qFuzzyIsNull(delta)) {
|
||||
return;
|
||||
}
|
||||
|
||||
disableAutoFollowFromUserAction();
|
||||
setZoomKeepingPoint(m_zoom + (delta > 0 ? 1 : -1), event->position());
|
||||
m_wheelZoomAccumulator += delta;
|
||||
while (std::abs(m_wheelZoomAccumulator) >= 1.0) {
|
||||
const int direction = m_wheelZoomAccumulator > 0.0 ? 1 : -1;
|
||||
setZoomKeepingPoint(m_zoom + direction, event->position());
|
||||
m_wheelZoomAccumulator -= direction;
|
||||
}
|
||||
if (event->phase() == Qt::ScrollEnd) {
|
||||
m_wheelZoomAccumulator = 0.0;
|
||||
}
|
||||
event->accept();
|
||||
}
|
||||
|
||||
bool MapWidget::event(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::NativeGesture) {
|
||||
auto *gesture = static_cast<QNativeGestureEvent *>(event);
|
||||
if (gesture->gestureType() == Qt::ZoomNativeGesture) {
|
||||
disableAutoFollowFromUserAction();
|
||||
// macOS reports pinch magnification as small deltas. Accumulate
|
||||
// them so map-tile zoom remains stable instead of flickering.
|
||||
m_wheelZoomAccumulator += gesture->value() * 5.0;
|
||||
while (std::abs(m_wheelZoomAccumulator) >= 1.0) {
|
||||
const int direction = m_wheelZoomAccumulator > 0.0 ? 1 : -1;
|
||||
setZoomKeepingPoint(m_zoom + direction, gesture->position());
|
||||
m_wheelZoomAccumulator -= direction;
|
||||
}
|
||||
gesture->accept();
|
||||
return true;
|
||||
}
|
||||
if (gesture->gestureType() == Qt::EndNativeGesture) {
|
||||
m_wheelZoomAccumulator = 0.0;
|
||||
}
|
||||
}
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
void MapWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::RightButton) {
|
||||
|
||||
Reference in New Issue
Block a user