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