Skip to content
Permalink
Browse files

[FEATURE][API] Add method to QgsMapSettings for specifying label bloc…

…king regions

These represent areas of the map where NO labels should be placed
  • Loading branch information
nyalldawson committed Dec 11, 2018
1 parent ed25a3e commit d8eac4797e381a2b3a3101e0ac3fb3cf11824f2e
@@ -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
{
@@ -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

@@ -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:
@@ -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);
@@ -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
@@ -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
@@ -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;
@@ -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;
@@ -546,6 +589,10 @@ class CORE_EXPORT QgsMapSettings
#endif

void updateDerived();

private:

QList< QgsLabelBlockingRegion > mLabelBlockingRegions;
};

Q_DECLARE_OPERATORS_FOR_FLAGS( QgsMapSettings::Flags )
@@ -53,6 +53,7 @@ class TestQgsLabelingEngine : public QObject
void testRotateHidePartial();
void testParallelLabelSmallFeature();
void testLabelBoundary();
void testLabelBlockingRegion();

private:
QgsVectorLayer *vl = nullptr;
@@ -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"
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit d8eac47

Please sign in to comment.
You can’t perform that action at this time.