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