Skip to content

Commit

Permalink
[api] Implementation of labeling engine rules
Browse files Browse the repository at this point in the history
See qgis/QGIS-Enhancement-Proposals#299

Implements the API framework for setting advanced labeling engine
rules for a project, and implements 4 initial rule types:

- QgsLabelingEngineRuleMinimumDistanceLabelToFeature: prevents labels
  being placed too *close* to features from a different layer
- QgsLabelingEngineRuleMaximumDistanceLabelToFeature: prevents labels
  being placed too *far* from features from a different layer
- QgsLabelingEngineRuleMinimumDistanceLabelToLabel: prevents labels
  being placed too close to labels from a different layer
- QgsLabelingEngineRuleAvoidLabelOverlapWithFeature: prevents labels
  being placed overlapping features from a different layer

(note that the first 3 rules require a build based on GEOS >= 3.10,
they are not available for older GEOS builds)

Also implements a registry for storing available rule classes,
and serialization of rules and configuration in QGIS projects
  • Loading branch information
nyalldawson committed Sep 10, 2024
1 parent aa8bcd7 commit 1cb0708
Show file tree
Hide file tree
Showing 38 changed files with 3,663 additions and 4 deletions.
1 change: 1 addition & 0 deletions doc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ if(WITH_APIDOC)
${CMAKE_SOURCE_DIR}/src/core/geometry
${CMAKE_SOURCE_DIR}/src/core/gps
${CMAKE_SOURCE_DIR}/src/core/labeling
${CMAKE_SOURCE_DIR}/src/core/labeling/rules
${CMAKE_SOURCE_DIR}/src/core/layertree
${CMAKE_SOURCE_DIR}/src/core/layout
${CMAKE_SOURCE_DIR}/src/core/locator
Expand Down
1 change: 1 addition & 0 deletions python/PyQt6/core/auto_additions/qgsapplication.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
QgsApplication.renderer3DRegistry = staticmethod(QgsApplication.renderer3DRegistry)
QgsApplication.symbol3DRegistry = staticmethod(QgsApplication.symbol3DRegistry)
QgsApplication.scaleBarRendererRegistry = staticmethod(QgsApplication.scaleBarRendererRegistry)
QgsApplication.labelingEngineRuleRegistry = staticmethod(QgsApplication.labelingEngineRuleRegistry)
QgsApplication.projectStorageRegistry = staticmethod(QgsApplication.projectStorageRegistry)
QgsApplication.layerMetadataProviderRegistry = staticmethod(QgsApplication.layerMetadataProviderRegistry)
QgsApplication.externalStorageRegistry = staticmethod(QgsApplication.externalStorageRegistry)
Expand Down
9 changes: 9 additions & 0 deletions python/PyQt6/core/auto_additions/qgslabelingenginerule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# The following has been generated automatically from src/core/labeling/rules/qgslabelingenginerule.h
try:
QgsLabelingEngineContext.__group__ = ['labeling', 'rules']
except NameError:
pass
try:
QgsAbstractLabelingEngineRule.__group__ = ['labeling', 'rules']
except NameError:
pass
21 changes: 21 additions & 0 deletions python/PyQt6/core/auto_additions/qgslabelingenginerule_impl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# The following has been generated automatically from src/core/labeling/rules/qgslabelingenginerule_impl.h
try:
QgsAbstractLabelingEngineRuleDistanceFromFeature.__group__ = ['labeling', 'rules']
except NameError:
pass
try:
QgsLabelingEngineRuleMinimumDistanceLabelToFeature.__group__ = ['labeling', 'rules']
except NameError:
pass
try:
QgsLabelingEngineRuleMaximumDistanceLabelToFeature.__group__ = ['labeling', 'rules']
except NameError:
pass
try:
QgsLabelingEngineRuleMinimumDistanceLabelToLabel.__group__ = ['labeling', 'rules']
except NameError:
pass
try:
QgsLabelingEngineRuleAvoidLabelOverlapWithFeature.__group__ = ['labeling', 'rules']
except NameError:
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# The following has been generated automatically from src/core/labeling/rules/qgslabelingengineruleregistry.h
try:
QgsLabelingEngineRuleRegistry.__group__ = ['labeling', 'rules']
except NameError:
pass
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ Stores global configuration for labeling engine
};

