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