1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2009-12-01
7  * Description : An abstract base class for tiling of markers
8  *
9  * Copyright (C) 2009-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10  * Copyright (C) 2009-2011 by Michael G. Hansen <mike at mghansen dot de>
11  *
12  * This program is free software; you can redistribute it
13  * and/or modify it under the terms of the GNU General
14  * Public License as published by the Free Software Foundation;
15  * either version 2, or (at your option)
16  * any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * ============================================================ */
24 
25 #include "abstractmarkertiler.h"
26 
27 // Qt includes
28 
29 #include <QPair>
30 #include <QScopedPointer>
31 
32 // Local includes
33 
34 #include "digikam_debug.h"
35 #include "geoifacecommon.h"
36 
37 namespace Digikam
38 {
39 
40 class Q_DECL_HIDDEN AbstractMarkerTiler::Private
41 {
42 public:
43 
44     explicit Private() = default;
45 
46     QScopedPointer<AbstractMarkerTiler::Tile>  rootTile;
47     bool                                       isDirty = true;
48 };
49 
AbstractMarkerTiler(QObject * const parent)50 AbstractMarkerTiler::AbstractMarkerTiler(QObject* const parent)
51     : QObject(parent),
52       d(new Private())
53 {
54 }
55 
~AbstractMarkerTiler()56 AbstractMarkerTiler::~AbstractMarkerTiler()
57 {
58     delete d;
59 }
60 
rootTile()61 AbstractMarkerTiler::Tile* AbstractMarkerTiler::rootTile()
62 {
63     if (isDirty())
64     {
65         regenerateTiles();
66     }
67 
68     return d->rootTile.get();
69 }
70 
isDirty() const71 bool AbstractMarkerTiler::isDirty() const
72 {
73     return d->isDirty;
74 }
75 
setDirty(const bool state)76 void AbstractMarkerTiler::setDirty(const bool state)
77 {
78     if (state && !d->isDirty)
79     {
80         d->isDirty = true;
81         emit signalTilesOrSelectionChanged();
82     }
83     else
84     {
85         d->isDirty = state;
86     }
87 }
88 
resetRootTile()89 void AbstractMarkerTiler::resetRootTile()
90 {
91     d->rootTile.reset(tileNew());
92 }
93 
onIndicesClicked(const ClickInfo & clickInfo)94 void AbstractMarkerTiler::onIndicesClicked(const ClickInfo& clickInfo)
95 {
96     Q_UNUSED(clickInfo)
97 }
98 
onIndicesMoved(const TileIndex::List & tileIndicesList,const GeoCoordinates & targetCoordinates,const QPersistentModelIndex & targetSnapIndex)99 void AbstractMarkerTiler::onIndicesMoved(const TileIndex::List& tileIndicesList,
100                                          const GeoCoordinates& targetCoordinates,
101                                          const QPersistentModelIndex& targetSnapIndex)
102 {
103     Q_UNUSED(tileIndicesList);
104     Q_UNUSED(targetCoordinates);
105     Q_UNUSED(targetSnapIndex);
106 }
107 
tilerFlags() const108 AbstractMarkerTiler::TilerFlags AbstractMarkerTiler::tilerFlags() const
109 {
110     return FlagNull;
111 }
112 
113 // -------------------------------------------------------------------------
114 
115 class Q_DECL_HIDDEN AbstractMarkerTiler::NonEmptyIterator::Private
116 {
117 public:
118 
Private()119     explicit Private()
120         : model(nullptr),
121           level(0),
122           startIndex(),
123           endIndex(),
124           currentIndex(),
125           atEnd(false)
126     {
127     }
128 
129     AbstractMarkerTiler*                model;
130     int                                 level;
131 
132     QList<QPair<TileIndex, TileIndex> > boundsList;
133 
134     TileIndex                           startIndex;
135     TileIndex                           endIndex;
136     TileIndex                           currentIndex;
137 
138     bool                                atEnd;
139 };
140 
~NonEmptyIterator()141 AbstractMarkerTiler::NonEmptyIterator::~NonEmptyIterator()
142 {
143     delete d;
144 }
145 
NonEmptyIterator(AbstractMarkerTiler * const model,const int level)146 AbstractMarkerTiler::NonEmptyIterator::NonEmptyIterator(AbstractMarkerTiler* const model,
147                                                         const int level)
148     : d(new Private())
149 {
150     d->model = model;
151     GEOIFACE_ASSERT(level <= TileIndex::MaxLevel);
152     d->level = level;
153 
154     TileIndex startIndex;
155     TileIndex endIndex;
156 
157     for (int i = 0 ; i <= level ; ++i)
158     {
159         startIndex.appendLinearIndex(0);
160         endIndex.appendLinearIndex(TileIndex::Tiling * TileIndex::Tiling - 1);
161     }
162 /*
163     qCDebug(DIGIKAM_GEOIFACE_LOG) << d->startIndexLinear << d->endIndexLinear;
164 */
165     d->boundsList << QPair<TileIndex, TileIndex>(startIndex, endIndex);
166 
167     initializeNextBounds();
168 }
169 
NonEmptyIterator(AbstractMarkerTiler * const model,const int level,const TileIndex & startIndex,const TileIndex & endIndex)170 AbstractMarkerTiler::NonEmptyIterator::NonEmptyIterator(AbstractMarkerTiler* const model,
171                                                         const int level,
172                                                         const TileIndex& startIndex,
173                                                         const TileIndex& endIndex)
174     : d(new Private())
175 {
176     d->model = model;
177     GEOIFACE_ASSERT(level <= TileIndex::MaxLevel);
178     d->level = level;
179 
180     GEOIFACE_ASSERT(startIndex.level() == level);
181     GEOIFACE_ASSERT(endIndex.level()   == level);
182     d->boundsList << QPair<TileIndex, TileIndex>(startIndex, endIndex);
183 
184     initializeNextBounds();
185 }
186 
NonEmptyIterator(AbstractMarkerTiler * const model,const int level,const GeoCoordinates::PairList & normalizedMapBounds)187 AbstractMarkerTiler::NonEmptyIterator::NonEmptyIterator(AbstractMarkerTiler* const model,
188                                                         const int level,
189                                                         const GeoCoordinates::PairList& normalizedMapBounds)
190     : d(new Private())
191 {
192     d->model = model;
193     GEOIFACE_ASSERT(level <= TileIndex::MaxLevel);
194     d->level = level;
195 
196     // store the coordinates of the bounds as indices:
197 
198     for (int i = 0 ; i < normalizedMapBounds.count() ; ++i)
199     {
200         GeoCoordinates::Pair currentBounds = normalizedMapBounds.at(i);
201         GEOIFACE_ASSERT(currentBounds.first.lat() < currentBounds.second.lat());
202         GEOIFACE_ASSERT(currentBounds.first.lon() < currentBounds.second.lon());
203 
204         const TileIndex startIndex = TileIndex::fromCoordinates(currentBounds.first,  d->level);
205         const TileIndex endIndex   = TileIndex::fromCoordinates(currentBounds.second, d->level);
206 /*
207          qCDebug(DIGIKAM_GEOIFACE_LOG) << currentBounds.first.geoUrl()
208                                        << startIndex
209                                        << currentBounds.second.geoUrl()
210                                        << endIndex;
211 */
212         d->boundsList << QPair<TileIndex, TileIndex>(startIndex, endIndex);
213     }
214 
215     initializeNextBounds();
216 }
217 
initializeNextBounds()218 bool AbstractMarkerTiler::NonEmptyIterator::initializeNextBounds()
219 {
220     if (d->boundsList.isEmpty())
221     {
222         d->atEnd = true;
223 
224         return false;
225     }
226 
227     QPair<TileIndex, TileIndex> nextBounds = d->boundsList.takeFirst();
228     d->startIndex                          = nextBounds.first;
229     d->endIndex                            = nextBounds.second;
230 
231     GEOIFACE_ASSERT(d->startIndex.level() == d->level);
232     GEOIFACE_ASSERT(d->endIndex.level()   == d->level);
233 
234     d->currentIndex.clear();
235     d->currentIndex.appendLinearIndex(-1);
236 
237     // make sure the root tile is split, using linear index -1 is ok here as
238     // this is handled in Tile::getChild
239     d->model->getTile(d->currentIndex, true);
240 
241     nextIndex();
242 
243     return d->atEnd;
244 }
245 
nextIndex()246 TileIndex AbstractMarkerTiler::NonEmptyIterator::nextIndex()
247 {
248     if (d->atEnd)
249     {
250         return d->currentIndex;
251     }
252 
253     Q_FOREVER
254     {
255         const int currentLevel = d->currentIndex.level();
256 /*
257         qCDebug(DIGIKAM_GEOIFACE_LOG) << d->level
258                                       << currentLevel
259                                       << d->currentIndex;
260 */
261         // determine the limits in the current level:
262         int limitLatBL   = 0;
263         int limitLonBL   = 0;
264         int limitLatTR   = TileIndex::Tiling-1;
265         int limitLonTR   = TileIndex::Tiling-1;
266 
267         {
268 
269             int compareLevel = currentLevel - 1;
270             // check limit on the left side:
271 
272             bool onLimit = true;
273 
274             for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i)
275             {
276                 onLimit = d->currentIndex.indexLat(i) == d->startIndex.indexLat(i);
277             }
278 
279             if (onLimit)
280             {
281                 limitLatBL = d->startIndex.indexLat(currentLevel);
282             }
283 
284             // check limit on the bottom side:
285 
286             onLimit = true;
287 
288             for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i)
289             {
290                 onLimit = d->currentIndex.indexLon(i) == d->startIndex.indexLon(i);
291             }
292 
293             if (onLimit)
294             {
295                 limitLonBL = d->startIndex.indexLon(currentLevel);
296             }
297 
298             // check limit on the right side:
299 
300             onLimit = true;
301 
302             for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i)
303             {
304                 onLimit = d->currentIndex.indexLat(i) == d->endIndex.indexLat(i);
305             }
306 
307             if (onLimit)
308             {
309                 limitLatTR = d->endIndex.indexLat(currentLevel);
310             }
311 
312             // check limit on the top side:
313 
314             onLimit = true;
315 
316             for (int i = 0 ; onLimit && (i <= compareLevel) ; ++i)
317             {
318                 onLimit = d->currentIndex.indexLon(i) == d->endIndex.indexLon(i);
319             }
320 
321             if (onLimit)
322             {
323                 limitLonTR = d->endIndex.indexLon(currentLevel);
324             }
325         }
326 
327         // get the parent tile of the current level
328 
329         int levelLinearIndex = d->currentIndex.lastIndex();
330 
331         d->currentIndex.oneUp();
332 
333         Tile* tile           = d->model->getTile(d->currentIndex, true);
334 
335         if (!tile)
336         {
337             // this can only happen if the model has changed during iteration. handle gracefully...
338 
339             d->currentIndex.oneUp();
340 
341             continue;
342         }
343         else
344         {
345             d->currentIndex.appendLinearIndex(levelLinearIndex);
346         }
347 
348         // helper function to check if index is inside bounds of the current level
349 
350         auto inBounds = [&](int idx)
351                         {
352                             int currentLat = idx / TileIndex::Tiling;
353                             int currentLon = idx % TileIndex::Tiling;
354 
355                             return (
356                                     currentLat >= limitLatBL &&
357                                     currentLat <= limitLatTR &&
358                                     currentLon >= limitLonBL &&
359                                     currentLon <= limitLonTR
360                                    );
361                         };
362 
363         // get the next linear index on the level which is in Bounds
364 
365         levelLinearIndex = tile->nextNonEmptyIndex(levelLinearIndex);
366 
367         while ((levelLinearIndex != -1) && !inBounds(levelLinearIndex))
368         {
369             levelLinearIndex = tile->nextNonEmptyIndex(levelLinearIndex);
370         }
371 
372 
373         if (levelLinearIndex == -1)
374         {
375             // no index in bound anymore on this level
376 
377             if (currentLevel == 0)
378             {
379                 // we are at the end!
380                 // are there other bounds to iterate over?
381 
382                 initializeNextBounds();
383 
384                 // initializeNextBounds() call nextIndex which updates d->currentIndex, if possible:
385 
386                 return d->currentIndex;
387             }
388 
389             // we need to go one level up, trim the indices:
390 
391             d->currentIndex.oneUp();
392 
393             continue;
394         }
395 
396         // save the new position:
397 
398         d->currentIndex.oneUp();
399         d->currentIndex.appendLinearIndex(levelLinearIndex);
400 
401         // are we at the target level?
402 
403         if (currentLevel == d->level)
404         {
405             // yes, return the current index:
406 
407             return d->currentIndex;
408         }
409 
410         // go one level down:
411 
412         d->currentIndex.appendLinearIndex(-1);
413 
414         // make sure the current level is split,using linear index -1 is ok here
415         // as this is handled in Tile::getChild
416 
417         d->model->getTile(d->currentIndex, true);
418     }
419 }
420 
currentIndex() const421 TileIndex AbstractMarkerTiler::NonEmptyIterator::currentIndex() const
422 {
423     return d->currentIndex;
424 }
425 
atEnd() const426 bool AbstractMarkerTiler::NonEmptyIterator::atEnd() const
427 {
428     return d->atEnd;
429 }
430 
model() const431 AbstractMarkerTiler* AbstractMarkerTiler::NonEmptyIterator::model() const
432 {
433     return d->model;
434 }
435 
436 // -------------------------------------------------------------------------
437 
438 AbstractMarkerTiler::Tile::Tile() = default;
439 
~Tile()440 AbstractMarkerTiler::Tile::~Tile()
441 {
442     for (auto* tile : children)
443     {
444         if (tile)
445         {
446             delete tile;
447         }
448     }
449 }
450 
maxChildCount()451 int AbstractMarkerTiler::Tile::maxChildCount()
452 {
453     return TileIndex::Tiling * TileIndex::Tiling;
454 }
455 
getChild(const int linearIndex)456 AbstractMarkerTiler::Tile* AbstractMarkerTiler::Tile::getChild(const int linearIndex)
457 {
458     if (children.isEmpty())
459     {
460         return nullptr;
461     }
462 
463     if ((linearIndex < 0) || (linearIndex >= maxChildCount()))
464     {
465         return nullptr;
466     }
467 
468     return children.at(linearIndex);
469 }
470 
addChild(const int linearIndex,Tile * tilePointer)471 AbstractMarkerTiler::Tile* AbstractMarkerTiler::Tile::addChild(const int linearIndex, Tile* tilePointer)
472 {
473     if ((tilePointer == nullptr) && children.isEmpty())
474     {
475         return nullptr;
476     }
477 
478     prepareForChildren();
479 
480     GEOIFACE_ASSERT(!children[linearIndex]);
481 
482     if (!children[linearIndex])
483     {
484         nonEmptyIndices.insert(std::upper_bound(nonEmptyIndices.begin(), nonEmptyIndices.end(), linearIndex), linearIndex);
485     }
486 
487     children[linearIndex] = tilePointer;
488 
489     return tilePointer;
490 }
491 
492 
deleteChild(Tile * const childTile,const int knownLinearIndex)493 void AbstractMarkerTiler::Tile::deleteChild(Tile* const childTile,
494                                             const int knownLinearIndex)
495 {
496     if (children.isEmpty())
497     {
498         return;
499     }
500 
501     int tileIndex = knownLinearIndex;
502 
503     if (tileIndex < 0)
504     {
505         tileIndex = std::distance(children.begin(), std::find(children.begin(), children.end(), childTile));
506     }
507 
508     if (children[tileIndex])
509     {
510         nonEmptyIndices.erase(std::lower_bound(nonEmptyIndices.begin(), nonEmptyIndices.end(), tileIndex));
511     }
512 
513     delete children[tileIndex];
514     children[tileIndex] = nullptr;
515 }
516 
childrenEmpty() const517 bool AbstractMarkerTiler::Tile::childrenEmpty() const
518 {
519     return children.empty();
520 }
521 
prepareForChildren()522 void AbstractMarkerTiler::Tile::prepareForChildren()
523 {
524     if (!children.isEmpty())
525     {
526         return;
527     }
528 
529     children = QVector<Tile*>(maxChildCount(), nullptr);
530 }
531 
nextNonEmptyIndex(int linearIndex) const532 int AbstractMarkerTiler::Tile::nextNonEmptyIndex(int linearIndex) const
533 {
534     auto it = std::upper_bound(nonEmptyIndices.begin(), nonEmptyIndices.end(), linearIndex);
535 
536     if (it != nonEmptyIndices.end())
537     {
538         return *it;
539     }
540 
541     return -1;
542 }
543 
544 } // namespace Digikam
545