QGIS API Documentation  3.24.2-Tisler (13c1a02865)
qgsalgorithmextractlabels.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsalgorithmextractlabels.cpp - QgsExtractLabelsAlgorithm
3  ---------------------
4  begin : 30.12.2021
5  copyright : (C) 2021 by Mathieu Pellerin
6  email : nirvn dot asia at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
19 #include "qgsmapthemecollection.h"
21 #include "qgsnullpainterdevice.h"
22 #include "qgslabelsink.h"
23 #include "qgslayertree.h"
24 #include "qgsvectorlayer.h"
25 #include "qgsscalecalculator.h"
26 #include "qgstextlabelfeature.h"
27 #include "qgsnullsymbolrenderer.h"
28 #include "qgsprocessingfeedback.h"
29 
30 #include "pal/feature.h"
31 #include "pal/pointset.h"
32 #include "pal/labelposition.h"
33 
34 #include <QPainter>
35 
36 #include <cmath>
37 
39 
40 QString QgsExtractLabelsAlgorithm::name() const
41 {
42  return QStringLiteral( "extractlabels" );
43 }
44 
45 QString QgsExtractLabelsAlgorithm::displayName() const
46 {
47  return QObject::tr( "Extract labels" );
48 }
49 
50 QStringList QgsExtractLabelsAlgorithm::tags() const
51 {
52  return QObject::tr( "map themes,font,position" ).split( ',' );
53 }
54 
55 QgsProcessingAlgorithm::Flags QgsExtractLabelsAlgorithm::flags() const
56 {
57  return QgsProcessingAlgorithm::flags() | FlagRequiresProject;
58 }
59 
60 QString QgsExtractLabelsAlgorithm::group() const
61 {
62  return QObject::tr( "Cartography" );
63 }
64 
65 QString QgsExtractLabelsAlgorithm::groupId() const
66 {
67  return QStringLiteral( "cartography" );
68 }
69 
70 void QgsExtractLabelsAlgorithm::initAlgorithm( const QVariantMap & )
71 {
72  addParameter( new QgsProcessingParameterExtent(
73  QStringLiteral( "EXTENT" ),
74  QObject::tr( "Map extent" ) ) );
75 
76  addParameter( new QgsProcessingParameterScale(
77  QStringLiteral( "SCALE" ),
78  QObject::tr( "Map scale" ) ) );
79 
80  std::unique_ptr<QgsProcessingParameterMapTheme> mapThemeParameter = std::make_unique<QgsProcessingParameterMapTheme>(
81  QStringLiteral( "MAP_THEME" ),
82  QObject::tr( "Map theme" ),
83  QVariant(), true );
84  mapThemeParameter->setHelp( QObject::tr( "This parameter is optional. When left unset, the algorithm will fallback to extracting labels from all currently visible layers in the project." ) );
85  addParameter( mapThemeParameter.release() );
86 
87  addParameter( new QgsProcessingParameterBoolean(
88  QStringLiteral( "INCLUDE_UNPLACED" ),
89  QObject::tr( "Include unplaced labels" ),
90  QVariant( true ), true ) );
91 
92  std::unique_ptr<QgsProcessingParameterNumber> dpiParameter = std::make_unique<QgsProcessingParameterNumber>(
93  QStringLiteral( "DPI" ),
94  QObject::tr( "Map resolution (in DPI)" ),
96  QVariant( 96.0 ), true );
97  dpiParameter->setFlags( dpiParameter->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
98  addParameter( dpiParameter.release() );
99 
100  addParameter( new QgsProcessingParameterFeatureSink(
101  QStringLiteral( "OUTPUT" ),
102  QObject::tr( "Extracted labels" ),
104 }
105 
106 QString QgsExtractLabelsAlgorithm::shortDescription() const
107 {
108  return QObject::tr( "Converts map labels to a point layer with relevant details saved as attributes." );
109 }
110 
111 QString QgsExtractLabelsAlgorithm::shortHelpString() const
112 {
113  return QObject::tr( "This algorithm extracts label information from a rendered map at a given extent and scale.\n\n"
114  "If a map theme is provided, the rendered map will match the visibility and symbology of that theme. If left blank, all visible layers from the project will be used.\n\n"
115  "Extracted label information include: position (served as point geometries), the associated layer name and feature ID, label text, rotation (in degree, clockwise), multiline alignment, and font details." );
116 }
117 
118 QgsExtractLabelsAlgorithm *QgsExtractLabelsAlgorithm::createInstance() const
119 {
120  return new QgsExtractLabelsAlgorithm();
121 }
122 
123 class ExtractLabelSink : public QgsLabelSink
124 {
125  public:
126  ExtractLabelSink( const QMap<QString, QString> &mapLayerNames, QgsProcessingFeedback *feedback )
127  : mMapLayerNames( mapLayerNames )
128  , mFeedback( feedback )
129  {
130  }
131 
132  void drawLabel( const QString &layerId, QgsRenderContext &context, pal::LabelPosition *label, const QgsPalLayerSettings &settings ) override
133  {
134  processLabel( layerId, context, label, settings, false );
135  }
136 
137  void drawUnplacedLabel( const QString &layerId, QgsRenderContext &context, pal::LabelPosition *label, const QgsPalLayerSettings &settings ) override
138  {
139  processLabel( layerId, context, label, settings, true );
140  }
141 
142  void processLabel( const QString &layerId, QgsRenderContext &context, pal::LabelPosition *label, const QgsPalLayerSettings &settings, bool unplacedLabel )
143  {
144  if ( mFeedback->isCanceled() )
145  {
146  context.setRenderingStopped( true );
147  }
148 
149  const QgsFeatureId fid = label->getFeaturePart()->featureId();
150  if ( settings.placement == QgsPalLayerSettings::Curved ||
152  {
153  if ( !mCurvedWarningPushed.contains( layerId ) )
154  {
155  mCurvedWarningPushed << layerId;
156  mFeedback->pushWarning( QObject::tr( "Curved placement not supported, skipping labels from layer %1" ).arg( mMapLayerNames.value( layerId ) ) );
157  }
158  return;
159  }
160 
161  QgsTextLabelFeature *labelFeature = dynamic_cast<QgsTextLabelFeature *>( label->getFeaturePart()->feature() );
162  if ( !labelFeature )
163  return;
164 
165  QgsPalLayerSettings labelSettings( settings );
166  const QMap< QgsPalLayerSettings::Property, QVariant > &dataDefinedValues = labelFeature->dataDefinedValues();
167 
168  if ( dataDefinedValues.contains( QgsPalLayerSettings::MultiLineWrapChar ) )
169  {
170  labelSettings.wrapChar = dataDefinedValues.value( QgsPalLayerSettings::MultiLineWrapChar ).toString();
171  }
172  if ( dataDefinedValues.contains( QgsPalLayerSettings::AutoWrapLength ) )
173  {
174  labelSettings.autoWrapLength = dataDefinedValues.value( QgsPalLayerSettings::AutoWrapLength ).toInt();
175  }
176  const QString labelText = QgsPalLabeling::splitToLines( labelFeature->text( -1 ),
177  labelSettings.wrapChar,
178  labelSettings.autoWrapLength,
179  labelSettings.useMaxLineLengthForAutoWrap ).join( '\n' );
180 
181  QString labelAlignment;
182  if ( dataDefinedValues.contains( QgsPalLayerSettings::MultiLineAlignment ) )
183  {
184  labelSettings.multilineAlign = static_cast< QgsPalLayerSettings::MultiLineAlign >( dataDefinedValues.value( QgsPalLayerSettings::MultiLineAlignment ).toInt() );
185  }
186  switch ( labelSettings.multilineAlign )
187  {
188  case QgsPalLayerSettings::QgsPalLayerSettings::MultiRight:
189  labelAlignment = QStringLiteral( "right" );
190  break;
191 
192  case QgsPalLayerSettings::QgsPalLayerSettings::MultiCenter:
193  labelAlignment = QStringLiteral( "center" );
194  break;
195 
196  case QgsPalLayerSettings::QgsPalLayerSettings::MultiLeft:
197  labelAlignment = QStringLiteral( "left" );
198  break;
199 
200  case QgsPalLayerSettings::QgsPalLayerSettings::MultiJustify:
201  labelAlignment = QStringLiteral( "justify" );
202  break;
203 
205  switch ( label->getQuadrant() )
206  {
210  labelAlignment = QStringLiteral( "right" );
211  break;
212 
216  labelAlignment = QStringLiteral( "center" );
217  break;
218 
222  labelAlignment = QStringLiteral( "left" );
223  break;
224  }
225  break;
226  }
227 
228  const double labelRotation = !qgsDoubleNear( label->getAlpha(), 0.0 )
229  ? -( label->getAlpha() * 180 / M_PI ) + 360
230  : 0.0;
231 
232  const QFont font = labelFeature->definedFont();
233  const QString fontFamily = font.family();
234  const QString fontStyle = font.styleName();
235  const double fontSize = static_cast<double>( font.pixelSize() ) * 72 / context.painter()->device()->logicalDpiX();
236  const bool fontItalic = font.italic();
237  const bool fontBold = font.bold();
238  const bool fontUnderline = font.underline();
239  const double fontLetterSpacing = font.letterSpacing();
240  const double fontWordSpacing = font.wordSpacing();
241 
242  QgsTextFormat format = labelSettings.format();
243  if ( dataDefinedValues.contains( QgsPalLayerSettings::Size ) )
244  {
245  format.setSize( dataDefinedValues.value( QgsPalLayerSettings::Size ).toDouble() );
246  }
247  if ( dataDefinedValues.contains( QgsPalLayerSettings::Color ) )
248  {
249  format.setColor( dataDefinedValues.value( QgsPalLayerSettings::Color ).value<QColor>() );
250  }
251  if ( dataDefinedValues.contains( QgsPalLayerSettings::FontOpacity ) )
252  {
253  format.setOpacity( dataDefinedValues.value( QgsPalLayerSettings::FontOpacity ).toDouble() / 100.0 );
254  }
255  if ( dataDefinedValues.contains( QgsPalLayerSettings::MultiLineHeight ) )
256  {
257  format.setLineHeight( dataDefinedValues.value( QgsPalLayerSettings::MultiLineHeight ).toDouble() );
258  }
259 
260  const QString formatColor = format.color().name();
261  const double formatOpacity = format.opacity() * 100;
262  const double formatLineHeight = format.lineHeight();
263 
264  QgsTextBufferSettings buffer = format.buffer();
265  if ( dataDefinedValues.contains( QgsPalLayerSettings::BufferDraw ) )
266  {
267  buffer.setEnabled( dataDefinedValues.value( QgsPalLayerSettings::BufferDraw ).toBool() );
268  }
269  const bool bufferDraw = buffer.enabled();
270  double bufferSize = 0.0;
271  QString bufferColor;
272  double bufferOpacity = 0.0;
273  if ( bufferDraw )
274  {
275  if ( dataDefinedValues.contains( QgsPalLayerSettings::BufferSize ) )
276  {
277  buffer.setSize( dataDefinedValues.value( QgsPalLayerSettings::BufferSize ).toDouble() );
278  }
279  if ( dataDefinedValues.contains( QgsPalLayerSettings::BufferColor ) )
280  {
281  buffer.setColor( dataDefinedValues.value( QgsPalLayerSettings::BufferColor ).value<QColor>() );
282  }
283  if ( dataDefinedValues.contains( QgsPalLayerSettings::BufferOpacity ) )
284  {
285  buffer.setOpacity( dataDefinedValues.value( QgsPalLayerSettings::BufferOpacity ).toDouble() / 100.0 );
286  }
287 
288  bufferSize = buffer.sizeUnit() == QgsUnitTypes::RenderPercentage
289  ? context.convertToPainterUnits( format.size(), format.sizeUnit(), format.sizeMapUnitScale() ) * buffer.size() / 100
290  : context.convertToPainterUnits( buffer.size(), buffer.sizeUnit(), buffer.sizeMapUnitScale() );
291  bufferSize = bufferSize * 72 / context.painter()->device()->logicalDpiX();
292  bufferColor = buffer.color().name();
293  bufferOpacity = buffer.opacity() * 100;
294  }
295 
296  QgsAttributes attributes;
297  attributes << mMapLayerNames.value( layerId ) << fid
298  << labelText << label->getWidth() << label->getHeight() << labelRotation << unplacedLabel
299  << fontFamily << fontSize << fontItalic << fontBold << fontUnderline << fontStyle << fontLetterSpacing << fontWordSpacing
300  << labelAlignment << formatLineHeight << formatColor << formatOpacity
301  << bufferDraw << bufferSize << bufferColor << bufferOpacity;
302 
303  double x = label->getX();
304  double y = label->getY();
305  QgsGeometry geometry( new QgsPoint( x, y ) );
306 
307  QgsFeature feature;
308  feature.setAttributes( attributes );
309  feature.setGeometry( geometry );
310  features << feature;
311  }
312 
313  QList<QgsFeature> features;
314 
315  private:
316 
317  QMap<QString, QString> mMapLayerNames;
318  QList<QString> mCurvedWarningPushed;
319 
320  QgsProcessingFeedback *mFeedback = nullptr;
321 };
322 
323 QVariantMap QgsExtractLabelsAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
324 {
325  const QgsRectangle extent = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context );
326  const double scale = parameterAsDouble( parameters, QStringLiteral( "SCALE" ), context );
327  if ( qgsDoubleNear( scale, 0.0 ) )
328  {
329  throw QgsProcessingException( QObject::tr( "Invalid scale value, a number greater than 0 is required" ) );
330  }
331  double dpi = parameterAsDouble( parameters, QStringLiteral( "DPI" ), context );
332  if ( qgsDoubleNear( dpi, 0.0 ) )
333  {
334  dpi = 96.0;
335  }
336 
337  QgsScaleCalculator calculator;
338  calculator.setDpi( dpi );
339  calculator.setMapUnits( mCrs.mapUnits() );
340  const QSize imageSize = calculator.calculateImageSize( extent, scale ).toSize();
341 
342  QgsFields fields;
343  fields.append( QgsField( QStringLiteral( "Layer" ), QVariant::String, QString(), 0, 0 ) );
344  fields.append( QgsField( QStringLiteral( "FeatureID" ), QVariant::LongLong, QString(), 20 ) );
345  fields.append( QgsField( QStringLiteral( "LabelText" ), QVariant::String, QString(), 0, 0 ) );
346  fields.append( QgsField( QStringLiteral( "LabelWidth" ), QVariant::Double, QString(), 20, 8 ) );
347  fields.append( QgsField( QStringLiteral( "LabelHeight" ), QVariant::Double, QString(), 20, 8 ) );
348  fields.append( QgsField( QStringLiteral( "LabelRotation" ), QVariant::Double, QString(), 20, 2 ) );
349  fields.append( QgsField( QStringLiteral( "LabelUnplaced" ), QVariant::Bool, QString(), 1, 0 ) );
350  fields.append( QgsField( QStringLiteral( "Family" ), QVariant::String, QString(), 0, 0 ) );
351  fields.append( QgsField( QStringLiteral( "Size" ), QVariant::Double, QString(), 20, 4 ) );
352  fields.append( QgsField( QStringLiteral( "Italic" ), QVariant::Bool, QString(), 1, 0 ) );
353  fields.append( QgsField( QStringLiteral( "Bold" ), QVariant::Bool, QString(), 1, 0 ) );
354  fields.append( QgsField( QStringLiteral( "Underline" ), QVariant::Bool, QString(), 1, 0 ) );
355  fields.append( QgsField( QStringLiteral( "FontStyle" ), QVariant::String, QString(), 0, 0 ) );
356  fields.append( QgsField( QStringLiteral( "FontLetterSpacing" ), QVariant::Double, QString(), 20, 4 ) );
357  fields.append( QgsField( QStringLiteral( "FontWordSpacing" ), QVariant::Double, QString(), 20, 4 ) );
358  fields.append( QgsField( QStringLiteral( "MultiLineAlignment" ), QVariant::String, QString(), 0, 0 ) );
359  fields.append( QgsField( QStringLiteral( "MultiLineHeight" ), QVariant::Double, QString(), 20, 2 ) );
360  fields.append( QgsField( QStringLiteral( "Color" ), QVariant::String, QString(), 7, 0 ) );
361  fields.append( QgsField( QStringLiteral( "FontOpacity" ), QVariant::Double, QString(), 20, 1 ) );
362  fields.append( QgsField( QStringLiteral( "BufferDraw" ), QVariant::Bool, QString(), 1, 0 ) );
363  fields.append( QgsField( QStringLiteral( "BufferSize" ), QVariant::Double, QString(), 20, 4 ) );
364  fields.append( QgsField( QStringLiteral( "BufferColor" ), QVariant::String, QString(), 7, 0 ) );
365  fields.append( QgsField( QStringLiteral( "BufferOpacity" ), QVariant::Double, QString(), 20, 1 ) );
366 
367  QString dest;
368  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, QgsWkbTypes::Point, mCrs, QgsFeatureSink::RegeneratePrimaryKey ) );
369  if ( !sink )
370  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
371 
372  QgsMapSettings mapSettings;
373  mapSettings.setDestinationCrs( mCrs );
374  mapSettings.setExtent( extent );
375  mapSettings.setOutputSize( imageSize );
376  mapSettings.setOutputDpi( dpi );
377  mapSettings.setFlag( Qgis::MapSettingsFlag::DrawLabeling, true );
380  mapSettings.setLayers( mMapLayers );
381  mapSettings.setLayerStyleOverrides( mMapThemeStyleOverrides );
382  mapSettings.setLabelingEngineSettings( mLabelSettings );
383 
384  //build the expression context
385  QgsExpressionContext expressionContext;
386  expressionContext << QgsExpressionContextUtils::globalScope()
389  mapSettings.setExpressionContext( expressionContext );
390 
391  QgsNullPaintDevice nullPaintDevice;
392  nullPaintDevice.setOutputSize( imageSize );
393  nullPaintDevice.setOutputDpi( static_cast< int >( std::round( dpi ) ) );
394  QPainter painter( &nullPaintDevice );
395 
396  QgsMapRendererCustomPainterJob renderJob( mapSettings, &painter );
397  ExtractLabelSink labelSink( mMapLayerNames, feedback );
398  renderJob.setLabelSink( &labelSink );
399 
400  feedback->pushInfo( QObject::tr( "Extracting labels" ) );
401 
402  QgsProcessingMultiStepFeedback multiStepFeedback( 10, feedback );
403  multiStepFeedback.setCurrentStep( 0 );
404 
405  QEventLoop loop;
406  QObject::connect( feedback, &QgsFeedback::canceled, &renderJob, &QgsMapRendererCustomPainterJob::cancel );
407  QObject::connect( &renderJob, &QgsMapRendererJob::renderingLayersFinished, feedback, [feedback]() { feedback->pushInfo( QObject::tr( "Calculating label placement" ) ); } );
408  int labelsCollectedFromLayers = 0;
409  QObject::connect( &renderJob, &QgsMapRendererJob::layerRenderingStarted, feedback, [this, &multiStepFeedback, &labelsCollectedFromLayers]( const QString & layerId )
410  {
411  multiStepFeedback.pushInfo( QObject::tr( "Collecting labelled features from %1" ).arg( mMapLayerNames.value( layerId ) ) );
412  multiStepFeedback.setProgress( 100.0 * static_cast< double >( labelsCollectedFromLayers ) / mMapLayers.size() );
413  labelsCollectedFromLayers++;
414  } );
415 
416  QObject::connect( renderJob.labelingEngineFeedback(), &QgsLabelingEngineFeedback::labelRegistrationAboutToBegin, &multiStepFeedback, [&multiStepFeedback]()
417  {
418  multiStepFeedback.setCurrentStep( 1 );
419  multiStepFeedback.pushInfo( QObject::tr( "Registering labels" ) );
420  } );
421 
422  QObject::connect( renderJob.labelingEngineFeedback(), &QgsLabelingEngineFeedback::providerRegistrationAboutToBegin, &multiStepFeedback, [this, &multiStepFeedback]( QgsAbstractLabelProvider * provider )
423  {
424  multiStepFeedback.setCurrentStep( 2 );
425  if ( !provider->layerId().isEmpty() )
426  {
427  multiStepFeedback.pushInfo( QObject::tr( "Adding labels from %1" ).arg( mMapLayerNames.value( provider->layerId() ) ) );
428  }
429  } );
430  QObject::connect( renderJob.labelingEngineFeedback(), &QgsLabelingEngineFeedback::candidateCreationAboutToBegin, &multiStepFeedback, [this, &multiStepFeedback]( QgsAbstractLabelProvider * provider )
431  {
432  multiStepFeedback.setCurrentStep( 3 );
433  if ( !provider->layerId().isEmpty() )
434  {
435  multiStepFeedback.pushInfo( QObject::tr( "Generating label placement candidates for %1" ).arg( mMapLayerNames.value( provider->layerId() ) ) );
436  }
437  } );
438  QObject::connect( renderJob.labelingEngineFeedback(), &QgsLabelingEngineFeedback::obstacleCostingAboutToBegin, &multiStepFeedback, [&multiStepFeedback]()
439  {
440  multiStepFeedback.setCurrentStep( 4 );
441  multiStepFeedback.setProgressText( QObject::tr( "Calculating obstacle costs" ) );
442  } );
443  QObject::connect( renderJob.labelingEngineFeedback(), &QgsLabelingEngineFeedback::calculatingConflictsAboutToBegin, &multiStepFeedback, [&multiStepFeedback]()
444  {
445  multiStepFeedback.setCurrentStep( 5 );
446  multiStepFeedback.setProgressText( QObject::tr( "Calculating label conflicts" ) );
447  } );
448  QObject::connect( renderJob.labelingEngineFeedback(), &QgsLabelingEngineFeedback::finalizingCandidatesAboutToBegin, &multiStepFeedback, [&multiStepFeedback]()
449  {
450  multiStepFeedback.setCurrentStep( 6 );
451  multiStepFeedback.setProgressText( QObject::tr( "Finalizing candidates" ) );
452  } );
453  QObject::connect( renderJob.labelingEngineFeedback(), &QgsLabelingEngineFeedback::reductionAboutToBegin, &multiStepFeedback, [&multiStepFeedback]()
454  {
455  multiStepFeedback.setCurrentStep( 7 );
456  multiStepFeedback.setProgressText( QObject::tr( "Reducing problem" ) );
457  } );
458  QObject::connect( renderJob.labelingEngineFeedback(), &QgsLabelingEngineFeedback::solvingPlacementAboutToBegin, &multiStepFeedback, [&multiStepFeedback]()
459  {
460  multiStepFeedback.setCurrentStep( 8 );
461  multiStepFeedback.setProgressText( QObject::tr( "Determining optimal label placements" ) );
462  } );
463  QObject::connect( renderJob.labelingEngineFeedback(), &QgsLabelingEngineFeedback::solvingPlacementFinished, &multiStepFeedback, [&multiStepFeedback]()
464  {
465  multiStepFeedback.setProgressText( QObject::tr( "Labeling complete" ) );
466  } );
467 
468  QObject::connect( renderJob.labelingEngineFeedback(), &QgsLabelingEngineFeedback::progressChanged, &multiStepFeedback, [&multiStepFeedback]( double progress )
469  {
470  multiStepFeedback.setProgress( progress );
471  } );
472 
473  QObject::connect( &renderJob, &QgsMapRendererJob::finished, &loop, [&loop]() { loop.exit(); } );
474  renderJob.start();
475  loop.exec();
476 
477  qDeleteAll( mMapLayers );
478  mMapLayers.clear();
479 
480  multiStepFeedback.setCurrentStep( 9 );
481  feedback->pushInfo( QObject::tr( "Writing %n label(s) to output layer", "", labelSink.features.count() ) );
482  const double step = !labelSink.features.empty() ? 100.0 / labelSink.features.count() : 1;
483  long long index = -1;
484  for ( QgsFeature &feature : labelSink.features )
485  {
486  index++;
487  multiStepFeedback.setProgress( step * index );
488  if ( feedback->isCanceled() )
489  break;
490 
491  if ( !sink->addFeature( feature, QgsFeatureSink::FastInsert ) )
492  throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
493  }
494  sink.reset();
495 
496  if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( QgsProcessingUtils::mapLayerFromString( dest, context ) ) )
497  {
498  vl->setRenderer( new QgsNullSymbolRenderer() );
499  vl->renderer()->setReferenceScale( scale );
500 
501  QgsPalLayerSettings settings;
502  QgsPropertyCollection settingsProperties;
503 
504  settings.fieldName = QStringLiteral( "LabelText" );
505  settings.obstacleSettings().setIsObstacle( false );
508  settings.displayAll = true;
509 
510  QgsTextFormat textFormat;
511  textFormat.setSize( 9 );
513  textFormat.setColor( QColor( 0, 0, 0 ) );
514 
515  QgsTextBufferSettings buffer = textFormat.buffer();
517 
518  textFormat.setBuffer( buffer );
519  settings.setFormat( textFormat );
520 
521  settingsProperties.setProperty( QgsPalLayerSettings::Color, QgsProperty::fromExpression( QStringLiteral( "if(\"LabelUnplaced\",'255,0,0',\"Color\")" ) ) );
522  settingsProperties.setProperty( QgsPalLayerSettings::FontOpacity, QgsProperty::fromField( QStringLiteral( "FontOpacity" ) ) );
523  settingsProperties.setProperty( QgsPalLayerSettings::Family, QgsProperty::fromField( QStringLiteral( "Family" ) ) );
524  settingsProperties.setProperty( QgsPalLayerSettings::Italic, QgsProperty::fromField( QStringLiteral( "Italic" ) ) );
525  settingsProperties.setProperty( QgsPalLayerSettings::Bold, QgsProperty::fromField( QStringLiteral( "Bold" ) ) );
526  settingsProperties.setProperty( QgsPalLayerSettings::Underline, QgsProperty::fromField( QStringLiteral( "Underline" ) ) );
527  settingsProperties.setProperty( QgsPalLayerSettings::Size, QgsProperty::fromField( QStringLiteral( "Size" ) ) );
528  settingsProperties.setProperty( QgsPalLayerSettings::FontLetterSpacing, QgsProperty::fromField( QStringLiteral( "FontLetterSpacing" ) ) );
529  settingsProperties.setProperty( QgsPalLayerSettings::FontWordSpacing, QgsProperty::fromField( QStringLiteral( "FontWordSpacing" ) ) );
530  settingsProperties.setProperty( QgsPalLayerSettings::MultiLineAlignment, QgsProperty::fromField( QStringLiteral( "MultiLineAlignment" ) ) );
531  settingsProperties.setProperty( QgsPalLayerSettings::MultiLineHeight, QgsProperty::fromField( QStringLiteral( "MultiLineHeight" ) ) );
532  settingsProperties.setProperty( QgsPalLayerSettings::LabelRotation, QgsProperty::fromField( QStringLiteral( "LabelRotation" ) ) );
533  settingsProperties.setProperty( QgsPalLayerSettings::BufferDraw, QgsProperty::fromField( QStringLiteral( "BufferDraw" ) ) );
534  settingsProperties.setProperty( QgsPalLayerSettings::BufferSize, QgsProperty::fromField( QStringLiteral( "BufferSize" ) ) );
535  settingsProperties.setProperty( QgsPalLayerSettings::BufferColor, QgsProperty::fromField( QStringLiteral( "BufferColor" ) ) );
536  settingsProperties.setProperty( QgsPalLayerSettings::BufferOpacity, QgsProperty::fromField( QStringLiteral( "BufferOpacity" ) ) );
537  settingsProperties.setProperty( QgsPalLayerSettings::Show, QgsProperty::fromExpression( QStringLiteral( "\"LabelUnplaced\"=false" ) ) );
538  settings.setDataDefinedProperties( settingsProperties );
539 
541  vl->setLabeling( labeling );
542  vl->setLabelsEnabled( true );
543 
544  QString errorMessage;
545  vl->saveStyleToDatabase( QString(), QString(), true, QString(), errorMessage );
546  }
547 
548  QVariantMap outputs;
549  outputs.insert( QStringLiteral( "OUTPUT" ), dest );
550  return outputs;
551 }
552 
553 
554 bool QgsExtractLabelsAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
555 {
556  // Retrieve and clone layers
557  const QString mapTheme = parameterAsString( parameters, QStringLiteral( "MAP_THEME" ), context );
558  if ( !mapTheme.isEmpty() && context.project()->mapThemeCollection()->hasMapTheme( mapTheme ) )
559  {
560  const QList<QgsMapLayer *> constLayers = context.project()->mapThemeCollection()->mapThemeVisibleLayers( mapTheme );
561  for ( const QgsMapLayer *l : constLayers )
562  {
563  // only copy vector layers as other layer types aren't actors in the labeling process
564  if ( l->type() == QgsMapLayerType::VectorLayer )
565  mMapLayers.push_back( l->clone() );
566  }
567  mMapThemeStyleOverrides = context.project()->mapThemeCollection( )->mapThemeStyleOverrides( mapTheme );
568  }
569 
570  if ( mMapLayers.isEmpty() )
571  {
572  QList<QgsMapLayer *> layers;
573  QgsLayerTree *root = context.project()->layerTreeRoot();
574  const QList<QgsLayerTreeLayer *> layerTreeLayers = root->findLayers();
575  layers.reserve( layerTreeLayers.size() );
576  for ( QgsLayerTreeLayer *nodeLayer : layerTreeLayers )
577  {
578  QgsMapLayer *layer = nodeLayer->layer();
579  if ( nodeLayer->isVisible() && root->layerOrder().contains( layer ) )
580  layers << layer;
581  }
582 
583  for ( const QgsMapLayer *l : std::as_const( layers ) )
584  {
585  if ( l->type() == QgsMapLayerType::VectorLayer )
586  mMapLayers.push_back( l->clone() );
587  }
588  }
589 
590  for ( const QgsMapLayer *l : std::as_const( mMapLayers ) )
591  {
592  mMapLayerNames.insert( l->id(), l->name() );
593  }
594 
595  mCrs = parameterAsExtentCrs( parameters, QStringLiteral( "EXTENT" ), context );
596  if ( !mCrs.isValid() )
597  mCrs = context.project()->crs();
598 
599  bool includeUnplaced = parameterAsBoolean( parameters, QStringLiteral( "INCLUDE_UNPLACED" ), context );
600  mLabelSettings = context.project()->labelingEngineSettings();
601  mLabelSettings.setFlag( QgsLabelingEngineSettings::DrawUnplacedLabels, includeUnplaced );
602  mLabelSettings.setFlag( QgsLabelingEngineSettings::CollectUnplacedLabels, includeUnplaced );
603 
604  return true;
605 }
606 
607 
@ UseRenderingOptimization
Enable vector simplification and other rendering optimizations.
@ DrawLabeling
Enable drawing of labels on top of the map.
@ SkipSymbolRendering
Disable symbol rendering while still drawing labels if enabled (since QGIS 3.24)
The QgsAbstractLabelProvider class is an interface class.
Abstract base class - its implementations define different approaches to the labeling of a vector lay...
A vector of attributes.
Definition: qgsattributes.h:58
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
@ RegeneratePrimaryKey
This flag indicates, that a primary key field cannot be guaranteed to be unique and the sink should i...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:153
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:163
void progressChanged(double progress)
Emitted when the feedback object reports a progress change.
void canceled()
Internal routines can connect to this signal if they use event loop.
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
Container of fields for a vector layer.
Definition: qgsfields.h:45
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false)
Definition: qgsfields.cpp:59
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
void setIsObstacle(bool isObstacle)
Sets whether features are obstacles to labels of other layers.
Abstract base class that can be used to intercept rendered labels from a labeling / rendering job.
Definition: qgslabelsink.h:38
virtual void drawUnplacedLabel(const QString &layerId, QgsRenderContext &context, pal::LabelPosition *label, const QgsPalLayerSettings &settings)
The drawLabel method is called for each unplaced label.
Definition: qgslabelsink.h:57
virtual void drawLabel(const QString &layerId, QgsRenderContext &context, pal::LabelPosition *label, const QgsPalLayerSettings &settings)=0
The drawLabel method is called for each label that is being drawn.
void obstacleCostingAboutToBegin()
Emitted when the obstacle costing is about to begin.
void solvingPlacementAboutToBegin()
Emitted when the problem solving step is about to begin.
void calculatingConflictsAboutToBegin()
Emitted when the conflict handling step is about to begin.
void reductionAboutToBegin()
Emitted when the candidate reduction step is about to begin.
void labelRegistrationAboutToBegin()
Emitted when the label registration is about to begin.
void solvingPlacementFinished()
Emitted when the problem solving step is finished.
void finalizingCandidatesAboutToBegin()
Emitted when the label candidates are about to be finalized.
void candidateCreationAboutToBegin(QgsAbstractLabelProvider *provider)
Emitted when the label candidate creation is about to begin for a provider.
void providerRegistrationAboutToBegin(QgsAbstractLabelProvider *provider)
Emitted when the label registration is about to begin for a provider.
@ CollectUnplacedLabels
Whether unplaced labels should be collected in the labeling results (regardless of whether they are b...
@ DrawUnplacedLabels
Whether to render unplaced labels as an indicator/warning for users.
void setFlag(Flag f, bool enabled=true)
Sets whether a particual flag is enabled.
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
Layer tree node points to a map layer.
Namespace with helper functions for layer tree operations.
Definition: qgslayertree.h:33
QList< QgsMapLayer * > layerOrder() const
The order in which layers will be rendered on the canvas.
Base class for all map layer types.
Definition: qgsmaplayer.h:73
Job implementation that renders everything sequentially using a custom painter.
void cancel() override
Stop the rendering job - does not return until the job has terminated.
void renderingLayersFinished()
Emitted when the layers are rendered.
void finished()
emitted when asynchronous rendering is finished (or canceled).
void layerRenderingStarted(const QString &layerId)
Emitted just before rendering starts for a particular layer.
The QgsMapSettings class contains configuration for rendering of the map.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers to render in the map.
void setOutputDpi(double dpi)
Sets the dpi (dots per inch) used for conversion between real world units (e.g.
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Sets the map of map layer style overrides (key: layer ID, value: style name) where a different style ...
void setExtent(const QgsRectangle &rect, bool magnified=true)
Sets the coordinates of the rectangle which should be rendered.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets the global configuration of the labeling engine.
void setOutputSize(QSize size)
Sets the size of the resulting map image, in pixels.
void setFlag(Qgis::MapSettingsFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination crs (coordinate reference system) for the map render.
bool hasMapTheme(const QString &name) const
Returns whether a map theme with a matching name exists.
QList< QgsMapLayer * > mapThemeVisibleLayers(const QString &name) const
Returns the list of layers that are visible for the specified map theme.
QMap< QString, QString > mapThemeStyleOverrides(const QString &name)
Gets layer style overrides (for QgsMapSettings) of the visible layers for given map theme.
Null painter device that can be used for map renderer jobs which use custom painters.
void setOutputSize(const QSize &size)
Sets the size of the device in pixels.
void setOutputDpi(const int dpi)
Sets the dpi of the device.
Null symbol renderer.
static QStringList splitToLines(const QString &text, const QString &wrapCharacter, int autoWrapLength=0, bool useMaxLineLengthWhenAutoWrapping=true)
Splits a text string to a list of separate lines, using a specified wrap character (wrapCharacter).
Contains settings for how a map layer will be labeled.
void setFormat(const QgsTextFormat &format)
Sets the label text formatting settings, e.g., font settings, buffer settings, etc.
bool displayAll
If true, all features will be labelled even when overlaps occur.
QuadrantPosition quadOffset
Sets the quadrant in which to offset labels from feature.
@ PerimeterCurved
Arranges candidates following the curvature of a polygon's boundary. Applies to polygon layers only.
@ Curved
Arranges candidates following the curvature of a line feature. Applies to line layers only.
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
@ LabelRotation
Label rotation.
@ Underline
Use underline.
@ FontLetterSpacing
Letter spacing.
@ Bold
Use bold style.
@ BufferOpacity
Buffer opacity.
@ Italic
Use italic style.
@ FontWordSpacing
Word spacing.
@ Family
Font family.
@ FontOpacity
Text opacity.
const QgsLabelObstacleSettings & obstacleSettings() const
Returns the label obstacle settings.
void setDataDefinedProperties(const QgsPropertyCollection &collection)
Sets the label's property collection, used for data defined overrides.
QString fieldName
Name of field (or an expression) to use for label text.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
virtual Flags flags() const
Returns the flags indicating how and when the algorithm operates and should be exposed to users.
Contains information about the context in which a processing algorithm is executed.
QgsProject * project() const
Returns the project in which the algorithm is being executed.
Custom exception class for processing related exceptions.
Definition: qgsexception.h:83
Base class for providing feedback from a processing algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
Processing feedback object for multi-step operations.
A boolean parameter for processing algorithms.
@ FlagAdvanced
Parameter is an advanced parameter which should be hidden from users by default.
A rectangular map extent parameter for processing algorithms.
A feature sink output for processing algorithms.
A double numeric parameter for map scale values.
static QgsMapLayer * mapLayerFromString(const QString &string, QgsProcessingContext &context, bool allowLoadingNewLayers=true, QgsProcessingUtils::LayerHint typeHint=QgsProcessingUtils::LayerHint::UnknownType)
Interprets a string as a map layer within the supplied context.
@ TypeVectorPoint
Vector point layers.
Definition: qgsprocessing.h:49
QgsMapThemeCollection * mapThemeCollection
Definition: qgsproject.h:109
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
QgsCoordinateReferenceSystem crs
Definition: qgsproject.h:106
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns project's global labeling engine settings.
A grouped map of multiple QgsProperty objects, each referenced by a integer key value.
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
static QgsProperty fromField(const QString &fieldName, bool isActive=true)
Returns a new FieldBasedProperty created from the specified field name.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
Contains information about the context of a rendering operation.
void setRenderingStopped(bool stopped)
Sets whether the rendering operation has been stopped and any ongoing rendering should be canceled im...
QPainter * painter()
Returns the destination QPainter for the render operation.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
Calculates scale for a given combination of canvas size, map extent, and monitor dpi.
void setDpi(double dpi)
Sets the dpi (dots per inch) for the output resolution, to be used in scale calculations.
void setMapUnits(QgsUnitTypes::DistanceUnit mapUnits)
Set the map units.
QSizeF calculateImageSize(const QgsRectangle &mapExtent, double scale) const
Calculate the image size in pixel (physical) units.
Container for settings relating to a text buffer.
double size() const
Returns the size of the buffer.
void setColor(const QColor &color)
Sets the color for the buffer.
void setOpacity(double opacity)
Sets the buffer opacity.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the buffer size.
bool enabled() const
Returns whether the buffer is enabled.
double opacity() const
Returns the buffer opacity.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units for the buffer size.
QColor color() const
Returns the color of the buffer.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units used for the buffer size.
void setSize(double size)
Sets the size of the buffer.
Container for all settings relating to text rendering.
Definition: qgstextformat.h:41
void setColor(const QColor &color)
Sets the color that text will be rendered in.
void setSize(double size)
Sets the size for rendered text.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the size.
double lineHeight() const
Returns the line height for text.
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units for the size of rendered text.
void setOpacity(double opacity)
Sets the text's opacity.
void setBuffer(const QgsTextBufferSettings &bufferSettings)
Sets the text's buffer settings.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the size of rendered text.
double opacity() const
Returns the text's opacity.
double size() const
Returns the size for rendered text.
QColor color() const
Returns the color that text will be rendered in.
QgsTextBufferSettings & buffer()
Returns a reference to the text buffer settings.
void setLineHeight(double height)
Sets the line height for text.
Class that adds extra information to QgsLabelFeature for text labels.
const QMap< QgsPalLayerSettings::Property, QVariant > & dataDefinedValues() const
Gets data-defined values.
QFont definedFont()
Font to be used for rendering.
QString text(int partId) const
Returns the text component corresponding to a specified label part.
@ RenderPercentage
Percentage of another measurement (e.g., canvas size, feature size)
Definition: qgsunittypes.h:172
@ RenderPoints
Points (e.g., for font sizes)
Definition: qgsunittypes.h:173
Basic implementation of the labeling interface.
Represents a vector layer which manages a vector based data sets.
QgsFeatureId featureId() const
Returns the unique ID of the feature.
Definition: feature.cpp:161
QgsLabelFeature * feature()
Returns the parent feature.
Definition: feature.h:94
LabelPosition is a candidate feature label position.
Definition: labelposition.h:56
double getAlpha() const
Returns the angle to rotate text (in rad).
double getHeight() const
Quadrant getQuadrant() const
double getWidth() const
FeaturePart * getFeaturePart() const
Returns the feature corresponding to this labelposition.
double getX(int i=0) const
Returns the down-left x coordinate.
double getY(int i=0) const
Returns the down-left y coordinate.
@ VectorLayer
Vector layer.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1578
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28