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