Skip to content
Permalink
Browse files

[FEATURE][layouts] Add new setting to control whether map items

should show partial labels

Layout map items no longer respect the default project setting
for "show partial labels", and instead have their own, per map
setting for this option. (Under the map item properties,
labeling settings button).

The map item setting always defaults to off (unlike the canvas
setting, which defaults to true for a new project) as layouts
should always default to the settings which produce the highest
quality cartographic outputs.

In general I suspect that most users would always want to avoid
rendering partial labels in layouts, but this setting was
previously so deeply hidden that most are unaware of how to
change it. (And previous discussion about changing the canvas
setting to hide partial labels deemed this default undesirable
for the canvas, where showing even a small part of a label
on the map border can help identify what sits just on/off
the edges of the map)
  • Loading branch information
nyalldawson committed Dec 18, 2018
1 parent 524bc79 commit 089a2f1c1787a55da842a31846adc34818501f2d
@@ -31,6 +31,13 @@ Layout graphical items for displaying a map.
Auto
};

enum MapItemFlag
{
ShowPartialLabels,
};
typedef QFlags<QgsLayoutItemMap::MapItemFlag> MapItemFlags;


explicit QgsLayoutItemMap( QgsLayout *layout );
%Docstring
Constructor for QgsLayoutItemMap, with the specified parent ``layout``.
@@ -44,6 +51,24 @@ Constructor for QgsLayoutItemMap, with the specified parent ``layout``.
virtual QgsLayoutItem::Flags itemFlags() const;


QgsLayoutItemMap::MapItemFlags mapFlags() const;
%Docstring
Returns the map item's flags, which control how the map content is drawn.

.. seealso:: :py:func:`setMapFlags`

.. versionadded:: 3.6
%End

void setMapFlags( QgsLayoutItemMap::MapItemFlags flags );
%Docstring
Sets the map item's ``flags``, which control how the map content is drawn.

.. seealso:: :py:func:`mapFlags`

.. versionadded:: 3.6
%End

void assignFreeId();
%Docstring
Sets the map id() to a number not yet used in the layout. The existing id() is kept if it is not in use.
@@ -583,6 +608,9 @@ Updates the bounding rect of this item. Call this function before doing any chan

};

QFlags<QgsLayoutItemMap::MapItemFlag> operator|(QgsLayoutItemMap::MapItemFlag f1, QFlags<QgsLayoutItemMap::MapItemFlag> f2);