QgsLabelingEngineSettings();
~QgsLabelingEngineSettings();

QgsLabelingEngineSettings( const QgsLabelingEngineSettings &other );

void clear();
%Docstring
Expand Down Expand Up @@ -125,13 +128,66 @@ Which search method to use for removal collisions between labels
Chain is always used.
%End


void readSettingsFromProject( QgsProject *project );
%Docstring
Read configuration of the labeling engine from a project

.. note::

Both this method and :py:func:`~QgsLabelingEngineSettings.readXml` must be called to completely restore the object's state from a project.
%End

void writeSettingsToProject( QgsProject *project );
%Docstring
Write configuration of the labeling engine to a project
Write configuration of the labeling engine to a project.

.. note::

Both this method and :py:func:`~QgsLabelingEngineSettings.writeXml` must be called to completely store the object's state in a project.
%End

void writeXml( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const;
%Docstring
Writes the label engine settings to an XML ``element``.

.. note::

Both this method and :py:func:`~QgsLabelingEngineSettings.writeSettingsToProject` must be called to completely store the object's state in a project.

.. seealso:: :py:func:`readXml`

.. seealso:: :py:func:`writeSettingsToProject`

.. versionadded:: 3.40
%End

void readXml( const QDomElement &element, const QgsReadWriteContext &context );
%Docstring
Reads the label engine settings from an XML ``element``.

.. note::

Both this method and :py:func:`~QgsLabelingEngineSettings.readSettingsFromProject` must be called to completely restore the object's state from a project.

.. note::

:py:func:`~QgsLabelingEngineSettings.resolveReferences` must be called following this method.

.. seealso:: :py:func:`writeXml`

.. seealso:: :py:func:`readSettingsFromProject`

.. versionadded:: 3.40
%End

void resolveReferences( const QgsProject *project );
%Docstring
Resolves reference to layers from stored layer ID.

Should be called following a call :py:func:`~QgsLabelingEngineSettings.readXml`.

.. versionadded:: 3.40
%End


Expand Down Expand Up @@ -187,6 +243,47 @@ Sets the placement engine ``version``, which dictates how the label placement pr
.. seealso:: :py:func:`placementVersion`

.. versionadded:: 3.10.2
%End

QList< QgsAbstractLabelingEngineRule * > rules();
%Docstring
Returns a list of labeling engine rules which must be satifisfied
while placing labels.

.. seealso:: :py:func:`addRule`

.. seealso:: :py:func:`setRules`

.. versionadded:: 3.40
%End


void addRule( QgsAbstractLabelingEngineRule *rule /Transfer/ );
%Docstring
Adds a labeling engine ``rule`` which must be satifisfied
while placing labels.

Ownership of the rule is transferred to the settings.

.. seealso:: :py:func:`rules`

.. seealso:: :py:func:`setRules`

.. versionadded:: 3.40
%End

void setRules( const QList< QgsAbstractLabelingEngineRule * > &rules /Transfer/ );
%Docstring
Sets the labeling engine ``rules`` which must be satifisfied
while placing labels.

Ownership of the rules are transferred to the settings.

.. seealso:: :py:func:`addRule`

.. seealso:: :py:func:`rules`

.. versionadded:: 3.40
%End

};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/labeling/rules/qgslabelingenginerule.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/



