Skip to content
Permalink
Browse files

[FEATURE][API] Add API to set a margin for labels for layout map items

This controls how close labels are permitted to the edges of the map
item. The labeling engine will then try other candidate positions
in order to avoid placing labels within this margin.
  • Loading branch information
nyalldawson committed Dec 15, 2018
1 parent 4252aab commit 35855b88e1686ce1f360d6e375ab224efd6a8aa8
@@ -187,6 +187,7 @@ Base class for graphical items within a :py:class:`QgsLayout`.
UndoMapGridAnnotationFontColor,
UndoMapGridLineSymbol,
UndoMapGridMarkerSymbol,
UndoMapLabelMargin,
UndoPictureRotation,
UndoPictureFillColor,
UndoPictureStrokeColor,
@@ -448,6 +448,30 @@ Returns the map item's first overview. This is a convenience function.
:return: pointer to first overview for map item

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

QgsLayoutMeasurement labelMargin() const;
%Docstring
Returns the margin from the map edges in which no labels may be placed.

If the margin is 0 then labels can be placed right up to the edge (and possibly overlapping the edge)
of the map.

.. seealso:: :py:func:`setLabelMargin`

.. versionadded:: 3.6
%End

void setLabelMargin( const QgsLayoutMeasurement &margin );
%Docstring
Sets the ``margin`` from the map edges in which no labels may be placed.

If the margin is 0 then labels can be placed right up to the edge (and possibly overlapping the edge)
of the map.

.. seealso:: :py:func:`labelMargin`

.. versionadded:: 3.6
%End

virtual QgsExpressionContext createExpressionContext() const;
@@ -236,6 +236,7 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
UndoMapGridAnnotationFontColor, //!< Map frame annotation color
UndoMapGridLineSymbol, //!< Grid line symbol
UndoMapGridMarkerSymbol, //!< Grid marker symbol
UndoMapLabelMargin, //!< Margin for labels from edge of map
UndoPictureRotation, //!< Picture rotation
UndoPictureFillColor, //!< Picture fill color
UndoPictureStrokeColor, //!< Picture stroke color
@@ -607,6 +607,8 @@ bool QgsLayoutItemMap::writePropertiesToElement( QDomElement &mapElem, QDomDocum
atlasElem.setAttribute( QStringLiteral( "margin" ), qgsDoubleToString( mAtlasMargin ) );
mapElem.appendChild( atlasElem );

mapElem.setAttribute( QStringLiteral( "labelMargin" ), mLabelMargin.encodeMeasurement() );

return true;
}

@@ -740,6 +742,8 @@ bool QgsLayoutItemMap::readPropertiesFromElement( const QDomElement &itemElem, c
mAtlasMargin = atlasElem.attribute( QStringLiteral( "margin" ), QStringLiteral( "0.1" ) ).toDouble();
}

mLabelMargin = QgsLayoutMeasurement::decodeMeasurement( itemElem.attribute( QStringLiteral( "labelMargin" ), QStringLiteral( "0" ) ) );

updateBoundingRect();

mUpdatesEnabled = true;
@@ -1127,6 +1131,17 @@ QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF
// override the default text render format inherited from the labeling engine settings using the layout's render context setting
jobMapSettings.setTextRenderFormat( mLayout->renderContext().textRenderFormat() );

if ( mLabelMargin.length() > 0 )
{
QPolygonF visiblePoly = jobMapSettings.visiblePolygon();
visiblePoly.append( visiblePoly.at( 0 ) ); //close polygon
const double layoutLabelMargin = mLayout->convertToLayoutUnits( mLabelMargin );
const double layoutLabelMarginInMapUnits = layoutLabelMargin / rect().width() * jobMapSettings.extent().width();
QgsGeometry mapBoundaryGeom = QgsGeometry::fromQPolygonF( visiblePoly );
mapBoundaryGeom = mapBoundaryGeom.buffer( -layoutLabelMarginInMapUnits, 0 );
jobMapSettings.setLabelBoundaryGeometry( mapBoundaryGeom );
}

