[FEATURE][layouts] Allow layout items to "block" map labels

This feature allows other layout items (such as scalebars,
north arrows, inset maps, etc) to be marked as a blockers for
the map labels in a map item. This prevents any map labels from
being placed under those items - causing the labeling engine
to either try alternative placement for these labels (or
discarding them altogether)

This allows for more cartographically pleasing maps -- placing
labels under other items can make them hard to read, yet without
this new setting it's non-trivial to get QGIS to avoid placing
the labels in these obscured areas.

The blocking items are set through a map item's properties, under
the label settings panel. The setting is per-map item, so you can have
a scalebar block the labels for one map in your layout and not others
(if you so desire!)
This commit is contained in:
Nyall Dawson 2018-12-18 12:21:39 +10:00
parent 620baa0d22
commit a2b5008b30
9 changed files with 556 additions and 11 deletions

View File

@ -525,6 +525,48 @@ will be calculated. This can be expensive to calculate, so if they are not requi
%Docstring
Returns a list of the layers which will be rendered within this map item, considering
any locked layers, linked map theme, and data defined settings.
%End
void addLabelBlockingItem( QgsLayoutItem *item );
%Docstring
Sets the specified layout ``item`` as a "label blocking item" for this map.
Items which are marked as label blocking items prevent any map labels from being placed
in the area of the map item covered by the ``item``.
.. seealso:: :py:func:`removeLabelBlockingItem`
.. seealso:: :py:func:`isLabelBlockingItem`
.. versionadded:: 3.6
%End
void removeLabelBlockingItem( QgsLayoutItem *item );
%Docstring
Removes the specified layout ``item`` from the map's "label blocking items".
Items which are marked as label blocking items prevent any map labels from being placed
in the area of the map item covered by the item.
.. seealso:: :py:func:`addLabelBlockingItem`
.. seealso:: :py:func:`isLabelBlockingItem`
.. versionadded:: 3.6
%End
bool isLabelBlockingItem( QgsLayoutItem *item ) const;
%Docstring
Returns true if the specified ``item`` is a "label blocking item".
Items which are marked as label blocking items prevent any map labels from being placed
in the area of the map item covered by the item.
.. seealso:: :py:func:`addLabelBlockingItem`
.. seealso:: :py:func:`removeLabelBlockingItem`
.. versionadded:: 3.6
%End
protected:

View File

