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