QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
problem.cpp
Go to the documentation of this file.
1/*
2 * libpal - Automated Placement of Labels Library
3 *
4 * Copyright (C) 2008 Maxence Laurent, MIS-TIC, HEIG-VD
5 * University of Applied Sciences, Western Switzerland
6 * http://www.hes-so.ch
7 *
8 * Contact:
9 * maxence.laurent <at> heig-vd <dot> ch
10 * or
11 * eric.taillard <at> heig-vd <dot> ch
12 *
13 * This file is part of libpal.
14 *
15 * libpal is free software: you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation, either version 3 of the License, or
18 * (at your option) any later version.
19 *
20 * libpal is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with libpal. If not, see <http://www.gnu.org/licenses/>.
27 *
28 */
29
30#include "problem.h"
31
32#include <limits> //for std::numeric_limits<int>
33
34#include "feature.h"
35#include "internalexception.h"
36#include "labelposition.h"
37#include "layer.h"
38#include "pal.h"
39#include "priorityqueue.h"
40#include "qgslabelingengine.h"
42#include "util.h"
43
44using namespace pal;
45
47 : mAllCandidatesIndex( extent )
48 , mActiveCandidatesIndex( extent )
49{
50
51}
52
53void Problem::addCandidatePosition( std::unique_ptr<LabelPosition> position )
54{
55 mLabelPositions.emplace_back( std::move( position ) );
56}
57
58Problem::~Problem() = default;
59
61{
62 int k;
63
64 int counter = 0;
65
66 int lpid;
67
68 std::vector<bool> ok( mTotalCandidates, false );
69
70 LabelPosition *lp2 = nullptr;
71
72 while ( true )
73 {
74 if ( pal->isCanceled() )
75 break;
76
77 bool finished = true;
78 for ( std::size_t feature = 0; feature < mFeatureCount; feature++ )
79 {
80 if ( pal->isCanceled() )
81 break;
82
83 // for each candidate
84 const int totalCandidatesForFeature = mCandidateCountForFeature[feature];
85 for ( int candidateIndex = 0; candidateIndex < totalCandidatesForFeature; candidateIndex++ )
86 {
87 if ( !ok[mFirstCandidateIndexForFeature[feature] + candidateIndex] )
88 {
89 if ( mLabelPositions.at( mFirstCandidateIndexForFeature[feature] + candidateIndex )->getNumOverlaps() == 0 ) // if candidate has no overlap
90 {
91 finished = false;
92 ok[mFirstCandidateIndexForFeature[feature] + candidateIndex] = true;
93 // 1) remove worse candidates from candidates
94 // 2) update nb_overlaps
95 counter += totalCandidatesForFeature - candidateIndex - 1;
96
97 for ( k = candidateIndex + 1; k < totalCandidatesForFeature; k++ )
98 {
99
100 lpid = mFirstCandidateIndexForFeature[feature] + k;
101 ok[lpid] = true;
102 lp2 = mLabelPositions[lpid ].get();
103
104 mNbOverlap -= lp2->getNumOverlaps();
105
106 const QgsRectangle searchBounds = lp2->boundingBoxForCandidateConflicts( pal );
107 mAllCandidatesIndex.intersects( searchBounds, [&lp2, this]( const LabelPosition * lp ) -> bool
108 {
109 if ( candidatesAreConflicting( lp2, lp ) )
110 {
111 const_cast< LabelPosition * >( lp )->decrementNumOverlaps();
113 }
114
115 return true;
116 } );
117 lp2->removeFromIndex( mAllCandidatesIndex, pal );
118 }
119
120 mCandidateCountForFeature[feature] = candidateIndex + 1;
121 break;
122 }
123 }
124 }
125 }
126
127 if ( finished )
128 break;
129 }
130
131 this->mTotalCandidates -= counter;
132}
133
134void Problem::ignoreLabel( const LabelPosition *lp, PriorityQueue &list, PalRtree< LabelPosition > &candidatesIndex )
135{
136 if ( list.isIn( lp->getId() ) )
137 {
138 list.remove( lp->getId() );
139
140 const QgsRectangle searchBounds = lp->boundingBoxForCandidateConflicts( pal );
141 candidatesIndex.intersects( searchBounds, [lp, &list, this]( const LabelPosition * lp2 )->bool
142 {
143 if ( lp2->getId() != lp->getId() && list.isIn( lp2->getId() ) && candidatesAreConflicting( lp2, lp ) )
144 {
145 list.decreaseKey( lp2->getId() );
146 }
147 return true;
148 } );
149 }
150}
151
152/* Better initial solution
153 * Step one FALP (Yamamoto, Camara, Lorena 2005)
154 */
156{
157 int label;
158
159 mSol.init( mFeatureCount );
160
161 PriorityQueue list( mTotalCandidates, mAllNblp, true );
162
163 LabelPosition *lp = nullptr;
164
165 for ( int feature = 0; feature < static_cast< int >( mFeatureCount ); feature++ )
166 {
167 const int totalCandidatesForFeature = mCandidateCountForFeature[feature];
168 for ( int candidateIndex = 0; candidateIndex < totalCandidatesForFeature; candidateIndex++ )
169 {
170 label = mFirstCandidateIndexForFeature[feature] + candidateIndex;
171 try
172 {
173 list.insert( label, mLabelPositions.at( label )->getNumOverlaps() );
174 }
176 {
177 continue;
178 }
179 }
180 }
181
182 while ( list.getSize() > 0 ) // O (log size)
183 {
184 if ( pal->isCanceled() )
185 {
186 return;
187 }
188
189 label = list.getBest(); // O (log size)
190
191 lp = mLabelPositions[ label ].get();
192
193 if ( lp->getId() != label )
194 {
195 //error
196 }
197
198 const int probFeatId = lp->getProblemFeatureId();
199 mSol.activeLabelIds[probFeatId] = label;
200
201 for ( int candidateIndex = mFirstCandidateIndexForFeature[probFeatId]; candidateIndex < mFirstCandidateIndexForFeature[probFeatId] + mCandidateCountForFeature[probFeatId]; candidateIndex++ )
202 {
203 ignoreLabel( mLabelPositions[ candidateIndex ].get(), list, mAllCandidatesIndex );
204 }
205
206
207 const QgsRectangle searchBounds = lp->boundingBoxForCandidateConflicts( pal );
208 std::vector< const LabelPosition * > conflictingPositions;
209 mAllCandidatesIndex.intersects( searchBounds, [lp, &conflictingPositions, this]( const LabelPosition * lp2 ) ->bool
210 {
211 if ( candidatesAreConflicting( lp, lp2 ) )
212 {
213 conflictingPositions.emplace_back( lp2 );
214 }
215 return true;
216 } );
217
218 for ( const LabelPosition *conflict : conflictingPositions )
219 {
220 ignoreLabel( conflict, list, mAllCandidatesIndex );
221 }
222
223 lp->insertIntoIndex( mActiveCandidatesIndex, pal );
224 }
225
226 if ( mDisplayAll )
227 {
228 LabelPosition *retainedLabel = nullptr;
229
230 for ( std::size_t i = 0; i < mFeatureCount; i++ ) // forearch hidden feature
231 {
232 if ( mSol.activeLabelIds[i] == -1 )
233 {
234 int nbOverlap = std::numeric_limits<int>::max();
235 const int firstCandidateIdForFeature = mFirstCandidateIndexForFeature[i];
236 const int totalCandidatesForFeature = mCandidateCountForFeature[i];
237 for ( int candidateIndexForFeature = 0; candidateIndexForFeature < totalCandidatesForFeature; candidateIndexForFeature++ )
238 {
239 lp = mLabelPositions[ firstCandidateIdForFeature + candidateIndexForFeature ].get();
240 lp->resetNumOverlaps();
241
242 const QgsRectangle searchBounds = lp->boundingBoxForCandidateConflicts( pal );
243 mActiveCandidatesIndex.intersects( searchBounds, [&lp, this]( const LabelPosition * lp2 )->bool
244 {
245 if ( candidatesAreConflicting( lp, lp2 ) )
246 {
248 }
249 return true;
250 } );
251
252 if ( lp->getNumOverlaps() < nbOverlap )
253 {
254 retainedLabel = lp;
255 nbOverlap = lp->getNumOverlaps();
256 }
257 }
258 mSol.activeLabelIds[i] = retainedLabel->getId();
259
260 retainedLabel->insertIntoIndex( mActiveCandidatesIndex, pal );
261
262 }
263 }
264 }
265}
266
267bool Problem::candidatesAreConflicting( const LabelPosition *lp1, const LabelPosition *lp2 ) const
268{
269 return pal->candidatesAreConflicting( lp1, lp2 );
270}
271
272inline std::unique_ptr<Chain> Problem::chain( int seed )
273{
274 int lid;
275
276 double delta;
277 double delta_min;
278 double delta_best = std::numeric_limits<double>::max();
279 double delta_tmp;
280
281 int next_seed;
282 int retainedLabel;
283 std::unique_ptr< Chain > retainedChain;
284
285 const int max_degree = pal->mEjChainDeg;
286
287 QVector<ElemTrans *> currentChain;
288 QVector<int> conflicts;
289
290 std::vector< int > tmpsol( mSol.activeLabelIds );
291
292 LabelPosition *lp = nullptr;
293
294 // delta is actually related to the cost?
295 delta = 0;
296 // seed is actually the feature number!
297 while ( seed != -1 )
298 {
299 const int totalCandidatesForThisFeature = mCandidateCountForFeature[seed];
300 delta_min = std::numeric_limits<double>::max();
301
302 next_seed = -1;
303 retainedLabel = -2;
304
305 // sol[seed] is ejected
306 if ( tmpsol[seed] == -1 )
307 delta -= mUnlabeledCostForFeature[seed];
308 else
309 delta -= mLabelPositions.at( tmpsol[seed] )->cost();
310
311 for ( int i = -1; i < totalCandidatesForThisFeature ; i++ )
312 {
313 try
314 {
315 // Skip active label !
316 if ( !( tmpsol[seed] == -1 && i == -1 ) && i + mFirstCandidateIndexForFeature[seed] != tmpsol[seed] )
317 {
318 if ( i != -1 ) // new_label
319 {
320 lid = mFirstCandidateIndexForFeature[seed] + i;
321 delta_tmp = delta;
322
323 lp = mLabelPositions[ lid ].get();
324
325 const QgsRectangle searchBounds = lp->boundingBoxForCandidateConflicts( pal );
326 // evaluate conflicts graph in solution after moving seed's label
327 mActiveCandidatesIndex.intersects( searchBounds, [lp, &delta_tmp, &conflicts, &currentChain, this]( const LabelPosition * lp2 ) -> bool
328 {
329 if ( candidatesAreConflicting( lp2, lp ) )
330 {
331 const int feat = lp2->getProblemFeatureId();
332
333 // is there any cycles ?
334 for ( auto cur = currentChain.begin(); cur != currentChain.end(); ++cur )
335 {
336 if ( ( *cur )->feat == feat )
337 {
338 throw - 1;
339 }
340 }
341
342 if ( !conflicts.contains( feat ) )
343 {
344 conflicts.append( feat );
345 delta_tmp += lp2->cost() + mUnlabeledCostForFeature[feat];
346 }
347 }
348 return true;
349 } );
350
351 // no conflict -> end of chain
352 if ( conflicts.isEmpty() )
353 {
354 if ( !retainedChain || delta + lp->cost() < delta_best )
355 {
356 if ( retainedChain )
357 {
358 retainedChain->label.clear();
359 retainedChain->feat.clear();
360 }
361 else
362 {
363 retainedChain = std::make_unique< Chain >();
364 }
365
366 delta_best = delta + lp->cost();
367
368 retainedChain->degree = currentChain.size() + 1;
369 retainedChain->feat.resize( retainedChain->degree );
370 retainedChain->label.resize( retainedChain->degree );
371 QVector<ElemTrans *>::iterator current = currentChain.begin();
372 ElemTrans *move = nullptr;
373 int j = 0;
374 while ( current != currentChain.end() )
375 {
376 move = *current;
377 retainedChain->feat[j] = move->feat;
378 retainedChain->label[j] = move->new_label;
379 ++current;
380 ++j;
381 }
382 retainedChain->feat[j] = seed;
383 retainedChain->label[j] = lid;
384 retainedChain->delta = delta + lp->cost();
385 }
386 }
387
388 // another feature can be ejected
389 else if ( conflicts.size() == 1 )
390 {
391 if ( delta_tmp < delta_min )
392 {
393 delta_min = delta_tmp;
394 retainedLabel = lid;
395 next_seed = conflicts.takeFirst();
396 }
397 else
398 {
399 conflicts.takeFirst();
400 }
401 }
402 else
403 {
404
405 // A lot of conflict : make them inactive and store chain
406 auto newChain = std::make_unique< Chain >();
407 newChain->degree = currentChain.size() + 1 + conflicts.size();
408 newChain->feat.resize( newChain->degree );
409 newChain->label.resize( newChain->degree );
410 QVector<ElemTrans *>::iterator current = currentChain.begin();
411 ElemTrans *move = nullptr;
412 int j = 0;
413
414 while ( current != currentChain.end() )
415 {
416 move = *current;
417 newChain->feat[j] = move->feat;
418 newChain->label[j] = move->new_label;
419 ++current;
420 ++j;
421 }
422
423 // add the current candidates into the chain
424 newChain->feat[j] = seed;
425 newChain->label[j] = lid;
426 newChain->delta = delta + mLabelPositions.at( newChain->label[j] )->cost();
427 j++;
428
429 // hide all conflictual candidates
430 while ( !conflicts.isEmpty() )
431 {
432 const int ftid = conflicts.takeFirst();
433 newChain->feat[j] = ftid;
434 newChain->label[j] = -1;
435 newChain->delta += mUnlabeledCostForFeature[ftid];
436 j++;
437 }
438
439 if ( newChain->delta < delta_best )
440 {
441 delta_best = newChain->delta;
442 retainedChain = std::move( newChain );
443 }
444 else
445 {
446 newChain.reset();
447 }
448 }
449
450 }
451 else // Current label == -1 end of chain ...
452 {
453 if ( !retainedChain || delta + mUnlabeledCostForFeature[seed] < delta_best )
454 {
455 if ( retainedChain )
456 {
457 retainedChain->label.clear();
458 retainedChain->feat.clear();
459 }
460 else
461 retainedChain = std::make_unique< Chain >();
462
463 delta_best = delta + mUnlabeledCostForFeature[seed];
464
465 retainedChain->degree = currentChain.size() + 1;
466 retainedChain->feat.resize( retainedChain->degree );
467 retainedChain->label.resize( retainedChain->degree );
468 QVector<ElemTrans *>::iterator current = currentChain.begin();
469 ElemTrans *move = nullptr;
470 int j = 0;
471 while ( current != currentChain.end() )
472 {
473 move = *current;
474 retainedChain->feat[j] = move->feat;
475 retainedChain->label[j] = move->new_label;
476 ++current;
477 ++j;
478 }
479 retainedChain->feat[j] = seed;
480 retainedChain->label[j] = -1;
481 retainedChain->delta = delta + mUnlabeledCostForFeature[seed];
482 }
483 }
484 }
485 }
486 catch ( int )
487 {
488 conflicts.clear();
489 }
490 } // end for each labelposition
491
492 if ( next_seed == -1 )
493 {
494 seed = -1;
495 }
496 else if ( currentChain.size() > max_degree )
497 {
498 // Max degree reached
499 seed = -1;
500 }
501 else
502 {
503 ElemTrans *et = new ElemTrans();
504 et->feat = seed;
505 et->old_label = tmpsol[seed];
506 et->new_label = retainedLabel;
507 currentChain.append( et );
508
509 if ( et->old_label != -1 )
510 {
511 mLabelPositions.at( et->old_label )->removeFromIndex( mActiveCandidatesIndex, pal );
512 }
513
514 if ( et->new_label != -1 )
515 {
516 mLabelPositions.at( et->new_label )->insertIntoIndex( mActiveCandidatesIndex, pal );
517 }
518
519
520 tmpsol[seed] = retainedLabel;
521 // cppcheck-suppress invalidFunctionArg
522 delta += mLabelPositions.at( retainedLabel )->cost();
523 seed = next_seed;
524 }
525 }
526
527 while ( !currentChain.isEmpty() )
528 {
529 std::unique_ptr< ElemTrans > et( currentChain.takeFirst() );
530
531 if ( et->new_label != -1 )
532 {
533 mLabelPositions.at( et->new_label )->removeFromIndex( mActiveCandidatesIndex, pal );
534 }
535
536 if ( et->old_label != -1 )
537 {
538 mLabelPositions.at( et->old_label )->insertIntoIndex( mActiveCandidatesIndex, pal );
539 }
540 }
541
542 return retainedChain;
543}
544
545
547{
548 if ( mFeatureCount == 0 )
549 return;
550
551 int i;
552 std::vector< bool > ok( mFeatureCount, false );
553 int fid;
554 int lid;
555
556 std::unique_ptr< Chain > retainedChain;
557
559
560 int iter = 0;
561
562 // seed is actually the feature ID, maybe should be renamed?
563 int seed;
564 while ( true )
565 {
566 for ( seed = ( iter + 1 ) % mFeatureCount;
567 ok[seed] && seed != iter;
568 seed = ( seed + 1 ) % mFeatureCount )
569 ;
570
571 // All seeds are OK
572 if ( seed == iter )
573 {
574 break;
575 }
576
577 iter = ( iter + 1 ) % mFeatureCount;
578 retainedChain = chain( seed );
579
580 if ( retainedChain && retainedChain->delta < - EPSILON )
581 {
582 // apply modification
583 for ( i = 0; i < retainedChain->degree; i++ )
584 {
585 fid = retainedChain->feat[i];
586 lid = retainedChain->label[i];
587
588 if ( mSol.activeLabelIds[fid] >= 0 )
589 {
590 LabelPosition *old = mLabelPositions[ mSol.activeLabelIds[fid] ].get();
591 old->removeFromIndex( mActiveCandidatesIndex, pal );
592
593 const QgsRectangle searchBounds = old->boundingBoxForCandidateConflicts( pal );
594 mAllCandidatesIndex.intersects( searchBounds, [&ok, old, this]( const LabelPosition * lp ) ->bool
595 {
596 if ( candidatesAreConflicting( old, lp ) )
597 {
598 ok[lp->getProblemFeatureId()] = false;
599 }
600
601 return true;
602 } );
603 }
604
605 mSol.activeLabelIds[fid] = lid;
606
607 if ( mSol.activeLabelIds[fid] >= 0 )
608 {
609 mLabelPositions.at( lid )->insertIntoIndex( mActiveCandidatesIndex, pal );
610 }
611
612 ok[fid] = false;
613 }
614 }
615 else
616 {
617 // no chain or the one is not good enough
618 ok[seed] = true;
619 }
620 }
621}
622
623QList<LabelPosition *> Problem::getSolution( bool returnInactive, QList<LabelPosition *> *unlabeled )
624{
625 QList<LabelPosition *> finalLabelPlacements;
626 finalLabelPlacements.reserve( mFeatureCount );
627
628 // loop through all features to be labeled
629 for ( std::size_t i = 0; i < mFeatureCount; i++ )
630 {
631 const int labelId = mSol.activeLabelIds[i];
632 const bool foundNonOverlappingPlacement = labelId != -1;
633 const int startIndexForLabelPlacements = mFirstCandidateIndexForFeature[i];
634 const bool foundCandidatesForFeature = startIndexForLabelPlacements < static_cast< int >( mLabelPositions.size() );
635
636 if ( foundNonOverlappingPlacement )
637 {
638 finalLabelPlacements.push_back( mLabelPositions[ labelId ].get() ); // active labels
639 }
640 else if ( foundCandidatesForFeature &&
641 ( returnInactive // allowing any overlapping labels regardless of where they are from
642 || mLabelPositions.at( startIndexForLabelPlacements )->getFeaturePart()->feature()->overlapHandling() == Qgis::LabelOverlapHandling::AllowOverlapIfRequired // allowing overlapping labels for the layer
643 || mLabelPositions.at( startIndexForLabelPlacements )->getFeaturePart()->alwaysShow() ) ) // allowing overlapping labels for the feature
644 {
645 finalLabelPlacements.push_back( mLabelPositions[ startIndexForLabelPlacements ].get() ); // unplaced label
646 }
647 else if ( unlabeled )
648 {
649 // need to be careful here -- if the next feature's start id is the same as this one, then this feature had no candidates!
650 if ( foundCandidatesForFeature && ( i == mFeatureCount - 1 || startIndexForLabelPlacements != mFirstCandidateIndexForFeature[i + 1] ) )
651 unlabeled->push_back( mLabelPositions[ startIndexForLabelPlacements ].get() );
652 }
653 }
654
655 // unlabeled features also include those with no candidates
656 if ( unlabeled )
657 {
658 unlabeled->reserve( mPositionsWithNoCandidates.size() );
659 for ( const std::unique_ptr< LabelPosition > &position : mPositionsWithNoCandidates )
660 unlabeled->append( position.get() );
661 }
662
663 return finalLabelPlacements;
664}
A rtree spatial index for use in the pal labeling engine.
Definition palrtree.h:37
bool intersects(const QgsRectangle &bounds, const std::function< bool(T *data)> &callback) const
Performs an intersection check against the index, for data intersecting the specified bounds.
Definition palrtree.h:105
@ AllowOverlapIfRequired
Avoids overlapping labels when possible, but permit overlaps if labels for features cannot otherwise ...
Definition qgis.h:1188
A rectangle specified with double values.
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
Contains information about the context of a rendering operation.
Thrown when something is added in a Full set.
LabelPosition is a candidate feature label position.
void incrementNumOverlaps()
Increases the number of overlaps recorded against this position by 1.
void decrementNumOverlaps()
Decreases the number of overlaps recorded against this position by 1.
int getId() const
Returns the id.
QgsRectangle boundingBoxForCandidateConflicts(Pal *pal) const
Returns the bounding box to use for candidate conflicts.
void removeFromIndex(PalRtree< LabelPosition > &index, Pal *pal)
Removes the label position from the specified index.
double cost() const
Returns the candidate label position's geographical cost.
int getNumOverlaps() const
int getProblemFeatureId() const
void insertIntoIndex(PalRtree< LabelPosition > &index, Pal *pal)
Inserts the label position into the specified index.
Custom priority queue for use in pal labeling engine.
void decreaseKey(int key)
void insert(int key, double p)
bool isIn(int key) const
QList< LabelPosition * > getSolution(bool returnInactive, QList< LabelPosition * > *unlabeled=nullptr)
Solves the labeling problem, selecting the best candidate locations for all labels and returns a list...
Definition problem.cpp:623
void addCandidatePosition(std::unique_ptr< LabelPosition > position)
Adds a candidate label position to the problem.
Definition problem.cpp:53
Problem(const QgsRectangle &extent)
Constructor for Problem.
Definition problem.cpp:46
void init_sol_falp()
Definition problem.cpp:155
void chainSearch(QgsRenderContext &context)
Test with very-large scale neighborhood.
Definition problem.cpp:546
void reduce()
Gets called AFTER extractProblem.
Definition problem.cpp:60
int new_label
Definition util.h:73
int old_label
Definition util.h:72
#define EPSILON
Definition util.h:81