@ -1742,6 +1742,16 @@ void QgsLayoutMapLabelingWidget::updateGuiElements()
whileBlocking( mLabelBoundaryUnitsCombo )->setUnit( mMapItem->labelMargin().units() );
whileBlocking( mShowPartialLabelsCheckBox )->setChecked( mMapItem->mapFlags() & QgsLayoutItemMap::ShowPartialLabels );
if ( mBlockingItemsListView->model() )
{
QAbstractItemModel *oldModel = mBlockingItemsListView->model();
mBlockingItemsListView->setModel( nullptr );
oldModel->deleteLater();
}
QgsLayoutMapItemBlocksLabelsModel *model = new QgsLayoutMapItemBlocksLabelsModel( mMapItem, mMapItem->layout()->itemsModel(), mBlockingItemsListView );
mBlockingItemsListView->setModel( model );
updateDataDefinedButton( mLabelMarginDDBtn );
}
@ -1782,3 +1792,108 @@ void QgsLayoutMapLabelingWidget::showPartialsToggled( bool checked )
mMapItem->layout()->undoStack()->endCommand();
mMapItem->invalidateCache();
}
QgsLayoutMapItemBlocksLabelsModel::QgsLayoutMapItemBlocksLabelsModel( QgsLayoutItemMap *map, QgsLayoutModel *layoutModel, QObject *parent )
: QSortFilterProxyModel( parent )
, mLayoutModel( layoutModel )
, mMapItem( map )
{
setSourceModel( layoutModel );
}
int QgsLayoutMapItemBlocksLabelsModel::columnCount( const QModelIndex & ) const
{
return 1;
}
QVariant QgsLayoutMapItemBlocksLabelsModel::data( const QModelIndex &i, int role ) const
{
if ( !i.isValid() )
return QVariant();
if ( i.column() != 0 )
return QVariant();
QModelIndex sourceIndex = mapToSource( index( i.row(), QgsLayoutModel::ItemId, i.parent() ) );
QgsLayoutItem *item = mLayoutModel->itemFromIndex( mapToSource( i ) );
if ( !item )
{
return QVariant();
}
switch ( role )
{
case Qt::CheckStateRole:
switch ( i.column() )
{
case 0:
return mMapItem ? ( mMapItem->isLabelBlockingItem( item ) ? Qt::Checked : Qt::Unchecked ) : Qt::Unchecked;
default:
return QVariant();
}
default:
return mLayoutModel->data( sourceIndex, role );
}
}
bool QgsLayoutMapItemBlocksLabelsModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
Q_UNUSED( role );
if ( !index.isValid() )
return false;
QgsLayoutItem *item = mLayoutModel->itemFromIndex( mapToSource( index ) );
if ( !item || !mMapItem )
{
return false;
}
mMapItem->layout()->undoStack()->beginCommand( mMapItem, tr( "Change Label Blocking Items" ) );
if ( value.toBool() )
{
mMapItem->addLabelBlockingItem( item );
}
else
{
mMapItem->removeLabelBlockingItem( item );
}
emit dataChanged( index, index, QVector<int>() << role );
mMapItem->layout()->undoStack()->endCommand();
mMapItem->invalidateCache();
return true;
}
Qt::ItemFlags QgsLayoutMapItemBlocksLabelsModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags( index );
if ( ! index.isValid() )
{
return flags ;
}
switch ( index.column() )
{
case 0:
return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
default:
return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
}
bool QgsLayoutMapItemBlocksLabelsModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
{
QgsLayoutItem *item = mLayoutModel->itemFromIndex( mLayoutModel->index( source_row, 0, source_parent ) );
if ( !item || item == mMapItem )
{
return false;
}
return true;
}

View File

