#include "mapview.h" #include "osm_downloader_p.h" #include "osm_math_p.h" #include "osm_tile_cache_p.h" #include "qad_types.h" #include #include #include #include #include #include #include #include const int tileSize = 256; MapView::MapView(QWidget * parent): QWidget(parent) { downloader = new OSMDownloader(this); cache = new OSMTileCache(this); setMouseTracking(true); connect(cache, &OSMTileCache::tileReady, this, [this]() { if (!is_downloading) drawBackground(); }); int size = 16; QPixmap px(QSize(size, size) * 2); QPainter p(&px); for (int i = 0; i < 2; ++i) { for (int j = 0; j < 2; ++j) { p.fillRect(i * size, j * size, size, size, (((i + j) % 2) == 0) ? Qt::white : Qt::lightGray); } } p.end(); brush_tr.setTexture(px); zoomTo(0); } MapView::~MapView() { for (auto * i: items_) i->parent = nullptr; qDeleteAll(items_); delete cache; delete downloader; } void MapView::addItem(MapItemBase * item) { if (items_.contains(item)) return; items_ << item; item->parent = this; item->updatePosition(); update(); } void MapView::removeItem(MapItemBase * item) { if (!items_.contains(item)) return; items_.removeOne(item); if (hover == item) hover = nullptr; } QPointF MapView::center() const { return OSM::xy2geo(center_); } void MapView::setCenter(QPointF c) { center_ = OSM::geo2xy(c); checkCenter(); updateViewRect(); drawBackground(); } void MapView::setZoom(double z) { zoomTo(z); } QString MapView::cachePath() const { return cache->cacheRoot(); } void MapView::setCachePath(const QString & p) { cache->setCacheRoot(p); drawBackground(); } void MapView::setTileObsoleteTime(int secs) { cache->setTileObsoleteTime(secs); } void collectDownloadIndeces(OSMTileCache * cache, QQueue & indeces, OSM::TileIndex index, int target_zoom_level, bool only_new) { if (only_new) { if (!cache->isTileFileExists(index)) indeces << index; } else indeces << index; if (index.z >= target_zoom_level) return; int z = index.z + 1; int x = index.x * 2; int y = index.y * 2; for (int i = 0; i < 2; ++i) { for (int j = 0; j < 2; ++j) { collectDownloadIndeces(cache, indeces, (OSM::TileIndex){z, x + i, y + j}, target_zoom_level, only_new); } } } void MapView::downloadCurrentView(int target_zoom_level, bool only_new) { int sx = qMax(0, (int)floor(view_rect.left() * tiles_side)); int sy = qMax(0, (int)floor(view_rect.top() * tiles_side)); int ex = qMin(tiles_side, (int)ceil(view_rect.right() * tiles_side)); int ey = qMin(tiles_side, (int)ceil(view_rect.bottom() * tiles_side)); target_zoom_level = qMin(target_zoom_level, max_level); QQueue indeces; for (int ix = sx; ix < ex; ++ix) { for (int iy = sy; iy < ey; ++iy) { collectDownloadIndeces(cache, indeces, (OSM::TileIndex){zoom_level, ix, iy}, target_zoom_level, only_new); } } if (indeces.size() < 4) return; QProgressDialog dialog; dialog.setWindowTitle(tr("MapView")); dialog.setLabelText(tr("Downloading ...")); dialog.setCancelButtonText(tr("Cancel")); dialog.setMaximum(indeces.size()); dialog.setValue(0); downloader->clearQueue(); std::atomic_int value(0); auto conn = connect( downloader, &OSMDownloader::tileDone, this, [this, &dialog, &indeces, &value]() { // qDebug() << "done"; value++; int cv = value; dialog.setValue(cv); if (indeces.isEmpty()) dialog.cancel(); else { downloader->queueTile(indeces.dequeue(), true); } }, Qt::DirectConnection); connect(&dialog, &QProgressDialog::canceled, this, [this, &dialog]() { dialog.cancel(); downloader->clearQueue(); }); is_downloading = true; for (int i = 0; i < 1; ++i) downloader->queueTile(indeces.dequeue()); // dialog.exec(); dialog.setModal(true); dialog.show(); while (!dialog.isHidden()) { QThread::msleep(1); QApplication::processEvents(); } disconnect(conn); is_downloading = false; drawBackground(); } void MapView::mousePressEvent(QMouseEvent * e) { is_pan = false; press_point = e->pos(); zoom_anchor = press_point; last_click_coord = OSM::xy2geo(mapToNorm(press_point)); } void MapView::mouseReleaseEvent(QMouseEvent * e) { if (!is_pan) { if (hover) emit itemClicked(hover); else emit mapClicked(last_click_coord); } is_pan = false; } void MapView::mouseDoubleClickEvent(QMouseEvent * e) {} void MapView::mouseMoveEvent(QMouseEvent * e) { // QPointF geo = OSM::xy2geo(mapToNorm(e->pos())); // qDebug() << QString("%1 , %2").arg(geo.x(), 0, 'f', 5).arg(geo.y(), 0, 'f', 5); if (e->buttons() == Qt::LeftButton) { if (is_pan) { double mins = qMin(width(), height()); center_ -= QPointF(e->pos() - press_point) / scale_ / mins; checkCenter(); press_point = e->pos(); updateViewRect(); drawBackground(); } else { if ((e->pos() - press_point).manhattanLength() >= QApplication::startDragDistance()) is_pan = true; } } if (e->buttons() == Qt::RightButton) { auto dp = e->pos() - press_point; press_point = e->pos(); zoom(1. - dp.y() / 50., zoom_anchor); } updateMouse(e->pos()); } void MapView::wheelEvent(QWheelEvent * e) { double scl = e->angleDelta().x() == 0 ? e->angleDelta().y() : e->angleDelta().x(); scl = 1. + (scl / 400.); QPoint mp; #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) mp = e->pos(); #else mp = e->position().toPoint(); #endif zoom(scl, mp); } void MapView::paintEvent(QPaintEvent *) { QPainter p(this); p.drawPixmap(0, 0, background); drawItems(p); updateMouse(mapFromGlobal(QCursor::pos())); } void MapView::resizeEvent(QResizeEvent *) { cache->setMinimumCacheSize((width() + tileSize) * (height() + tileSize) * 4 * 3); checkZoom(); updateZoomLevel(); updateViewRect(); drawBackground(); } void MapView::drawBackground() { if (size() != background.size()) background = QPixmap(size()); background.fill(palette().window().color()); QPainter p(&background); double mins = qMin(width(), height()); int sx = qMax(0, (int)floor(view_rect.left() * tiles_side)); int sy = qMax(0, (int)floor(view_rect.top() * tiles_side)); int ex = qMin(tiles_side, (int)ceil(view_rect.right() * tiles_side)); int ey = qMin(tiles_side, (int)ceil(view_rect.bottom() * tiles_side)); double ts = 1. / tiles_side / qMax(view_rect.width(), view_rect.height()) * qMax(width(), height()); QPointF offset = view_rect.topLeft() * mins * scale_; p.setRenderHint(QPainter::SmoothPixmapTransform); p.setPen(Qt::white); for (int ix = sx; ix < ex; ++ix) { for (int iy = sy; iy < ey; ++iy) { QRectF r((ix)*ts, (iy)*ts, ts, ts); r.translate(-offset); QRectF px_rct(QPointF(), QSizeF(tileSize, tileSize)); auto tile = cache->getTile((OSM::TileIndex){zoom_level, ix, iy}, px_rct); if (!tile.isEmpty()) { p.drawPixmap(r, tile.pixmap, px_rct); } else { p.fillRect(r, brush_tr); } // p.setPen(Qt::white); // p.drawText(r, Qt::AlignCenter, QString("%1x%2").arg(ix).arg(iy)); } } // qDebug() << sx << sy << ex << ey << ts; update(); } void MapView::drawItems(QPainter & p) { auto src_tr = p.transform(); p.setRenderHint(QPainter::Antialiasing); p.setRenderHint(QPainter::SmoothPixmapTransform); QPoint mouse = mapFromGlobal(QCursor::pos()); MapItemBase * hover = nullptr; for (auto * i: items_) { if (!i->isVisible()) continue; QPointF ipos = mapFromNorm(i->norm_pos); p.setTransform(src_tr); p.translate(ipos); p.translate(i->m_offset.x(), -i->m_offset.y()); p.rotate(i->getRotation()); if (!i->ignore_scale) p.scale(i->getScale().x(), i->getScale().y()); i->draw(&p); QTransform mtr; mtr.rotate(-i->getRotation()); mtr.translate(-ipos.x(), -ipos.y()); QPoint m = mtr.map(mouse); if (i->m_bounding.contains(m)) { hover = i; } } if (hover) setCursor(hover->cursor()); else unsetCursor(); } void MapView::checkZoom() { static const double max = 20; if (zoom_ < 0.) zoom_ = 0.; if (zoom_ > max) zoom_ = max; double mins = qMin(width(), height()) / appScale(this); scale_ = pow(2., zoom_) * tileSize / mins; updateViewRect(); } void MapView::checkCenter() { if (center_.x() < 0.) center_.setX(0.); if (center_.x() > 1.) center_.setX(1.); if (center_.y() < 0.) center_.setY(0.); if (center_.y() > 1.) center_.setY(1.); } void MapView::zoomLevelChanged(int new_level) { zoom_level = qBound(0, new_level, max_level); // qDebug() << "level changed" << new_level << zoom_level; tiles_side = 1 << zoom_level; // for (int x = 0; x < tiles_side; ++x) // for (int y = 0; y < tiles_side; ++y) // downloader->queueTile(OSM::TileIndex{zoom_level, x, y}); } void MapView::updateZoomLevel() { int zl = qRound(zoom_); if (zl != zoom_level) zoomLevelChanged(zl); } void MapView::updateViewRect() { double mins = qMin(width(), height()); if (mins <= 0) return; view_rect.setRect(0, 0, (width() / mins) / scale_, (height() / mins) / scale_); view_rect.moveCenter(center_); for (auto * i: items_) i->zoomChanged(); } void MapView::updateMouse(QPoint mouse) { MapItemBase * new_hover = nullptr; for (auto * i: items_) { if (!i->isVisible() || !i->isInteracive()) continue; QPointF ipos = mapFromNorm(i->norm_pos); QTransform mtr; if (!i->ignore_scale) mtr.scale(1. / i->getScale().x(), 1. / i->getScale().y()); mtr.rotate(-i->getRotation()); mtr.translate(-i->m_offset.x(), i->m_offset.y()); mtr.translate(-ipos.x(), -ipos.y()); QPoint m = mtr.map(mouse); if (i->m_bounding.contains(m)) { new_hover = i; } } if (new_hover) setCursor(new_hover->cursor()); else unsetCursor(); if (new_hover != hover) { if (hover) emit itemLeaved(hover); if (new_hover) emit itemEntered(new_hover); } hover = new_hover; } double MapView::scalePx2M(QPointF norm) { double lat = OSM::y2lat(norm.y()) * M_PI / 180.; px2m = qMax(view_rect.width(), view_rect.height()) / qMax(width(), height()) * tileSize; px2m *= 156543.03 * cos(lat); // qDebug() << px2m << lat; return px2m; } QPointF MapView::mapToNorm(QPoint screen) const { QPointF s(screen.x() / (double)width(), screen.y() / (double)height()); return view_rect.topLeft() + QPointF(view_rect.width() * s.x(), view_rect.height() * s.y()); } QPoint MapView::mapFromNorm(QPointF norm) const { QPointF s = norm - view_rect.topLeft(); return QPoint(qRound((s.x() / view_rect.width()) * width()), qRound((s.y() / view_rect.height()) * height())); } void MapView::zoom(double factor) { zoom(factor, rect().center()); } void MapView::zoom(double factor, QPoint anchor) { QPointF prev_center = mapToNorm(anchor); zoom_ += (factor - 1.); checkZoom(); QPointF new_center = mapToNorm(anchor); center_ += prev_center - new_center; checkCenter(); view_rect.moveCenter(center_); updateZoomLevel(); drawBackground(); } void MapView::zoomTo(double level) { zoom_ = level; checkZoom(); updateZoomLevel(); drawBackground(); } void MapView::centerTo(QPointF coord) { center_ = OSM::geo2xy(coord); checkCenter(); updateViewRect(); drawBackground(); }