From 04c51db5515c6228301e9ef503016f4d513783a5 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 20 Jun 2019 10:56:38 +1000 Subject: [PATCH] Fix priority of parallel placements Ensure that above line placement is preferred when both above and below line placements are valid --- src/core/pal/feature.cpp | 29 +++++++--- tests/src/core/testqgslabelingengine.cpp | 53 ++++++++++++++++++ .../expected_parallel_prefer_above.png | Bin 0 -> 2175 bytes 3 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 tests/testdata/control_images/labelingengine/expected_parallel_prefer_above/expected_parallel_prefer_above.png 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 0000000000000000000000000000000000000000..0e6a7d1920943e01febf8e7d900cc05e6605d1a1 GIT binary patch literal 2175 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV0^&A1Qgk|*?TjP;wb| z85lSWJzX3_D(1Ysd$9Lb0E6pA`@mU=*0MgbB@dcjG+1dg7oBLjsG=>>-R# zMx{rCWtb<21Lvz4Y>t~O`~A?w@2yb7e#HZCj{mNZPp;|dXTL6U?BT)Xk9gO(HQelc zz2hEpjm+`<`pydG)q)8>eD^e77Ad!%*)8O?TPli=Y@IG4>;Xpm5ccS$D?S*~dy`j#sfBu#t04 z;y?Ge#$Lm4gDBIPXLFCed~MVF^5E3|cuC!R2WGL%i2Z4Nthm#BgBoL-|D(N%?S(7U z4+Jss_$%By5W~b{__6J}%7G1uk9cpZ9{8~6h;$Sa&l$CQ8|E-Dul`m3u`Rc9_uag& tYz!5a5I>HVC8I^`Xw@