Files
qad/libs/map/mapview.cpp
peri4 958c81fb1d version 2.13.0
add Map library (MapView with OSM maps and items) and mapviewer util
2023-01-20 09:16:42 +03:00

318 lines
7.7 KiB
C++

#include "mapview.h"
#include "osm_downloader_p.h"
#include "osm_math_p.h"
#include "osm_tile_cache_p.h"
#include "qad_types.h"
#include <QGraphicsPixmapItem>
#include <QGraphicsScene>
#include <QPainter>
#include <QPixmap>
#include <QScrollBar>
#include <QWheelEvent>
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]() { 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);
updateViewRect();
drawBackground();
}
void MapView::setZoom(double z) {
zoomTo(z);
}
void MapView::mousePressEvent(QMouseEvent * e) {
is_pan = false;
press_point = e->pos();
}
void MapView::mouseReleaseEvent(QMouseEvent * e) {
if (!is_pan) {
if (hover) emit itemClicked(hover);
}
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;
press_point = e->pos();
updateViewRect();
drawBackground();
} else {
if ((e->pos() - press_point).manhattanLength() >= QApplication::startDragDistance()) is_pan = true;
}
}
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);
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);
auto tile = cache->getTile((OSM::TileIndex){zoom_level, ix, iy});
if (!tile.isEmpty()) {
p.drawPixmap(r, tile.pixmap, QRectF(tile.pixmap.rect()));
} 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.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_ = std::pow(2., zoom_) * tileSize / mins;
updateViewRect();
}
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(-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;
view_rect.translate(prev_center - new_center);
updateZoomLevel();
drawBackground();
}
void MapView::zoomTo(double level) {
zoom_ = level;
checkZoom();
updateZoomLevel();
drawBackground();
}
void MapView::centerTo(QPointF coord) {
center_ = OSM::geo2xy(coord);
updateViewRect();
drawBackground();
}