Skip to content

Commit

Permalink
[FEATURE][API] Add method to QgsMapSettings for specifying label bloc…
Browse files Browse the repository at this point in the history
…king regions

These represent areas of the map where NO labels should be placed
  • Loading branch information
nyalldawson committed Dec 15, 2018
1 parent ed25a3e commit d8eac47
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 0 deletions.
51 changes: 51 additions & 0 deletions python/core/auto_generated/qgsmapsettings.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,29 @@



class QgsLabelBlockingRegion
{
%Docstring

Label blocking region (in map coordinates and CRS).

.. versionadded:: 3.6
%End

%TypeHeaderCode
#include "qgsmapsettings.h"
%End
public:

explicit QgsLabelBlockingRegion( const QgsGeometry &geometry );
%Docstring
Constructor for a label blocking region
%End

QgsGeometry geometry;

};


class QgsMapSettings
{
Expand Down Expand Up @@ -541,6 +564,8 @@ The geometry is specified using the map's destinationCrs().

.. seealso:: :py:func:`setLabelBoundaryGeometry`

.. seealso:: :py:func:`labelBlockingRegions`

.. versionadded:: 3.6
%End

Expand All @@ -556,7 +581,31 @@ The geometry is specified using the map's destinationCrs().

.. seealso:: :py:func:`labelBoundaryGeometry`

.. seealso:: :py:func:`setLabelBlockingRegions`

.. versionadded:: 3.6
%End

void setLabelBlockingRegions( const QList< QgsLabelBlockingRegion > &regions );
%Docstring
Sets a list of ``regions`` to avoid placing labels within.

.. versionadded:: 3.6

.. seealso:: :py:func:`labelBlockingRegions`

.. seealso:: :py:func:`setLabelBoundaryGeometry`
%End

QList< QgsLabelBlockingRegion > labelBlockingRegions() const;
%Docstring
Returns the list of regions to avoid placing labels within.

.. versionadded:: 3.6

.. seealso:: :py:func:`setLabelBlockingRegions`

.. seealso:: :py:func:`labelBoundaryGeometry`
%End

protected:
Expand All @@ -578,7 +627,9 @@ The geometry is specified using the map's destinationCrs().




void updateDerived();

};

QFlags<QgsMapSettings::Flag> operator|(QgsMapSettings::Flag f1, QFlags<QgsMapSettings::Flag> f2);
Expand Down
8 changes: 8 additions & 0 deletions src/core/qgslabelingengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,14 @@ void QgsLabelingEngine::run( QgsRenderContext &context )

// get map label boundary geometry - if one hasn't been explicitly set, we use the whole of the map's visible polygon
QgsGeometry mapBoundaryGeom = !mMapSettings.labelBoundaryGeometry().isNull() ? mMapSettings.labelBoundaryGeometry() : QgsGeometry::fromQPolygonF( visiblePoly );

// label blocking regions work by "chopping away" those regions from the permissible labelling area
const QList< QgsLabelBlockingRegion > blockingRegions = mMapSettings.labelBlockingRegions();
for ( const QgsLabelBlockingRegion &region : blockingRegions )
{
mapBoundaryGeom = mapBoundaryGeom.difference( region.geometry );
}

