mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
[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:
parent
620baa0d22
commit
a2b5008b30
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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 map’s edges.</string>
|
||||
<string>No labels will be placed within this distance of the map’s 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>
|
||||
|
@ -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"
|
||||
|
@ -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 |
Loading…
x
Reference in New Issue
Block a user