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