Skip to content

Commit

Permalink
[api] Add labeling flag to collect unplaced labels without rendering …
Browse files Browse the repository at this point in the history
…them
  • Loading branch information
nyalldawson committed Jun 9, 2021
1 parent 8e2fb49 commit c95fed5
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 16 deletions.
Expand Up @@ -29,6 +29,7 @@ Stores global configuration for labeling engine
DrawLabelRectOnly,
DrawCandidates,
DrawUnplacedLabels,
CollectUnplacedLabels,
};
typedef QFlags<QgsLabelingEngineSettings::Flag> Flags;

Expand Down
6 changes: 4 additions & 2 deletions src/core/labeling/qgslabelingengine.cpp
Expand Up @@ -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 ) );
Expand Down Expand Up @@ -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 ) )
{
Expand Down
1 change: 1 addition & 0 deletions src/core/labeling/qgslabelingenginesettings.h
Expand Up @@ -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 )

Expand Down
21 changes: 12 additions & 9 deletions src/core/labeling/qgsvectorlayerlabelprovider.cpp
Expand Up @@ -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<QgsTextLabelFeature *>( 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
Expand Down
47 changes: 42 additions & 5 deletions tests/src/core/testqgslabelingengine.cpp
Expand Up @@ -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() );
Expand All @@ -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 );

Expand All @@ -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<QgsMapLayer *>() << vl2.get() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() << vl3.get() );
mapSettings.setOutputDpi( 96 );

QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
Expand All @@ -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 );
Expand Down Expand Up @@ -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 );
Expand Down

0 comments on commit c95fed5

Please sign in to comment.