/************************************************************************
* This file has been generated automatically from *
* *
@@ -186,6 +186,8 @@ bool QgsLayoutMapWidget::setNewItem( QgsLayoutItem *item )

mMapItem = qobject_cast< QgsLayoutItemMap * >( item );
mItemPropertiesWidget->setItem( mMapItem );
if ( mLabelWidget )
mLabelWidget->setItem( mMapItem );

if ( mMapItem )
{
@@ -359,8 +361,8 @@ void QgsLayoutMapWidget::overviewSymbolChanged()

void QgsLayoutMapWidget::showLabelSettings()
{
QgsLayoutMapLabelingWidget *w = new QgsLayoutMapLabelingWidget( mMapItem );
openPanel( w );
mLabelWidget = new QgsLayoutMapLabelingWidget( mMapItem );
openPanel( mLabelWidget );
}

void QgsLayoutMapWidget::switchToMoveContentTool()
@@ -1703,14 +1705,43 @@ QgsLayoutMapLabelingWidget::QgsLayoutMapLabelingWidget( QgsLayoutItemMap *map )
mLabelBoundaryUnitsCombo->linkToWidget( mLabelBoundarySpinBox );
mLabelBoundaryUnitsCombo->setConverter( &mMapItem->layout()->renderContext().measurementConverter() );

mLabelBoundarySpinBox->setValue( mMapItem->labelMargin().length() );
mLabelBoundaryUnitsCombo->setUnit( mMapItem->labelMargin().units() );

connect( mLabelBoundaryUnitsCombo, &QgsLayoutUnitsComboBox::changed, this, &QgsLayoutMapLabelingWidget::labelMarginUnitsChanged );
connect( mLabelBoundarySpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutMapLabelingWidget::labelMarginChanged );
connect( mShowPartialLabelsCheckBox, &QCheckBox::toggled, this, &QgsLayoutMapLabelingWidget::showPartialsToggled );

registerDataDefinedButton( mLabelMarginDDBtn, QgsLayoutObject::MapLabelMargin );

setNewItem( map );
}

bool QgsLayoutMapLabelingWidget::setNewItem( QgsLayoutItem *item )
{
if ( item->type() != QgsLayoutItemRegistry::LayoutMap )
return false;

if ( mMapItem )
{
disconnect( mMapItem, &QgsLayoutObject::changed, this, &QgsLayoutMapLabelingWidget::updateGuiElements );
}

mMapItem = qobject_cast< QgsLayoutItemMap * >( item );

if ( mMapItem )
{
connect( mMapItem, &QgsLayoutObject::changed, this, &QgsLayoutMapLabelingWidget::updateGuiElements );
}

updateGuiElements();

return true;
}

void QgsLayoutMapLabelingWidget::updateGuiElements()
{
whileBlocking( mLabelBoundarySpinBox )->setValue( mMapItem->labelMargin().length() );
whileBlocking( mLabelBoundaryUnitsCombo )->setUnit( mMapItem->labelMargin().units() );
whileBlocking( mShowPartialLabelsCheckBox )->setChecked( mMapItem->mapFlags() & QgsLayoutItemMap::ShowPartialLabels );

updateDataDefinedButton( mLabelMarginDDBtn );
}

@@ -1735,3 +1766,19 @@ void QgsLayoutMapLabelingWidget::labelMarginUnitsChanged()
mMapItem->layout()->undoStack()->endCommand();
mMapItem->invalidateCache();
}

void QgsLayoutMapLabelingWidget::showPartialsToggled( bool checked )
{
if ( !mMapItem )
return;

mMapItem->layout()->undoStack()->beginCommand( mMapItem, tr( "Change Label Visibility" ) );
QgsLayoutItemMap::MapItemFlags flags = mMapItem->mapFlags();
if ( checked )
flags |= QgsLayoutItemMap::ShowPartialLabels;
else
flags &= ~QgsLayoutItemMap::ShowPartialLabels;
mMapItem->setMapFlags( flags );
mMapItem->layout()->undoStack()->endCommand();
mMapItem->invalidateCache();
}
@@ -26,6 +26,7 @@
class QgsMapLayer;
class QgsLayoutItemMap;
class QgsLayoutItemMapOverview;
class QgsLayoutMapLabelingWidget;

/**
* \ingroup app
@@ -127,6 +128,7 @@ class QgsLayoutMapWidget: public QgsLayoutItemBaseWidget, private Ui::QgsLayoutM
QPointer< QgsLayoutItemMap > mMapItem;
QgsLayoutItemPropertiesWidget *mItemPropertiesWidget = nullptr;
QgsLayoutDesignerInterface *mInterface = nullptr;
QPointer< QgsLayoutMapLabelingWidget > mLabelWidget;

//! Sets extent of composer map from line edits
void updateComposerExtentFromGui();
@@ -184,9 +186,14 @@ class QgsLayoutMapLabelingWidget: public QgsLayoutItemBaseWidget, private Ui::Qg
public:
explicit QgsLayoutMapLabelingWidget( QgsLayoutItemMap *map );

protected:
bool setNewItem( QgsLayoutItem *item ) override;

private slots:
void updateGuiElements();
void labelMarginChanged( double val );
void labelMarginUnitsChanged();
void showPartialsToggled( bool checked );

private:
QPointer< QgsLayoutItemMap > mMapItem;
@@ -608,6 +608,7 @@ bool QgsLayoutItemMap::writePropertiesToElement( QDomElement &mapElem, QDomDocum
mapElem.appendChild( atlasElem );

mapElem.setAttribute( QStringLiteral( "labelMargin" ), mLabelMargin.encodeMeasurement() );
mapElem.setAttribute( QStringLiteral( "mapFlags" ), static_cast< int>( mMapFlags ) );

return true;
}
@@ -744,6 +745,8 @@ bool QgsLayoutItemMap::readPropertiesFromElement( const QDomElement &itemElem, c

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

mMapFlags = static_cast< MapItemFlags>( itemElem.attribute( QStringLiteral( "mapFlags" ), nullptr ).toInt() );

updateBoundingRect();

mUpdatesEnabled = true;
@@ -1070,6 +1073,16 @@ void QgsLayoutItemMap::recreateCachedImageInBackground()
mDrawingPreview = false;
}

QgsLayoutItemMap::MapItemFlags QgsLayoutItemMap::mapFlags() const
{
return mMapFlags;
}

void QgsLayoutItemMap::setMapFlags( QgsLayoutItemMap::MapItemFlags mapFlags )
{
mMapFlags = mapFlags;
}

QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF size, double dpi, bool includeLayerSettings ) const
{
QgsExpressionContext expressionContext = createExpressionContext();
@@ -1127,7 +1140,12 @@ QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF
jobMapSettings.setTransformContext( mLayout->project()->transformContext() );
jobMapSettings.setPathResolver( mLayout->project()->pathResolver() );

jobMapSettings.setLabelingEngineSettings( mLayout->project()->labelingEngineSettings() );
QgsLabelingEngineSettings labelSettings = mLayout->project()->labelingEngineSettings();

// override project "show partial labels" setting with this map's setting
labelSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, mMapFlags & ShowPartialLabels );
jobMapSettings.setLabelingEngineSettings( labelSettings );

// override the default text render format inherited from the labeling engine settings using the layout's render context setting
jobMapSettings.setTextRenderFormat( mLayout->renderContext().textRenderFormat() );

@@ -63,6 +63,16 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
Auto
};

/**
* Various flags that affect drawing of map items.
* \since QGIS 3.6
*/
enum MapItemFlag
{
ShowPartialLabels = 1 << 0, //!< Whether to draw labels which are partially outside of the map view
};
Q_DECLARE_FLAGS( MapItemFlags, MapItemFlag )

