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