diff --git a/src/core/pal/feature.cpp b/src/core/pal/feature.cpp index ae00e1e88e5..66ce080184f 100644 --- a/src/core/pal/feature.cpp +++ b/src/core/pal/feature.cpp @@ -772,27 +772,29 @@ int FeaturePart::createCandidatesAlongLineNearStraightSegments( QListpermissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) ) { - lPos.append( new LabelPosition( i, candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, cost + placementCost, this, isRightToLeft ) ); // Line - placementCost += 0.001; + const double candidateCost = cost + ( reversed ? 0 : 0.001 ); + lPos.append( new LabelPosition( i, candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft ) ); // Line } } if ( aboveLine ) { if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle ) ) { - lPos.append( new LabelPosition( i, candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, cost + placementCost, this, isRightToLeft ) ); // Line - placementCost += 0.001; + const double candidateCost = cost + ( !reversed ? 0 : 0.001 ); // no extra cost for above line placements + lPos.append( new LabelPosition( i, candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft ) ); // Line } } if ( flags & FLAG_ON_LINE ) { if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle ) ) - lPos.append( new LabelPosition( i, candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle, cost + placementCost, this, isRightToLeft ) ); // Line + { + const double candidateCost = cost + 0.002; + lPos.append( new LabelPosition( i, candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft ) ); // Line + } } } else if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal ) @@ -925,17 +927,26 @@ int FeaturePart::createCandidatesAlongLineNearMidpoint( QList & if ( aboveLine ) { if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle ) ) - positions.append( new LabelPosition( i, candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line + { + const double candidateCost = cost + ( !reversed ? 0 : 0.001 ); // no extra cost for above line placements + positions.append( new LabelPosition( i, candidateStartX + std::cos( beta ) *distanceLineToLabel, candidateStartY + std::sin( beta ) *distanceLineToLabel, labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft ) ); // Line + } } if ( belowLine ) { if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle ) ) - positions.append( new LabelPosition( i, candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line + { + const double candidateCost = cost + ( !reversed ? 0.001 : 0 ); + positions.append( new LabelPosition( i, candidateStartX - std::cos( beta ) * ( distanceLineToLabel + labelHeight ), candidateStartY - std::sin( beta ) * ( distanceLineToLabel + labelHeight ), labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft ) ); // Line + } } if ( flags & FLAG_ON_LINE ) { if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle ) ) - positions.append( new LabelPosition( i, candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle, cost, this, isRightToLeft ) ); // Line + { + const double candidateCost = cost + 0.002; + positions.append( new LabelPosition( i, candidateStartX - labelHeight * std::cos( beta ) / 2, candidateStartY - labelHeight * std::sin( beta ) / 2, labelWidth, labelHeight, angle, candidateCost, this, isRightToLeft ) ); // Line + } } } else if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal ) diff --git a/tests/src/core/testqgslabelingengine.cpp b/tests/src/core/testqgslabelingengine.cpp index 6ebb40019b4..8d7f0fa962c 100644 --- a/tests/src/core/testqgslabelingengine.cpp +++ b/tests/src/core/testqgslabelingengine.cpp @@ -58,6 +58,7 @@ class TestQgsLabelingEngine : public QObject void testCurvedLabelsWithTinySegments(); void testCurvedLabelCorrectLinePlacement(); void testCurvedLabelNegativeDistance(); + void testParallelPlacementPreferAbove(); void testLabelBoundary(); void testLabelBlockingRegion(); void testLabelRotationWithReprojection(); @@ -1158,6 +1159,58 @@ void TestQgsLabelingEngine::testCurvedLabelNegativeDistance() QVERIFY( imageCheck( QStringLiteral( "label_curved_negative_distance" ), img, 20 ) ); } +void TestQgsLabelingEngine::testParallelPlacementPreferAbove() +{ + // given the choice of above or below placement, labels should always be placed above + 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::Line; + settings.placementFlags = QgsPalLayerSettings::AboveLine | QgsPalLayerSettings::BelowLine | QgsPalLayerSettings::MapOrientation; + settings.labelPerPart = false; + + std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) ); + vl2->setRenderer( new QgsNullSymbolRenderer() ); + + QgsFeature f; + f.setAttributes( QgsAttributes() << 1 ); + f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190000 5000010, 190200 5000000)" ) ) ); + QVERIFY( vl2->dataProvider()->addFeature( f ) ); + + vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) ); // TODO: this should not be necessary! + vl2->setLabelsEnabled( true ); + + // make a fake render context + QSize size( 640, 480 ); + QgsMapSettings mapSettings; + mapSettings.setDestinationCrs( vl2->crs() ); + + mapSettings.setOutputSize( size ); + mapSettings.setExtent( f.geometry().boundingBox() ); + mapSettings.setLayers( QList() << 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( "parallel_prefer_above" ), img, 20 ) ); +} + void TestQgsLabelingEngine::testLabelBoundary() { // test that no labels are drawn outside of the specified label boundary diff --git a/tests/testdata/control_images/labelingengine/expected_parallel_prefer_above/expected_parallel_prefer_above.png b/tests/testdata/control_images/labelingengine/expected_parallel_prefer_above/expected_parallel_prefer_above.png new file mode 100644 index 00000000000..0e6a7d19209 Binary files /dev/null and b/tests/testdata/control_images/labelingengine/expected_parallel_prefer_above/expected_parallel_prefer_above.png differ