Skip to content

Commit

Permalink
Fix line dependent orientation rendering of curved labels
Browse files Browse the repository at this point in the history
Fixes #45051
  • Loading branch information
nyalldawson authored and github-actions[bot] committed Feb 2, 2022
1 parent 13d8468 commit e3909cb
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 11 deletions.
31 changes: 20 additions & 11 deletions src/core/pal/feature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1337,12 +1337,16 @@ std::size_t FeaturePart::createCurvedCandidatesAlongLine( std::vector< std::uniq
if ( hasAboveBelowLinePlacement && !qgsDoubleNear( offsetDistance, 0 ) )
{
// create offseted map shapes to be used for above and below line placements
mapShapeOffsetPositive = mapShape->clone();
mapShapeOffsetNegative = mapShape->clone();
if ( offsetDistance >= 0.0 )
if ( ( flags & QgsLabeling::LinePlacementFlag::MapOrientation ) || ( flags & QgsLabeling::LinePlacementFlag::AboveLine ) )
mapShapeOffsetPositive = mapShape->clone();
if ( ( flags & QgsLabeling::LinePlacementFlag::MapOrientation ) || ( flags & QgsLabeling::LinePlacementFlag::BelowLine ) )
mapShapeOffsetNegative = mapShape->clone();
if ( offsetDistance >= 0.0 || !( flags & QgsLabeling::LinePlacementFlag::MapOrientation ) )
{
mapShapeOffsetPositive->offsetCurveByDistance( offsetDistance );
mapShapeOffsetNegative->offsetCurveByDistance( offsetDistance * -1 );
if ( mapShapeOffsetPositive )
mapShapeOffsetPositive->offsetCurveByDistance( offsetDistance );
if ( mapShapeOffsetNegative )
mapShapeOffsetNegative->offsetCurveByDistance( offsetDistance * -1 );
}
else
{
Expand All @@ -1359,8 +1363,10 @@ std::size_t FeaturePart::createCurvedCandidatesAlongLine( std::vector< std::uniq
flags &= ~QgsLabeling::LinePlacementFlag::BelowLine;
flags |= QgsLabeling::LinePlacementFlag::AboveLine;
}
mapShapeOffsetPositive->offsetCurveByDistance( offsetDistance * -1 );
mapShapeOffsetNegative->offsetCurveByDistance( offsetDistance );
if ( mapShapeOffsetPositive )
mapShapeOffsetPositive->offsetCurveByDistance( offsetDistance * -1 );
if ( mapShapeOffsetNegative )
mapShapeOffsetNegative->offsetCurveByDistance( offsetDistance );
}
}

Expand Down Expand Up @@ -1422,10 +1428,13 @@ std::size_t FeaturePart::createCurvedCandidatesAlongLine( std::vector< std::uniq

if ( !labelPosition )
continue;
if ( ( offset != NoOffset ) && !labeledLineSegmentIsRightToLeft && !( flags & QgsLabeling::LinePlacementFlag::AboveLine ) )
continue;
if ( ( offset != NoOffset ) && labeledLineSegmentIsRightToLeft && !( flags & QgsLabeling::LinePlacementFlag::BelowLine ) )
continue;
if ( flags & QgsLabeling::LinePlacementFlag::MapOrientation )
{
if ( ( offset != NoOffset ) && !labeledLineSegmentIsRightToLeft && !( flags & QgsLabeling::LinePlacementFlag::AboveLine ) )
continue;
if ( ( offset != NoOffset ) && labeledLineSegmentIsRightToLeft && !( flags & QgsLabeling::LinePlacementFlag::BelowLine ) )
continue;
}

// evaluate cost
const double angleDiff = labelPosition->angleDifferential();
Expand Down
132 changes: 132 additions & 0 deletions tests/src/core/testqgslabelingengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class TestQgsLabelingEngine : public QObject
void testCurvedLabelCorrectLinePlacement();
void testCurvedLabelNegativeDistance();
void testCurvedLabelOnSmallLineNearCenter();
void testCurvedLabelLineOrientationAbove();
void testCurvedLabelLineOrientationBelow();
void testRepeatDistanceWithSmallLine();
void testParallelPlacementPreferAbove();
void testLabelBoundary();
Expand Down Expand Up @@ -1535,6 +1537,136 @@ void TestQgsLabelingEngine::testCurvedLabelOnSmallLineNearCenter()
QVERIFY( imageCheck( QStringLiteral( "label_curved_small_feature_centered" ), img, 20 ) );
}

void TestQgsLabelingEngine::testCurvedLabelLineOrientationAbove()
{
QgsPalLayerSettings settings;
setDefaultLabelParams( settings );

QgsTextFormat format = settings.format();
format.setSize( 20 );
format.setColor( QColor( 0, 0, 0 ) );
settings.setFormat( format );

settings.fieldName = QStringLiteral( "'XXXXXXXX'" );
settings.isExpression = true;
settings.placement = QgsPalLayerSettings::Curved;
settings.labelPerPart = false;
settings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine );

std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
vl2->setRenderer( new QgsSingleSymbolRenderer( QgsLineSymbol::createSimple( { {QStringLiteral( "color" ), QStringLiteral( "#000000" )}, {QStringLiteral( "outline_width" ), 0.6} } ) ) );

QgsFeature f;
f.setAttributes( QgsAttributes() << 1 );
f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190100 5000007, 190094 5000012, 190096 5000019, 190103 5000024, 190111 5000023, 190114 5000018)" ) ) );
QVERIFY( vl2->dataProvider()->addFeature( f ) );

vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
vl2->setLabelsEnabled( true );

// make a fake render context
const QSize size( 640, 480 );
QgsMapSettings mapSettings;
mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
mapSettings.setDestinationCrs( vl2->crs() );

mapSettings.setOutputSize( size );
mapSettings.setExtent( QgsRectangle( 190080, 5000000, 190130, 5000030 ) );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
mapSettings.setOutputDpi( 96 );

QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
//engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
mapSettings.setLabelingEngineSettings( engineSettings );

QgsMapRendererSequentialJob job( mapSettings );
job.start();
job.waitForFinished();

QImage img = job.renderedImage();
QVERIFY( imageCheck( QStringLiteral( "label_curved_line_orientation_above" ), img, 20 ) );

// reverse line and retry, label should be flipped to other side of line
f.setGeometry( QgsGeometry( qgsgeometry_cast< QgsLineString * >( f.geometry().constGet() )->reversed() ) );
vl2->dataProvider()->truncate();
QVERIFY( vl2->dataProvider()->addFeature( f ) );


QgsMapRendererSequentialJob job2( mapSettings );
job2.start();
job2.waitForFinished();

img = job2.renderedImage();
QVERIFY( imageCheck( QStringLiteral( "label_curved_line_orientation_reversed_above" ), img, 20 ) );
}

void TestQgsLabelingEngine::testCurvedLabelLineOrientationBelow()
{
QgsPalLayerSettings settings;
setDefaultLabelParams( settings );

QgsTextFormat format = settings.format();
format.setSize( 20 );
format.setColor( QColor( 0, 0, 0 ) );
settings.setFormat( format );

settings.fieldName = QStringLiteral( "'XXXXXXXX'" );
settings.isExpression = true;
settings.placement = QgsPalLayerSettings::Curved;
settings.labelPerPart = false;
settings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::BelowLine );

std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
vl2->setRenderer( new QgsSingleSymbolRenderer( QgsLineSymbol::createSimple( { {QStringLiteral( "color" ), QStringLiteral( "#000000" )}, {QStringLiteral( "outline_width" ), 0.6} } ) ) );

QgsFeature f;
f.setAttributes( QgsAttributes() << 1 );
f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190100 5000007, 190094 5000012, 190096 5000019, 190103 5000024, 190111 5000023, 190114 5000018)" ) ) );
QVERIFY( vl2->dataProvider()->addFeature( f ) );

vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary!
vl2->setLabelsEnabled( true );

// make a fake render context
const QSize size( 640, 480 );
QgsMapSettings mapSettings;
mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
mapSettings.setDestinationCrs( vl2->crs() );

mapSettings.setOutputSize( size );
mapSettings.setExtent( QgsRectangle( 190080, 5000000, 190130, 5000030 ) );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
mapSettings.setOutputDpi( 96 );

QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
//engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
mapSettings.setLabelingEngineSettings( engineSettings );

QgsMapRendererSequentialJob job( mapSettings );
job.start();
job.waitForFinished();

QImage img = job.renderedImage();
QVERIFY( imageCheck( QStringLiteral( "label_curved_line_orientation_below" ), img, 20 ) );

// reverse line and retry, label should be flipped to other side of line
f.setGeometry( QgsGeometry( qgsgeometry_cast< QgsLineString * >( f.geometry().constGet() )->reversed() ) );
vl2->dataProvider()->truncate();
QVERIFY( vl2->dataProvider()->addFeature( f ) );


QgsMapRendererSequentialJob job2( mapSettings );
job2.start();
job2.waitForFinished();

img = job2.renderedImage();
QVERIFY( imageCheck( QStringLiteral( "label_curved_line_orientation_reversed_below" ), img, 20 ) );
}

void TestQgsLabelingEngine::testRepeatDistanceWithSmallLine()
{
// test a small line relative to label size still gives sufficient candidates to ensure more centered placements
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit e3909cb

Please sign in to comment.