QGIS API Documentation 3.99.0-Master (26c88405ac0)
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 QLinkedList<ElemTrans *> currentChain;
288 QLinkedList<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 QLinkedList< ElemTrans * >::iterator cur;
335 for ( cur = currentChain.begin(); cur != currentChain.end(); ++cur )
336 {
337 if ( ( *cur )->feat == feat )
338 {
339 throw - 1;
340 }
341 }
342
343 if ( !conflicts.contains( feat ) )
344 {
345 conflicts.append( feat );
346 delta_tmp += lp2->cost() + mUnlabeledCostForFeature[feat];
347 }
348 }
349 return true;
350 } );
351
352 // no conflict -> end of chain
353 if ( conflicts.isEmpty() )
354 {
355 if ( !retainedChain || delta + lp->cost() < delta_best )
356 {
357 if ( retainedChain )
358 {
359 retainedChain->label.clear();
360 retainedChain->feat.clear();
361 }
362 else
363 {
364 retainedChain = std::make_unique< Chain >();
365 }
366
367 delta_best = delta + lp->cost();
368
369 retainedChain->degree = currentChain.size() + 1;
370 retainedChain->feat.resize( retainedChain->degree );
371 retainedChain->label.resize( retainedChain->degree );
372 QLinkedList<ElemTrans *>::iterator current = currentChain.begin();
373 ElemTrans *move = nullptr;
374 int j = 0;
375 while ( current != currentChain.end() )
376 {
377 move = *current;
378 retainedChain->feat[j] = move->feat;
379 retainedChain->label[j] = move->new_label;
380 ++current;
381 ++j;
382 }
383 retainedChain->feat[j] = seed;
384 retainedChain->label[j] = lid;
385 retainedChain->delta = delta + lp->cost();
386 }
387 }
388
389 // another feature can be ejected
390 else if ( conflicts.size() == 1 )
391 {
392 if ( delta_tmp < delta_min )
393 {
394 delta_min = delta_tmp;
395 retainedLabel = lid;
396 next_seed = conflicts.takeFirst();
397 }
398 else
399 {
400 conflicts.takeFirst();
401 }
402 }
403 else
404 {
405
406 // A lot of conflict : make them inactive and store chain
407 auto newChain = std::make_unique< Chain >();
408 newChain->degree = currentChain.size() + 1 + conflicts.size();
409 newChain->feat.resize( newChain->degree );
410 newChain->label.resize( newChain->degree );
411 QLinkedList<ElemTrans *>::iterator current = currentChain.begin();
412 ElemTrans *move = nullptr;
413 int j = 0;
414
415 while ( current != currentChain.end() )
416 {
417 move = *current;
418 newChain->feat[j] = move->feat;
419 newChain->label[j] = move->new_label;
420 ++current;
421 ++j;
422 }
423
424 // add the current candidates into the chain
425 newChain->feat[j] = seed;
426 newChain->label[j] = lid;
427 newChain->delta = delta + mLabelPositions.at( newChain->label[j] )->cost();
428 j++;
429
430 // hide all conflictual candidates
431 while ( !conflicts.isEmpty() )
432 {
433 const int ftid = conflicts.takeFirst();
434 newChain->feat[j] = ftid;
435 newChain->label[j] = -1;
436 newChain->delta += mUnlabeledCostForFeature[ftid];
437 j++;
438 }
439
440 if ( newChain->delta < delta_best )
441 {
442 delta_best = newChain->delta;
443 retainedChain = std::move( newChain );
444 }
445 else
446 {
447 newChain.reset();
448 }
449 }
450
451 }
452 else // Current label == -1 end of chain ...
453 {
454 if ( !retainedChain || delta + mUnlabeledCostForFeature[seed] < delta_best )
455 {
456 if ( retainedChain )
457 {
458 retainedChain->label.clear();
459 retainedChain->feat.clear();
460 }
461 else
462 retainedChain = std::make_unique< Chain >();
463
464 delta_best = delta + mUnlabeledCostForFeature[seed];
465
466 retainedChain->degree = currentChain.size() + 1;
467 retainedChain->feat.resize( retainedChain->degree );
468 retainedChain->label.resize( retainedChain->degree );
469 QLinkedList<ElemTrans *>::iterator current = currentChain.begin();
470 ElemTrans *move = nullptr;
471 int j = 0;
472 while ( current != currentChain.end() )
473 {
474 move = *current;
475 retainedChain->feat[j] = move->feat;
476 retainedChain->label[j] = move->new_label;
477 ++current;
478 ++j;
479 }
480 retainedChain->feat[j] = seed;
481 retainedChain->label[j] = -1;
482 retainedChain->delta = delta + mUnlabeledCostForFeature[seed];
483 }
484 }
485 }
486 }
487 catch ( int )
488 {
489 conflicts.clear();
490 }
491 } // end for each labelposition
492
493 if ( next_seed == -1 )
494 {
495 seed = -1;
496 }
497 else if ( currentChain.size() > max_degree )
498 {
499 // Max degree reached
500 seed = -1;
501 }
502 else
503 {
504 ElemTrans *et = new ElemTrans();
505 et->feat = seed;
506 et->old_label = tmpsol[seed];
507 et->new_label = retainedLabel;
508 currentChain.append( et );
509
510 if ( et->old_label != -1 )
511 {
512 mLabelPositions.at( et->old_label )->removeFromIndex( mActiveCandidatesIndex, pal );
513 }
514
515 if ( et->new_label != -1 )
516 {
517 mLabelPositions.at( et->new_label )->insertIntoIndex( mActiveCandidatesIndex, pal );
518 }
519
520
521 tmpsol[seed] = retainedLabel;
522 // cppcheck-suppress invalidFunctionArg
523 delta += mLabelPositions.at( retainedLabel )->cost();
524 seed = next_seed;
525 }
526 }
527
528 while ( !currentChain.isEmpty() )
529 {
530 std::unique_ptr< ElemTrans > et( currentChain.takeFirst() );
531
532 if ( et->new_label != -1 )
533 {
534 mLabelPositions.at( et->new_label )->removeFromIndex( mActiveCandidatesIndex, pal );
535 }
536
537 if ( et->old_label != -1 )
538 {
539 mLabelPositions.at( et->old_label )->insertIntoIndex( mActiveCandidatesIndex, pal );
540 }
541 }
542
543 return retainedChain;
544}
545
546
548{
549 if ( mFeatureCount == 0 )
550 return;
551
552 int i;
553 std::vector< bool > ok( mFeatureCount, false );
554 int fid;
555 int lid;
556
557 std::unique_ptr< Chain > retainedChain;
558
560
561 int iter = 0;
562
563 // seed is actually the feature ID, maybe should be renamed?
564 int seed;
565 while ( true )
566 {
567 for ( seed = ( iter + 1 ) % mFeatureCount;
568 ok[seed] && seed != iter;
569 seed = ( seed + 1 ) % mFeatureCount )
570 ;
571
572 // All seeds are OK
573 if ( seed == iter )
574 {
575 break;
576 }
577
578 iter = ( iter + 1 ) % mFeatureCount;
579 retainedChain = chain( seed );
580
581 if ( retainedChain && retainedChain->delta < - EPSILON )
582 {
583 // apply modification
584 for ( i = 0; i < retainedChain->degree; i++ )
585 {
586 fid = retainedChain->feat[i];
587 lid = retainedChain->label[i];
588
589 if ( mSol.activeLabelIds[fid] >= 0 )
590 {
591 LabelPosition *old = mLabelPositions[ mSol.activeLabelIds[fid] ].get();
592 old->removeFromIndex( mActiveCandidatesIndex, pal );
593
594 const QgsRectangle searchBounds = old->boundingBoxForCandidateConflicts( pal );
595 mAllCandidatesIndex.intersects( searchBounds, [&ok, old, this]( const LabelPosition * lp ) ->bool
596 {
597 if ( candidatesAreConflicting( old, lp ) )
598 {
599 ok[lp->getProblemFeatureId()] = false;
600 }
601
602 return true;
603 } );
604 }
605
606 mSol.activeLabelIds[fid] = lid;
607
608 if ( mSol.activeLabelIds[fid] >= 0 )
609 {
610 mLabelPositions.at( lid )->insertIntoIndex( mActiveCandidatesIndex, pal );
611 }
612
613 ok[fid] = false;
614 }
615 }
616 else
617 {
618 // no chain or the one is not good enough
619 ok[seed] = true;
620 }
621 }
622}
623
624QList<LabelPosition *> Problem::getSolution( bool returnInactive, QList<LabelPosition *> *unlabeled )
625{
626 QList<LabelPosition *> finalLabelPlacements;
627 finalLabelPlacements.reserve( mFeatureCount );
628
629 // loop through all features to be labeled
630 for ( std::size_t i = 0; i < mFeatureCount; i++ )
631 {
632 const int labelId = mSol.activeLabelIds[i];
633 const bool foundNonOverlappingPlacement = labelId != -1;
634 const int startIndexForLabelPlacements = mFirstCandidateIndexForFeature[i];
635 const bool foundCandidatesForFeature = startIndexForLabelPlacements < static_cast< int >( mLabelPositions.size() );
636
637 if ( foundNonOverlappingPlacement )
638 {
639 finalLabelPlacements.push_back( mLabelPositions[ labelId ].get() ); // active labels
640 }
641 else if ( foundCandidatesForFeature &&
642 ( returnInactive // allowing any overlapping labels regardless of where they are from
643 || mLabelPositions.at( startIndexForLabelPlacements )->getFeaturePart()->feature()->overlapHandling() == Qgis::LabelOverlapHandling::AllowOverlapIfRequired // allowing overlapping labels for the layer
644 || mLabelPositions.at( startIndexForLabelPlacements )->getFeaturePart()->alwaysShow() ) ) // allowing overlapping labels for the feature
645 {
646 finalLabelPlacements.push_back( mLabelPositions[ startIndexForLabelPlacements ].get() ); // unplaced label
647 }
648 else if ( unlabeled )
649 {
650 // need to be careful here -- if the next feature's start id is the same as this one, then this feature had no candidates!
651 if ( foundCandidatesForFeature && ( i == mFeatureCount - 1 || startIndexForLabelPlacements != mFirstCandidateIndexForFeature[i + 1] ) )
652 unlabeled->push_back( mLabelPositions[ startIndexForLabelPlacements ].get() );
653 }
654 }
655
656 // unlabeled features also include those with no candidates
657 if ( unlabeled )
658 {
659 unlabeled->reserve( mPositionsWithNoCandidates.size() );
660 for ( const std::unique_ptr< LabelPosition > &position : mPositionsWithNoCandidates )
661 unlabeled->append( position.get() );
662 }
663
664 return finalLabelPlacements;
665}
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:1169
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:624
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:547
void reduce()
Gets called AFTER extractProblem.
Definition problem.cpp:60
int new_label
Definition util.h:74
int old_label
Definition util.h:73
#define EPSILON
Definition util.h:82