40 QString QgsExtractLabelsAlgorithm::name()
const
42 return QStringLiteral(
"extractlabels" );
45 QString QgsExtractLabelsAlgorithm::displayName()
const
47 return QObject::tr(
"Extract labels" );
50 QStringList QgsExtractLabelsAlgorithm::tags()
const
52 return QObject::tr(
"map themes,font,position" ).split(
',' );
55 QgsProcessingAlgorithm::Flags QgsExtractLabelsAlgorithm::flags()
const
60 QString QgsExtractLabelsAlgorithm::group()
const
62 return QObject::tr(
"Cartography" );
65 QString QgsExtractLabelsAlgorithm::groupId()
const
67 return QStringLiteral(
"cartography" );
70 void QgsExtractLabelsAlgorithm::initAlgorithm(
const QVariantMap & )
73 QStringLiteral(
"EXTENT" ),
74 QObject::tr(
"Map extent" ) ) );
77 QStringLiteral(
"SCALE" ),
78 QObject::tr(
"Map scale" ) ) );
80 std::unique_ptr<QgsProcessingParameterMapTheme> mapThemeParameter = std::make_unique<QgsProcessingParameterMapTheme>(
81 QStringLiteral(
"MAP_THEME" ),
82 QObject::tr(
"Map theme" ),
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() );
88 QStringLiteral(
"INCLUDE_UNPLACED" ),
89 QObject::tr(
"Include unplaced labels" ),
90 QVariant(
true ),
true ) );
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 );
98 addParameter( dpiParameter.release() );
101 QStringLiteral(
"OUTPUT" ),
102 QObject::tr(
"Extracted labels" ),
106 QString QgsExtractLabelsAlgorithm::shortDescription()
const
108 return QObject::tr(
"Converts map labels to a point layer with relevant details saved as attributes." );
111 QString QgsExtractLabelsAlgorithm::shortHelpString()
const
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." );
118 QgsExtractLabelsAlgorithm *QgsExtractLabelsAlgorithm::createInstance()
const
120 return new QgsExtractLabelsAlgorithm();
127 : mMapLayerNames( mapLayerNames )
128 , mFeedback( feedback )
134 processLabel( layerId, context, label, settings,
false );
139 processLabel( layerId, context, label, settings,
true );
144 if ( mFeedback->isCanceled() )
153 if ( !mCurvedWarningPushed.contains( layerId ) )
155 mCurvedWarningPushed << layerId;
156 mFeedback->pushWarning( QObject::tr(
"Curved placement not supported, skipping labels from layer %1" ).arg( mMapLayerNames.value( layerId ) ) );
166 const QMap< QgsPalLayerSettings::Property, QVariant > &dataDefinedValues = labelFeature->
dataDefinedValues();
177 labelSettings.wrapChar,
178 labelSettings.autoWrapLength,
179 labelSettings.useMaxLineLengthForAutoWrap ).join(
'\n' );
181 QString labelAlignment;
186 switch ( labelSettings.multilineAlign )
188 case Qgis::LabelMultiLineAlignment::Right:
189 labelAlignment = QStringLiteral(
"right" );
192 case Qgis::LabelMultiLineAlignment::Center:
193 labelAlignment = QStringLiteral(
"center" );
196 case Qgis::LabelMultiLineAlignment::Left:
197 labelAlignment = QStringLiteral(
"left" );
200 case Qgis::LabelMultiLineAlignment::Justify:
201 labelAlignment = QStringLiteral(
"justify" );
204 case Qgis::LabelMultiLineAlignment::FollowPlacement:
210 labelAlignment = QStringLiteral(
"right" );
216 labelAlignment = QStringLiteral(
"center" );
222 labelAlignment = QStringLiteral(
"left" );
229 ? -( label->
getAlpha() * 180 / M_PI ) + 360
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();
260 const QString formatColor = format.
color().name();
261 const double formatOpacity = format.
opacity() * 100;
262 const double formatLineHeight = format.
lineHeight();
269 const bool bufferDraw = buffer.
enabled();
270 double bufferSize = 0.0;
272 double bufferOpacity = 0.0;
291 bufferSize = bufferSize * 72 / context.
painter()->device()->logicalDpiX();
292 bufferColor = buffer.
color().name();
293 bufferOpacity = buffer.
opacity() * 100;
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;
303 double x = label->
getX();
304 double y = label->
getY();
313 QList<QgsFeature> features;
317 QMap<QString, QString> mMapLayerNames;
318 QList<QString> mCurvedWarningPushed;
325 const QgsRectangle extent = parameterAsExtent( parameters, QStringLiteral(
"EXTENT" ), context );
326 const double scale = parameterAsDouble( parameters, QStringLiteral(
"SCALE" ), context );
329 throw QgsProcessingException( QObject::tr(
"Invalid scale value, a number greater than 0 is required" ) );
331 double dpi = parameterAsDouble( parameters, QStringLiteral(
"DPI" ), context );
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 ) );
393 nullPaintDevice.
setOutputDpi(
static_cast< int >( std::round( dpi ) ) );
394 QPainter painter( &nullPaintDevice );
397 ExtractLabelSink labelSink( mMapLayerNames, feedback );
398 renderJob.setLabelSink( &labelSink );
400 feedback->
pushInfo( QObject::tr(
"Extracting labels" ) );
403 multiStepFeedback.setCurrentStep( 0 );
408 int labelsCollectedFromLayers = 0;
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++;
418 multiStepFeedback.setCurrentStep( 1 );
419 multiStepFeedback.pushInfo( QObject::tr(
"Registering labels" ) );
424 multiStepFeedback.setCurrentStep( 2 );
425 if ( !provider->layerId().isEmpty() )
427 multiStepFeedback.pushInfo( QObject::tr(
"Adding labels from %1" ).arg( mMapLayerNames.value( provider->layerId() ) ) );
432 multiStepFeedback.setCurrentStep( 3 );
433 if ( !provider->layerId().isEmpty() )
435 multiStepFeedback.pushInfo( QObject::tr(
"Generating label placement candidates for %1" ).arg( mMapLayerNames.value( provider->layerId() ) ) );
440 multiStepFeedback.setCurrentStep( 4 );
441 multiStepFeedback.setProgressText( QObject::tr(
"Calculating obstacle costs" ) );
445 multiStepFeedback.setCurrentStep( 5 );
446 multiStepFeedback.setProgressText( QObject::tr(
"Calculating label conflicts" ) );
450 multiStepFeedback.setCurrentStep( 6 );
451 multiStepFeedback.setProgressText( QObject::tr(
"Finalizing candidates" ) );
455 multiStepFeedback.setCurrentStep( 7 );
456 multiStepFeedback.setProgressText( QObject::tr(
"Reducing problem" ) );
460 multiStepFeedback.setCurrentStep( 8 );
461 multiStepFeedback.setProgressText( QObject::tr(
"Determining optimal label placements" ) );
465 multiStepFeedback.setProgressText( QObject::tr(
"Labeling complete" ) );
470 multiStepFeedback.setProgress( progress );
477 qDeleteAll( mMapLayers );
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 )
487 multiStepFeedback.setProgress( step * index );
499 if ( vl->renderer() )
501 vl->renderer()->setReferenceScale( scale );
506 settings.
fieldName = QStringLiteral(
"LabelText" );
509 settings.
quadOffset = Qgis::LabelQuadrantPosition::AboveRight;
516 textFormat.
setColor( QColor( 0, 0, 0 ) );
544 vl->setLabeling( labeling );
545 vl->setLabelsEnabled(
true );
547 QString errorMessage;
548 vl->saveStyleToDatabase( QString(), QString(),
true, QString(), errorMessage );
553 outputs.insert( QStringLiteral(
"OUTPUT" ), dest );
561 const QString mapTheme = parameterAsString( parameters, QStringLiteral(
"MAP_THEME" ), context );
569 mMapLayers.push_back( l->clone() );
574 if ( mMapLayers.isEmpty() )
576 QList<QgsMapLayer *> layers;
578 const QList<QgsLayerTreeLayer *> layerTreeLayers = root->
findLayers();
579 layers.reserve( layerTreeLayers.size() );
583 if ( nodeLayer->isVisible() && root->
layerOrder().contains( layer ) )
587 for (
const QgsMapLayer *l : std::as_const( layers ) )
590 mMapLayers.push_back( l->clone() );
594 for (
const QgsMapLayer *l : std::as_const( mMapLayers ) )
596 mMapLayerNames.insert( l->id(), l->name() );
599 mCrs = parameterAsExtentCrs( parameters, QStringLiteral(
"EXTENT" ), context );
600 if ( !mCrs.isValid() )
603 bool includeUnplaced = parameterAsBoolean( parameters, QStringLiteral(
"INCLUDE_UNPLACED" ), context );