// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include #include #include "slippymap.h" #include "qmath.h" // tile size in pixels const int tdim = 256; QPointF tileForCoordinate(qreal lat, qreal lng, int zoom) { qreal radianLat = qDegreesToRadians(lat); qreal zn = static_cast(1 << zoom); qreal tx = (lng + 180.0) / 360.0; qreal ty = 0.5 - log(tan(radianLat) + 1.0 / cos(radianLat)) / M_PI / 2.0; return QPointF(tx * zn, ty * zn); } qreal longitudeFromTile(qreal tx, int zoom) { qreal zn = static_cast(1 << zoom); qreal lat = tx / zn * 360.0 - 180.0; return lat; } qreal latitudeFromTile(qreal ty, int zoom) { qreal zn = static_cast(1 << zoom); qreal n = M_PI - 2 * M_PI * ty / zn; return qRadiansToDegrees(atan(sinh(n))); } SlippyMap::SlippyMap(QObject *parent) : QObject(parent), width(400), height(300), zoom(15), latitude(59.9138204), longitude(10.7387413) { m_emptyTile = QPixmap(tdim, tdim); m_emptyTile.fill(Qt::lightGray); QNetworkDiskCache *cache = new QNetworkDiskCache; cache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); m_manager.setCache(cache); connect(&m_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(handleNetworkData(QNetworkReply*))); } void SlippyMap::invalidate() { if (width <= 0 || height <= 0) return; QPointF ct = tileForCoordinate(latitude, longitude, zoom); qreal tx = ct.x(); qreal ty = ct.y(); // top-left corner of the center tile int xp = width / 2 - (tx - floor(tx)) * tdim; int yp = height / 2 - (ty - floor(ty)) * tdim; // first tile vertical and horizontal int xa = (xp + tdim - 1) / tdim; int ya = (yp + tdim - 1) / tdim; int xs = static_cast(tx) - xa; int ys = static_cast(ty) - ya; // offset for top-left tile m_offset = QPoint(xp - xa * tdim, yp - ya * tdim); // last tile vertical and horizontal int xe = static_cast(tx) + (width - xp - 1) / tdim; int ye = static_cast(ty) + (height - yp - 1) / tdim; // build a rect m_tilesRect = QRect(xs, ys, xe - xs + 1, ye - ys + 1); if (m_url.isEmpty()) download(); emit updated(QRect(0, 0, width, height)); } void SlippyMap::render(QPainter *p, const QRect &rect) { for (int x = 0; x <= m_tilesRect.width(); ++x) for (int y = 0; y <= m_tilesRect.height(); ++y) { QPoint tp(x + m_tilesRect.left(), y + m_tilesRect.top()); QRect box = tileRect(tp); if (rect.intersects(box)) { if (m_tilePixmaps.contains(tp)) p->drawPixmap(box, m_tilePixmaps.value(tp)); else p->drawPixmap(box, m_emptyTile); } } } void SlippyMap::pan(const QPoint &delta) { QPointF dx = QPointF(delta) / qreal(tdim); QPointF center = tileForCoordinate(latitude, longitude, zoom) - dx; latitude = latitudeFromTile(center.y(), zoom); longitude = longitudeFromTile(center.x(), zoom); invalidate(); } void SlippyMap::handleNetworkData(QNetworkReply *reply) { QImage img; QPoint tp = reply->request().attribute(QNetworkRequest::User).toPoint(); if (!reply->error()) if (!img.load(reply, 0)) img = QImage(); reply->deleteLater(); m_tilePixmaps[tp] = QPixmap::fromImage(img); if (img.isNull()) m_tilePixmaps[tp] = m_emptyTile; emit updated(tileRect(tp)); // purge unused spaces const QRect bound = m_tilesRect.adjusted(-2, -2, 2, 2); for (auto it = m_tilePixmaps.keyBegin(); it != m_tilePixmaps.keyEnd(); ++it) { const QPoint &tp = *it; if (!bound.contains(tp)) m_tilePixmaps.remove(tp); } download(); } void SlippyMap::download() { QPoint grab(0, 0); for (int x = 0; x <= m_tilesRect.width(); ++x) for (int y = 0; y <= m_tilesRect.height(); ++y) { QPoint tp = m_tilesRect.topLeft() + QPoint(x, y); if (!m_tilePixmaps.contains(tp)) { grab = tp; break; } } if (grab == QPoint(0, 0)) { m_url = QUrl(); return; } QString path = "http://tile.openstreetmap.org/%1/%2/%3.png"; m_url = QUrl(path.arg(zoom).arg(grab.x()).arg(grab.y())); QNetworkRequest request; request.setUrl(m_url); request.setRawHeader("User-Agent", "The Qt Company (Qt) Graphics Dojo 1.0"); request.setAttribute(QNetworkRequest::User, QVariant(grab)); m_manager.get(request); } QRect SlippyMap::tileRect(const QPoint &tp) { QPoint t = tp - m_tilesRect.topLeft(); int x = t.x() * tdim + m_offset.x(); int y = t.y() * tdim + m_offset.y(); return QRect(x, y, tdim, tdim); }