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