334 lines
8.2 KiB
C++
334 lines
8.2 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);
|
|
checkCenter();
|
|
updateViewRect();
|
|
drawBackground();
|
|
}
|
|
|
|
|
|
void MapView::setZoom(double z) {
|
|
zoomTo(z);
|
|
}
|
|
|
|
|
|
void MapView::mousePressEvent(QMouseEvent * e) {
|
|
is_pan = false;
|
|
press_point = e->pos();
|
|
last_click_coord = OSM::xy2geo(mapToNorm(press_point));
|
|
}
|
|
|
|
|
|
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;
|
|
checkCenter();
|
|
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 * 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_ = std::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();
|
|
}
|