@ -175,6 +175,29 @@ class QgsLayoutMapWidget: public QgsLayoutItemBaseWidget, private Ui::QgsLayoutM
};
class QgsLayoutMapItemBlocksLabelsModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit QgsLayoutMapItemBlocksLabelsModel( QgsLayoutItemMap *map, QgsLayoutModel *layoutModel, QObject *parent = nullptr );
int columnCount( const QModelIndex &parent = QModelIndex() ) const override;
QVariant data( const QModelIndex &index, int role ) const override;
bool setData( const QModelIndex &index, const QVariant &value, int role ) override;
Qt::ItemFlags flags( const QModelIndex &index ) const override;
protected:
bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const override;
private:
QgsLayoutModel *mLayoutModel = nullptr;
QPointer< QgsLayoutItemMap > mMapItem;
};
/**
* \ingroup app
* Allows configuration of layout map labeling settings.

View File

@ -610,6 +610,18 @@ bool QgsLayoutItemMap::writePropertiesToElement( QDomElement &mapElem, QDomDocum
mapElem.setAttribute( QStringLiteral( "labelMargin" ), mLabelMargin.encodeMeasurement() );
mapElem.setAttribute( QStringLiteral( "mapFlags" ), static_cast< int>( mMapFlags ) );
QDomElement labelBlockingItemsElem = doc.createElement( QStringLiteral( "labelBlockingItems" ) );
for ( const auto &item : qgis::as_const( mBlockingLabelItems ) )
{
if ( !item )
continue;
QDomElement blockingItemElem = doc.createElement( QStringLiteral( "item" ) );
blockingItemElem.setAttribute( QStringLiteral( "uuid" ), item->uuid() );
labelBlockingItemsElem.appendChild( blockingItemElem );
}
mapElem.appendChild( labelBlockingItemsElem );
return true;
}
@ -747,6 +759,22 @@ bool QgsLayoutItemMap::readPropertiesFromElement( const QDomElement &itemElem, c
mMapFlags = static_cast< MapItemFlags>( itemElem.attribute( QStringLiteral( "mapFlags" ), nullptr ).toInt() );
// label blocking items
mBlockingLabelItems.clear();
mBlockingLabelItemUuids.clear();
QDomNodeList labelBlockingNodeList = itemElem.elementsByTagName( QStringLiteral( "labelBlockingItems" ) );
if ( !labelBlockingNodeList.isEmpty() )
{
QDomElement blockingItems = labelBlockingNodeList.at( 0 ).toElement();
QDomNodeList labelBlockingNodeList = blockingItems.childNodes();
for ( int i = 0; i < labelBlockingNodeList.size(); ++i )
{
const QDomElement &itemBlockingElement = labelBlockingNodeList.at( i ).toElement();
const QString itemUuid = itemBlockingElement.attribute( QStringLiteral( "uuid" ) );
mBlockingLabelItemUuids << itemUuid;
}
}
updateBoundingRect();
mUpdatesEnabled = true;
@ -1160,6 +1188,11 @@ QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF
jobMapSettings.setLabelBoundaryGeometry( mapBoundaryGeom );
}
if ( !mBlockingLabelItems.isEmpty() )
{
jobMapSettings.setLabelBlockingRegions( createLabelBlockingRegions( jobMapSettings ) );
}
return jobMapSettings;
}
@ -1167,6 +1200,16 @@ void QgsLayoutItemMap::finalizeRestoreFromXml()
{
assignFreeId();
mBlockingLabelItems.clear();
for ( const QString &uuid : qgis::as_const( mBlockingLabelItemUuids ) )
{
QgsLayoutItem *item = mLayout->itemByUuid( uuid, true );
if ( item )
{
addLabelBlockingItem( item );
}
}
mOverviewStack->finalizeRestoreFromXml();
mGridStack->finalizeRestoreFromXml();
}
@ -1254,6 +1297,26 @@ QPolygonF QgsLayoutItemMap::transformedMapPolygon() const
return poly;
}
void QgsLayoutItemMap::addLabelBlockingItem( QgsLayoutItem *item )
{
if ( !mBlockingLabelItems.contains( item ) )
mBlockingLabelItems.append( item );
connect( item, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItemMap::invalidateCache, Qt::UniqueConnection );
}
void QgsLayoutItemMap::removeLabelBlockingItem( QgsLayoutItem *item )
{
mBlockingLabelItems.removeAll( item );
if ( item )
disconnect( item, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItemMap::invalidateCache );
}
bool QgsLayoutItemMap::isLabelBlockingItem( QgsLayoutItem *item ) const
{
return mBlockingLabelItems.contains( item );
}
QPointF QgsLayoutItemMap::mapToItemCoords( QPointF mapCoords ) const
{
QPolygonF mapPoly = transformedMapPolygon();
@ -1450,6 +1513,43 @@ void QgsLayoutItemMap::connectUpdateSlot()
connect( project->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsLayoutItemMap::mapThemeChanged );
}
QTransform QgsLayoutItemMap::layoutToMapCoordsTransform() const
{
QPolygonF thisExtent = visibleExtentPolygon();
QTransform mapTransform;
QPolygonF thisRectPoly = QPolygonF( QRectF( 0, 0, rect().width(), rect().height() ) );
//workaround QT Bug #21329
thisRectPoly.pop_back();
thisExtent.pop_back();
QPolygonF thisItemPolyInLayout = mapToScene( thisRectPoly );
//create transform from layout coordinates to map coordinates
QTransform::quadToQuad( thisItemPolyInLayout, thisExtent, mapTransform );
return mapTransform;
}
QList<QgsLabelBlockingRegion> QgsLayoutItemMap::createLabelBlockingRegions( const QgsMapSettings &mapSettings ) const
{
const QTransform mapTransform = layoutToMapCoordsTransform();
QList< QgsLabelBlockingRegion > blockers;
blockers.reserve( mBlockingLabelItems.count() );
for ( const auto &item : qgis::as_const( mBlockingLabelItems ) )
{
if ( !item || !item->isVisible() ) // invisible items don't block labels!
continue;
QPolygonF itemRectInMapCoordinates = mapTransform.map( item->mapToScene( item->rect() ) );
itemRectInMapCoordinates.append( itemRectInMapCoordinates.at( 0 ) ); //close polygon
QgsGeometry blockingRegion = QgsGeometry::fromQPolygonF( itemRectInMapCoordinates );
const double labelMargin = mLayout->convertToLayoutUnits( mEvaluatedLabelMargin );
const double labelMarginInMapUnits = labelMargin / rect().width() * mapSettings.extent().width();
blockingRegion = blockingRegion.buffer( labelMarginInMapUnits, 0, QgsGeometry::CapSquare, QgsGeometry::JoinStyleMiter, 2 );
blockers << QgsLabelBlockingRegion( blockingRegion );
}
return blockers;
}
QgsLayoutMeasurement QgsLayoutItemMap::labelMargin() const
{
return mLabelMargin;

View File

@ -472,6 +472,45 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
*/
QList<QgsMapLayer *> layersToRender( const QgsExpressionContext *context = nullptr ) const;
/**
* Sets the specified layout \a item as a "label blocking item" for this map.
*
* Items which are marked as label blocking items prevent any map labels from being placed
* in the area of the map item covered by the \a item.
*
* \see removeLabelBlockingItem()
* \see isLabelBlockingItem()
*
* \since QGIS 3.6
*/
void addLabelBlockingItem( QgsLayoutItem *item );
/**
* Removes the specified layout \a item from the map's "label blocking items".
*
* Items which are marked as label blocking items prevent any map labels from being placed
* in the area of the map item covered by the item.
*
* \see addLabelBlockingItem()
* \see isLabelBlockingItem()
*
* \since QGIS 3.6
*/
void removeLabelBlockingItem( QgsLayoutItem *item );
/**
* Returns true if the specified \a item is a "label blocking item".
*
* Items which are marked as label blocking items prevent any map labels from being placed
* in the area of the map item covered by the item.
*
* \see addLabelBlockingItem()
* \see removeLabelBlockingItem()
*
* \since QGIS 3.6
*/
bool isLabelBlockingItem( QgsLayoutItem *item ) const;
protected:
void draw( QgsLayoutItemRenderContext &context ) override;
@ -654,6 +693,17 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
//! Returns first map overview or creates an empty one if none
const QgsLayoutItemMapOverview *constFirstMapOverview() const;
/**
* Creates a transform from layout coordinates to map coordinates.
*/
QTransform layoutToMapCoordsTransform() const;
/**
* Creates a list of label blocking regions for the map, which correspond to the
* map areas covered by other layout items marked as label blockers for this map.
*/
QList< QgsLabelBlockingRegion > createLabelBlockingRegions( const QgsMapSettings &mapSettings ) const;
//! Current bounding rectangle. This is used to check if notification to the graphics scene is necessary
QRectF mCurrentRectangle;
//! True if annotation items, rubber band, etc. from the main canvas should be displayed
@ -673,6 +723,9 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
QgsLayoutMeasurement mLabelMargin{ 0 };
QgsLayoutMeasurement mEvaluatedLabelMargin{ 0 };
QStringList mBlockingLabelItemUuids;
QList< QPointer< QgsLayoutItem > > mBlockingLabelItems;
void init();
//! Resets the item tooltip to reflect current map id

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>174</width>
<height>424</height>
<width>211</width>
<height>408</height>
</rect>
</property>
<property name="sizePolicy">
@ -41,7 +41,7 @@
<item row="1" column="0" colspan="4">
<widget class="QLabel" name="label_10">
<property name="text">
<string>No labels will be placed within this distance from the maps edges.</string>
<string>No labels will be placed within this distance of the maps edges, or from any label-blocking items which are checked below.</string>
</property>
<property name="scaledContents">
<bool>false</bool>
@ -100,6 +100,28 @@
</layout>
</widget>
</item>
<item>
<widget class="QgsCollapsibleGroupBoxBasic" name="groupBox">
<property name="title">
<string>Label Blocking Items</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_9">
<property name="text">
<string>Avoid placing labels under these items</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="mBlockingItemsListView"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
@ -117,6 +139,12 @@
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>QgsCollapsibleGroupBoxBasic</class>
<extends>QGroupBox</extends>
<header>qgscollapsiblegroupbox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsDoubleSpinBox</class>
<extends>QDoubleSpinBox</extends>
@ -127,12 +155,6 @@
<extends>QToolButton</extends>
<header>qgspropertyoverridebutton.h</header>
</customwidget>
<customwidget>
<class>QgsCollapsibleGroupBoxBasic</class>
<extends>QGroupBox</extends>
<header location="global">qgscollapsiblegroupbox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsLayoutUnitsComboBox</class>
<extends>QComboBox</extends>