/**
* Constructor for QgsLayoutItemMap, with the specified parent \a layout.
*/
@@ -73,6 +83,20 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
QIcon icon() const override;
QgsLayoutItem::Flags itemFlags() const override;

/**
* Returns the map item's flags, which control how the map content is drawn.
* \see setMapFlags()
* \since QGIS 3.6
*/
QgsLayoutItemMap::MapItemFlags mapFlags() const;

/**
* Sets the map item's \a flags, which control how the map content is drawn.
* \see mapFlags()
* \since QGIS 3.6
*/
void setMapFlags( QgsLayoutItemMap::MapItemFlags flags );

/**
* Sets the map id() to a number not yet used in the layout. The existing id() is kept if it is not in use.
*/
@@ -521,6 +545,7 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem

private:

QgsLayoutItemMap::MapItemFlags mMapFlags = nullptr;

//! Unique identifier
int mMapId = 1;
@@ -709,4 +734,6 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem

};

Q_DECLARE_OPERATORS_FOR_FLAGS( QgsLayoutItemMap::MapItemFlags )

#endif //QGSLAYOUTITEMMAP_H
@@ -90,6 +90,13 @@
</property>
</widget>
</item>
<item row="3" column="0" colspan="4">
<widget class="QCheckBox" name="mShowPartialLabelsCheckBox">
<property name="text">
<string>Allow truncated labels on edges of map</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -339,6 +339,69 @@ def testLabelMargin(self):
self.report += checker.report()
self.assertTrue(result, message)

def testPartialLabels(self):
"""
Test rendering map item with a show partial labels flag
"""
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)

# default should always be to hide partial labels
self.assertFalse(map.mapFlags() & QgsLayoutItemMap.ShowPartialLabels)

# hiding partial labels (the default)
map.setMapFlags(QgsLayoutItemMap.MapItemFlags())
checker = QgsLayoutChecker('composermap_label_nomargin', layout)
checker.setControlPathPrefix("composer_map")
result, message = checker.testLayout()
self.report += checker.report()
self.assertTrue(result, message)

# showing partial labels
map.setMapFlags(QgsLayoutItemMap.ShowPartialLabels)
checker = QgsLayoutChecker('composermap_show_partial_labels', layout)
checker.setControlPathPrefix("composer_map")
result, message = checker.testLayout()
self.report += checker.report()
self.assertTrue(result, message)


if __name__ == '__main__':
unittest.main()
@@ -35,7 +35,8 @@
QgsLayoutExporter,
QgsMapSettings,
QgsProject,
QgsVectorLayerSimpleLabeling)
QgsVectorLayerSimpleLabeling,
QgsLabelingEngineSettings)


