From c95fed55f145bd67bff1a672a9bf240b6b600de1 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 8 Jun 2021 10:38:15 +1000 Subject: [PATCH] [api] Add labeling flag to collect unplaced labels without rendering them --- .../labeling/qgslabelingenginesettings.sip.in | 1 + src/core/labeling/qgslabelingengine.cpp | 6 ++- src/core/labeling/qgslabelingenginesettings.h | 1 + .../labeling/qgsvectorlayerlabelprovider.cpp | 21 +++++---- tests/src/core/testqgslabelingengine.cpp | 47 +++++++++++++++++-- 5 files changed, 60 insertions(+), 16 deletions(-) diff --git a/python/core/auto_generated/labeling/qgslabelingenginesettings.sip.in b/python/core/auto_generated/labeling/qgslabelingenginesettings.sip.in index 20d528c86e3..ee4623fd303 100644 --- a/python/core/auto_generated/labeling/qgslabelingenginesettings.sip.in +++ b/python/core/auto_generated/labeling/qgslabelingenginesettings.sip.in @@ -29,6 +29,7 @@ Stores global configuration for labeling engine DrawLabelRectOnly, DrawCandidates, DrawUnplacedLabels, + CollectUnplacedLabels, }; typedef QFlags Flags; diff --git a/src/core/labeling/qgslabelingengine.cpp b/src/core/labeling/qgslabelingengine.cpp index 96e81fef481..eca64080b20 100644 --- a/src/core/labeling/qgslabelingengine.cpp +++ b/src/core/labeling/qgslabelingengine.cpp @@ -392,7 +392,9 @@ void QgsLabelingEngine::solve( QgsRenderContext &context ) } // find the solution - mLabels = mPal->solveProblem( mProblem.get(), settings.testFlag( QgsLabelingEngineSettings::UseAllLabels ), settings.testFlag( QgsLabelingEngineSettings::DrawUnplacedLabels ) ? &mUnlabeled : nullptr ); + mLabels = mPal->solveProblem( mProblem.get(), + settings.testFlag( QgsLabelingEngineSettings::UseAllLabels ), + settings.testFlag( QgsLabelingEngineSettings::DrawUnplacedLabels ) || settings.testFlag( QgsLabelingEngineSettings::CollectUnplacedLabels ) ? &mUnlabeled : nullptr ); // sort labels std::sort( mLabels.begin(), mLabels.end(), QgsLabelSorter( mMapSettings ) ); @@ -476,7 +478,7 @@ void QgsLabelingEngine::drawLabels( QgsRenderContext &context, const QString &la } // draw unplaced labels. These are always rendered on top - if ( settings.testFlag( QgsLabelingEngineSettings::DrawUnplacedLabels ) ) + if ( settings.testFlag( QgsLabelingEngineSettings::DrawUnplacedLabels ) || settings.testFlag( QgsLabelingEngineSettings::CollectUnplacedLabels ) ) { for ( pal::LabelPosition *label : std::as_const( mUnlabeled ) ) { diff --git a/src/core/labeling/qgslabelingenginesettings.h b/src/core/labeling/qgslabelingenginesettings.h index d9aba4e6a52..92583e2ed76 100644 --- a/src/core/labeling/qgslabelingenginesettings.h +++ b/src/core/labeling/qgslabelingenginesettings.h @@ -40,6 +40,7 @@ class CORE_EXPORT QgsLabelingEngineSettings DrawLabelRectOnly = 1 << 4, //!< Whether to only draw the label rect and not the actual label text (used for unit tests) DrawCandidates = 1 << 5, //!< Whether to draw rectangles of generated candidates (good for debugging) DrawUnplacedLabels = 1 << 6, //!< Whether to render unplaced labels as an indicator/warning for users + CollectUnplacedLabels = 1 << 7, //!< Whether unplaced labels should be collected in the labeling results (regardless of whether they are being rendered). Since QGIS 3.20 }; Q_DECLARE_FLAGS( Flags, Flag ) diff --git a/src/core/labeling/qgsvectorlayerlabelprovider.cpp b/src/core/labeling/qgsvectorlayerlabelprovider.cpp index 892cf1395cd..6770a46e887 100644 --- a/src/core/labeling/qgsvectorlayerlabelprovider.cpp +++ b/src/core/labeling/qgsvectorlayerlabelprovider.cpp @@ -466,20 +466,23 @@ void QgsVectorLayerLabelProvider::drawLabel( QgsRenderContext &context, pal::Lab void QgsVectorLayerLabelProvider::drawUnplacedLabel( QgsRenderContext &context, LabelPosition *label ) const { - if ( !mSettings.drawLabels || mSettings.unplacedVisibility() == Qgis::UnplacedLabelVisibility::NeverShow ) - return; - QgsTextLabelFeature *lf = dynamic_cast( label->getFeaturePart()->feature() ); - QgsPalLayerSettings tmpLyr( mSettings ); - QgsTextFormat format = tmpLyr.format(); - format.setColor( mEngine->engineSettings().unplacedLabelColor() ); - tmpLyr.setFormat( format ); - drawLabelPrivate( label, context, tmpLyr, QgsTextRenderer::Text ); + QgsTextFormat format = mSettings.format(); + if ( mSettings.drawLabels + && mSettings.unplacedVisibility() != Qgis::UnplacedLabelVisibility::NeverShow + && mEngine->engineSettings().flags() & QgsLabelingEngineSettings::DrawUnplacedLabels ) + { + QgsPalLayerSettings tmpLyr( mSettings ); + format = tmpLyr.format(); + format.setColor( mEngine->engineSettings().unplacedLabelColor() ); + tmpLyr.setFormat( format ); + drawLabelPrivate( label, context, tmpLyr, QgsTextRenderer::Text ); + } // add to the results QString labeltext = label->getFeaturePart()->feature()->labelText(); - mEngine->results()->mLabelSearchTree->insertLabel( label, label->getFeaturePart()->featureId(), mLayerId, labeltext, tmpLyr.format().font(), false, lf->hasFixedPosition(), mProviderId, true ); + mEngine->results()->mLabelSearchTree->insertLabel( label, label->getFeaturePart()->featureId(), mLayerId, labeltext, format.font(), false, lf->hasFixedPosition(), mProviderId, true ); } void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition *label, QgsRenderContext &context, QgsPalLayerSettings &tmpLyr, QgsTextRenderer::TextPart drawType, double dpiRatio ) const diff --git a/tests/src/core/testqgslabelingengine.cpp b/tests/src/core/testqgslabelingengine.cpp index f82de4d1a62..dfbecfc3af1 100644 --- a/tests/src/core/testqgslabelingengine.cpp +++ b/tests/src/core/testqgslabelingengine.cpp @@ -1914,7 +1914,7 @@ void TestQgsLabelingEngine::labelingResults() settings.fieldName = QStringLiteral( "\"id\"" ); settings.isExpression = true; settings.placement = QgsPalLayerSettings::OverPoint; - + settings.priority = 10; std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) ); vl2->setRenderer( new QgsNullSymbolRenderer() ); @@ -1931,6 +1931,8 @@ void TestQgsLabelingEngine::labelingResults() QVERIFY( vl2->dataProvider()->addFeature( f ) ); vl2->updateExtents(); + std::unique_ptr< QgsVectorLayer> vl3( vl2->clone() ); + vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary! vl2->setLabelsEnabled( true ); @@ -1944,7 +1946,7 @@ void TestQgsLabelingEngine::labelingResults() mapSettings.setOutputSize( size ); mapSettings.setExtent( QgsRectangle( -4137976.6, 6557092.6, 1585557.4, 9656515.0 ) ); // mapSettings.setRotation( 60 ); - mapSettings.setLayers( QList() << vl2.get() ); + mapSettings.setLayers( QList() << vl2.get() << vl3.get() ); mapSettings.setOutputDpi( 96 ); QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings(); @@ -1965,11 +1967,11 @@ void TestQgsLabelingEngine::labelingResults() QCOMPARE( labels.count(), 3 ); std::sort( labels.begin(), labels.end(), []( const QgsLabelPosition & a, const QgsLabelPosition & b ) { - return a.labelText.compare( b.labelText ); + return a.labelText.compare( b.labelText ) < 0; } ); QCOMPARE( labels.at( 0 ).labelText, QStringLiteral( "1" ) ); - QCOMPARE( labels.at( 1 ).labelText, QStringLiteral( "8888" ) ); - QCOMPARE( labels.at( 2 ).labelText, QStringLiteral( "33333" ) ); + QCOMPARE( labels.at( 1 ).labelText, QStringLiteral( "33333" ) ); + QCOMPARE( labels.at( 2 ).labelText, QStringLiteral( "8888" ) ); labels = results->labelsAtPosition( QgsPointXY( -654732, 7003282 ) ); QCOMPARE( labels.count(), 1 ); @@ -2015,6 +2017,41 @@ void TestQgsLabelingEngine::labelingResults() labels = results->labelsAtPosition( QgsPointXY( -2463392, 6708478 ) ); QCOMPARE( labels.count(), 0 ); + // with unplaced labels -- all vl3 labels will be unplaced, because they are conflicting with those in vl2 + settings.priority = 1; + vl3->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); + vl3->setLabelsEnabled( true ); + engineSettings.setFlag( QgsLabelingEngineSettings::CollectUnplacedLabels, true ); + mapSettings.setLabelingEngineSettings( engineSettings ); + + QgsMapRendererSequentialJob jobB( mapSettings ); + jobB.start(); + jobB.waitForFinished(); + + results.reset( jobB.takeLabelingResults() ); + QVERIFY( results ); + + labels = results->allLabels(); + QCOMPARE( labels.count(), 6 ); + std::sort( labels.begin(), labels.end(), []( const QgsLabelPosition & a, const QgsLabelPosition & b ) + { + return a.isUnplaced == b.isUnplaced ? a.labelText.compare( b.labelText ) < 0 : a.isUnplaced < b.isUnplaced; + } ); + QCOMPARE( labels.at( 0 ).labelText, QStringLiteral( "1" ) ); + QVERIFY( !labels.at( 0 ).isUnplaced ); + QCOMPARE( labels.at( 1 ).labelText, QStringLiteral( "33333" ) ); + QVERIFY( !labels.at( 1 ).isUnplaced ); + QCOMPARE( labels.at( 2 ).labelText, QStringLiteral( "8888" ) ); + QVERIFY( !labels.at( 2 ).isUnplaced ); + QCOMPARE( labels.at( 3 ).labelText, QStringLiteral( "1" ) ); + QVERIFY( labels.at( 3 ).isUnplaced ); + QCOMPARE( labels.at( 4 ).labelText, QStringLiteral( "33333" ) ); + QVERIFY( labels.at( 4 ).isUnplaced ); + QCOMPARE( labels.at( 5 ).labelText, QStringLiteral( "8888" ) ); + QVERIFY( labels.at( 5 ).isUnplaced ); + + mapSettings.setLayers( {vl2.get() } ); + // with rotation mapSettings.setRotation( 60 ); QgsMapRendererSequentialJob job2( mapSettings );