View File

@ -58,6 +58,8 @@ class TestQgsLayoutMap : public QObject
void mapRotation();
void mapItemRotation();
void expressionContext();
void layoutToMapCoordsTransform();
void labelBlockingRegions();
private:
QgsRasterLayer *mRasterLayer = nullptr;
@ -632,10 +634,118 @@ void TestQgsLayoutMap::expressionContext()
r = e6.evaluate( &c );
QCOMPARE( r.toBool(), true );
QgsExpression e7( QStringLiteral( "is_layer_visible( 'aaaaaa' )" ).arg( layer->id() ) );
QgsExpression e7( QStringLiteral( "is_layer_visible( 'aaaaaa' )" ) );
r = e7.evaluate( &c );
QCOMPARE( r.toBool(), false );
}
void TestQgsLayoutMap::layoutToMapCoordsTransform()
{
QgsRectangle extent( 2000, 2800, 2500, 2900 );
QgsLayout l( QgsProject::instance() );
QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
map->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
map->attemptSetSceneRect( QRectF( 30, 60, 200, 100 ) );
map->setExtent( extent );
l.addLayoutItem( map );
QTransform t = map->layoutToMapCoordsTransform();
QGSCOMPARENEAR( t.map( QPointF( 30, 60 ) ).x(), 2000, 1 );
QGSCOMPARENEAR( t.map( QPointF( 30, 60 ) ).y(), 2900, 1 );
QGSCOMPARENEAR( t.map( QPointF( 230, 60 ) ).x(), 2500, 1 );
QGSCOMPARENEAR( t.map( QPointF( 230, 60 ) ).y(), 2900, 1 );
QGSCOMPARENEAR( t.map( QPointF( 30, 100 ) ).x(), 2000, 1 );
QGSCOMPARENEAR( t.map( QPointF( 30, 100 ) ).y(), 2800, 1 );
QGSCOMPARENEAR( t.map( QPointF( 230, 100 ) ).x(), 2500, 1 );
QGSCOMPARENEAR( t.map( QPointF( 230, 100 ) ).y(), 2800, 1 );
// with map rotation
map->setMapRotation( 75 );
t = map->layoutToMapCoordsTransform();
QGSCOMPARENEAR( t.map( QPointF( 30, 60 ) ).x(), 2136.998947, 1 );
QGSCOMPARENEAR( t.map( QPointF( 30, 60 ) ).y(), 2621.459496, 1 );
QGSCOMPARENEAR( t.map( QPointF( 230, 60 ) ).x(), 2266.408470, 1 );
QGSCOMPARENEAR( t.map( QPointF( 230, 60 ) ).y(), 3104.422409, 1 );
QGSCOMPARENEAR( t.map( QPointF( 30, 100 ) ).x(), 2233.591530, 1 );
QGSCOMPARENEAR( t.map( QPointF( 30, 100 ) ).y(), 2595.577591, 1 );
QGSCOMPARENEAR( t.map( QPointF( 230, 100 ) ).x(), 2363.001053, 1 );
QGSCOMPARENEAR( t.map( QPointF( 230, 100 ) ).y(), 3078.540504, 1 );
// with item rotation
map->setItemRotation( -30 );
t = map->layoutToMapCoordsTransform();
QGSCOMPARENEAR( t.map( QPointF( 30, 60 ) ).x(), 2037.867966, 1 );
QGSCOMPARENEAR( t.map( QPointF( 30, 60 ) ).y(), 2708.578644, 1 );
QGSCOMPARENEAR( t.map( QPointF( 230, 60 ) ).x(), 2391.421356, 1 );
QGSCOMPARENEAR( t.map( QPointF( 230, 60 ) ).y(), 3062.132034, 1 );
QGSCOMPARENEAR( t.map( QPointF( 30, 100 ) ).x(), 2108.578644, 1 );
QGSCOMPARENEAR( t.map( QPointF( 30, 100 ) ).y(), 2637.867966, 1 );
QGSCOMPARENEAR( t.map( QPointF( 230, 100 ) ).x(), 2462.132034, 1 );
QGSCOMPARENEAR( t.map( QPointF( 230, 100 ) ).y(), 2991.421356, 1 );
}
void TestQgsLayoutMap::labelBlockingRegions()
{
QgsRectangle extent( 2000, 2800, 2500, 2900 );
QgsLayout l( QgsProject::instance() );
QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
map->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
map->attemptSetSceneRect( QRectF( 30, 60, 200, 100 ) );
map->setExtent( extent );
l.addLayoutItem( map );
QgsLayoutItemMap *map2 = new QgsLayoutItemMap( &l );
map2->attemptSetSceneRect( QRectF( 10, 30, 100, 200 ) );
l.addLayoutItem( map2 );
QgsLayoutItemMap *map3 = new QgsLayoutItemMap( &l );
map3->attemptSetSceneRect( QRectF( 210, 70, 100, 200 ) );
l.addLayoutItem( map3 );
QgsLayoutItemMap *map4 = new QgsLayoutItemMap( &l );
map4->attemptSetSceneRect( QRectF( 210, 70, 100, 200 ) );
l.addLayoutItem( map4 );
QVERIFY( !map->isLabelBlockingItem( map2 ) );
QVERIFY( !map->isLabelBlockingItem( map3 ) );
QVERIFY( !map->isLabelBlockingItem( map4 ) );
map->addLabelBlockingItem( map2 );
QVERIFY( map->isLabelBlockingItem( map2 ) );
map->addLabelBlockingItem( map3 );
QVERIFY( map->isLabelBlockingItem( map3 ) );
map->addLabelBlockingItem( map4 );
QVERIFY( map->isLabelBlockingItem( map4 ) );
map->removeLabelBlockingItem( map4 );
QVERIFY( !map->isLabelBlockingItem( map4 ) );
QList<QgsLabelBlockingRegion> regions = map->createLabelBlockingRegions( map->mapSettings( map->extent(), map->rect().size(), 300, false ) );
QCOMPARE( regions.count(), 2 );
QCOMPARE( regions.at( 0 ).geometry.asWkt( 0 ), QStringLiteral( "Polygon ((1950 2975, 2200 2975, 2200 2475, 1950 2475, 1950 2975))" ) );
QCOMPARE( regions.at( 1 ).geometry.asWkt( 0 ), QStringLiteral( "Polygon ((2450 2875, 2700 2875, 2700 2375, 2450 2375, 2450 2875))" ) );
map->setLabelMargin( QgsLayoutMeasurement( 2, QgsUnitTypes::LayoutCentimeters ) );
regions = map->createLabelBlockingRegions( map->mapSettings( map->extent(), map->rect().size(), 300, false ) );
QCOMPARE( regions.count(), 2 );
QCOMPARE( regions.at( 0 ).geometry.asWkt( 0 ), QStringLiteral( "Polygon ((1900 3025, 2250 3025, 2250 2425, 1900 2425, 1900 3025))" ) );
QCOMPARE( regions.at( 1 ).geometry.asWkt( 0 ), QStringLiteral( "Polygon ((2400 2925, 2750 2925, 2750 2325, 2400 2325, 2400 2925))" ) );
map2->setRotation( 45 );
regions = map->createLabelBlockingRegions( map->mapSettings( map->extent(), map->rect().size(), 300, false ) );
QCOMPARE( regions.count(), 2 );
QCOMPARE( regions.at( 0 ).geometry.asWkt( 0 ), QStringLiteral( "Polygon ((1950 3046, 2197 2798, 1773 2374, 1526 2621, 1950 3046))" ) );
QCOMPARE( regions.at( 1 ).geometry.asWkt( 0 ), QStringLiteral( "Polygon ((2400 2925, 2750 2925, 2750 2325, 2400 2325, 2400 2925))" ) );
regions = map2->createLabelBlockingRegions( map2->mapSettings( map2->extent(), map2->rect().size(), 300, false ) );
QVERIFY( regions.isEmpty() );
// invisible items don't block
map2->setVisibility( false );
regions = map->createLabelBlockingRegions( map->mapSettings( map->extent(), map->rect().size(), 300, false ) );
QCOMPARE( regions.count(), 1 );
QCOMPARE( regions.at( 0 ).geometry.asWkt( 0 ), QStringLiteral( "Polygon ((2400 2925, 2750 2925, 2750 2325, 2400 2325, 2400 2925))" ) );
}
QGSTEST_MAIN( TestQgsLayoutMap )
#include "testqgslayoutmap.moc"