if ( settings.flags() & QgsLabelingEngineSettings::DrawCandidates )
{
// draw map boundary
Expand Down
47 changes: 47 additions & 0 deletions src/core/qgsmapsettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,38 @@
#include "qgsscalecalculator.h"
#include "qgsexpressioncontext.h"
#include "qgsmaplayer.h"
#include "qgsgeometry.h"

class QPainter;

class QgsCoordinateTransform;
class QgsScaleCalculator;
class QgsMapRendererJob;

/**
* \class QgsLabelBlockingRegion
* \ingroup core
*
* Label blocking region (in map coordinates and CRS).
*
* \since QGIS 3.6
*/
class CORE_EXPORT QgsLabelBlockingRegion
{
public:

/**
* Constructor for a label blocking region
*/
explicit QgsLabelBlockingRegion( const QgsGeometry &geometry )
: geometry( geometry )
{}

//! Geometry of region to avoid placing labels within (in destination map coordinates and CRS)
QgsGeometry geometry;

};


/**
* \ingroup core
Expand Down Expand Up @@ -471,6 +496,7 @@ class CORE_EXPORT QgsMapSettings
* The geometry is specified using the map's destinationCrs().
*
* \see setLabelBoundaryGeometry()
* \see labelBlockingRegions()
* \since QGIS 3.6
*/
QgsGeometry labelBoundaryGeometry() const;
Expand All @@ -485,10 +511,27 @@ class CORE_EXPORT QgsMapSettings
* The geometry is specified using the map's destinationCrs().
*
* \see labelBoundaryGeometry()
* \see setLabelBlockingRegions()
* \since QGIS 3.6
*/
void setLabelBoundaryGeometry( const QgsGeometry &boundary );

/**
* Sets a list of \a regions to avoid placing labels within.
* \since QGIS 3.6
* \see labelBlockingRegions()
* \see setLabelBoundaryGeometry()
*/
void setLabelBlockingRegions( const QList< QgsLabelBlockingRegion > &regions ) { mLabelBlockingRegions = regions; }

/**
* Returns the list of regions to avoid placing labels within.
* \since QGIS 3.6
* \see setLabelBlockingRegions()
* \see labelBoundaryGeometry()
*/
QList< QgsLabelBlockingRegion > labelBlockingRegions() const { return mLabelBlockingRegions; }

protected:

double mDpi;
Expand Down Expand Up @@ -546,6 +589,10 @@ class CORE_EXPORT QgsMapSettings
#endif

void updateDerived();

private:

QList< QgsLabelBlockingRegion > mLabelBlockingRegions;
};

Q_DECLARE_OPERATORS_FOR_FLAGS( QgsMapSettings::Flags )
Expand Down
85 changes: 85 additions & 0 deletions tests/src/core/testqgslabelingengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class TestQgsLabelingEngine : public QObject
void testRotateHidePartial();
void testParallelLabelSmallFeature();
void testLabelBoundary();
void testLabelBlockingRegion();

private:
QgsVectorLayer *vl = nullptr;
Expand Down Expand Up @@ -874,5 +875,89 @@ void TestQgsLabelingEngine::testLabelBoundary()
QVERIFY( imageCheck( QStringLiteral( "rotated_label_boundary_geometry" ), img, 20 ) );
}

void TestQgsLabelingEngine::testLabelBlockingRegion()
{
// test that no labels are drawn inside blocking regions
QgsPalLayerSettings settings;
setDefaultLabelParams( settings );

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

settings.fieldName = QStringLiteral( "'X'" );
settings.isExpression = true;
settings.placement = QgsPalLayerSettings::OverPoint;

std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
vl2->setRenderer( new QgsNullSymbolRenderer() );

QgsFeature f( vl2->fields(), 1 );

for ( int x = 0; x < 15; x++ )
{
for ( int y = 0; y < 12; y++ )
{
f.setGeometry( qgis::make_unique< QgsPoint >( x, y ) );
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;
QgsCoordinateReferenceSystem tgtCrs;
tgtCrs.createFromString( QStringLiteral( "EPSG:4326" ) );
mapSettings.setDestinationCrs( tgtCrs );

mapSettings.setOutputSize( size );
mapSettings.setExtent( vl2->extent() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
mapSettings.setOutputDpi( 96 );

QList< QgsLabelBlockingRegion > regions;
regions << QgsLabelBlockingRegion( QgsGeometry::fromWkt( QStringLiteral( "Polygon((6 1, 12 1, 12 9, 6 9, 6 1),(8 4, 10 4, 10 7, 8 7, 8 4))" ) ) );
regions << QgsLabelBlockingRegion( QgsGeometry::fromWkt( QStringLiteral( "Polygon((0 0, 3 0, 3 3, 0 3, 0 0))" ) ) );
mapSettings.setLabelBlockingRegions( regions );

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_blocking_geometry" ), img, 20 ) );

// with rotation
mapSettings.setRotation( 45 );
QgsMapRendererSequentialJob job2( mapSettings );
job2.start();
job2.waitForFinished();

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

// blocking regions WITH label margin
mapSettings.setRotation( 0 );
mapSettings.setLabelBoundaryGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((1 1, 14 1, 14 9, 1 9, 1 1))" ) ) );

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

img = job3.renderedImage();
QVERIFY( imageCheck( QStringLiteral( "label_blocking_boundary_geometry" ), img, 20 ) );

}

QGSTEST_MAIN( TestQgsLabelingEngine )
#include "testqgslabelingengine.moc"
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
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 d8eac47

Please sign in to comment.