QGIS API Documentation 3.43.0-Master (b60ef06885e)
pal.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 "qgsgeometry.h"
31#include "pal.h"
32#include "layer.h"
33#include "costcalculator.h"
34#include "feature.h"
35#include "geomfunction.h"
36#include "labelposition.h"
37#include "problem.h"
38#include "pointset.h"
39#include "internalexception.h"
40#include "util.h"
41#include "palrtree.h"
42#include "qgslabelingengine.h"
43#include "qgsrendercontext.h"
45#include "qgsruntimeprofiler.h"
47
48#include <cfloat>
49#include <list>
50
51
52using namespace pal;
53
54const QgsSettingsEntryInteger *Pal::settingsRenderingLabelCandidatesLimitPoints = new QgsSettingsEntryInteger( QStringLiteral( "label-candidates-limit-points" ), sTreePal, 0 );
55const QgsSettingsEntryInteger *Pal::settingsRenderingLabelCandidatesLimitLines = new QgsSettingsEntryInteger( QStringLiteral( "label-candidates-limit-lines" ), sTreePal, 0 );
56const QgsSettingsEntryInteger *Pal::settingsRenderingLabelCandidatesLimitPolygons = new QgsSettingsEntryInteger( QStringLiteral( "label-candidates-limit-polygons" ), sTreePal, 0 );
57
58
60{
61 mGlobalCandidatesLimitPoint = Pal::settingsRenderingLabelCandidatesLimitPoints->value();
62 mGlobalCandidatesLimitLine = Pal::settingsRenderingLabelCandidatesLimitLines->value();
63 mGlobalCandidatesLimitPolygon = Pal::settingsRenderingLabelCandidatesLimitPolygons->value();
64}
65
66Pal::~Pal() = default;
67
68void Pal::removeLayer( Layer *layer )
69{
70 if ( !layer )
71 return;
72
73 mMutex.lock();
74
75 for ( auto it = mLayers.begin(); it != mLayers.end(); ++it )
76 {
77 if ( it->second.get() == layer )
78 {
79 mLayers.erase( it );
80 break;
81 }
82 }
83 mMutex.unlock();
84}
85
86Layer *Pal::addLayer( QgsAbstractLabelProvider *provider, const QString &layerName, Qgis::LabelPlacement arrangement, double defaultPriority, bool active, bool toLabel )
87{
88 mMutex.lock();
89
90#ifdef QGISDEBUG
91 for ( const auto &it : mLayers )
92 {
93 Q_ASSERT( it.first != provider );
94 }
95#endif
96
97 auto layer = std::make_unique< Layer >( provider, layerName, arrangement, defaultPriority, active, toLabel, this );
98 Layer *res = layer.get();
99 mLayers.emplace_back( std::make_pair( provider, std::move( layer ) ) );
100 mMutex.unlock();
101
102 // cppcheck-suppress returnDanglingLifetime
103 return res;
104}
105
106std::unique_ptr<Problem> Pal::extractProblem( const QgsRectangle &extent, const QgsGeometry &mapBoundary, QgsRenderContext &context )
107{
108 QgsLabelingEngineFeedback *feedback = qobject_cast< QgsLabelingEngineFeedback * >( context.feedback() );
109 QgsLabelingEngineContext labelContext( context );
110 labelContext.setExtent( extent );
111 labelContext.setMapBoundaryGeometry( mapBoundary );
112
113 std::unique_ptr< QgsScopedRuntimeProfile > extractionProfile;
115 {
116 extractionProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Placing labels" ), QStringLiteral( "rendering" ) );
117 }
118
119 // expand out the incoming buffer by 1000x -- that's the visible map extent, yet we may be getting features which exceed this extent
120 // (while 1000x may seem excessive here, this value is only used for scaling coordinates in the spatial indexes
121 // and the consequence of inserting coordinates outside this extent is worse than the consequence of setting this value too large.)
122 const QgsRectangle maxCoordinateExtentForSpatialIndices = extent.buffered( std::max( extent.width(), extent.height() ) * 1000 );
123
124 // to store obstacles
125 PalRtree< FeaturePart > obstacles( maxCoordinateExtentForSpatialIndices );
126 PalRtree< LabelPosition > allCandidatesFirstRound( maxCoordinateExtentForSpatialIndices );
127 std::vector< FeaturePart * > allObstacleParts;
128 auto prob = std::make_unique< Problem >( maxCoordinateExtentForSpatialIndices );
129
130 double bbx[4];
131 double bby[4];
132
133 bbx[0] = bbx[3] = prob->mMapExtentBounds[0] = extent.xMinimum();
134 bby[0] = bby[1] = prob->mMapExtentBounds[1] = extent.yMinimum();
135 bbx[1] = bbx[2] = prob->mMapExtentBounds[2] = extent.xMaximum();
136 bby[2] = bby[3] = prob->mMapExtentBounds[3] = extent.yMaximum();
137
138 prob->pal = this;
139
140 std::list< std::unique_ptr< Feats > > features;
141
142 // prepare map boundary
143 geos::unique_ptr mapBoundaryGeos( QgsGeos::asGeos( mapBoundary ) );
144 geos::prepared_unique_ptr mapBoundaryPrepared( GEOSPrepare_r( QgsGeosContext::get(), mapBoundaryGeos.get() ) );
145
146 int obstacleCount = 0;
147
148 // first step : extract features from layers
149
150 std::size_t previousFeatureCount = 0;
151 int previousObstacleCount = 0;
152
153 QStringList layersWithFeaturesInBBox;
154
155 QMutexLocker palLocker( &mMutex );
156
157 double step = !mLayers.empty() ? 100.0 / mLayers.size() : 1;
158 int index = -1;
159 std::unique_ptr< QgsScopedRuntimeProfile > candidateProfile;
161 {
162 candidateProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Generating label candidates" ), QStringLiteral( "rendering" ) );
163 }
164
165 for ( auto it = mLayers.rbegin(); it != mLayers.rend(); ++it )
166 {
167 index++;
168 if ( feedback )
169 feedback->setProgress( index * step );
170
171 Layer *layer = it->second.get();
172 if ( !layer )
173 {
174 // invalid layer name
175 continue;
176 }
177
178 // only select those who are active
179 if ( !layer->active() )
180 continue;
181
182 if ( feedback )
183 feedback->emit candidateCreationAboutToBegin( it->first );
184
185 std::unique_ptr< QgsScopedRuntimeProfile > layerProfile;
187 {
188 layerProfile = std::make_unique< QgsScopedRuntimeProfile >( it->first->providerId(), QStringLiteral( "rendering" ) );
189 }
190
191 // check for connected features with the same label text and join them
192 if ( layer->mergeConnectedLines() )
193 layer->joinConnectedFeatures();
194
195 if ( isCanceled() )
196 return nullptr;
197
199
200 if ( isCanceled() )
201 return nullptr;
202
203 QMutexLocker locker( &layer->mMutex );
204
205 const double featureStep = !layer->mFeatureParts.empty() ? step / layer->mFeatureParts.size() : 1;
206 std::size_t featureIndex = 0;
207 // generate candidates for all features
208 for ( const std::unique_ptr< FeaturePart > &featurePart : std::as_const( layer->mFeatureParts ) )
209 {
210 if ( feedback )
211 feedback->setProgress( index * step + featureIndex * featureStep );
212 featureIndex++;
213
214 if ( isCanceled() )
215 break;
216
217 // Holes of the feature are obstacles
218 for ( int i = 0; i < featurePart->getNumSelfObstacles(); i++ )
219 {
220 FeaturePart *selfObstacle = featurePart->getSelfObstacle( i );
221 obstacles.insert( selfObstacle, selfObstacle->boundingBox() );
222 allObstacleParts.emplace_back( selfObstacle );
223
224 if ( !featurePart->getSelfObstacle( i )->getHoleOf() )
225 {
226 //ERROR: SHOULD HAVE A PARENT!!!!!
227 }
228 }
229
230 // generate candidates for the feature part
231 std::vector< std::unique_ptr< LabelPosition > > candidates = featurePart->createCandidates( this );
232
233 if ( isCanceled() )
234 break;
235
236 // purge candidates that violate known constraints, eg
237 // - they are outside the bbox
238 // - they violate a labeling rule
239 candidates.erase( std::remove_if( candidates.begin(), candidates.end(), [&mapBoundaryPrepared, &labelContext, this]( std::unique_ptr< LabelPosition > &candidate )
240 {
241 if ( showPartialLabels() )
242 {
243 if ( !candidate->intersects( mapBoundaryPrepared.get() ) )
244 return true;
245 }
246 else
247 {
248 if ( !candidate->within( mapBoundaryPrepared.get() ) )
249 return true;
250 }
251
252 for ( QgsAbstractLabelingEngineRule *rule : std::as_const( mRules ) )
253 {
254 if ( rule->candidateIsIllegal( candidate.get(), labelContext ) )
255 {
256 return true;
257 }
258 }
259 return false;
260 } ), candidates.end() );
261
262 if ( isCanceled() )
263 break;
264
265 if ( !candidates.empty() )
266 {
267 for ( std::unique_ptr< LabelPosition > &candidate : candidates )
268 {
269 candidate->insertIntoIndex( allCandidatesFirstRound );
270 candidate->setGlobalId( mNextCandidateId++ );
271 }
272
273 std::sort( candidates.begin(), candidates.end(), CostCalculator::candidateSortGrow );
274
275 // valid features are added to fFeats
276 auto ft = std::make_unique< Feats >();
277 ft->feature = featurePart.get();
278 ft->shape = nullptr;
279 ft->candidates = std::move( candidates );
280 ft->priority = featurePart->calculatePriority();
281 features.emplace_back( std::move( ft ) );
282 }
283 else
284 {
285 // no candidates, so generate a default "point on surface" one
286 std::unique_ptr< LabelPosition > unplacedPosition = featurePart->createCandidatePointOnSurface( featurePart.get() );
287 if ( !unplacedPosition )
288 continue;
289
290 if ( featurePart->feature()->allowDegradedPlacement() )
291 {
292 // if we are allowing degraded placements, we throw the default candidate in too
293 unplacedPosition->insertIntoIndex( allCandidatesFirstRound );
294 unplacedPosition->setGlobalId( mNextCandidateId++ );
295 candidates.emplace_back( std::move( unplacedPosition ) );
296
297 // valid features are added to fFeats
298 auto ft = std::make_unique< Feats >();
299 ft->feature = featurePart.get();
300 ft->shape = nullptr;
301 ft->candidates = std::move( candidates );
302 ft->priority = featurePart->calculatePriority();
303 features.emplace_back( std::move( ft ) );
304 }
305 else
306 {
307 // not displaying all labels for this layer, so it goes into the unlabeled feature list
308 prob->positionsWithNoCandidates()->emplace_back( std::move( unplacedPosition ) );
309 }
310 }
311 }
312 if ( isCanceled() )
313 return nullptr;
314
315 // collate all layer obstacles
316 for ( FeaturePart *obstaclePart : std::as_const( layer->mObstacleParts ) )
317 {
318 if ( isCanceled() )
319 break; // do not continue searching
320
321 // insert into obstacles
322 obstacles.insert( obstaclePart, obstaclePart->boundingBox() );
323 allObstacleParts.emplace_back( obstaclePart );
324 obstacleCount++;
325 }
326
327 if ( isCanceled() )
328 return nullptr;
329
330 locker.unlock();
331
332 if ( features.size() - previousFeatureCount > 0 || obstacleCount > previousObstacleCount )
333 {
334 layersWithFeaturesInBBox << layer->name();
335 }
336 previousFeatureCount = features.size();
337 previousObstacleCount = obstacleCount;
338
339 if ( feedback )
340 feedback->emit candidateCreationFinished( it->first );
341 }
342
343 candidateProfile.reset();
344
345 palLocker.unlock();
346
347 if ( isCanceled() )
348 return nullptr;
349
350 prob->mLayerCount = layersWithFeaturesInBBox.size();
351 prob->labelledLayersName = layersWithFeaturesInBBox;
352
353 prob->mFeatureCount = features.size();
354 prob->mTotalCandidates = 0;
355 prob->mCandidateCountForFeature.resize( prob->mFeatureCount );
356 prob->mFirstCandidateIndexForFeature.resize( prob->mFeatureCount );
357 prob->mUnlabeledCostForFeature.resize( prob->mFeatureCount );
358
359 if ( !features.empty() )
360 {
361 if ( feedback )
362 feedback->emit obstacleCostingAboutToBegin();
363
364 std::unique_ptr< QgsScopedRuntimeProfile > costingProfile;
365 if ( context.flags() & Qgis::RenderContextFlag::RecordProfile )
366 {
367 costingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Assigning label costs" ), QStringLiteral( "rendering" ) );
368 }
369
370 // allow rules to alter candidate costs
371 for ( const auto &feature : features )
372 {
373 for ( auto &candidate : feature->candidates )
374 {
375 for ( QgsAbstractLabelingEngineRule *rule : std::as_const( mRules ) )
376 {
377 rule->alterCandidateCost( candidate.get(), labelContext );
378 }
379 }
380 }
381
382 // Filtering label positions against obstacles
383 index = -1;
384 step = !allObstacleParts.empty() ? 100.0 / allObstacleParts.size() : 1;
385
386 for ( FeaturePart *obstaclePart : allObstacleParts )
387 {
388 index++;
389 if ( feedback )
390 feedback->setProgress( step * index );
391
392 if ( isCanceled() )
393 break; // do not continue searching
394
395 allCandidatesFirstRound.intersects( obstaclePart->boundingBox(), [obstaclePart, this]( const LabelPosition * candidatePosition ) -> bool
396 {
397 // test whether we should ignore this obstacle for the candidate. We do this if:
398 // 1. it's not a hole, and the obstacle belongs to the same label feature as the candidate (e.g.,
399 // features aren't obstacles for their own labels)
400 // 2. it IS a hole, and the hole belongs to a different label feature to the candidate (e.g., holes
401 // are ONLY obstacles for the labels of the feature they belong to)
402 // 3. The label is set to "Always Allow" overlap mode
403 if ( candidatePosition->getFeaturePart()->feature()->overlapHandling() == Qgis::LabelOverlapHandling::AllowOverlapAtNoCost
404 || ( !obstaclePart->getHoleOf() && candidatePosition->getFeaturePart()->hasSameLabelFeatureAs( obstaclePart ) )
405 || ( obstaclePart->getHoleOf() && !candidatePosition->getFeaturePart()->hasSameLabelFeatureAs( dynamic_cast< FeaturePart * >( obstaclePart->getHoleOf() ) ) ) )
406 {
407 return true;
408 }
409
410 CostCalculator::addObstacleCostPenalty( const_cast< LabelPosition * >( candidatePosition ), obstaclePart, this );
411 return true;
412 } );
413 }
414
415 if ( feedback )
416 feedback->emit obstacleCostingFinished();
417 costingProfile.reset();
418
419 if ( isCanceled() )
420 {
421 return nullptr;
422 }
423
424 step = prob->mFeatureCount != 0 ? 100.0 / prob->mFeatureCount : 1;
425 if ( feedback )
426 feedback->emit calculatingConflictsAboutToBegin();
427
428 std::unique_ptr< QgsScopedRuntimeProfile > conflictProfile;
429 if ( context.flags() & Qgis::RenderContextFlag::RecordProfile )
430 {
431 conflictProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Calculating conflicts" ), QStringLiteral( "rendering" ) );
432 }
433
434 int currentLabelPositionIndex = 0;
435 // loop through all the features registered in the problem
436 for ( std::size_t featureIndex = 0; featureIndex < prob->mFeatureCount; featureIndex++ )
437 {
438 if ( feedback )
439 feedback->setProgress( static_cast< double >( featureIndex ) * step );
440
441 std::unique_ptr< Feats > feat = std::move( features.front() );
442 features.pop_front();
443
444 prob->mFirstCandidateIndexForFeature[featureIndex] = currentLabelPositionIndex;
445 prob->mUnlabeledCostForFeature[featureIndex] = std::pow( 2, 10 - 10 * feat->priority );
446
447 std::size_t maxCandidates = 0;
448 switch ( feat->feature->getGeosType() )
449 {
450 case GEOS_POINT:
451 // this is usually 0, i.e. no maximum
452 maxCandidates = feat->feature->maximumPointCandidates();
453 break;
454
455 case GEOS_LINESTRING:
456 maxCandidates = feat->feature->maximumLineCandidates();
457 break;
458
459 case GEOS_POLYGON:
460 maxCandidates = std::max( static_cast< std::size_t >( 16 ), feat->feature->maximumPolygonCandidates() );
461 break;
462 }
463
464 if ( isCanceled() )
465 return nullptr;
466
467 auto pruneHardConflicts = [&]
468 {
469 switch ( mPlacementVersion )
470 {
472 break;
473
475 {
476 // v2 placement rips out candidates where the candidate cost is too high when compared to
477 // their inactive cost
478
479 // note, we start this at the SECOND candidate (you'll see why after this loop)
480 feat->candidates.erase( std::remove_if( feat->candidates.begin() + 1, feat->candidates.end(), [ & ]( std::unique_ptr< LabelPosition > &candidate )
481 {
482 if ( candidate->hasHardObstacleConflict() )
483 {
484 return true;
485 }
486 return false;
487 } ), feat->candidates.end() );
488
489 if ( feat->candidates.size() == 1 && feat->candidates[ 0 ]->hasHardObstacleConflict() )
490 {
491 switch ( feat->feature->feature()->overlapHandling() )
492 {
494 {
495 // we're going to end up removing ALL candidates for this label. Oh well, that's allowed. We just need to
496 // make sure we move this last candidate to the unplaced labels list
497 prob->positionsWithNoCandidates()->emplace_back( std::move( feat->candidates.front() ) );
498 feat->candidates.clear();
499 break;
500 }
501
504 // we can't avoid overlaps for this label, but in this mode we are allowing overlaps as a last resort.
505 // => don't discard this last remaining candidate.
506 break;
507 }
508 }
509 }
510 }
511 };
512
513 // if we're not showing all labels (including conflicts) for this layer, then we prune the candidates
514 // upfront to avoid extra work...
515 switch ( feat->feature->feature()->overlapHandling() )
516 {
518 pruneHardConflicts();
519 break;
520
523 break;
524 }
525
526 if ( feat->candidates.empty() )
527 continue;
528
529 // calculate final costs
530 CostCalculator::finalizeCandidatesCosts( feat.get(), bbx, bby );
531
532 // sort candidates list, best label to worst
533 std::sort( feat->candidates.begin(), feat->candidates.end(), CostCalculator::candidateSortGrow );
534
535 // but if we ARE showing all labels (including conflicts), let's go ahead and prune them now.
536 // Since we've calculated all their costs and sorted them, if we've hit the situation that ALL
537 // candidates have conflicts, then at least when we pick the first candidate to display it will be
538 // the lowest cost (i.e. best possible) overlapping candidate...
539 switch ( feat->feature->feature()->overlapHandling() )
540 {
542 break;
545 pruneHardConflicts();
546 break;
547 }
548
549 // only keep the 'maxCandidates' best candidates
550 if ( maxCandidates > 0 && feat->candidates.size() > maxCandidates )
551 {
552 feat->candidates.resize( maxCandidates );
553 }
554
555 if ( isCanceled() )
556 return nullptr;
557
558 // update problem's # candidate
559 prob->mCandidateCountForFeature[featureIndex] = static_cast< int >( feat->candidates.size() );
560 prob->mTotalCandidates += static_cast< int >( feat->candidates.size() );
561
562 // add all candidates into a rtree (to speed up conflicts searching)
563 for ( std::unique_ptr< LabelPosition > &candidate : feat->candidates )
564 {
565 candidate->insertIntoIndex( prob->allCandidatesIndex() );
566 candidate->setProblemIds( static_cast< int >( featureIndex ), currentLabelPositionIndex++ );
567 }
568 features.emplace_back( std::move( feat ) );
569 }
570
571 if ( feedback )
572 feedback->emit calculatingConflictsFinished();
573
574 conflictProfile.reset();
575
576 int nbOverlaps = 0;
577
578 if ( feedback )
579 feedback->emit finalizingCandidatesAboutToBegin();
580
581 std::unique_ptr< QgsScopedRuntimeProfile > finalizingProfile;
582 if ( context.flags() & Qgis::RenderContextFlag::RecordProfile )
583 {
584 finalizingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Finalizing labels" ), QStringLiteral( "rendering" ) );
585 }
586
587 index = -1;
588 step = !features.empty() ? 100.0 / features.size() : 1;
589 while ( !features.empty() ) // for each feature
590 {
591 index++;
592 if ( feedback )
593 feedback->setProgress( step * index );
594
595 if ( isCanceled() )
596 return nullptr;
597
598 std::unique_ptr< Feats > feat = std::move( features.front() );
599 features.pop_front();
600
601 for ( std::unique_ptr< LabelPosition > &candidate : feat->candidates )
602 {
603 std::unique_ptr< LabelPosition > lp = std::move( candidate );
604
605 lp->resetNumOverlaps();
606
607 // make sure that candidate's cost is less than 1
608 lp->validateCost();
609
610 //prob->feat[idlp] = j;
611
612 // lookup for overlapping candidate
613 const QgsRectangle searchBounds = lp->boundingBoxForCandidateConflicts( this );
614 prob->allCandidatesIndex().intersects( searchBounds, [&lp, this]( const LabelPosition * lp2 )->bool
615 {
616 if ( candidatesAreConflicting( lp.get(), lp2 ) )
617 {
618 lp->incrementNumOverlaps();
619 }
620
621 return true;
622
623 } );
624
625 nbOverlaps += lp->getNumOverlaps();
626
627 prob->addCandidatePosition( std::move( lp ) );
628
629 if ( isCanceled() )
630 return nullptr;
631 }
632 }
633
634 if ( feedback )
635 feedback->emit finalizingCandidatesFinished();
636
637 finalizingProfile.reset();
638
639 nbOverlaps /= 2;
640 prob->mAllNblp = prob->mTotalCandidates;
641 prob->mNbOverlap = nbOverlaps;
642 }
643
644 return prob;
645}
646
648{
649 fnIsCanceled = fnCanceled;
650 fnIsCanceledContext = context;
651}
652
653
654QList<LabelPosition *> Pal::solveProblem( Problem *prob, QgsRenderContext &context, bool displayAll, QList<LabelPosition *> *unlabeled )
655{
656 QgsLabelingEngineFeedback *feedback = qobject_cast< QgsLabelingEngineFeedback * >( context.feedback() );
657
658 if ( !prob )
659 return QList<LabelPosition *>();
660
661 std::unique_ptr< QgsScopedRuntimeProfile > calculatingProfile;
663 {
664 calculatingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Calculating optimal labeling" ), QStringLiteral( "rendering" ) );
665 }
666
667 if ( feedback )
668 feedback->emit reductionAboutToBegin();
669
670 {
671 std::unique_ptr< QgsScopedRuntimeProfile > reductionProfile;
673 {
674 reductionProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Reducing labeling" ), QStringLiteral( "rendering" ) );
675 }
676
677 prob->reduce();
678 }
679
680 if ( feedback )
681 feedback->emit reductionFinished();
682
683 if ( feedback )
684 feedback->emit solvingPlacementAboutToBegin();
685
686 {
687 std::unique_ptr< QgsScopedRuntimeProfile > solvingProfile;
689 {
690 solvingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Solving labeling" ), QStringLiteral( "rendering" ) );
691 }
692 try
693 {
694 prob->chainSearch( context );
695 }
696 catch ( InternalException::Empty & )
697 {
698 return QList<LabelPosition *>();
699 }
700 }
701
702 if ( feedback )
703 feedback->emit solvingPlacementFinished();
704
705 return prob->getSolution( displayAll, unlabeled );
706}
707
708void Pal::setMinIt( int min_it )
709{
710 if ( min_it >= 0 )
711 mTabuMinIt = min_it;
712}
713
714void Pal::setMaxIt( int max_it )
715{
716 if ( max_it > 0 )
717 mTabuMaxIt = max_it;
718}
719
720void Pal::setPopmusicR( int r )
721{
722 if ( r > 0 )
723 mPopmusicR = r;
724}
725
726void Pal::setEjChainDeg( int degree )
727{
728 this->mEjChainDeg = degree;
729}
730
731void Pal::setTenure( int tenure )
732{
733 this->mTenure = tenure;
734}
735
736void Pal::setCandListSize( double fact )
737{
738 this->mCandListSize = fact;
739}
740
742{
743 this->mShowPartialLabels = show;
744}
745
747{
748 return mPlacementVersion;
749}
750
752{
753 mPlacementVersion = placementVersion;
754}
755
757{
758 // we cache the value -- this can be costly to calculate, and we check this multiple times
759 // per candidate during the labeling problem solving
760
761 if ( lp1->getProblemFeatureId() == lp2->getProblemFeatureId() )
762 return false;
763
764 // conflicts are commutative - so we always store them in the cache using the smaller id as the first element of the key pair
765 auto key = qMakePair( std::min( lp1->globalId(), lp2->globalId() ), std::max( lp1->globalId(), lp2->globalId() ) );
766 auto it = mCandidateConflicts.constFind( key );
767 if ( it != mCandidateConflicts.constEnd() )
768 return *it;
769
770 bool res = false;
771 for ( QgsAbstractLabelingEngineRule *rule : mRules )
772 {
773 if ( rule->candidatesAreConflicting( lp1, lp2 ) )
774 {
775 res = true;
776 break;
777 }
778 }
779
780 res |= lp1->isInConflict( lp2 );
781
782 mCandidateConflicts.insert( key, res );
783 return res;
784}
785
786void Pal::setRules( const QList<QgsAbstractLabelingEngineRule *> &rules )
787{
788 mRules = rules;
789}
790
791int Pal::getMinIt() const
792{
793 return mTabuMaxIt;
794}
795
796int Pal::getMaxIt() const
797{
798 return mTabuMinIt;
799}
800
802{
803 return mShowPartialLabels;
804}
A rtree spatial index for use in the pal labeling engine.
Definition palrtree.h:36
void insert(T *data, const QgsRectangle &bounds)
Inserts new data into the spatial index, with the specified bounds.
Definition palrtree.h:59
LabelPlacement
Placement modes which determine how label candidates are generated for a feature.
Definition qgis.h:1158
@ RecordProfile
Enable run-time profiling while rendering.
LabelPlacementEngineVersion
Labeling placement engine version.
Definition qgis.h:2787
@ Version2
Version 2 (default for new projects since QGIS 3.12)
@ Version1
Version 1, matches placement from QGIS <= 3.10.1.
@ AllowOverlapAtNoCost
Labels may freely overlap other labels, at no cost.
@ AllowOverlapIfRequired
Avoids overlapping labels when possible, but permit overlaps if labels for features cannot otherwise ...
@ PreventOverlap
Do not allow labels to overlap other labels.
An abstract interface class for label providers.
Abstract base class for labeling engine rules.
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:61
A geometry is the spatial representation of a feature.
static GEOSContextHandle_t get()
Returns a thread local instance of a GEOS context, safe for use in the current thread.
static geos::unique_ptr asGeos(const QgsGeometry &geometry, double precision=0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlags())
Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destr...
Definition qgsgeos.cpp:257
Encapsulates the context for a labeling engine run.
void setMapBoundaryGeometry(const QgsGeometry &geometry)
Sets the map label boundary geometry, which defines the limits within which labels may be placed in t...
void setExtent(const QgsRectangle &extent)
Sets the map extent defining the limits for labeling.
QgsFeedback subclass for granular reporting of labeling engine progress.
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
double yMaximum
QgsRectangle buffered(double width) const
Gets rectangle enlarged by buffer.
Contains information about the context of a rendering operation.
QgsFeedback * feedback() const
Returns the feedback object that can be queried regularly during rendering to check if rendering shou...
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
An integer settings entry.
static void addObstacleCostPenalty(pal::LabelPosition *lp, pal::FeaturePart *obstacle, Pal *pal)
Increase candidate's cost according to its collision with passed feature.
static void finalizeCandidatesCosts(Feats *feat, double bbx[4], double bby[4])
Sort candidates by costs, skip the worse ones, evaluate polygon candidates.
static bool candidateSortGrow(const std::unique_ptr< pal::LabelPosition > &c1, const std::unique_ptr< pal::LabelPosition > &c2)
Sorts label candidates in ascending order of cost.
Represents a part of a label feature.
Definition feature.h:58
FeaturePart * getSelfObstacle(int i)
Gets hole (inner ring) - considered as obstacle.
Definition feature.h:300
Thrown when trying to access an empty data set.
LabelPosition is a candidate feature label position.
bool isInConflict(const LabelPosition *ls) const
Check whether or not this overlap with another labelPosition.
unsigned int globalId() const
Returns the global ID for the candidate, which is unique for a single run of the pal labelling engine...
int getProblemFeatureId() const
A set of features which influence the labeling process.
Definition layer.h:62
QMutex mMutex
Definition layer.h:344
std::deque< std::unique_ptr< FeaturePart > > mFeatureParts
List of feature parts.
Definition layer.h:315
bool active() const
Returns whether the layer is currently active.
Definition layer.h:197
bool mergeConnectedLines() const
Returns whether connected lines will be merged before labeling.
Definition layer.h:255
void joinConnectedFeatures()
Join connected features with the same label text.
Definition layer.cpp:297
void chopFeaturesAtRepeatDistance()
Chop layer features at the repeat distance.
Definition layer.cpp:365
void setPlacementVersion(Qgis::LabelPlacementEngineVersion placementVersion)
Sets the placement engine version, which dictates how the label placement problem is solved.
Definition pal.cpp:751
void setShowPartialLabels(bool show)
Sets whether partial labels show be allowed.
Definition pal.cpp:741
std::unique_ptr< Problem > extractProblem(const QgsRectangle &extent, const QgsGeometry &mapBoundary, QgsRenderContext &context)
Extracts the labeling problem for the specified map extent - only features within this extent will be...
Definition pal.cpp:106
Qgis::LabelPlacementEngineVersion placementVersion() const
Returns the placement engine version, which dictates how the label placement problem is solved.
Definition pal.cpp:746
void setRules(const QList< QgsAbstractLabelingEngineRule * > &rules)
Sets rules which the labeling solution must satisfy.
Definition pal.cpp:786
void removeLayer(Layer *layer)
remove a layer
Definition pal.cpp:68
bool candidatesAreConflicting(const LabelPosition *lp1, const LabelPosition *lp2) const
Returns true if a labelling candidate lp1 conflicts with lp2.
Definition pal.cpp:756
bool(* FnIsCanceled)(void *ctx)
Cancelation check callback function.
Definition pal.h:123
bool showPartialLabels() const
Returns whether partial labels should be allowed.
Definition pal.cpp:801
static const QgsSettingsEntryInteger * settingsRenderingLabelCandidatesLimitLines
Definition pal.h:92
static const QgsSettingsEntryInteger * settingsRenderingLabelCandidatesLimitPoints
Definition pal.h:91
static const QgsSettingsEntryInteger * settingsRenderingLabelCandidatesLimitPolygons
Definition pal.h:93
Pal()
Definition pal.cpp:59
bool isCanceled()
Check whether the job has been canceled.
Definition pal.h:129
QList< LabelPosition * > solveProblem(Problem *prob, QgsRenderContext &context, bool displayAll, QList< pal::LabelPosition * > *unlabeled=nullptr)
Solves the labeling problem, selecting the best candidate locations for all labels and returns a list...
Definition pal.cpp:654
void registerCancellationCallback(FnIsCanceled fnCanceled, void *context)
Register a function that returns whether this job has been canceled - PAL calls it during the computa...
Definition pal.cpp:647
QList< QgsAbstractLabelingEngineRule * > rules() const
Returns the rules which the labeling solution must satisify.
Definition pal.h:279
Layer * addLayer(QgsAbstractLabelProvider *provider, const QString &layerName, Qgis::LabelPlacement arrangement, double defaultPriority, bool active, bool toLabel)
add a new layer
Definition pal.cpp:86
QgsRectangle boundingBox() const
Returns the point set bounding box.
Definition pointset.h:163
Representation of a labeling problem.
Definition problem.h:73
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:644
void chainSearch(QgsRenderContext &context)
Test with very-large scale neighborhood.
Definition problem.cpp:561
void reduce()
Gets called AFTER extractProblem.
Definition problem.cpp:71