View File

@ -40,7 +40,9 @@ from qgis.core import (QgsLayoutItemMap,
QgsLayoutMeasurement,
QgsUnitTypes,
QgsLayoutObject,
QgsProperty)
QgsProperty,
QgsReadWriteContext,
QgsPrintLayout)
from qgis.testing import start_app, unittest
from utilities import unitTestDataPath
@ -402,6 +404,84 @@ class TestQgsLayoutMap(unittest.TestCase, LayoutItemTestCase):
self.report += checker.report()
self.assertTrue(result, message)
def testBlockingItems(self):
"""
Test rendering map item with blocking items
"""
format = QgsTextFormat()
format.setFont(QgsFontUtils.getStandardTestFont("Bold"))
format.setSize(20)
format.setNamedStyle("Bold")
format.setColor(QColor(0, 0, 0))
settings = QgsPalLayerSettings()
settings.setFormat(format)
settings.fieldName = "'X'"
settings.isExpression = True
settings.placement = QgsPalLayerSettings.OverPoint
vl = QgsVectorLayer("Point?crs=epsg:4326&field=id:integer", "vl", "memory")
vl.setRenderer(QgsNullSymbolRenderer())
f = QgsFeature(vl.fields(), 1)
for x in range(15):
for y in range(15):
f.setGeometry(QgsPoint(x, y))
vl.dataProvider().addFeature(f)
vl.setLabeling(QgsVectorLayerSimpleLabeling(settings))
vl.setLabelsEnabled(True)
p = QgsProject()
engine_settings = QgsLabelingEngineSettings()
engine_settings.setFlag(QgsLabelingEngineSettings.DrawLabelRectOnly, True)
p.setLabelingEngineSettings(engine_settings)
p.addMapLayer(vl)
layout = QgsLayout(p)
layout.initializeDefaults()
p.setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
map = QgsLayoutItemMap(layout)
map.attemptSetSceneRect(QRectF(10, 10, 180, 180))
map.setFrameEnabled(True)
map.zoomToExtent(vl.extent())
map.setLayers([vl])
map.setId('map')
layout.addLayoutItem(map)
map2 = QgsLayoutItemMap(layout)
map2.attemptSetSceneRect(QRectF(0, 5, 50, 80))
map2.setFrameEnabled(True)
map2.setBackgroundEnabled(False)
map2.setId('map2')
layout.addLayoutItem(map2)
map3 = QgsLayoutItemMap(layout)
map3.attemptSetSceneRect(QRectF(150, 160, 50, 50))
map3.setFrameEnabled(True)
map3.setBackgroundEnabled(False)
map3.setId('map3')
layout.addLayoutItem(map3)
map.addLabelBlockingItem(map2)
map.addLabelBlockingItem(map3)
map.setMapFlags(QgsLayoutItemMap.MapItemFlags())
checker = QgsLayoutChecker('composermap_label_blockers', layout)
checker.setControlPathPrefix("composer_map")
result, message = checker.testLayout()
self.report += checker.report()
self.assertTrue(result, message)
doc = QDomDocument("testdoc")
elem = layout.writeXml(doc, QgsReadWriteContext())
l2 = QgsLayout(p)
self.assertTrue(l2.readXml(elem, doc, QgsReadWriteContext()))
map_restore = [i for i in l2.items() if isinstance(i, QgsLayoutItemMap) and i.id() == 'map'][0]
map2_restore = [i for i in l2.items() if isinstance(i, QgsLayoutItemMap) and i.id() == 'map2'][0]
map3_restore = [i for i in l2.items() if isinstance(i, QgsLayoutItemMap) and i.id() == 'map3'][0]
self.assertTrue(map_restore.isLabelBlockingItem(map2_restore))
self.assertTrue(map_restore.isLabelBlockingItem(map3_restore))
if __name__ == '__main__':
unittest.main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB