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