return jobMapSettings;
}

@@ -1413,6 +1428,16 @@ void QgsLayoutItemMap::connectUpdateSlot()
connect( project->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsLayoutItemMap::mapThemeChanged );
}

QgsLayoutMeasurement QgsLayoutItemMap::labelMargin() const
{
return mLabelMargin;
}

void QgsLayoutItemMap::setLabelMargin( const QgsLayoutMeasurement &margin )
{
mLabelMargin = margin;
}

void QgsLayoutItemMap::updateToolTip()
{
setToolTip( displayName() );
@@ -398,6 +398,30 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
*/
QgsLayoutItemMapOverview *overview();

/**
* Returns the margin from the map edges in which no labels may be placed.
*
* If the margin is 0 then labels can be placed right up to the edge (and possibly overlapping the edge)
* of the map.
*
* \see setLabelMargin()
*
* \since QGIS 3.6
*/
QgsLayoutMeasurement labelMargin() const;

/**
* Sets the \a margin from the map edges in which no labels may be placed.
*
* If the margin is 0 then labels can be placed right up to the edge (and possibly overlapping the edge)
* of the map.
*
* \see labelMargin()
*
* \since QGIS 3.6
*/
void setLabelMargin( const QgsLayoutMeasurement &margin );

QgsExpressionContext createExpressionContext() const override;

/**
@@ -621,6 +645,8 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
std::unique_ptr< QgsMapRendererCustomPainterJob > mPainterJob;
bool mPainterCancelWait = false;

QgsLayoutMeasurement mLabelMargin{ 0 };

void init();

//! Resets the item tooltip to reflect current map id
@@ -28,8 +28,17 @@
QgsMapSettings,
QgsProject,
QgsMultiBandColorRenderer,
QgsCoordinateReferenceSystem
)
QgsCoordinateReferenceSystem,
QgsTextFormat,
QgsFontUtils,
QgsPalLayerSettings,
QgsNullSymbolRenderer,
QgsPoint,
QgsFeature,
QgsVectorLayerSimpleLabeling,
QgsLabelingEngineSettings,
QgsLayoutMeasurement,
QgsUnitTypes)

from qgis.testing import start_app, unittest
from utilities import unitTestDataPath
@@ -244,6 +253,79 @@ def testRasterization(self):

self.vector_layer.setBlendMode(QPainter.CompositionMode_SourceOver)

def testLabelMargin(self):
"""
Test rendering map item with a label margin set
"""
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.UsePartialCandidates, False)
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])
layout.addLayoutItem(map)

checker = QgsLayoutChecker('composermap_label_nomargin', layout)
checker.setControlPathPrefix("composer_map")
result, message = checker.testLayout()
self.report += checker.report()
self.assertTrue(result, message)

map.setLabelMargin(QgsLayoutMeasurement(15, QgsUnitTypes.LayoutMillimeters))
checker = QgsLayoutChecker('composermap_label_margin', layout)
checker.setControlPathPrefix("composer_map")
result, message = checker.testLayout()
self.report += checker.report()
self.assertTrue(result, message)

map.setLabelMargin(QgsLayoutMeasurement(3, QgsUnitTypes.LayoutCentimeters))
checker = QgsLayoutChecker('composermap_label_cm_margin', layout)
checker.setControlPathPrefix("composer_map")
result, message = checker.testLayout()
self.report += checker.report()
self.assertTrue(result, message)

map.setMapRotation(45)
map.zoomToExtent(vl.extent())
map.setScale(map.scale() * 1.2)
checker = QgsLayoutChecker('composermap_rotated_label_margin', layout)
checker.setControlPathPrefix("composer_map")
result, message = checker.testLayout()
self.report += checker.report()
self.assertTrue(result, message)


if __name__ == '__main__':
unittest.main()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit 35855b8

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