diff --git a/CMakeLists.txt b/CMakeLists.txt index 1612b32..781c2e7 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ add_subdirectory(samples/layers) add_subdirectory(samples/fun) add_subdirectory(samples/10000) add_subdirectory(samples/debug) +add_subdirectory(samples/performance) add_subdirectory(samples/custom-tiles) add_subdirectory(samples/moving-objects) add_subdirectory(samples/mouse-actions) diff --git a/HOWTO.md b/HOWTO.md index c4c964a..14df37e 100644 --- a/HOWTO.md +++ b/HOWTO.md @@ -28,6 +28,14 @@ Special flags for draw items in [flags](samples/flags): Map with several object-layers in [layers](samples/layers) +### Performance + +Background map has biggest impact to performance because it covers whole map area all the time. +QGVLayerTiles always calculates needed set of tiles for current camera state and performance parameters +will adjust algorithm. + +Example for performance tuning can be found in [performance](samples/performance) + ### Debug and logging How to catch debug info in qDebug or visually on map [debug](samples/debug) diff --git a/QGeoView.pro b/QGeoView.pro index cef1b10..1ba28ba 100644 --- a/QGeoView.pro +++ b/QGeoView.pro @@ -10,6 +10,7 @@ SUBDIRS = \ samples/fun \ samples/10000 \ samples/debug \ + samples/performance \ samples/custom-tiles \ samples/moving-objects \ samples/mouse-actions \ @@ -24,6 +25,7 @@ samples.layers.depends = lib samples.shared samples.fun.depends = lib samples.shared samples.10000.depends = lib samples.shared samples.debug.depends = lib samples.shared +samples.performance.depends = lib samples.shared samples.custom-tiles.depends = lib samples.shared samples.moving-objects.depends = lib samples.shared samples.mouse-actions.depends = lib samples.shared diff --git a/lib/include/QGeoView/QGVLayerTiles.h b/lib/include/QGeoView/QGVLayerTiles.h index b6b1ea0..3f26480 100644 --- a/lib/include/QGeoView/QGVLayerTiles.h +++ b/lib/include/QGeoView/QGVLayerTiles.h @@ -29,6 +29,13 @@ class QGV_LIB_DECL QGVLayerTiles : public QGVLayer public: QGVLayerTiles(); + void setTilesMarginWithZoomChange(size_t value); + void setTilesMarginNoZoomChange(size_t value); + void setAnimationUpdateDelayMs(size_t value); + void setVisibleZoomLayersBelowCurrent(size_t value); + void setVisibleZoomLayersAboveCurrent(size_t value); + void setCameraUpdatesDuringAnimation(bool value); + protected: void onProjection(QGVMap* geoMap) override; void onCamera(const QGVCameraState& oldState, const QGVCameraState& newState) override; @@ -46,6 +53,7 @@ class QGV_LIB_DECL QGVLayerTiles : public QGVLayer void processCamera(); void removeAllAbove(const QGV::GeoTilePos& tilePos); void removeWhenCovered(const QGV::GeoTilePos& tilePos); + void removeForPerfomance(const QGV::GeoTilePos& tilePos); void addTile(const QGV::GeoTilePos& tilePos, QGVDrawItem* tileObj); void removeTile(const QGV::GeoTilePos& tilePos); bool isTileExists(const QGV::GeoTilePos& tilePos) const; @@ -56,5 +64,16 @@ class QGV_LIB_DECL QGVLayerTiles : public QGVLayer int mCurZoom; QRect mCurRect; QMap> mIndex; + QElapsedTimer mLastAnimation; + + struct + { + size_t TilesMarginWithZoomChange = 1; + size_t TilesMarginNoZoomChange = 3; + size_t AnimationUpdateDelayMs = 200; + bool CameraUpdatesDuringAnimation = true; + size_t VisibleZoomLayersBelowCurrent = 10; + size_t VisibleZoomLayersAboveCurrent = 10; + } mPerfomanceProfile; }; diff --git a/lib/src/QGVLayerTiles.cpp b/lib/src/QGVLayerTiles.cpp index 60ca08a..d65c8f0 100644 --- a/lib/src/QGVLayerTiles.cpp +++ b/lib/src/QGVLayerTiles.cpp @@ -21,18 +21,48 @@ #include -namespace { -int minMargin = 1; -int maxMargin = 3; -int msAnimationUpdateDelay = 250; -} - QGVLayerTiles::QGVLayerTiles() { mCurZoom = -1; sendToBack(); } +void QGVLayerTiles::setTilesMarginWithZoomChange(size_t value) +{ + mPerfomanceProfile.TilesMarginWithZoomChange = value; + qgvDebug() << "TilesMarginWithZoomChange changed to" << value; +} + +void QGVLayerTiles::setTilesMarginNoZoomChange(size_t value) +{ + mPerfomanceProfile.TilesMarginNoZoomChange = value; + qgvDebug() << "TilesMarginNoZoomChange changed to" << value; +} + +void QGVLayerTiles::setAnimationUpdateDelayMs(size_t value) +{ + mPerfomanceProfile.AnimationUpdateDelayMs = value; + qgvDebug() << "AnimationUpdateDelayMs changed to" << value; +} + +void QGVLayerTiles::setVisibleZoomLayersBelowCurrent(size_t value) +{ + mPerfomanceProfile.VisibleZoomLayersBelowCurrent = value; + qgvDebug() << "VisibleZoomLayersBelowCurrent changed to" << value; +} + +void QGVLayerTiles::setVisibleZoomLayersAboveCurrent(size_t value) +{ + mPerfomanceProfile.VisibleZoomLayersAboveCurrent = value; + qgvDebug() << "VisibleZoomLayersAboveCurrent changed to" << value; +} + +void QGVLayerTiles::setCameraUpdatesDuringAnimation(bool value) +{ + mPerfomanceProfile.CameraUpdatesDuringAnimation = value; + qgvDebug() << "CameraUpdatesDuringAnimation changed to" << value; +} + void QGVLayerTiles::onProjection(QGVMap* geoMap) { QGVLayer::onProjection(geoMap); @@ -41,15 +71,19 @@ void QGVLayerTiles::onProjection(QGVMap* geoMap) void QGVLayerTiles::onCamera(const QGVCameraState& oldState, const QGVCameraState& newState) { QGVLayer::onCamera(oldState, newState); + if (oldState == newState) { return; } bool needUpdate = true; + if (newState.animation()) { - if (!mLastAnimation.isValid()) { + if (!mPerfomanceProfile.CameraUpdatesDuringAnimation) { + needUpdate = false; + } else if (!mLastAnimation.isValid()) { mLastAnimation.start(); - } else if (mLastAnimation.elapsed() < msAnimationUpdateDelay) { + } else if (mLastAnimation.elapsed() < static_cast(mPerfomanceProfile.AnimationUpdateDelayMs)) { needUpdate = false; } else { mLastAnimation.restart(); @@ -57,6 +91,7 @@ void QGVLayerTiles::onCamera(const QGVCameraState& oldState, const QGVCameraStat } else { mLastAnimation.invalidate(); } + if (needUpdate) { processCamera(); } @@ -123,7 +158,8 @@ void QGVLayerTiles::processCamera() const bool zoomChanged = (mCurZoom != newZoom); mCurZoom = newZoom; - const int margin = (zoomChanged) ? minMargin : maxMargin; + const int margin = + (zoomChanged) ? mPerfomanceProfile.TilesMarginWithZoomChange : mPerfomanceProfile.TilesMarginNoZoomChange; const int sizePerZoom = static_cast(qPow(2, mCurZoom)); const QRect maxRect = QRect(QPoint(0, 0), QPoint(sizePerZoom, sizePerZoom)); const QPoint topLeft = QGV::GeoTilePos::geoToTilePos(mCurZoom, areaGeoRect.topLeft()).pos(); @@ -147,17 +183,15 @@ void QGVLayerTiles::processCamera() for (const QGV::GeoTilePos& current : existingTiles(zoom)) { removeAllAbove(current); } - continue; - } - for (const QGV::GeoTilePos& nonCurrent : existingTiles(zoom)) { - if (!isTileFinished(nonCurrent)) { - qgvDebug() << "cancel non-finished" << nonCurrent; - removeTile(nonCurrent); - continue; - } - if (zoom < mCurZoom) { - removeWhenCovered(nonCurrent); - continue; + } else { + for (const QGV::GeoTilePos& nonCurrent : existingTiles(zoom)) { + if (!isTileFinished(nonCurrent)) { + qgvDebug() << "cancel non-finished" << nonCurrent; + removeTile(nonCurrent); + } else if (zoom < mCurZoom) { + removeWhenCovered(nonCurrent); + } + removeForPerfomance(nonCurrent); } } } @@ -184,6 +218,7 @@ void QGVLayerTiles::processCamera() missing.insert(radius, tilePos); } } + for (const QGV::GeoTilePos& tilePos : missing) { addTile(tilePos, nullptr); } @@ -229,6 +264,17 @@ void QGVLayerTiles::removeWhenCovered(const QGV::GeoTilePos& tilePos) } } +void QGVLayerTiles::removeForPerfomance(const QGV::GeoTilePos& tilePos) +{ + const auto minZoom = mCurZoom - static_cast(mPerfomanceProfile.VisibleZoomLayersBelowCurrent); + const auto maxZoom = mCurZoom + static_cast(mPerfomanceProfile.VisibleZoomLayersAboveCurrent); + + if (tilePos.zoom() < minZoom || tilePos.zoom() > maxZoom) { + qgvDebug() << "delete because of performance request" << minZoom << maxZoom; + removeTile(tilePos); + } +} + void QGVLayerTiles::addTile(const QGV::GeoTilePos& tilePos, QGVDrawItem* tileObj) { if (isTileFinished(tilePos)) { diff --git a/lib/src/QGVMapQGView.cpp b/lib/src/QGVMapQGView.cpp index 40eef12..f5e6221 100644 --- a/lib/src/QGVMapQGView.cpp +++ b/lib/src/QGVMapQGView.cpp @@ -34,8 +34,8 @@ namespace { int wheelAreaMargin = 10; -double wheelExponentDown = qPow(2, 1.0 / 5.0); -double wheelExponentUp = qPow(2, 1.0 / 2.0); +double wheelExponentDown = qPow(2, 1.0 / 2.0); +double wheelExponentUp = qPow(2, 1.0 / 1.5); } QGVMapQGView::QGVMapQGView(QGVMap* geoMap) diff --git a/samples/performance/CMakeLists.txt b/samples/performance/CMakeLists.txt new file mode 100644 index 0000000..002b492 --- /dev/null +++ b/samples/performance/CMakeLists.txt @@ -0,0 +1,37 @@ +set(CMAKE_CXX_STANDARD 11) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Set the QT version +find_package(Qt6 COMPONENTS Core QUIET) +if (NOT Qt6_FOUND) + set(QT_VERSION 5 CACHE STRING "Qt version for QGeoView") +else() + set(QT_VERSION 6 CACHE STRING "Qt version for QGeoView") +endif() + +find_package(Qt${QT_VERSION} REQUIRED COMPONENTS + Core + Gui + Widgets + Network +) + +add_executable(qgeoview-samples-performance + main.cpp + mainwindow.h + mainwindow.cpp +) + +target_link_libraries(qgeoview-samples-performance + PRIVATE + Qt${QT_VERSION}::Core + Qt${QT_VERSION}::Network + Qt${QT_VERSION}::Gui + Qt${QT_VERSION}::Widgets + QGeoView + qgeoview-samples-shared +) diff --git a/samples/performance/main.cpp b/samples/performance/main.cpp new file mode 100644 index 0000000..6c72f24 --- /dev/null +++ b/samples/performance/main.cpp @@ -0,0 +1,37 @@ +/*************************************************************************** + * QGeoView is a Qt / C ++ widget for visualizing geographic data. + * Copyright (C) 2018-2023 Andrey Yaroshenko. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see https://www.gnu.org/licenses. + ****************************************************************************/ + +#include +#include + +#include "mainwindow.h" + +int main(int argc, char* argv[]) +{ + QApplication app(argc, argv); + app.setApplicationName("QGeoView Samples"); + + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + parser.process(app); + + MainWindow window; + window.show(); + return app.exec(); +} diff --git a/samples/performance/mainwindow.cpp b/samples/performance/mainwindow.cpp new file mode 100644 index 0000000..bbddf15 --- /dev/null +++ b/samples/performance/mainwindow.cpp @@ -0,0 +1,248 @@ +/*************************************************************************** + * QGeoView is a Qt / C ++ widget for visualizing geographic data. + * Copyright (C) 2018-2023 Andrey Yaroshenko. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see https://www.gnu.org/licenses. + ****************************************************************************/ + +#include "mainwindow.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +MainWindow::MainWindow() +{ + setWindowTitle("QGeoView Samples - performance profiles"); + + mMap = new QGVMap(this); + setCentralWidget(mMap); + + Helpers::setupCachedNetworkAccessManager(this); + + // Background layer + mBackground = new QGVLayerGoogle(); + mMap->addItem(mBackground); + + // Widgets + mMap->addWidget(new QGVWidgetCompass()); + + // 10000 layer + mMap->addItem(create10000Layer()); + + // Options list + centralWidget()->layout()->addWidget(createOptionsList()); + + // Show whole world + auto target = mMap->getProjection()->boundaryGeoRect(); + mMap->cameraTo(QGVCameraActions(mMap).scaleTo(target)); + + // Enable debug + QGV::setPrintDebug(true); +} + +MainWindow::~MainWindow() +{ +} + +QGV::GeoRect MainWindow::target10000Area() const +{ + return mMap->getProjection()->boundaryGeoRect(); +} + +QGVLayer* MainWindow::create10000Layer() const +{ + /* + * Layers will be owned by map. + */ + auto target = target10000Area(); + auto layer = new QGVLayer(); + layer->setName("10000 elements"); + layer->setDescription("Demo for 10000 elements"); + + /* + * Items will be owned by layer. + */ + const int size = 20000; + for (int i = 0; i < 10000; i++) { + auto item = new Rectangle(Helpers::randRect(mMap, target, size), Qt::red); + layer->addItem(item); + } + + return layer; +} + +QGroupBox* MainWindow::createOptionsList() +{ + /* + * Options in this list is only recommendations and hard-coded values can be mixed or adopted to your application. + * + * Background map has biggest impact to performance because it covers whole map area all the time. + * QGVLayerTiles always calculates needed set of tiles for current camera state and parameters below will adjust + * algorithm. + * + * TilesMarginWithZoomChange, TilesMarginNoZoomChange are defining margin around visible map area. + * First right after zoom change and second after map move/rotate. Bigger margin gives better feedback for user + * when map moved by mouse, but from other side it increases number of tiles loaded to map. Don't use values + * below 1. + * + * AnimationUpdateDelayMs, CameraUpdatesDuringAnimation are defining layer behavior during animation (flyTo method) + * First defines time interval between camera update processing (during animation only!) and second will disable + * any processing during animation completely. This parameter will have large impact to performance in case + * if application performs many "jumps" by flyTo method. When processing is disabled or interval is very large QGV + * will show gray areas on missing tiles during animation. + * + * VisibleZoomLayersBelowCurrent, VisibleZoomLayersAboveCurrent are defining number of zoom levels which layer can + * keep below and above current level. This is most impacting to performance parameters (especially + * VisibleZoomLayersBelowCurrent). By default QGVLayerTiles cleanups tiles based on tiles coverage. For example when + * tile below (bigger one) is fully covered by tiles above (smaller) then big one will be deleted. Or when bigger + * tile of current zoom level is loaded all tiles above it will be deleted. This gives nice user feedback, but can + * lead to high load on scene, especially when network had low latency and tiles from low levels are consistently + * upscaled. VisibleZoomLayersBelowCurrent, VisibleZoomLayersAboveCurrent are limiting QGVLayerTiles to keep only + * given number of zoom levels above or below current one. When is equals to 0 then only current level is allowed. + */ + + QGroupBox* groupBox = new QGroupBox(tr("Profiles")); + groupBox->setLayout(new QVBoxLayout); + + { + QPushButton* button = new QPushButton("Try it!"); + groupBox->layout()->addWidget(button); + + connect(button, &QPushButton::clicked, this, [this]() { flyToRandomArea(); }); + } + + QButtonGroup* group = new QButtonGroup(this); + + { + QRadioButton* radioButton = new QRadioButton("Best look"); + group->addButton(radioButton); + + QLabel* description = new QLabel(); + description->setText("Set of parameters which gives best look for user but with price of performance.\nThis is " + "default values for any tile map."); + + QWidget* item = new QWidget(); + item->setLayout(new QVBoxLayout()); + item->layout()->addWidget(radioButton); + item->layout()->addWidget(description); + + groupBox->layout()->addWidget(item); + + connect(radioButton, &QRadioButton::clicked, this, [this](const bool checked) { + if (checked) + setupProfileLook(); + }); + } + + { + QRadioButton* radioButton = new QRadioButton("Balanced"); + group->addButton(radioButton); + + QLabel* description = new QLabel(); + description->setText("Set of parameters which gives optimal look for user with optimal performance. Can " + "produce 'gray' areas during animation and camera actions."); + + QWidget* item = new QWidget(); + item->setLayout(new QVBoxLayout()); + item->layout()->addWidget(radioButton); + item->layout()->addWidget(description); + + groupBox->layout()->addWidget(item); + + connect(radioButton, &QRadioButton::clicked, this, [this](const bool checked) { + if (checked) + setupProfileBalance(); + }); + + radioButton->click(); + } + + { + QRadioButton* radioButton = new QRadioButton("Fast"); + group->addButton(radioButton); + + QLabel* description = new QLabel(); + description->setText("Set of parameters which gives best performance. This setup will produce 'gray' areas " + "every time during animation and camera actions."); + + QWidget* item = new QWidget(); + item->setLayout(new QVBoxLayout()); + item->layout()->addWidget(radioButton); + item->layout()->addWidget(description); + + groupBox->layout()->addWidget(item); + + connect(radioButton, &QRadioButton::clicked, this, [this](const bool checked) { + if (checked) + setupProfileFast(); + }); + } + + return groupBox; +} + +void MainWindow::flyToRandomArea() +{ + static int current = 0; + static const std::vector areas = { + QGV::GeoRect(QGV::GeoPos(56.316425, 80.670445), QGV::GeoPos(53.280950, 86.641856)), + QGV::GeoRect(QGV::GeoPos(52.131852, 4.989964), QGV::GeoPos(44.071465, 18.708665)), + QGV::GeoRect(QGV::GeoPos(-17.631899, 20.654501), QGV::GeoPos(-29.494330, 35.357840)), + }; + + QTimer::singleShot(100, this, [&]() { + mMap->flyTo(QGVCameraActions(mMap).scaleTo(areas[current % areas.size()])); + current++; + }); +} + +void MainWindow::setupProfileLook() +{ + mBackground->setTilesMarginWithZoomChange(1); + mBackground->setTilesMarginNoZoomChange(3); + mBackground->setAnimationUpdateDelayMs(200); + mBackground->setVisibleZoomLayersBelowCurrent(10); + mBackground->setVisibleZoomLayersAboveCurrent(10); + mBackground->setCameraUpdatesDuringAnimation(true); +} + +void MainWindow::setupProfileBalance() +{ + mBackground->setTilesMarginWithZoomChange(1); + mBackground->setTilesMarginNoZoomChange(2); + mBackground->setAnimationUpdateDelayMs(250); + mBackground->setVisibleZoomLayersBelowCurrent(1); + mBackground->setVisibleZoomLayersAboveCurrent(3); + mBackground->setCameraUpdatesDuringAnimation(true); +} + +void MainWindow::setupProfileFast() +{ + mBackground->setTilesMarginWithZoomChange(1); + mBackground->setTilesMarginNoZoomChange(1); + mBackground->setAnimationUpdateDelayMs(500); + mBackground->setVisibleZoomLayersBelowCurrent(1); + mBackground->setVisibleZoomLayersAboveCurrent(1); + mBackground->setCameraUpdatesDuringAnimation(false); +} diff --git a/samples/performance/mainwindow.h b/samples/performance/mainwindow.h new file mode 100644 index 0000000..a4a1150 --- /dev/null +++ b/samples/performance/mainwindow.h @@ -0,0 +1,49 @@ +/*************************************************************************** + * QGeoView is a Qt / C ++ widget for visualizing geographic data. + * Copyright (C) 2018-2023 Andrey Yaroshenko. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see https://www.gnu.org/licenses. + ****************************************************************************/ + +#pragma once + +#include +#include + +#include +#include +#include + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(); + ~MainWindow(); + + QGV::GeoRect target10000Area() const; + QGVLayer* create10000Layer() const; + + QGroupBox* createOptionsList(); + + void flyToRandomArea(); + void setupProfileLook(); + void setupProfileBalance(); + void setupProfileFast(); + +private: + QGVMap* mMap; + QGVLayerTiles* mBackground; +}; diff --git a/samples/performance/performance.pro b/samples/performance/performance.pro new file mode 100644 index 0000000..0247594 --- /dev/null +++ b/samples/performance/performance.pro @@ -0,0 +1,16 @@ +TARGET = qgeoview-samples-performance +TEMPLATE = app +CONFIG-= console + +include(../../lib/lib.pri) +include(../shared/shared.pri) + +LIBS += -L$$OUT_PWD/../../lib -lqgeoview +LIBS += -L$$OUT_PWD/../shared -lqgeoview-samples-shared + +SOURCES += \ + main.cpp \ + mainwindow.cpp + +HEADERS += \ + mainwindow.h