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 : 2009-12-01 0007 * Description : An abstract base class for tiling of markers 0008 * 0009 * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * SPDX-FileCopyrightText: 2009-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 "abstractmarkertiler.h" 0017 0018 // Qt includes 0019 0020 #include <QPair> 0021 #include <QScopedPointer> 0022 0023 // Local includes 0024 0025 #include "digikam_debug.h" 0026 #include "geoifacecommon.h" 0027 0028 namespace Digikam 0029 { 0030 0031 class Q_DECL_HIDDEN AbstractMarkerTiler::Private 0032 { 0033 public: 0034 0035 explicit Private() = default; 0036 0037 QScopedPointer<AbstractMarkerTiler::Tile> rootTile; 0038 bool isDirty = true; 0039 }; 0040 0041 AbstractMarkerTiler::AbstractMarkerTiler(QObject* const parent) 0042 : QObject(parent), 0043 d(new Private()) 0044 { 0045 } 0046 0047 AbstractMarkerTiler::~AbstractMarkerTiler() 0048 { 0049 delete d; 0050 } 0051 0052 AbstractMarkerTiler::Tile* AbstractMarkerTiler::rootTile() 0053 { 0054 if (isDirty()) 0055 { 0056 regenerateTiles(); 0057 } 0058 0059 return d->rootTile.data(); 0060 } 0061 0062 bool AbstractMarkerTiler::isDirty() const 0063 { 0064 return d->isDirty; 0065 } 0066 0067 void AbstractMarkerTiler::setDirty(const bool state) 0068 { 0069 if (state && !d->isDirty) 0070 { 0071 d->isDirty = true; 0072 Q_EMIT signalTilesOrSelectionChanged(); 0073 } 0074 else 0075 { 0076 d->isDirty = state; 0077 } 0078 } 0079 0080 void AbstractMarkerTiler::resetRootTile() 0081 { 0082 d->rootTile.reset(tileNew()); 0083 } 0084 0085 void AbstractMarkerTiler::onIndicesClicked(const ClickInfo& clickInfo) 0086 { 0087 Q_UNUSED(clickInfo) 0088 } 0089 0090 void AbstractMarkerTiler::onIndicesMoved(const TileIndex::List& tileIndicesList, 0091 const GeoCoordinates& targetCoordinates, 0092 const QPersistentModelIndex& targetSnapIndex) 0093 { 0094 Q_UNUSED(tileIndicesList); 0095 Q_UNUSED(targetCoordinates); 0096 Q_UNUSED(targetSnapIndex); 0097 } 0098 0099 AbstractMarkerTiler::TilerFlags AbstractMarkerTiler::tilerFlags() const 0100 { 0101 return FlagNull; 0102 } 0103 0104 // ------------------------------------------------------------------------- 0105 0106 class Q_DECL_HIDDEN AbstractMarkerTiler::NonEmptyIterator::Private 0107 { 0108 public: 0109 0110 explicit Private() 0111 : model(nullptr), 0112 level(0), 0113 startIndex(), 0114 endIndex(), 0115 currentIndex(), 0116 atEnd(false) 0117 { 0118 } 0119 0120 AbstractMarkerTiler* model; 0121 int level; 0122 0123 QList<QPair<TileIndex, TileIndex> > boundsList; 0124 0125 TileIndex startIndex; 0126 TileIndex endIndex; 0127 TileIndex currentIndex; 0128 0129 bool atEnd; 0130 }; 0131 0132 AbstractMarkerTiler::NonEmptyIterator::~NonEmptyIterator() 0133 { 0134 delete d; 0135 } 0136 0137 AbstractMarkerTiler::NonEmptyIterator::NonEmptyIterator(AbstractMarkerTiler* const model, 0138 const int level) 0139 : d(new Private()) 0140 { 0141 d->model = model; 0142 GEOIFACE_ASSERT(level <= TileIndex::MaxLevel); 0143 d->level = level; 0144 0145 TileIndex startIndex; 0146 TileIndex endIndex; 0147 0148 for (int i = 0 ; i <= level ; ++i) 0149 { 0150 startIndex.appendLinearIndex(0); 0151 endIndex.appendLinearIndex(TileIndex::Tiling * TileIndex::Tiling - 1); 0152 } 0153 /* 0154 qCDebug(DIGIKAM_GEOIFACE_LOG) << d->startIndexLinear << d->endIndexLinear; 0155 */ 0156 d->boundsList << QPair<TileIndex, TileIndex>(startIndex, endIndex); 0157 0158 initializeNextBounds(); 0159 } 0160 0161 AbstractMarkerTiler::NonEmptyIterator::NonEmptyIterator(AbstractMarkerTiler* const model, 0162 const int level, 0163 const TileIndex& startIndex, 0164 const TileIndex& endIndex) 0165 : d(new Private()) 0166 { 0167 d->model = model; 0168 GEOIFACE_ASSERT(level <= TileIndex::MaxLevel); 0169 d->level = level; 0170 0171 GEOIFACE_ASSERT(startIndex.level() == level); 0172 GEOIFACE_ASSERT(endIndex.level() == level); 0173 d->boundsList << QPair<TileIndex, TileIndex>(startIndex, endIndex); 0174 0175 initializeNextBounds(); 0176 } 0177 0178 AbstractMarkerTiler::NonEmptyIterator::NonEmptyIterator(AbstractMarkerTiler* const model, 0179 const int level, 0180 const GeoCoordinates::PairList& normalizedMapBounds) 0181 : d(new Private()) 0182 { 0183 d->model = model; 0184 GEOIFACE_ASSERT(level <= TileIndex::MaxLevel); 0185 d->level = level; 0186 0187 // store the coordinates of the bounds as indices: 0188 0189 for (int i = 0 ; i < normalizedMapBounds.count() ; ++i) 0190 { 0191 GeoCoordinates::Pair currentBounds = normalizedMapBounds.at(i); 0192 GEOIFACE_ASSERT(currentBounds.first.lat() < currentBounds.second.lat()); 0193 GEOIFACE_ASSERT(currentBounds.first.lon() < currentBounds.second.lon()); 0194 0195 const TileIndex startIndex = TileIndex::fromCoordinates(currentBounds.first, d->level); 0196 const TileIndex endIndex = TileIndex::fromCoordinates(currentBounds.second, d->level); 0197 /* 0198 qCDebug(DIGIKAM_GEOIFACE_LOG) << currentBounds.first.geoUrl() 0199 << startIndex 0200 << currentBounds.second.geoUrl() 0201 << endIndex; 0202 */ 0203 d->boundsList << QPair<TileIndex, TileIndex>(startIndex, endIndex); 0204 } 0205 0206 initializeNextBounds(); 0207 } 0208 0209 bool AbstractMarkerTiler::NonEmptyIterator::initializeNextBounds() 0210 { 0211 if (d->boundsList.isEmpty()) 0212 { 0213 d->atEnd = true; 0214 0215 return false; 0216 } 0217 0218 QPair<TileIndex, TileIndex> nextBounds = d->boundsList.takeFirst(); 0219 d->startIndex = nextBounds.first; 0220 d->endIndex = nextBounds.second; 0221 0222 GEOIFACE_ASSERT(d->startIndex.level() == d->level); 0223 GEOIFACE_ASSERT(d->endIndex.level() == d->level); 0224 0225 d->currentIndex.clear(); 0226 d->currentIndex.appendLinearIndex(-1); 0227 0228 // make sure the root tile is split, using linear index -1 is ok here as 0229 // this is handled in Tile::getChild 0230 d->model->getTile(d->currentIndex, true); 0231 0232 nextIndex(); 0233 0234 return d->atEnd; 0235 } 0236 0237 TileIndex AbstractMarkerTiler::NonEmptyIterator::nextIndex() 0238 { 0239 if (d->atEnd) 0240 { 0241 return d->currentIndex; 0242 } 0243 0244 Q_FOREVER 0245 { 0246 const int currentLevel = d->currentIndex.level(); 0247 /* 0248 qCDebug(DIGIKAM_GEOIFACE_LOG) << d->level 0249 << currentLevel 0250 << d->currentIndex; 0251 */ 0252 // determine the limits in the current level: 0253 int limitLatBL = 0; 0254 int limitLonBL = 0; 0255 int limitLatTR = TileIndex::Tiling-1; 0256 int limitLonTR = TileIndex::Tiling-1; 0257 0258 { 0259 0260 int compareLevel = currentLevel - 1; 0261 // check limit on the left side: 0262 0263 bool onLimit = true; 0264 0265 for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) 0266 { 0267 onLimit = d->currentIndex.indexLat(i) == d->startIndex.indexLat(i); 0268 } 0269 0270 if (onLimit) 0271 { 0272 limitLatBL = d->startIndex.indexLat(currentLevel); 0273 } 0274 0275 // check limit on the bottom side: 0276 0277 onLimit = true; 0278 0279 for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) 0280 { 0281 onLimit = d->currentIndex.indexLon(i) == d->startIndex.indexLon(i); 0282 } 0283 0284 if (onLimit) 0285 { 0286 limitLonBL = d->startIndex.indexLon(currentLevel); 0287 } 0288 0289 // check limit on the right side: 0290 0291 onLimit = true; 0292 0293 for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) 0294 { 0295 onLimit = d->currentIndex.indexLat(i) == d->endIndex.indexLat(i); 0296 } 0297 0298 if (onLimit) 0299 { 0300 limitLatTR = d->endIndex.indexLat(currentLevel); 0301 } 0302 0303 // check limit on the top side: 0304 0305 onLimit = true; 0306 0307 for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i) 0308 { 0309 onLimit = d->currentIndex.indexLon(i) == d->endIndex.indexLon(i); 0310 } 0311 0312 if (onLimit) 0313 { 0314 limitLonTR = d->endIndex.indexLon(currentLevel); 0315 } 0316 } 0317 0318 // get the parent tile of the current level 0319 0320 int levelLinearIndex = d->currentIndex.lastIndex(); 0321 0322 d->currentIndex.oneUp(); 0323 0324 Tile* tile = d->model->getTile(d->currentIndex, true); 0325 0326 if (!tile) 0327 { 0328 // this can only happen if the model has changed during iteration. handle gracefully... 0329 0330 d->currentIndex.oneUp(); 0331 0332 continue; 0333 } 0334 else 0335 { 0336 d->currentIndex.appendLinearIndex(levelLinearIndex); 0337 } 0338 0339 // helper function to check if index is inside bounds of the current level 0340 0341 auto inBounds = [&](int idx) 0342 { 0343 int currentLat = idx / TileIndex::Tiling; 0344 int currentLon = idx % TileIndex::Tiling; 0345 0346 return ( 0347 currentLat >= limitLatBL && 0348 currentLat <= limitLatTR && 0349 currentLon >= limitLonBL && 0350 currentLon <= limitLonTR 0351 ); 0352 }; 0353 0354 // get the next linear index on the level which is in Bounds 0355 0356 levelLinearIndex = tile->nextNonEmptyIndex(levelLinearIndex); 0357 0358 while ((levelLinearIndex != -1) && !inBounds(levelLinearIndex)) 0359 { 0360 levelLinearIndex = tile->nextNonEmptyIndex(levelLinearIndex); 0361 } 0362 0363 0364 if (levelLinearIndex == -1) 0365 { 0366 // no index in bound anymore on this level 0367 0368 if (currentLevel == 0) 0369 { 0370 // we are at the end! 0371 // are there other bounds to iterate over? 0372 0373 initializeNextBounds(); 0374 0375 // initializeNextBounds() call nextIndex which updates d->currentIndex, if possible: 0376 0377 return d->currentIndex; 0378 } 0379 0380 // we need to go one level up, trim the indices: 0381 0382 d->currentIndex.oneUp(); 0383 0384 continue; 0385 } 0386 0387 // save the new position: 0388 0389 d->currentIndex.oneUp(); 0390 d->currentIndex.appendLinearIndex(levelLinearIndex); 0391 0392 // are we at the target level? 0393 0394 if (currentLevel == d->level) 0395 { 0396 // yes, return the current index: 0397 0398 return d->currentIndex; 0399 } 0400 0401 // go one level down: 0402 0403 d->currentIndex.appendLinearIndex(-1); 0404 0405 // make sure the current level is split,using linear index -1 is ok here 0406 // as this is handled in Tile::getChild 0407 0408 d->model->getTile(d->currentIndex, true); 0409 } 0410 } 0411 0412 TileIndex AbstractMarkerTiler::NonEmptyIterator::currentIndex() const 0413 { 0414 return d->currentIndex; 0415 } 0416 0417 bool AbstractMarkerTiler::NonEmptyIterator::atEnd() const 0418 { 0419 return d->atEnd; 0420 } 0421 0422 AbstractMarkerTiler* AbstractMarkerTiler::NonEmptyIterator::model() const 0423 { 0424 return d->model; 0425 } 0426 0427 // ------------------------------------------------------------------------- 0428 0429 AbstractMarkerTiler::Tile::Tile() = default; 0430 0431 AbstractMarkerTiler::Tile::~Tile() 0432 { 0433 for (auto* tile : children) 0434 { 0435 if (tile) 0436 { 0437 delete tile; 0438 } 0439 } 0440 } 0441 0442 int AbstractMarkerTiler::Tile::maxChildCount() 0443 { 0444 return TileIndex::Tiling * TileIndex::Tiling; 0445 } 0446 0447 AbstractMarkerTiler::Tile* AbstractMarkerTiler::Tile::getChild(const int linearIndex) 0448 { 0449 if (children.isEmpty()) 0450 { 0451 return nullptr; 0452 } 0453 0454 if ((linearIndex < 0) || (linearIndex >= maxChildCount())) 0455 { 0456 return nullptr; 0457 } 0458 0459 return children.at(linearIndex); 0460 } 0461 0462 AbstractMarkerTiler::Tile* AbstractMarkerTiler::Tile::addChild(const int linearIndex, Tile* tilePointer) 0463 { 0464 if ((tilePointer == nullptr) && children.isEmpty()) 0465 { 0466 return nullptr; 0467 } 0468 0469 prepareForChildren(); 0470 0471 GEOIFACE_ASSERT(!children[linearIndex]); 0472 0473 if (!children[linearIndex]) 0474 { 0475 nonEmptyIndices.insert(std::upper_bound(nonEmptyIndices.begin(), nonEmptyIndices.end(), linearIndex), linearIndex); 0476 } 0477 0478 children[linearIndex] = tilePointer; 0479 0480 return tilePointer; 0481 } 0482 0483 0484 void AbstractMarkerTiler::Tile::deleteChild(Tile* const childTile, 0485 const int knownLinearIndex) 0486 { 0487 if (children.isEmpty()) 0488 { 0489 return; 0490 } 0491 0492 int tileIndex = knownLinearIndex; 0493 0494 if (tileIndex < 0) 0495 { 0496 tileIndex = std::distance(children.begin(), std::find(children.begin(), children.end(), childTile)); 0497 } 0498 0499 if (children[tileIndex]) 0500 { 0501 nonEmptyIndices.erase(std::lower_bound(nonEmptyIndices.begin(), nonEmptyIndices.end(), tileIndex)); 0502 } 0503 0504 delete children[tileIndex]; 0505 children[tileIndex] = nullptr; 0506 } 0507 0508 bool AbstractMarkerTiler::Tile::childrenEmpty() const 0509 { 0510 return children.empty(); 0511 } 0512 0513 void AbstractMarkerTiler::Tile::prepareForChildren() 0514 { 0515 if (!children.isEmpty()) 0516 { 0517 return; 0518 } 0519 0520 children = QVector<Tile*>(maxChildCount(), nullptr); 0521 } 0522 0523 int AbstractMarkerTiler::Tile::nextNonEmptyIndex(int linearIndex) const 0524 { 0525 auto it = std::upper_bound(nonEmptyIndices.begin(), nonEmptyIndices.end(), linearIndex); 0526 0527 if (it != nonEmptyIndices.end()) 0528 { 0529 return *it; 0530 } 0531 0532 return -1; 0533 } 0534 0535 } // namespace Digikam 0536 0537 #include "moc_abstractmarkertiler.cpp"