File indexing completed on 2025-01-19 03:58:09
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2010-12-05 0007 * Description : Merges tiles into groups 0008 * 0009 * SPDX-FileCopyrightText: 2010-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * SPDX-FileCopyrightText: 2010-2011 by Michael G. Hansen <mike at mghansen dot de> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "tilegrouper.h" 0017 0018 // Qt includes 0019 0020 #include <QElapsedTimer> 0021 0022 // C++ includes 0023 0024 #include <cmath> 0025 0026 // local includes 0027 0028 #include "abstractmarkertiler.h" 0029 #include "mapbackend.h" 0030 #include "digikam_debug.h" 0031 0032 namespace Digikam 0033 { 0034 0035 class Q_DECL_HIDDEN TileGrouper::Private 0036 { 0037 public: 0038 0039 explicit Private() 0040 : clustersDirty(true), 0041 currentBackend(nullptr) 0042 { 0043 } 0044 0045 bool clustersDirty; 0046 MapBackend* currentBackend; 0047 }; 0048 0049 TileGrouper::TileGrouper(const QExplicitlySharedDataPointer<GeoIfaceSharedData>& sharedData, 0050 QObject* const parent) 0051 : QObject(parent), 0052 d(new Private), 0053 s(sharedData) 0054 { 0055 qRegisterMetaType<QVector<int>>("QVector<int>"); 0056 } 0057 0058 TileGrouper::~TileGrouper() 0059 { 0060 } 0061 0062 void TileGrouper::setClustersDirty() 0063 { 0064 d->clustersDirty = true; 0065 } 0066 0067 bool TileGrouper::getClustersDirty() const 0068 { 0069 return d->clustersDirty; 0070 } 0071 0072 void TileGrouper::setCurrentBackend(MapBackend* const backend) 0073 { 0074 d->currentBackend = backend; 0075 } 0076 0077 bool TileGrouper::currentBackendReady() 0078 { 0079 if (!d->currentBackend) 0080 { 0081 return false; 0082 } 0083 0084 return d->currentBackend->isReady(); 0085 } 0086 0087 void TileGrouper::updateClusters() 0088 { 0089 if (!s->markerModel) 0090 { 0091 return; 0092 } 0093 0094 if (s->haveMovingCluster) 0095 { 0096 // do not re-cluster while a cluster is being moved 0097 0098 return; 0099 } 0100 0101 if (!currentBackendReady()) 0102 { 0103 return; 0104 } 0105 0106 if (!d->clustersDirty) 0107 { 0108 return; 0109 } 0110 0111 d->clustersDirty = false; 0112 0113 // constants for clusters 0114 0115 const int ClusterRadius = s->showThumbnails ? s->thumbnailGroupingRadius : s->markerGroupingRadius; 0116 /* 0117 const QSize ClusterDefaultSize = QSize(2*ClusterRadius, 2*ClusterRadius); 0118 */ 0119 const int ClusterGridSizeScreen = 4 * ClusterRadius; 0120 /* 0121 const QSize ClusterMaxPixmapSize = QSize(ClusterGridSizeScreen, ClusterGridSizeScreen); 0122 */ 0123 // qCDebug(DIGIKAM_GEOIFACE_LOG)<<"updateClusters starting..."; 0124 0125 s->clusterList.clear(); 0126 0127 const int markerLevel = d->currentBackend->getMarkerModelLevel(); 0128 QList<QPair<GeoCoordinates, GeoCoordinates> > mapBounds = d->currentBackend->getNormalizedBounds(); 0129 /* 0130 // debug output for tile level diagnostics: 0131 0132 QIntList tile1; 0133 tile1<<520; 0134 QIntList tile2 = tile1; 0135 0136 for (int i = 1; i <= s->markerModel->maxLevel()-1; ++i) 0137 { 0138 tile2 = tile1; 0139 tile2<<0; 0140 tile1<<1; 0141 const GeoCoordinates tile1Coordinate = s->markerModel->tileIndexToCoordinate(tile1); 0142 const GeoCoordinates tile2Coordinate = s->markerModel->tileIndexToCoordinate(tile2); 0143 QPoint tile1Point, tile2Point; 0144 d->currentBackend->screenCoordinates(tile1Coordinate, &tile1Point); 0145 d->currentBackend->screenCoordinates(tile2Coordinate, &tile2Point); 0146 qCDebug(DIGIKAM_GEOIFACE_LOG)<<i<<tile1Point<<tile2Point<<(tile1Point-tile2Point); 0147 } 0148 */ 0149 const int gridSize = ClusterGridSizeScreen; 0150 const QSize mapSize = d->currentBackend->mapSize(); 0151 const int gridWidth = mapSize.width(); 0152 const int gridHeight = mapSize.height(); 0153 QVector<QList<TileIndex> > pixelNonEmptyTileIndexGrid(gridWidth*gridHeight, QList<TileIndex>()); 0154 QVector<int> pixelCountGrid(gridWidth*gridHeight, 0); 0155 QList<QPair<QPoint, QPair<int, QList<TileIndex> > > > leftOverList; 0156 0157 /// @todo Iterate only over the visible part of the map 0158 0159 int debugCountNonEmptyTiles = 0; 0160 int debugTilesSearched = 0; 0161 0162 /// @todo Review this 0163 0164 for (int i = 0 ; i < mapBounds.count() ; ++i) 0165 { 0166 s->markerModel->prepareTiles(mapBounds.at(i).first, mapBounds.at(i).second, markerLevel); 0167 } 0168 0169 QElapsedTimer elapsedTimer; 0170 elapsedTimer.start(); 0171 0172 for (AbstractMarkerTiler::NonEmptyIterator tileIterator(s->markerModel, markerLevel, mapBounds) ; 0173 !tileIterator.atEnd() ; tileIterator.nextIndex()) 0174 { 0175 const TileIndex tileIndex = tileIterator.currentIndex(); 0176 0177 // find out where the tile is on the map: 0178 0179 const GeoCoordinates tileCoordinate = tileIndex.toCoordinates(); 0180 debugTilesSearched++; 0181 QPoint tilePoint; 0182 0183 if (!d->currentBackend->screenCoordinates(tileCoordinate, &tilePoint)) 0184 { 0185 continue; 0186 } 0187 0188 // make sure we are in the grid (in case there are rounding errors somewhere in the backend 0189 0190 if ((tilePoint.x() < 0) || (tilePoint.y() < 0) || (tilePoint.x() >= gridWidth) || (tilePoint.y() >= gridHeight)) 0191 { 0192 continue; 0193 } 0194 0195 debugCountNonEmptyTiles++; 0196 const int linearIndex = tilePoint.x() + tilePoint.y()*gridWidth; 0197 pixelNonEmptyTileIndexGrid[linearIndex] << tileIndex; 0198 pixelCountGrid[linearIndex] += s->markerModel->getTileMarkerCount(tileIndex); 0199 /* 0200 qCDebug(DIGIKAM_GEOIFACE_LOG) << QString::fromLatin1("pixel at: %1, %2 (%3): %4 markers") 0201 .arg(tilePoint.x()) 0202 .arg(tilePoint.y()).arg(linearIndex) 0203 .arg(pixelCountGrid[linearIndex]); 0204 */ 0205 } 0206 0207 qCDebug(DIGIKAM_GEOIFACE_LOG) << "iterating over " << debugCountNonEmptyTiles 0208 << " took " << elapsedTimer.nsecsElapsed() / 1e9 << " seconds"; 0209 0210 /// @todo Cleanup this list every ... iterations in the next loop, too 0211 0212 QIntList nonEmptyPixelIndices; 0213 0214 for (int i = 0 ; i < gridWidth*gridHeight ; ++i) 0215 { 0216 if (pixelCountGrid.at(i) > 0) 0217 { 0218 nonEmptyPixelIndices << i; 0219 } 0220 } 0221 0222 // re-add the markers to clusters: 0223 /* 0224 int lastTooCloseClusterIndex = 0; 0225 */ 0226 0227 Q_FOREVER 0228 { 0229 // here we store candidates for clusters: 0230 0231 int markerMax = 0; 0232 int markerX = 0; 0233 int markerY = 0; 0234 int pixelGridMetaIndexMax = 0; 0235 0236 for (int pixelGridMetaIndex = 0 ; pixelGridMetaIndex < nonEmptyPixelIndices.size() ; ++pixelGridMetaIndex) 0237 { 0238 const int index = nonEmptyPixelIndices.at(pixelGridMetaIndex); 0239 0240 if (index < 0) 0241 { 0242 continue; 0243 } 0244 0245 if (pixelCountGrid.at(index) == 0) 0246 { 0247 /// @todo Also remove this entry from the list to speed up the loop! 0248 0249 nonEmptyPixelIndices[pixelGridMetaIndex] = -1; 0250 continue; 0251 } 0252 0253 if (pixelCountGrid.at(index) > markerMax) 0254 { 0255 // calculate x,y from the linear index: 0256 0257 const int x = index % gridWidth; 0258 const int y = (index-x)/gridWidth; 0259 const QPoint markerPosition(x, y); 0260 0261 // only use this as a candidate for a cluster if it is not too close to another cluster: 0262 0263 bool tooClose = false; 0264 0265 /// @todo Check the cluster that was a problem last time first: 0266 /* 0267 if (lastTooCloseClusterIndex<s->clusterList.size()) 0268 { 0269 tooClose = QPointSquareDistance(s->clusterList.at(lastTooCloseClusterIndex).pixelPos, markerPosition) < pow(ClusterGridSizeScreen/2, 2); 0270 } 0271 */ 0272 // now check all other clusters: 0273 0274 for (int i = 0 ; (!tooClose) && (i < s->clusterList.size()) ; ++i) 0275 { 0276 if (i == index) 0277 { 0278 continue; 0279 } 0280 0281 tooClose = QPointSquareDistance(s->clusterList.at(i).pixelPos, markerPosition) < pow(ClusterGridSizeScreen/2, 2); 0282 /* 0283 if (tooClose) 0284 { 0285 lastTooCloseClusterIndex = i; 0286 } 0287 */ 0288 } 0289 0290 if (tooClose) 0291 { 0292 // move markers into leftover list 0293 0294 leftOverList << QPair<QPoint, QPair<int, QList<TileIndex> > >(QPoint(x,y), 0295 QPair<int, QList<TileIndex> >(pixelCountGrid.at(index), pixelNonEmptyTileIndexGrid.at(index))); 0296 pixelCountGrid[index] = 0; 0297 pixelNonEmptyTileIndexGrid[index].clear(); 0298 nonEmptyPixelIndices[pixelGridMetaIndex] = -1; 0299 } 0300 else 0301 { 0302 markerMax = pixelCountGrid.at(index); 0303 markerX = x; 0304 markerY = y; 0305 pixelGridMetaIndexMax = pixelGridMetaIndex; 0306 } 0307 } 0308 } 0309 0310 if (markerMax == 0) 0311 { 0312 break; 0313 } 0314 0315 GeoCoordinates clusterCoordinates = pixelNonEmptyTileIndexGrid.at(markerX+markerY*gridWidth).first().toCoordinates(); 0316 GeoIfaceCluster cluster; 0317 cluster.coordinates = clusterCoordinates; 0318 cluster.pixelPos = QPoint(markerX, markerY); 0319 cluster.tileIndicesList = pixelNonEmptyTileIndexGrid.at(markerX+markerY*gridWidth); 0320 cluster.markerCount = pixelCountGrid.at(markerX+markerY*gridWidth); 0321 0322 // mark the pixel as done: 0323 0324 pixelCountGrid[markerX+markerY*gridWidth] = 0; 0325 pixelNonEmptyTileIndexGrid[markerX+markerY*gridWidth].clear(); 0326 nonEmptyPixelIndices[pixelGridMetaIndexMax] = -1; 0327 0328 // absorb all markers around it: 0329 // Now we only remove the markers from the pixelgrid. They will be cleared from the 0330 // pixelGridIndices in the loop above 0331 // make sure we do not go over the grid boundaries: 0332 0333 const int eatRadius = gridSize/4; 0334 const int xStart = qMax( (markerX-eatRadius), 0); 0335 const int yStart = qMax( (markerY-eatRadius), 0); 0336 const int xEnd = qMin( (markerX+eatRadius), gridWidth-1); 0337 const int yEnd = qMin( (markerY+eatRadius), gridHeight-1); 0338 0339 for (int indexX = xStart ; indexX <= xEnd ; ++indexX) 0340 { 0341 for (int indexY = yStart ; indexY <= yEnd ; ++indexY) 0342 { 0343 const int index = indexX + indexY*gridWidth; 0344 cluster.tileIndicesList << pixelNonEmptyTileIndexGrid.at(index); 0345 pixelNonEmptyTileIndexGrid[index].clear(); 0346 cluster.markerCount += pixelCountGrid.at(index); 0347 pixelCountGrid[index] = 0; 0348 } 0349 } 0350 0351 qCDebug(DIGIKAM_GEOIFACE_LOG) << QString::fromLatin1("created cluster %1: %2 tiles") 0352 .arg(s->clusterList.size()) 0353 .arg(cluster.tileIndicesList.count()); 0354 0355 s->clusterList << cluster; 0356 } 0357 0358 // now move all leftover markers into clusters: 0359 0360 for (QList<QPair<QPoint, QPair<int, QList<TileIndex> > > >::const_iterator it = leftOverList.constBegin(); 0361 it != leftOverList.constEnd() ; ++it) 0362 { 0363 const QPoint markerPosition = it->first; 0364 0365 // find the closest cluster: 0366 0367 int closestSquareDistance = 0; 0368 int closestIndex = -1; 0369 0370 for (int i = 0 ; i < s->clusterList.size() ; ++i) 0371 { 0372 const int squareDistance = QPointSquareDistance(s->clusterList.at(i).pixelPos, markerPosition); 0373 0374 if ((closestIndex < 0) || (squareDistance < closestSquareDistance)) 0375 { 0376 closestSquareDistance = squareDistance; 0377 closestIndex = i; 0378 } 0379 } 0380 0381 if (closestIndex >= 0) 0382 { 0383 s->clusterList[closestIndex].markerCount += it->second.first; 0384 s->clusterList[closestIndex].tileIndicesList << it->second.second; 0385 } 0386 } 0387 0388 // determine the selected states of the clusters: 0389 0390 for (int i = 0 ; i < s->clusterList.count() ; ++i) 0391 { 0392 GeoIfaceCluster& cluster = s->clusterList[i]; 0393 int clusterSelectedCount = 0; 0394 GroupStateComputer clusterStateComputer; 0395 0396 for (int iTile = 0 ; (iTile < cluster.tileIndicesList.count()) ; ++iTile) 0397 { 0398 const TileIndex tileIndex = cluster.tileIndicesList.at(iTile); 0399 const GeoGroupState tileGroupState = s->markerModel->getTileGroupState(tileIndex); 0400 clusterStateComputer.addState(tileGroupState); 0401 clusterSelectedCount += s->markerModel->getTileSelectedCount(tileIndex); 0402 } 0403 0404 cluster.markerSelectedCount = clusterSelectedCount; 0405 cluster.groupState = clusterStateComputer.getState(); 0406 } 0407 0408 // qCDebug(DIGIKAM_GEOIFACE_LOG) << s->clusterList.size(); 0409 0410 qCDebug(DIGIKAM_GEOIFACE_LOG) << QString::fromLatin1("level %1: %2 non empty tiles sorted into %3 clusters (%4 searched)") 0411 .arg(markerLevel) 0412 .arg(debugCountNonEmptyTiles) 0413 .arg(s->clusterList.count()) 0414 .arg(debugTilesSearched); 0415 0416 d->currentBackend->updateClusters(); 0417 } 0418 0419 } // namespace Digikam 0420 0421 #include "moc_tilegrouper.cpp"