from utilities import (
@@ -132,6 +133,8 @@ def _set_up_composition(self, width, height, dpi, engine_settings):
""":type: QgsLayoutItemMap"""
self._cmap.setFrameEnabled(False)
self._cmap.setLayers(self._TestMapSettings.layers())
if self._TestMapSettings.labelingEngineSettings().flags() & QgsLabelingEngineSettings.UsePartialCandidates:
self._cmap.setMapFlags(QgsLayoutItemMap.ShowPartialLabels)
self._c.addLayoutItem(self._cmap)
# now expand map to fill page and set its extent
self._cmap.attemptSetSceneRect(QRectF(0, 0, paperw, paperw))
Binary file not shown.
@@ -1130,7 +1130,7 @@
</LayoutItem>
<GuideCollection visible="1"/>
</PageCollection>
<LayoutItem type="65639" followPreset="false" followPresetName="" positionLock="false" visibility="1" itemRotation="0" positionOnPage="126,143,mm" blendMode="0" zValue="2" frameJoinStyle="miter" frame="false" background="true" excludeFromExports="0" size="61,26,mm" outlineWidthM="0.3,mm" uuid="{5ed7a90f-8af2-4535-a15e-e18a7f6c5d1f}" groupUuid="" drawCanvasItems="true" referencePoint="0" id="" keepLayerSet="false" templateUuid="{5ed7a90f-8af2-4535-a15e-e18a7f6c5d1f}" position="126,143,mm" mapRotation="0" opacity="1">
<LayoutItem type="65639" followPreset="false" followPresetName="" positionLock="false" visibility="1" itemRotation="0" positionOnPage="126,143,mm" blendMode="0" zValue="2" frameJoinStyle="miter" frame="false" background="true" excludeFromExports="0" size="61,26,mm" outlineWidthM="0.3,mm" uuid="{5ed7a90f-8af2-4535-a15e-e18a7f6c5d1f}" groupUuid="" drawCanvasItems="true" referencePoint="0" id="" keepLayerSet="false" templateUuid="{5ed7a90f-8af2-4535-a15e-e18a7f6c5d1f}" position="126,143,mm" mapRotation="0" mapFlags="1" opacity="1">
<FrameColor blue="0" green="0" red="0" alpha="255"/>
<BackgroundColor blue="255" green="255" red="255" alpha="255"/>
<LayoutObject>
@@ -1209,7 +1209,7 @@
</ComposerMapGrid>
<AtlasMap margin="0.10000000000000001" atlasDriven="0" scalingMode="2"/>
</LayoutItem>
<LayoutItem type="65639" followPreset="false" followPresetName="" positionLock="false" visibility="1" itemRotation="0" positionOnPage="98.7716,20.1872,mm" blendMode="0" zValue="1" frameJoinStyle="miter" frame="false" background="true" excludeFromExports="0" size="87,103,mm" outlineWidthM="0.3,mm" uuid="{8fec18d6-8ba0-47d6-914e-3daffe8a8633}" groupUuid="" drawCanvasItems="true" referencePoint="0" id="" keepLayerSet="false" templateUuid="{8fec18d6-8ba0-47d6-914e-3daffe8a8633}" position="98.7716,20.1872,mm" mapRotation="0" opacity="1">
<LayoutItem type="65639" followPreset="false" followPresetName="" positionLock="false" visibility="1" itemRotation="0" positionOnPage="98.7716,20.1872,mm" blendMode="0" zValue="1" frameJoinStyle="miter" frame="false" background="true" excludeFromExports="0" size="87,103,mm" outlineWidthM="0.3,mm" uuid="{8fec18d6-8ba0-47d6-914e-3daffe8a8633}" groupUuid="" drawCanvasItems="true" referencePoint="0" id="" keepLayerSet="false" templateUuid="{8fec18d6-8ba0-47d6-914e-3daffe8a8633}" position="98.7716,20.1872,mm" mapRotation="0" mapFlags="1" opacity="1">
<FrameColor blue="0" green="0" red="0" alpha="255"/>
<BackgroundColor blue="255" green="255" red="255" alpha="255"/>
<LayoutObject>
@@ -2921,7 +2921,7 @@ def my_form_open(dialog, layer, feature):
<LabelFont style="" description="DejaVu Sans,10,-1,5,50,0,0,0,0,0"/>
<FontColor red="0" green="0" blue="0"/>
</LayoutItem>
<LayoutItem itemRotation="0" referencePoint="0" drawCanvasItems="true" followPreset="false" position="21.3911,46,mm" positionOnPage="21.3911,46,mm" followPresetName="" frame="false" uuid="{e2138dff-8012-45c7-9a52-a543ca96ac60}" keepLayerSet="false" background="true" positionLock="false" blendMode="0" size="242,104,mm" opacity="1" outlineWidthM="0.3,mm" groupUuid="" zValue="3" excludeFromExports="0" type="65639" templateUuid="{e2138dff-8012-45c7-9a52-a543ca96ac60}" frameJoinStyle="miter" visibility="1" mapRotation="0" id="">
<LayoutItem itemRotation="0" referencePoint="0" drawCanvasItems="true" followPreset="false" position="21.3911,46,mm" positionOnPage="21.3911,46,mm" followPresetName="" frame="false" uuid="{e2138dff-8012-45c7-9a52-a543ca96ac60}" keepLayerSet="false" background="true" positionLock="false" blendMode="0" size="242,104,mm" opacity="1" outlineWidthM="0.3,mm" groupUuid="" zValue="3" excludeFromExports="0" type="65639" templateUuid="{e2138dff-8012-45c7-9a52-a543ca96ac60}" frameJoinStyle="miter" visibility="1" mapRotation="0" mapFlags="1" id="">
<FrameColor alpha="255" red="0" green="0" blue="0"/>
<BackgroundColor alpha="255" red="255" green="255" blue="255"/>
<LayoutObject>

0 comments on commit 089a2f1

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