class QgsLabelingEngineContext
{
%Docstring(signature="appended")
Encapsulates the context for a labeling engine run.

.. versionadded:: 3.40
%End

%TypeHeaderCode
#include "qgslabelingenginerule.h"
%End
public:

QgsLabelingEngineContext( QgsRenderContext &renderContext );
%Docstring
Constructor for QgsLabelingEngineContext.
%End


QgsRenderContext &renderContext();
%Docstring
Returns a reference to the context's render context.
%End


QgsRectangle extent() const;
%Docstring
Returns the map extent defining the limits for labeling.

.. seealso:: :py:func:`mapBoundaryGeometry`

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

void setExtent( const QgsRectangle &extent );
%Docstring
Sets the map ``extent`` defining the limits for labeling.

.. seealso:: :py:func:`setMapBoundaryGeometry`

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

QgsGeometry mapBoundaryGeometry() const;
%Docstring
Returns the map label boundary geometry, which defines the limits within which labels may be placed
in the map.

The map boundary geometry specifies the actual geometry of the map
boundary, which will be used to detect whether a label is visible (or partially visible) in
the rendered map. This may differ from :py:func:`~QgsLabelingEngineContext.extent` in the case of rotated or non-rectangular
maps.

.. seealso:: :py:func:`setMapBoundaryGeometry`

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

void setMapBoundaryGeometry( const QgsGeometry &geometry );
%Docstring
Sets the map label boundary ``geometry``, which defines the limits within which labels may be placed
in the map.

The map boundary geometry specifies the actual geometry of the map
boundary, which will be used to detect whether a label is visible (or partially visible) in
the rendered map. This may differ from :py:func:`~QgsLabelingEngineContext.extent` in the case of rotated or non-rectangular
maps.

.. seealso:: :py:func:`setExtent`

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

private:
QgsLabelingEngineContext( const QgsLabelingEngineContext &other );
};

class QgsAbstractLabelingEngineRule
{
%Docstring(signature="appended")
Abstract base class for labeling engine rules.

Labeling engine rules implement custom logic to modify the labeling solution for a map render,
e.g. by preventing labels being placed which violate custom constraints.

.. note::

:py:class:`QgsAbstractLabelingEngineRule` cannot be subclassed in Python. Use one of the existing
implementations of this class instead.

.. versionadded:: 3.40
%End

%TypeHeaderCode
#include "qgslabelingenginerule.h"
%End
%ConvertToSubClassCode
if ( sipCpp->id() == "minimumDistanceLabelToFeature" )
{
sipType = sipType_QgsLabelingEngineRuleMinimumDistanceLabelToFeature;
}
else if ( sipCpp->id() == "minimumDistanceLabelToLabel" )
{
sipType = sipType_QgsLabelingEngineRuleMinimumDistanceLabelToLabel;
}
else if ( sipCpp->id() == "maximumDistanceLabelToFeature" )
{
sipType = sipType_QgsLabelingEngineRuleMaximumDistanceLabelToFeature;
}
else if ( sipCpp->id() == "avoidLabelOverlapWithFeature" )
{
sipType = sipType_QgsLabelingEngineRuleAvoidLabelOverlapWithFeature;
}
else
{
sipType = 0;
}
%End
public:

virtual ~QgsAbstractLabelingEngineRule();

virtual QgsAbstractLabelingEngineRule *clone() const = 0 /Factory/;
%Docstring
Creates a clone of this rule.

The caller takes ownership of the returned object.
%End

virtual QString id() const = 0;
%Docstring
Returns a string uniquely identifying the rule subclass.
%End

virtual bool prepare( QgsRenderContext &context ) = 0;
%Docstring
Prepares the rule.

This must be called on the main render thread, prior to commencing the render operation. Thread sensitive
logic (such as creation of feature sources) can be performed in this method.
%End

virtual void writeXml( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const = 0;
%Docstring
Writes the rule properties to an XML ``element``.

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

virtual void readXml( const QDomElement &element, const QgsReadWriteContext &context ) = 0;
%Docstring
Reads the rule properties from an XML ``element``.

.. seealso:: :py:func:`resolveReferences`

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

virtual void resolveReferences( const QgsProject *project );
%Docstring
Resolves reference to layers from stored layer ID.

Should be called following a call :py:func:`~QgsAbstractLabelingEngineRule.readXml`.
%End





};

/************************************************************************
* This file has been generated automatically from *
* *
* src/core/labeling/rules/qgslabelingenginerule.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/
Loading

0 comments on commit 1cb0708

Please sign in to comment.