Branch data Line data Source code
1 : :
2 : : /***************************************************************************
3 : : qgslabelingengine.cpp
4 : : --------------------------------------
5 : : Date : September 2015
6 : : Copyright : (C) 2015 by Martin Dobias
7 : : Email : wonder dot sk at gmail dot com
8 : : ***************************************************************************
9 : : * *
10 : : * This program is free software; you can redistribute it and/or modify *
11 : : * it under the terms of the GNU General Public License as published by *
12 : : * the Free Software Foundation; either version 2 of the License, or *
13 : : * (at your option) any later version. *
14 : : * *
15 : : ***************************************************************************/
16 : :
17 : : #include "qgslabelingengine.h"
18 : :
19 : : #include "qgslogger.h"
20 : :
21 : : #include "feature.h"
22 : : #include "labelposition.h"
23 : : #include "layer.h"
24 : : #include "pal.h"
25 : : #include "problem.h"
26 : : #include "qgsrendercontext.h"
27 : : #include "qgsmaplayer.h"
28 : : #include "qgssymbol.h"
29 : : #include "qgsexpressioncontextutils.h"
30 : : #include "qgsvectorlayerlabelprovider.h"
31 : : #include "qgslabelingresults.h"
32 : :
33 : : // helper function for checking for job cancellation within PAL
34 : 0 : static bool _palIsCanceled( void *ctx )
35 : : {
36 : 0 : return ( reinterpret_cast< QgsRenderContext * >( ctx ) )->renderingStopped();
37 : : }
38 : :
39 : : /**
40 : : * \ingroup core
41 : : * \class QgsLabelSorter
42 : : * \brief Helper class for sorting labels into correct draw order
43 : : */
44 : : class QgsLabelSorter
45 : : {
46 : : public:
47 : :
48 : 0 : explicit QgsLabelSorter( const QgsMapSettings &mapSettings )
49 : 0 : : mMapSettings( mapSettings )
50 : 0 : {}
51 : :
52 : 0 : bool operator()( pal::LabelPosition *lp1, pal::LabelPosition *lp2 ) const
53 : : {
54 : 0 : QgsLabelFeature *lf1 = lp1->getFeaturePart()->feature();
55 : 0 : QgsLabelFeature *lf2 = lp2->getFeaturePart()->feature();
56 : :
57 : 0 : if ( !qgsDoubleNear( lf1->zIndex(), lf2->zIndex() ) )
58 : 0 : return lf1->zIndex() < lf2->zIndex();
59 : :
60 : : //equal z-index, so fallback to respecting layer render order
61 : 0 : QStringList layerIds = mMapSettings.layerIds();
62 : 0 : int layer1Pos = layerIds.indexOf( lf1->provider()->layerId() );
63 : 0 : int layer2Pos = layerIds.indexOf( lf2->provider()->layerId() );
64 : 0 : if ( layer1Pos != layer2Pos && layer1Pos >= 0 && layer2Pos >= 0 )
65 : 0 : return layer1Pos > layer2Pos; //higher positions are rendered first
66 : :
67 : : //same layer, so render larger labels first
68 : 0 : return lf1->size().width() * lf1->size().height() > lf2->size().width() * lf2->size().height();
69 : 0 : }
70 : :
71 : : private:
72 : :
73 : : const QgsMapSettings &mMapSettings;
74 : : };
75 : :
76 : : //
77 : : // QgsLabelingEngine
78 : : //
79 : :
80 : 0 : QgsLabelingEngine::QgsLabelingEngine()
81 : 0 : : mResults( new QgsLabelingResults )
82 : 0 : {}
83 : :
84 : 0 : QgsLabelingEngine::~QgsLabelingEngine()
85 : 0 : {
86 : 0 : qDeleteAll( mProviders );
87 : 0 : qDeleteAll( mSubProviders );
88 : 0 : }
89 : :
90 : 0 : void QgsLabelingEngine::setMapSettings( const QgsMapSettings &mapSettings )
91 : : {
92 : 0 : mMapSettings = mapSettings;
93 : 0 : if ( mResults )
94 : 0 : mResults->setMapSettings( mapSettings );
95 : 0 : }
96 : :
97 : 0 : QList< QgsMapLayer * > QgsLabelingEngine::participatingLayers() const
98 : : {
99 : 0 : QList< QgsMapLayer * > layers;
100 : :
101 : : // try to return layers sorted in the desired z order for rendering
102 : 0 : QList< QgsAbstractLabelProvider * > providersByZ = mProviders;
103 : 0 : std::sort( providersByZ.begin(), providersByZ.end(),
104 : 0 : []( const QgsAbstractLabelProvider * a, const QgsAbstractLabelProvider * b ) -> bool
105 : : {
106 : 0 : const QgsVectorLayerLabelProvider *providerA = dynamic_cast<const QgsVectorLayerLabelProvider *>( a );
107 : 0 : const QgsVectorLayerLabelProvider *providerB = dynamic_cast<const QgsVectorLayerLabelProvider *>( b );
108 : :
109 : 0 : if ( providerA && providerB )
110 : : {
111 : 0 : return providerA->settings().zIndex < providerB->settings().zIndex ;
112 : : }
113 : 0 : return false;
114 : 0 : } );
115 : :
116 : 0 : QList< QgsAbstractLabelProvider * > subProvidersByZ = mSubProviders;
117 : 0 : std::sort( subProvidersByZ.begin(), subProvidersByZ.end(),
118 : 0 : []( const QgsAbstractLabelProvider * a, const QgsAbstractLabelProvider * b ) -> bool
119 : : {
120 : 0 : const QgsVectorLayerLabelProvider *providerA = dynamic_cast<const QgsVectorLayerLabelProvider *>( a );
121 : 0 : const QgsVectorLayerLabelProvider *providerB = dynamic_cast<const QgsVectorLayerLabelProvider *>( b );
122 : :
123 : 0 : if ( providerA && providerB )
124 : : {
125 : 0 : return providerA->settings().zIndex < providerB->settings().zIndex ;
126 : : }
127 : 0 : return false;
128 : 0 : } );
129 : :
130 : 0 : for ( QgsAbstractLabelProvider *provider : std::as_const( providersByZ ) )
131 : : {
132 : 0 : if ( provider->layer() && !layers.contains( provider->layer() ) )
133 : 0 : layers << provider->layer();
134 : : }
135 : 0 : for ( QgsAbstractLabelProvider *provider : std::as_const( subProvidersByZ ) )
136 : : {
137 : 0 : if ( provider->layer() && !layers.contains( provider->layer() ) )
138 : 0 : layers << provider->layer();
139 : : }
140 : 0 : return layers;
141 : 0 : }
142 : :
143 : 0 : QStringList QgsLabelingEngine::participatingLayerIds() const
144 : : {
145 : 0 : QStringList layers;
146 : :
147 : : // try to return layers sorted in the desired z order for rendering
148 : 0 : QList< QgsAbstractLabelProvider * > providersByZ = mProviders;
149 : 0 : std::sort( providersByZ.begin(), providersByZ.end(),
150 : 0 : []( const QgsAbstractLabelProvider * a, const QgsAbstractLabelProvider * b ) -> bool
151 : : {
152 : 0 : const QgsVectorLayerLabelProvider *providerA = dynamic_cast<const QgsVectorLayerLabelProvider *>( a );
153 : 0 : const QgsVectorLayerLabelProvider *providerB = dynamic_cast<const QgsVectorLayerLabelProvider *>( b );
154 : :
155 : 0 : if ( providerA && providerB )
156 : : {
157 : 0 : return providerA->settings().zIndex < providerB->settings().zIndex ;
158 : : }
159 : 0 : return false;
160 : 0 : } );
161 : :
162 : 0 : QList< QgsAbstractLabelProvider * > subProvidersByZ = mSubProviders;
163 : 0 : std::sort( subProvidersByZ.begin(), subProvidersByZ.end(),
164 : 0 : []( const QgsAbstractLabelProvider * a, const QgsAbstractLabelProvider * b ) -> bool
165 : : {
166 : 0 : const QgsVectorLayerLabelProvider *providerA = dynamic_cast<const QgsVectorLayerLabelProvider *>( a );
167 : 0 : const QgsVectorLayerLabelProvider *providerB = dynamic_cast<const QgsVectorLayerLabelProvider *>( b );
168 : :
169 : 0 : if ( providerA && providerB )
170 : : {
171 : 0 : return providerA->settings().zIndex < providerB->settings().zIndex ;
172 : : }
173 : 0 : return false;
174 : 0 : } );
175 : :
176 : 0 : for ( QgsAbstractLabelProvider *provider : std::as_const( providersByZ ) )
177 : : {
178 : 0 : if ( !layers.contains( provider->layerId() ) )
179 : 0 : layers << provider->layerId();
180 : : }
181 : 0 : for ( QgsAbstractLabelProvider *provider : std::as_const( subProvidersByZ ) )
182 : : {
183 : 0 : if ( !layers.contains( provider->layerId() ) )
184 : 0 : layers << provider->layerId();
185 : : }
186 : 0 : return layers;
187 : 0 : }
188 : :
189 : 0 : void QgsLabelingEngine::addProvider( QgsAbstractLabelProvider *provider )
190 : : {
191 : 0 : provider->setEngine( this );
192 : 0 : mProviders << provider;
193 : 0 : }
194 : :
195 : 0 : void QgsLabelingEngine::removeProvider( QgsAbstractLabelProvider *provider )
196 : : {
197 : 0 : int idx = mProviders.indexOf( provider );
198 : 0 : if ( idx >= 0 )
199 : : {
200 : 0 : delete mProviders.takeAt( idx );
201 : 0 : }
202 : 0 : }
203 : :
204 : 0 : void QgsLabelingEngine::processProvider( QgsAbstractLabelProvider *provider, QgsRenderContext &context, pal::Pal &p )
205 : : {
206 : 0 : QgsAbstractLabelProvider::Flags flags = provider->flags();
207 : :
208 : : // create the pal layer
209 : 0 : pal::Layer *l = p.addLayer( provider,
210 : 0 : provider->name(),
211 : 0 : provider->placement(),
212 : 0 : provider->priority(),
213 : : true,
214 : 0 : flags.testFlag( QgsAbstractLabelProvider::DrawLabels ),
215 : 0 : flags.testFlag( QgsAbstractLabelProvider::DrawAllLabels ) );
216 : :
217 : : // set whether adjacent lines should be merged
218 : 0 : l->setMergeConnectedLines( flags.testFlag( QgsAbstractLabelProvider::MergeConnectedLines ) );
219 : :
220 : : // set obstacle type
221 : 0 : l->setObstacleType( provider->obstacleType() );
222 : :
223 : : // set whether location of centroid must be inside of polygons
224 : 0 : l->setCentroidInside( flags.testFlag( QgsAbstractLabelProvider::CentroidMustBeInside ) );
225 : :
226 : : // set how to show upside-down labels
227 : 0 : pal::Layer::UpsideDownLabels upsdnlabels = pal::Layer::ShowAll;
228 : 0 : switch ( provider->upsidedownLabels() )
229 : : {
230 : : case QgsPalLayerSettings::Upright:
231 : 0 : upsdnlabels = pal::Layer::Upright;
232 : 0 : break;
233 : : case QgsPalLayerSettings::ShowDefined:
234 : 0 : upsdnlabels = pal::Layer::ShowDefined;
235 : 0 : break;
236 : : case QgsPalLayerSettings::ShowAll:
237 : 0 : upsdnlabels = pal::Layer::ShowAll;
238 : 0 : break;
239 : : }
240 : 0 : l->setUpsidedownLabels( upsdnlabels );
241 : :
242 : :
243 : 0 : const QList<QgsLabelFeature *> features = provider->labelFeatures( context );
244 : :
245 : 0 : for ( QgsLabelFeature *feature : features )
246 : : {
247 : : try
248 : : {
249 : 0 : l->registerFeature( feature );
250 : 0 : }
251 : : catch ( std::exception &e )
252 : : {
253 : 0 : Q_UNUSED( e )
254 : 0 : QgsDebugMsgLevel( QStringLiteral( "Ignoring feature %1 due PAL exception:" ).arg( feature->id() ) + QString::fromLatin1( e.what() ), 4 );
255 : : continue;
256 : 0 : }
257 : : }
258 : :
259 : : // any sub-providers?
260 : 0 : const auto subproviders = provider->subProviders();
261 : 0 : for ( QgsAbstractLabelProvider *subProvider : subproviders )
262 : : {
263 : 0 : mSubProviders << subProvider;
264 : 0 : processProvider( subProvider, context, p );
265 : : }
266 : 0 : }
267 : :
268 : 0 : void QgsLabelingEngine::registerLabels( QgsRenderContext &context )
269 : : {
270 : 0 : const QgsLabelingEngineSettings &settings = mMapSettings.labelingEngineSettings();
271 : :
272 : 0 : mPal = std::make_unique< pal::Pal >();
273 : :
274 : 0 : mPal->setMaximumLineCandidatesPerMapUnit( settings.maximumLineCandidatesPerCm() / context.convertToMapUnits( 10, QgsUnitTypes::RenderMillimeters ) );
275 : 0 : mPal->setMaximumPolygonCandidatesPerMapUnitSquared( settings.maximumPolygonCandidatesPerCmSquared() / std::pow( context.convertToMapUnits( 10, QgsUnitTypes::RenderMillimeters ), 2 ) );
276 : :
277 : 0 : mPal->setShowPartialLabels( settings.testFlag( QgsLabelingEngineSettings::UsePartialCandidates ) );
278 : 0 : mPal->setPlacementVersion( settings.placementVersion() );
279 : :
280 : : // for each provider: get labels and register them in PAL
281 : 0 : for ( QgsAbstractLabelProvider *provider : std::as_const( mProviders ) )
282 : : {
283 : 0 : std::unique_ptr< QgsExpressionContextScopePopper > layerScopePopper;
284 : 0 : if ( QgsMapLayer *ml = provider->layer() )
285 : : {
286 : 0 : layerScopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.expressionContext(), QgsExpressionContextUtils::layerScope( ml ) );
287 : 0 : }
288 : 0 : processProvider( provider, context, *mPal );
289 : 0 : }
290 : 0 : }
291 : :
292 : 0 : void QgsLabelingEngine::solve( QgsRenderContext &context )
293 : : {
294 : : Q_ASSERT( mPal.get() );
295 : :
296 : : // NOW DO THE LAYOUT (from QgsPalLabeling::drawLabeling)
297 : 0 : const QgsLabelingEngineSettings &settings = mMapSettings.labelingEngineSettings();
298 : :
299 : 0 : QPainter *painter = context.painter();
300 : :
301 : 0 : QgsGeometry extentGeom = QgsGeometry::fromRect( mMapSettings.visibleExtent() );
302 : 0 : QPolygonF visiblePoly = mMapSettings.visiblePolygon();
303 : 0 : visiblePoly.append( visiblePoly.at( 0 ) ); //close polygon
304 : :
305 : : // get map label boundary geometry - if one hasn't been explicitly set, we use the whole of the map's visible polygon
306 : 0 : QgsGeometry mapBoundaryGeom = !mMapSettings.labelBoundaryGeometry().isNull() ? mMapSettings.labelBoundaryGeometry() : QgsGeometry::fromQPolygonF( visiblePoly );
307 : :
308 : : // label blocking regions work by "chopping away" those regions from the permissible labeling area
309 : 0 : const QList< QgsLabelBlockingRegion > blockingRegions = mMapSettings.labelBlockingRegions();
310 : 0 : for ( const QgsLabelBlockingRegion ®ion : blockingRegions )
311 : : {
312 : 0 : mapBoundaryGeom = mapBoundaryGeom.difference( region.geometry );
313 : : }
314 : :
315 : 0 : if ( settings.flags() & QgsLabelingEngineSettings::DrawCandidates )
316 : : {
317 : : // draw map boundary
318 : 0 : QgsFeature f;
319 : 0 : f.setGeometry( mapBoundaryGeom );
320 : 0 : QVariantMap properties;
321 : 0 : properties.insert( QStringLiteral( "style" ), QStringLiteral( "no" ) );
322 : 0 : properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
323 : 0 : properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "#0000ff" ) );
324 : 0 : properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "0.3" ) );
325 : 0 : properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
326 : 0 : std::unique_ptr< QgsFillSymbol > boundarySymbol( QgsFillSymbol::createSimple( properties ) );
327 : 0 : boundarySymbol->startRender( context );
328 : 0 : boundarySymbol->renderFeature( f, context );
329 : 0 : boundarySymbol->stopRender( context );
330 : 0 : }
331 : :
332 : 0 : if ( !qgsDoubleNear( mMapSettings.rotation(), 0.0 ) )
333 : : {
334 : : //PAL features are prerotated, so extent also needs to be unrotated
335 : 0 : extentGeom.rotate( -mMapSettings.rotation(), mMapSettings.visibleExtent().center() );
336 : : // yes - this is rotated in the opposite direction... phew, this is confusing!
337 : 0 : mapBoundaryGeom.rotate( mMapSettings.rotation(), mMapSettings.visibleExtent().center() );
338 : 0 : }
339 : :
340 : 0 : QgsRectangle extent = extentGeom.boundingBox();
341 : :
342 : 0 : mPal->registerCancellationCallback( &_palIsCanceled, reinterpret_cast< void * >( &context ) );
343 : :
344 : 0 : QElapsedTimer t;
345 : 0 : t.start();
346 : :
347 : : // do the labeling itself
348 : : try
349 : : {
350 : 0 : mProblem = mPal->extractProblem( extent, mapBoundaryGeom );
351 : 0 : }
352 : : catch ( std::exception &e )
353 : : {
354 : 0 : Q_UNUSED( e )
355 : 0 : QgsDebugMsgLevel( "PAL EXCEPTION :-( " + QString::fromLatin1( e.what() ), 4 );
356 : : return;
357 : 0 : }
358 : :
359 : 0 : if ( context.renderingStopped() )
360 : : {
361 : 0 : return; // it has been canceled
362 : : }
363 : :
364 : : #if 1 // XXX strk
365 : : // features are pre-rotated but not scaled/translated,
366 : : // so we only disable rotation here. Ideally, they'd be
367 : : // also pre-scaled/translated, as suggested here:
368 : : // https://github.com/qgis/QGIS/issues/20071
369 : 0 : QgsMapToPixel xform = mMapSettings.mapToPixel();
370 : 0 : xform.setMapRotation( 0, 0, 0 );
371 : : #else
372 : : const QgsMapToPixel &xform = mMapSettings->mapToPixel();
373 : : #endif
374 : :
375 : : // draw rectangles with all candidates
376 : : // this is done before actual solution of the problem
377 : : // before number of candidates gets reduced
378 : : // TODO mCandidates.clear();
379 : 0 : if ( settings.testFlag( QgsLabelingEngineSettings::DrawCandidates ) && mProblem )
380 : : {
381 : 0 : painter->setBrush( Qt::NoBrush );
382 : 0 : for ( int i = 0; i < static_cast< int >( mProblem->featureCount() ); i++ )
383 : : {
384 : 0 : for ( int j = 0; j < mProblem->featureCandidateCount( i ); j++ )
385 : : {
386 : 0 : pal::LabelPosition *lp = mProblem->featureCandidate( i, j );
387 : :
388 : 0 : QgsPalLabeling::drawLabelCandidateRect( lp, painter, &xform );
389 : 0 : }
390 : 0 : }
391 : 0 : }
392 : :
393 : : // find the solution
394 : 0 : mLabels = mPal->solveProblem( mProblem.get(), settings.testFlag( QgsLabelingEngineSettings::UseAllLabels ), settings.testFlag( QgsLabelingEngineSettings::DrawUnplacedLabels ) ? &mUnlabeled : nullptr );
395 : :
396 : : // sort labels
397 : 0 : std::sort( mLabels.begin(), mLabels.end(), QgsLabelSorter( mMapSettings ) );
398 : :
399 : 0 : QgsDebugMsgLevel( QStringLiteral( "LABELING work: %1 ms ... labels# %2" ).arg( t.elapsed() ).arg( mLabels.size() ), 4 );
400 : 0 : }
401 : :
402 : 0 : void QgsLabelingEngine::drawLabels( QgsRenderContext &context, const QString &layerId )
403 : : {
404 : 0 : QElapsedTimer t;
405 : 0 : t.start();
406 : :
407 : 0 : const QgsLabelingEngineSettings &settings = mMapSettings.labelingEngineSettings();
408 : :
409 : 0 : context.setPainterFlagsUsingContext();
410 : 0 : QPainter *painter = context.painter();
411 : :
412 : : // prepare for rendering
413 : 0 : for ( QgsAbstractLabelProvider *provider : std::as_const( mProviders ) )
414 : : {
415 : 0 : if ( !layerId.isEmpty() && provider->layerId() != layerId )
416 : 0 : continue;
417 : :
418 : : // provider will require the correct layer scope for expression preparation - at this stage, the existing expression context
419 : : // only contains generic scopes
420 : 0 : QgsExpressionContextScopePopper popper( context.expressionContext(), QgsExpressionContextUtils::layerScope( provider->layer() ) );
421 : 0 : provider->startRender( context );
422 : 0 : }
423 : :
424 : 0 : QgsExpressionContextScope *symbolScope = new QgsExpressionContextScope();
425 : 0 : std::unique_ptr< QgsExpressionContextScopePopper > symbolScopePopper = std::make_unique< QgsExpressionContextScopePopper >( context.expressionContext(), symbolScope );
426 : :
427 : : // draw label backgrounds
428 : 0 : for ( pal::LabelPosition *label : std::as_const( mLabels ) )
429 : : {
430 : 0 : if ( context.renderingStopped() )
431 : 0 : break;
432 : :
433 : 0 : QgsLabelFeature *lf = label->getFeaturePart()->feature();
434 : 0 : if ( !lf )
435 : : {
436 : 0 : continue;
437 : : }
438 : :
439 : 0 : if ( !layerId.isEmpty() && lf->provider()->layerId() != layerId )
440 : 0 : continue;
441 : :
442 : 0 : context.expressionContext().setFeature( lf->feature() );
443 : 0 : context.expressionContext().setFields( lf->feature().fields() );
444 : 0 : if ( lf->symbol() )
445 : : {
446 : 0 : symbolScope = QgsExpressionContextUtils::updateSymbolScope( lf->symbol(), symbolScope );
447 : 0 : }
448 : 0 : lf->provider()->drawLabelBackground( context, label );
449 : : }
450 : :
451 : : // draw the labels
452 : 0 : for ( pal::LabelPosition *label : std::as_const( mLabels ) )
453 : : {
454 : 0 : if ( context.renderingStopped() )
455 : 0 : break;
456 : :
457 : 0 : QgsLabelFeature *lf = label->getFeaturePart()->feature();
458 : 0 : if ( !lf )
459 : : {
460 : 0 : continue;
461 : : }
462 : :
463 : 0 : if ( !layerId.isEmpty() && lf->provider()->layerId() != layerId )
464 : 0 : continue;
465 : :
466 : 0 : context.expressionContext().setFeature( lf->feature() );
467 : 0 : context.expressionContext().setFields( lf->feature().fields() );
468 : 0 : if ( lf->symbol() )
469 : : {
470 : 0 : symbolScope = QgsExpressionContextUtils::updateSymbolScope( lf->symbol(), symbolScope );
471 : 0 : }
472 : 0 : lf->provider()->drawLabel( context, label );
473 : : // finished with symbol -- we can't keep it around after this, it may be deleted
474 : 0 : lf->setSymbol( nullptr );
475 : : }
476 : :
477 : : // draw unplaced labels. These are always rendered on top
478 : 0 : if ( settings.testFlag( QgsLabelingEngineSettings::DrawUnplacedLabels ) )
479 : : {
480 : 0 : for ( pal::LabelPosition *label : std::as_const( mUnlabeled ) )
481 : : {
482 : 0 : if ( context.renderingStopped() )
483 : 0 : break;
484 : 0 : QgsLabelFeature *lf = label->getFeaturePart()->feature();
485 : 0 : if ( !lf )
486 : : {
487 : 0 : continue;
488 : : }
489 : :
490 : 0 : if ( !layerId.isEmpty() && lf->provider()->layerId() != layerId )
491 : 0 : continue;
492 : :
493 : 0 : context.expressionContext().setFeature( lf->feature() );
494 : 0 : context.expressionContext().setFields( lf->feature().fields() );
495 : 0 : if ( lf->symbol() )
496 : : {
497 : 0 : symbolScope = QgsExpressionContextUtils::updateSymbolScope( lf->symbol(), symbolScope );
498 : 0 : }
499 : 0 : lf->provider()->drawUnplacedLabel( context, label );
500 : : // finished with symbol -- we can't keep it around after this, it may be deleted
501 : 0 : lf->setSymbol( nullptr );
502 : : }
503 : 0 : }
504 : :
505 : 0 : symbolScopePopper.reset();
506 : :
507 : : // cleanup
508 : 0 : for ( QgsAbstractLabelProvider *provider : std::as_const( mProviders ) )
509 : : {
510 : 0 : if ( !layerId.isEmpty() && provider->layerId() != layerId )
511 : 0 : continue;
512 : :
513 : 0 : provider->stopRender( context );
514 : : }
515 : :
516 : : // Reset composition mode for further drawing operations
517 : 0 : painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
518 : :
519 : 0 : QgsDebugMsgLevel( QStringLiteral( "LABELING draw: %1 ms" ).arg( t.elapsed() ), 4 );
520 : 0 : }
521 : :
522 : 0 : void QgsLabelingEngine::cleanup()
523 : : {
524 : 0 : mUnlabeled.clear();
525 : 0 : mLabels.clear();
526 : 0 : mProblem.reset();
527 : 0 : mPal.reset();
528 : 0 : }
529 : :
530 : 0 : QgsLabelingResults *QgsLabelingEngine::takeResults()
531 : : {
532 : 0 : return mResults.release();
533 : : }
534 : :
535 : :
536 : : //
537 : : // QgsDefaultLabelingEngine
538 : : //
539 : :
540 : 0 : QgsDefaultLabelingEngine::QgsDefaultLabelingEngine()
541 : 0 : : QgsLabelingEngine()
542 : 0 : {
543 : :
544 : 0 : }
545 : :
546 : 0 : void QgsDefaultLabelingEngine::run( QgsRenderContext &context )
547 : : {
548 : 0 : registerLabels( context );
549 : 0 : if ( context.renderingStopped() )
550 : : {
551 : 0 : cleanup();
552 : 0 : return; // it has been canceled
553 : : }
554 : :
555 : 0 : solve( context );
556 : 0 : if ( context.renderingStopped() )
557 : : {
558 : 0 : cleanup();
559 : 0 : return;
560 : : }
561 : :
562 : 0 : drawLabels( context );
563 : 0 : cleanup();
564 : 0 : }
565 : :
566 : :
567 : : //
568 : : // QgsStagedRenderLabelingEngine
569 : : //
570 : :
571 : 0 : QgsStagedRenderLabelingEngine::QgsStagedRenderLabelingEngine()
572 : 0 : : QgsLabelingEngine()
573 : 0 : {
574 : :
575 : 0 : }
576 : :
577 : 0 : void QgsStagedRenderLabelingEngine::run( QgsRenderContext &context )
578 : : {
579 : 0 : registerLabels( context );
580 : 0 : if ( context.renderingStopped() )
581 : : {
582 : 0 : cleanup();
583 : 0 : return; // it has been canceled
584 : : }
585 : :
586 : 0 : solve( context );
587 : 0 : if ( context.renderingStopped() )
588 : : {
589 : 0 : cleanup();
590 : 0 : return;
591 : : }
592 : 0 : }
593 : :
594 : :
595 : 0 : void QgsStagedRenderLabelingEngine::renderLabelsForLayer( QgsRenderContext &context, const QString &layerId )
596 : : {
597 : 0 : drawLabels( context, layerId );
598 : 0 : }
599 : :
600 : 0 : void QgsStagedRenderLabelingEngine::finalize()
601 : : {
602 : 0 : cleanup();
603 : 0 : }
604 : :
605 : :
606 : : ////
607 : :
608 : 0 : QgsAbstractLabelProvider *QgsLabelFeature::provider() const
609 : : {
610 : 0 : return mLayer ? mLayer->provider() : nullptr;
611 : :
612 : : }
613 : :
614 : 0 : QgsAbstractLabelProvider::QgsAbstractLabelProvider( QgsMapLayer *layer, const QString &providerId )
615 : 0 : : mLayerId( layer ? layer->id() : QString() )
616 : 0 : , mLayer( layer )
617 : 0 : , mProviderId( providerId )
618 : 0 : , mFlags( DrawLabels )
619 : 0 : , mPlacement( QgsPalLayerSettings::AroundPoint )
620 : 0 : , mPriority( 0.5 )
621 : 0 : , mUpsidedownLabels( QgsPalLayerSettings::Upright )
622 : 0 : {
623 : 0 : }
624 : :
625 : 0 : void QgsAbstractLabelProvider::drawUnplacedLabel( QgsRenderContext &, pal::LabelPosition * ) const
626 : : {
627 : :
628 : 0 : }
629 : :
630 : 0 : void QgsAbstractLabelProvider::drawLabelBackground( QgsRenderContext &, pal::LabelPosition * ) const
631 : : {
632 : :
633 : 0 : }
634 : :
635 : 0 : void QgsAbstractLabelProvider::startRender( QgsRenderContext &context )
636 : : {
637 : 0 : const auto subproviders = subProviders();
638 : 0 : for ( QgsAbstractLabelProvider *subProvider : subproviders )
639 : : {
640 : 0 : subProvider->startRender( context );
641 : : }
642 : 0 : }
643 : :
644 : 0 : void QgsAbstractLabelProvider::stopRender( QgsRenderContext &context )
645 : : {
646 : 0 : const auto subproviders = subProviders();
647 : 0 : for ( QgsAbstractLabelProvider *subProvider : subproviders )
648 : : {
649 : 0 : subProvider->stopRender( context );
650 : : }
651 : 0 : }
652 : :
653 : : //
654 : : // QgsLabelingUtils
655 : : //
656 : :
657 : 0 : QString QgsLabelingUtils::encodePredefinedPositionOrder( const QVector<QgsPalLayerSettings::PredefinedPointPosition> &positions )
658 : : {
659 : 0 : QStringList predefinedOrderString;
660 : 0 : const auto constPositions = positions;
661 : 0 : for ( QgsPalLayerSettings::PredefinedPointPosition position : constPositions )
662 : : {
663 : 0 : switch ( position )
664 : : {
665 : : case QgsPalLayerSettings::TopLeft:
666 : 0 : predefinedOrderString << QStringLiteral( "TL" );
667 : 0 : break;
668 : : case QgsPalLayerSettings::TopSlightlyLeft:
669 : 0 : predefinedOrderString << QStringLiteral( "TSL" );
670 : 0 : break;
671 : : case QgsPalLayerSettings::TopMiddle:
672 : 0 : predefinedOrderString << QStringLiteral( "T" );
673 : 0 : break;
674 : : case QgsPalLayerSettings::TopSlightlyRight:
675 : 0 : predefinedOrderString << QStringLiteral( "TSR" );
676 : 0 : break;
677 : : case QgsPalLayerSettings::TopRight:
678 : 0 : predefinedOrderString << QStringLiteral( "TR" );
679 : 0 : break;
680 : : case QgsPalLayerSettings::MiddleLeft:
681 : 0 : predefinedOrderString << QStringLiteral( "L" );
682 : 0 : break;
683 : : case QgsPalLayerSettings::MiddleRight:
684 : 0 : predefinedOrderString << QStringLiteral( "R" );
685 : 0 : break;
686 : : case QgsPalLayerSettings::BottomLeft:
687 : 0 : predefinedOrderString << QStringLiteral( "BL" );
688 : 0 : break;
689 : : case QgsPalLayerSettings::BottomSlightlyLeft:
690 : 0 : predefinedOrderString << QStringLiteral( "BSL" );
691 : 0 : break;
692 : : case QgsPalLayerSettings::BottomMiddle:
693 : 0 : predefinedOrderString << QStringLiteral( "B" );
694 : 0 : break;
695 : : case QgsPalLayerSettings::BottomSlightlyRight:
696 : 0 : predefinedOrderString << QStringLiteral( "BSR" );
697 : 0 : break;
698 : : case QgsPalLayerSettings::BottomRight:
699 : 0 : predefinedOrderString << QStringLiteral( "BR" );
700 : 0 : break;
701 : : }
702 : : }
703 : 0 : return predefinedOrderString.join( ',' );
704 : 0 : }
705 : :
706 : 0 : QVector<QgsPalLayerSettings::PredefinedPointPosition> QgsLabelingUtils::decodePredefinedPositionOrder( const QString &positionString )
707 : : {
708 : 0 : QVector<QgsPalLayerSettings::PredefinedPointPosition> result;
709 : 0 : const QStringList predefinedOrderList = positionString.split( ',' );
710 : 0 : result.reserve( predefinedOrderList.size() );
711 : 0 : for ( const QString &position : predefinedOrderList )
712 : : {
713 : 0 : QString cleaned = position.trimmed().toUpper();
714 : 0 : if ( cleaned == QLatin1String( "TL" ) )
715 : 0 : result << QgsPalLayerSettings::TopLeft;
716 : 0 : else if ( cleaned == QLatin1String( "TSL" ) )
717 : 0 : result << QgsPalLayerSettings::TopSlightlyLeft;
718 : 0 : else if ( cleaned == QLatin1String( "T" ) )
719 : 0 : result << QgsPalLayerSettings::TopMiddle;
720 : 0 : else if ( cleaned == QLatin1String( "TSR" ) )
721 : 0 : result << QgsPalLayerSettings::TopSlightlyRight;
722 : 0 : else if ( cleaned == QLatin1String( "TR" ) )
723 : 0 : result << QgsPalLayerSettings::TopRight;
724 : 0 : else if ( cleaned == QLatin1String( "L" ) )
725 : 0 : result << QgsPalLayerSettings::MiddleLeft;
726 : 0 : else if ( cleaned == QLatin1String( "R" ) )
727 : 0 : result << QgsPalLayerSettings::MiddleRight;
728 : 0 : else if ( cleaned == QLatin1String( "BL" ) )
729 : 0 : result << QgsPalLayerSettings::BottomLeft;
730 : 0 : else if ( cleaned == QLatin1String( "BSL" ) )
731 : 0 : result << QgsPalLayerSettings::BottomSlightlyLeft;
732 : 0 : else if ( cleaned == QLatin1String( "B" ) )
733 : 0 : result << QgsPalLayerSettings::BottomMiddle;
734 : 0 : else if ( cleaned == QLatin1String( "BSR" ) )
735 : 0 : result << QgsPalLayerSettings::BottomSlightlyRight;
736 : 0 : else if ( cleaned == QLatin1String( "BR" ) )
737 : 0 : result << QgsPalLayerSettings::BottomRight;
738 : 0 : }
739 : 0 : return result;
740 : 0 : }
741 : :
742 : 0 : QString QgsLabelingUtils::encodeLinePlacementFlags( QgsLabeling::LinePlacementFlags flags )
743 : : {
744 : 0 : QStringList parts;
745 : 0 : if ( flags & QgsLabeling::LinePlacementFlag::OnLine )
746 : 0 : parts << QStringLiteral( "OL" );
747 : 0 : if ( flags & QgsLabeling::LinePlacementFlag::AboveLine )
748 : 0 : parts << QStringLiteral( "AL" );
749 : 0 : if ( flags & QgsLabeling::LinePlacementFlag::BelowLine )
750 : 0 : parts << QStringLiteral( "BL" );
751 : 0 : if ( !( flags & QgsLabeling::LinePlacementFlag::MapOrientation ) )
752 : 0 : parts << QStringLiteral( "LO" );
753 : 0 : return parts.join( ',' );
754 : 0 : }
755 : :
756 : 0 : QgsLabeling::LinePlacementFlags QgsLabelingUtils::decodeLinePlacementFlags( const QString &string )
757 : : {
758 : 0 : QgsLabeling::LinePlacementFlags flags = QgsLabeling::LinePlacementFlags();
759 : 0 : const QStringList flagList = string.split( ',' );
760 : 0 : bool foundLineOrientationFlag = false;
761 : 0 : for ( const QString &flag : flagList )
762 : : {
763 : 0 : QString cleaned = flag.trimmed().toUpper();
764 : 0 : if ( cleaned == QLatin1String( "OL" ) )
765 : 0 : flags |= QgsLabeling::LinePlacementFlag::OnLine;
766 : 0 : else if ( cleaned == QLatin1String( "AL" ) )
767 : 0 : flags |= QgsLabeling::LinePlacementFlag::AboveLine;
768 : 0 : else if ( cleaned == QLatin1String( "BL" ) )
769 : 0 : flags |= QgsLabeling::LinePlacementFlag::BelowLine;
770 : 0 : else if ( cleaned == QLatin1String( "LO" ) )
771 : 0 : foundLineOrientationFlag = true;
772 : 0 : }
773 : 0 : if ( !foundLineOrientationFlag )
774 : 0 : flags |= QgsLabeling::LinePlacementFlag::MapOrientation;
775 : : return flags;
776 : 0 : }
777 : :
|