[api] Implementation of labeling engine rules

See https://github.com/qgis/QGIS-Enhancement-Proposals/issues/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
This commit is contained in:
Nyall Dawson 2024-09-02 10:31:59 +10:00
parent 5e1059946f
commit 15a7079a39
38 changed files with 3663 additions and 4 deletions

View File

@ -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

View File

@ -133,6 +133,7 @@ try:
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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -30,6 +30,9 @@ Stores global configuration for labeling engine
};
QgsLabelingEngineSettings();
~QgsLabelingEngineSettings();
QgsLabelingEngineSettings( const QgsLabelingEngineSettings &other );
void clear();
%Docstring
@ -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
@ -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
};

View File

@ -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 *
************************************************************************/

View File

@ -0,0 +1,393 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/labeling/rules/qgslabelingenginerule_impl.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/
class QgsAbstractLabelingEngineRuleDistanceFromFeature : QgsAbstractLabelingEngineRule
{
%Docstring(signature="appended")
Base class for labeling engine rules which prevents labels being placed too close or to far from features from a different layer.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgslabelingenginerule_impl.h"
%End
public:
QgsAbstractLabelingEngineRuleDistanceFromFeature();
~QgsAbstractLabelingEngineRuleDistanceFromFeature();
virtual bool prepare( QgsRenderContext &context );
virtual void writeXml( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const;
virtual void readXml( const QDomElement &element, const QgsReadWriteContext &context );
virtual void resolveReferences( const QgsProject *project );
QgsVectorLayer *labeledLayer();
%Docstring
Returns the layer providing the labels.
.. seealso:: :py:func:`setLabeledLayer`
%End
void setLabeledLayer( QgsVectorLayer *layer );
%Docstring
Sets the ``layer`` providing the labels.
.. seealso:: :py:func:`labeledLayer`
%End
QgsVectorLayer *targetLayer();
%Docstring
Returns the layer providing the features which labels must be distant from (or close to).
.. seealso:: :py:func:`setTargetLayer`
%End
void setTargetLayer( QgsVectorLayer *layer );
%Docstring
Sets the ``layer`` providing the features which labels must be distant from (or close to).
.. seealso:: :py:func:`targetLayer`
%End
double distance() const;
%Docstring
Returns the acceptable distance threshold between labels and the features
from the :py:func:`~QgsAbstractLabelingEngineRuleDistanceFromFeature.targetLayer`.
.. seealso:: :py:func:`setDistance`
.. seealso:: :py:func:`distanceUnits`
%End
void setDistance( double distance );
%Docstring
Sets the acceptable ``distance`` threshold between labels and the features
from the :py:func:`~QgsAbstractLabelingEngineRuleDistanceFromFeature.targetLayer`.
.. seealso:: :py:func:`distance`
.. seealso:: :py:func:`setDistanceUnits`
%End
Qgis::RenderUnit distanceUnit() const;
%Docstring
Returns the units for the distance between labels and the features
from the :py:func:`~QgsAbstractLabelingEngineRuleDistanceFromFeature.targetLayer`.
.. seealso:: :py:func:`setDistanceUnit`
.. seealso:: :py:func:`distance`
%End
void setDistanceUnit( Qgis::RenderUnit unit );
%Docstring
Sets the ``unit`` for the distance between labels and the features
from the :py:func:`~QgsAbstractLabelingEngineRuleDistanceFromFeature.targetLayer`.
.. seealso:: :py:func:`distanceUnit`
.. seealso:: :py:func:`setDistance`
%End
const QgsMapUnitScale &distanceUnitScale() const;
%Docstring
Returns the scaling for the distance between labels and the features
from the :py:func:`~QgsAbstractLabelingEngineRuleDistanceFromFeature.targetLayer`.
.. seealso:: :py:func:`setDistanceUnitScale`
.. seealso:: :py:func:`distance`
%End
void setDistanceUnitScale( const QgsMapUnitScale &scale );
%Docstring
Sets the ``scale`` for the distance between labels and the features
from the :py:func:`~QgsAbstractLabelingEngineRuleDistanceFromFeature.targetLayer`.
.. seealso:: :py:func:`distanceUnitScale`
.. seealso:: :py:func:`setDistance`
%End
double cost() const;
%Docstring
Returns the penalty cost incurred when the rule is violated.
This is a value between 0 and 10, where 10 indicates that the rule must never be violated,
and 1-9 = nice to have if possible, where higher numbers will try harder to avoid violating the rule.
.. seealso:: :py:func:`setCost`
%End
void setCost( double cost );
%Docstring
Sets the penalty ``cost`` incurred when the rule is violated.
This is a value between 0 and 10, where 10 indicates that the rule must never be violated,
and 1-9 = nice to have if possible, where higher numbers will try harder to avoid violating the rule.
.. seealso:: :py:func:`cost`
%End
protected:
void copyCommonProperties( QgsAbstractLabelingEngineRuleDistanceFromFeature *other ) const;
%Docstring
Copies common properties from this object to an ``other``.
%End
private:
QgsAbstractLabelingEngineRuleDistanceFromFeature( const QgsAbstractLabelingEngineRuleDistanceFromFeature &other );
};
class QgsLabelingEngineRuleMinimumDistanceLabelToFeature : QgsAbstractLabelingEngineRuleDistanceFromFeature
{
%Docstring(signature="appended")
A labeling engine rule which prevents labels being placed too close to features from a different layer.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgslabelingenginerule_impl.h"
%End
public:
QgsLabelingEngineRuleMinimumDistanceLabelToFeature();
~QgsLabelingEngineRuleMinimumDistanceLabelToFeature();
virtual QgsLabelingEngineRuleMinimumDistanceLabelToFeature *clone() const /Factory/;
virtual QString id() const;
private:
QgsLabelingEngineRuleMinimumDistanceLabelToFeature( const QgsLabelingEngineRuleMinimumDistanceLabelToFeature & );
};
class QgsLabelingEngineRuleMaximumDistanceLabelToFeature : QgsAbstractLabelingEngineRuleDistanceFromFeature
{
%Docstring(signature="appended")
A labeling engine rule which prevents labels being placed too far from features from a different layer.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgslabelingenginerule_impl.h"
%End
public:
QgsLabelingEngineRuleMaximumDistanceLabelToFeature();
~QgsLabelingEngineRuleMaximumDistanceLabelToFeature();
virtual QgsLabelingEngineRuleMaximumDistanceLabelToFeature *clone() const /Factory/;
virtual QString id() const;
private:
QgsLabelingEngineRuleMaximumDistanceLabelToFeature( const QgsLabelingEngineRuleMaximumDistanceLabelToFeature & );
};
class QgsLabelingEngineRuleMinimumDistanceLabelToLabel : QgsAbstractLabelingEngineRule
{
%Docstring(signature="appended")
A labeling engine rule which prevents labels being placed too close to labels from a different layer.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgslabelingenginerule_impl.h"
%End
public:
QgsLabelingEngineRuleMinimumDistanceLabelToLabel();
~QgsLabelingEngineRuleMinimumDistanceLabelToLabel();
virtual QgsLabelingEngineRuleMinimumDistanceLabelToLabel *clone() const /Factory/;
virtual QString id() const;
virtual void writeXml( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const;
virtual void readXml( const QDomElement &element, const QgsReadWriteContext &context );
virtual void resolveReferences( const QgsProject *project );
virtual bool prepare( QgsRenderContext &context );
QgsVectorLayer *labeledLayer();
%Docstring
Returns the layer providing the labels.
.. seealso:: :py:func:`setLabeledLayer`
%End
void setLabeledLayer( QgsVectorLayer *layer );
%Docstring
Sets the ``layer`` providing the labels.
.. seealso:: :py:func:`labeledLayer`
%End
QgsVectorLayer *targetLayer();
%Docstring
Returns the layer providing the labels which labels must be distant from.
.. seealso:: :py:func:`setTargetLayer`
%End
void setTargetLayer( QgsVectorLayer *layer );
%Docstring
Sets the ``layer`` providing the labels which labels must be distant from.
.. seealso:: :py:func:`targetLayer`
%End
double distance() const;
%Docstring
Returns the minimum permitted distance between labels from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.labeledLayer` and the labels
from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.targetLayer`.
.. seealso:: :py:func:`setDistance`
.. seealso:: :py:func:`distanceUnits`
%End
void setDistance( double distance );
%Docstring
Sets the minimum permitted ``distance`` between labels from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.labeledLayer` and the labels
from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.targetLayer`.
.. seealso:: :py:func:`distance`
.. seealso:: :py:func:`setDistanceUnits`
%End
Qgis::RenderUnit distanceUnit() const;
%Docstring
Returns the units for the distance between labels from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.labeledLayer` and the labels
from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.targetLayer`.
.. seealso:: :py:func:`setDistanceUnit`
.. seealso:: :py:func:`distance`
%End
void setDistanceUnit( Qgis::RenderUnit unit );
%Docstring
Sets the ``unit`` for the distance between labels from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.labeledLayer` and the labels
from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.targetLayer`.
.. seealso:: :py:func:`distanceUnit`
.. seealso:: :py:func:`setDistance`
%End
const QgsMapUnitScale &distanceUnitScale() const;
%Docstring
Returns the scaling for the distance between labels from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.labeledLayer` and the labels
from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.targetLayer`.
.. seealso:: :py:func:`setDistanceUnitScale`
.. seealso:: :py:func:`distance`
%End
void setDistanceUnitScale( const QgsMapUnitScale &scale );
%Docstring
Sets the ``scale`` for the distance between labels from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.labeledLayer` and the labels
from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.targetLayer`.
.. seealso:: :py:func:`distanceUnitScale`
.. seealso:: :py:func:`setDistance`
%End
private:
QgsLabelingEngineRuleMinimumDistanceLabelToLabel( const QgsLabelingEngineRuleMinimumDistanceLabelToLabel & );
};
class QgsLabelingEngineRuleAvoidLabelOverlapWithFeature : QgsAbstractLabelingEngineRule
{
%Docstring(signature="appended")
A labeling engine rule which prevents labels being placed overlapping features from a different layer.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgslabelingenginerule_impl.h"
%End
public:
QgsLabelingEngineRuleAvoidLabelOverlapWithFeature();
~QgsLabelingEngineRuleAvoidLabelOverlapWithFeature();
virtual QgsLabelingEngineRuleAvoidLabelOverlapWithFeature *clone() const /Factory/;
virtual QString id() const;
virtual bool prepare( QgsRenderContext &context );
virtual void writeXml( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const;
virtual void readXml( const QDomElement &element, const QgsReadWriteContext &context );
virtual void resolveReferences( const QgsProject *project );
QgsVectorLayer *labeledLayer();
%Docstring
Returns the layer providing the labels.
.. seealso:: :py:func:`setLabeledLayer`
%End
void setLabeledLayer( QgsVectorLayer *layer );
%Docstring
Sets the ``layer`` providing the labels.
.. seealso:: :py:func:`labeledLayer`
%End
QgsVectorLayer *targetLayer();
%Docstring
Returns the layer providing the features which labels must not overlap.
.. seealso:: :py:func:`setTargetLayer`
%End
void setTargetLayer( QgsVectorLayer *layer );
%Docstring
Sets the ``layer`` providing the features which labels must not overlap.
.. seealso:: :py:func:`targetLayer`
%End
private:
QgsLabelingEngineRuleAvoidLabelOverlapWithFeature( const QgsLabelingEngineRuleAvoidLabelOverlapWithFeature & );
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/labeling/rules/qgslabelingenginerule_impl.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/

View File

@ -0,0 +1,82 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/labeling/rules/qgslabelingengineruleregistry.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/
class QgsLabelingEngineRuleRegistry
{
%Docstring(signature="appended")
A registry 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.
This registry stores available rules and is responsible for creating rules.
:py:class:`QgsLabelingEngineRuleRegistry` is not usually directly created, but rather accessed through
:py:func:`QgsApplication.labelEngineRuleRegistry()`.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgslabelingengineruleregistry.h"
%End
public:
QgsLabelingEngineRuleRegistry();
%Docstring
Constructor for QgsLabelingEngineRuleRegistry, containing a set of
default rules.
%End
~QgsLabelingEngineRuleRegistry();
QStringList ruleIds() const;
%Docstring
Returns a list of the rule IDs for rules present in the registry.
%End
QgsAbstractLabelingEngineRule *create( const QString &id ) const /TransferBack/;
%Docstring
Creates a new rule from the type with matching ``id``.
Returns ``None`` if no matching rule was found in the registry.
The caller takes ownership of the returned object.
%End
bool addRule( QgsAbstractLabelingEngineRule *rule /Transfer/ );
%Docstring
Adds a new ``rule`` type to the registry.
The registry takes ownership of ``rule``.
:return: ``True`` if the rule was successfully added.
.. seealso:: :py:func:`removeRule`
%End
void removeRule( const QString &id );
%Docstring
Removes the rule with matching ``id`` from the registry.
.. seealso:: :py:func:`addRule`
%End
private:
QgsLabelingEngineRuleRegistry( const QgsLabelingEngineRuleRegistry &other );
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/labeling/rules/qgslabelingengineruleregistry.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/

View File

@ -944,6 +944,13 @@ Returns registry of available 3D symbols.
Gets the registry of available scalebar renderers.
.. versionadded:: 3.14
%End
static QgsLabelingEngineRuleRegistry *labelingEngineRuleRegistry() /KeepReference/;
%Docstring
Gets the registry of available labeling engine rules.
.. versionadded:: 3.40
%End
static QgsProjectStorageRegistry *projectStorageRegistry() /KeepReference/;

View File

@ -391,6 +391,9 @@
%Include auto_generated/labeling/qgspallabeling.sip
%Include auto_generated/labeling/qgsrulebasedlabeling.sip
%Include auto_generated/labeling/qgsvectorlayerlabeling.sip
%Include auto_generated/labeling/rules/qgslabelingenginerule.sip
%Include auto_generated/labeling/rules/qgslabelingenginerule_impl.sip
%Include auto_generated/labeling/rules/qgslabelingengineruleregistry.sip
%Include auto_generated/layertree/qgscolorramplegendnode.sip
%Include auto_generated/layertree/qgscolorramplegendnodesettings.sip
%Include auto_generated/layertree/qgslayertree.sip

View File

@ -122,6 +122,7 @@ try:
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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -30,6 +30,9 @@ Stores global configuration for labeling engine
};
QgsLabelingEngineSettings();
~QgsLabelingEngineSettings();
QgsLabelingEngineSettings( const QgsLabelingEngineSettings &other );
void clear();
%Docstring
@ -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
@ -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
};

View File

@ -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 *
************************************************************************/

View File

@ -0,0 +1,393 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/labeling/rules/qgslabelingenginerule_impl.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/
class QgsAbstractLabelingEngineRuleDistanceFromFeature : QgsAbstractLabelingEngineRule
{
%Docstring(signature="appended")
Base class for labeling engine rules which prevents labels being placed too close or to far from features from a different layer.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgslabelingenginerule_impl.h"
%End
public:
QgsAbstractLabelingEngineRuleDistanceFromFeature();
~QgsAbstractLabelingEngineRuleDistanceFromFeature();
virtual bool prepare( QgsRenderContext &context );
virtual void writeXml( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const;
virtual void readXml( const QDomElement &element, const QgsReadWriteContext &context );
virtual void resolveReferences( const QgsProject *project );
QgsVectorLayer *labeledLayer();
%Docstring
Returns the layer providing the labels.
.. seealso:: :py:func:`setLabeledLayer`
%End
void setLabeledLayer( QgsVectorLayer *layer );
%Docstring
Sets the ``layer`` providing the labels.
.. seealso:: :py:func:`labeledLayer`
%End
QgsVectorLayer *targetLayer();
%Docstring
Returns the layer providing the features which labels must be distant from (or close to).
.. seealso:: :py:func:`setTargetLayer`
%End
void setTargetLayer( QgsVectorLayer *layer );
%Docstring
Sets the ``layer`` providing the features which labels must be distant from (or close to).
.. seealso:: :py:func:`targetLayer`
%End
double distance() const;
%Docstring
Returns the acceptable distance threshold between labels and the features
from the :py:func:`~QgsAbstractLabelingEngineRuleDistanceFromFeature.targetLayer`.
.. seealso:: :py:func:`setDistance`
.. seealso:: :py:func:`distanceUnits`
%End
void setDistance( double distance );
%Docstring
Sets the acceptable ``distance`` threshold between labels and the features
from the :py:func:`~QgsAbstractLabelingEngineRuleDistanceFromFeature.targetLayer`.
.. seealso:: :py:func:`distance`
.. seealso:: :py:func:`setDistanceUnits`
%End
Qgis::RenderUnit distanceUnit() const;
%Docstring
Returns the units for the distance between labels and the features
from the :py:func:`~QgsAbstractLabelingEngineRuleDistanceFromFeature.targetLayer`.
.. seealso:: :py:func:`setDistanceUnit`
.. seealso:: :py:func:`distance`
%End
void setDistanceUnit( Qgis::RenderUnit unit );
%Docstring
Sets the ``unit`` for the distance between labels and the features
from the :py:func:`~QgsAbstractLabelingEngineRuleDistanceFromFeature.targetLayer`.
.. seealso:: :py:func:`distanceUnit`
.. seealso:: :py:func:`setDistance`
%End
const QgsMapUnitScale &distanceUnitScale() const;
%Docstring
Returns the scaling for the distance between labels and the features
from the :py:func:`~QgsAbstractLabelingEngineRuleDistanceFromFeature.targetLayer`.
.. seealso:: :py:func:`setDistanceUnitScale`
.. seealso:: :py:func:`distance`
%End
void setDistanceUnitScale( const QgsMapUnitScale &scale );
%Docstring
Sets the ``scale`` for the distance between labels and the features
from the :py:func:`~QgsAbstractLabelingEngineRuleDistanceFromFeature.targetLayer`.
.. seealso:: :py:func:`distanceUnitScale`
.. seealso:: :py:func:`setDistance`
%End
double cost() const;
%Docstring
Returns the penalty cost incurred when the rule is violated.
This is a value between 0 and 10, where 10 indicates that the rule must never be violated,
and 1-9 = nice to have if possible, where higher numbers will try harder to avoid violating the rule.
.. seealso:: :py:func:`setCost`
%End
void setCost( double cost );
%Docstring
Sets the penalty ``cost`` incurred when the rule is violated.
This is a value between 0 and 10, where 10 indicates that the rule must never be violated,
and 1-9 = nice to have if possible, where higher numbers will try harder to avoid violating the rule.
.. seealso:: :py:func:`cost`
%End
protected:
void copyCommonProperties( QgsAbstractLabelingEngineRuleDistanceFromFeature *other ) const;
%Docstring
Copies common properties from this object to an ``other``.
%End
private:
QgsAbstractLabelingEngineRuleDistanceFromFeature( const QgsAbstractLabelingEngineRuleDistanceFromFeature &other );
};
class QgsLabelingEngineRuleMinimumDistanceLabelToFeature : QgsAbstractLabelingEngineRuleDistanceFromFeature
{
%Docstring(signature="appended")
A labeling engine rule which prevents labels being placed too close to features from a different layer.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgslabelingenginerule_impl.h"
%End
public:
QgsLabelingEngineRuleMinimumDistanceLabelToFeature();
~QgsLabelingEngineRuleMinimumDistanceLabelToFeature();
virtual QgsLabelingEngineRuleMinimumDistanceLabelToFeature *clone() const /Factory/;
virtual QString id() const;
private:
QgsLabelingEngineRuleMinimumDistanceLabelToFeature( const QgsLabelingEngineRuleMinimumDistanceLabelToFeature & );
};
class QgsLabelingEngineRuleMaximumDistanceLabelToFeature : QgsAbstractLabelingEngineRuleDistanceFromFeature
{
%Docstring(signature="appended")
A labeling engine rule which prevents labels being placed too far from features from a different layer.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgslabelingenginerule_impl.h"
%End
public:
QgsLabelingEngineRuleMaximumDistanceLabelToFeature();
~QgsLabelingEngineRuleMaximumDistanceLabelToFeature();
virtual QgsLabelingEngineRuleMaximumDistanceLabelToFeature *clone() const /Factory/;
virtual QString id() const;
private:
QgsLabelingEngineRuleMaximumDistanceLabelToFeature( const QgsLabelingEngineRuleMaximumDistanceLabelToFeature & );
};
class QgsLabelingEngineRuleMinimumDistanceLabelToLabel : QgsAbstractLabelingEngineRule
{
%Docstring(signature="appended")
A labeling engine rule which prevents labels being placed too close to labels from a different layer.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgslabelingenginerule_impl.h"
%End
public:
QgsLabelingEngineRuleMinimumDistanceLabelToLabel();
~QgsLabelingEngineRuleMinimumDistanceLabelToLabel();
virtual QgsLabelingEngineRuleMinimumDistanceLabelToLabel *clone() const /Factory/;
virtual QString id() const;
virtual void writeXml( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const;
virtual void readXml( const QDomElement &element, const QgsReadWriteContext &context );
virtual void resolveReferences( const QgsProject *project );
virtual bool prepare( QgsRenderContext &context );
QgsVectorLayer *labeledLayer();
%Docstring
Returns the layer providing the labels.
.. seealso:: :py:func:`setLabeledLayer`
%End
void setLabeledLayer( QgsVectorLayer *layer );
%Docstring
Sets the ``layer`` providing the labels.
.. seealso:: :py:func:`labeledLayer`
%End
QgsVectorLayer *targetLayer();
%Docstring
Returns the layer providing the labels which labels must be distant from.
.. seealso:: :py:func:`setTargetLayer`
%End
void setTargetLayer( QgsVectorLayer *layer );
%Docstring
Sets the ``layer`` providing the labels which labels must be distant from.
.. seealso:: :py:func:`targetLayer`
%End
double distance() const;
%Docstring
Returns the minimum permitted distance between labels from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.labeledLayer` and the labels
from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.targetLayer`.
.. seealso:: :py:func:`setDistance`
.. seealso:: :py:func:`distanceUnits`
%End
void setDistance( double distance );
%Docstring
Sets the minimum permitted ``distance`` between labels from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.labeledLayer` and the labels
from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.targetLayer`.
.. seealso:: :py:func:`distance`
.. seealso:: :py:func:`setDistanceUnits`
%End
Qgis::RenderUnit distanceUnit() const;
%Docstring
Returns the units for the distance between labels from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.labeledLayer` and the labels
from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.targetLayer`.
.. seealso:: :py:func:`setDistanceUnit`
.. seealso:: :py:func:`distance`
%End
void setDistanceUnit( Qgis::RenderUnit unit );
%Docstring
Sets the ``unit`` for the distance between labels from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.labeledLayer` and the labels
from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.targetLayer`.
.. seealso:: :py:func:`distanceUnit`
.. seealso:: :py:func:`setDistance`
%End
const QgsMapUnitScale &distanceUnitScale() const;
%Docstring
Returns the scaling for the distance between labels from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.labeledLayer` and the labels
from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.targetLayer`.
.. seealso:: :py:func:`setDistanceUnitScale`
.. seealso:: :py:func:`distance`
%End
void setDistanceUnitScale( const QgsMapUnitScale &scale );
%Docstring
Sets the ``scale`` for the distance between labels from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.labeledLayer` and the labels
from the :py:func:`~QgsLabelingEngineRuleMinimumDistanceLabelToLabel.targetLayer`.
.. seealso:: :py:func:`distanceUnitScale`
.. seealso:: :py:func:`setDistance`
%End
private:
QgsLabelingEngineRuleMinimumDistanceLabelToLabel( const QgsLabelingEngineRuleMinimumDistanceLabelToLabel & );
};
class QgsLabelingEngineRuleAvoidLabelOverlapWithFeature : QgsAbstractLabelingEngineRule
{
%Docstring(signature="appended")
A labeling engine rule which prevents labels being placed overlapping features from a different layer.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgslabelingenginerule_impl.h"
%End
public:
QgsLabelingEngineRuleAvoidLabelOverlapWithFeature();
~QgsLabelingEngineRuleAvoidLabelOverlapWithFeature();
virtual QgsLabelingEngineRuleAvoidLabelOverlapWithFeature *clone() const /Factory/;
virtual QString id() const;
virtual bool prepare( QgsRenderContext &context );
virtual void writeXml( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const;
virtual void readXml( const QDomElement &element, const QgsReadWriteContext &context );
virtual void resolveReferences( const QgsProject *project );
QgsVectorLayer *labeledLayer();
%Docstring
Returns the layer providing the labels.
.. seealso:: :py:func:`setLabeledLayer`
%End
void setLabeledLayer( QgsVectorLayer *layer );
%Docstring
Sets the ``layer`` providing the labels.
.. seealso:: :py:func:`labeledLayer`
%End
QgsVectorLayer *targetLayer();
%Docstring
Returns the layer providing the features which labels must not overlap.
.. seealso:: :py:func:`setTargetLayer`
%End
void setTargetLayer( QgsVectorLayer *layer );
%Docstring
Sets the ``layer`` providing the features which labels must not overlap.
.. seealso:: :py:func:`targetLayer`
%End
private:
QgsLabelingEngineRuleAvoidLabelOverlapWithFeature( const QgsLabelingEngineRuleAvoidLabelOverlapWithFeature & );
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/labeling/rules/qgslabelingenginerule_impl.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/

View File

@ -0,0 +1,82 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/labeling/rules/qgslabelingengineruleregistry.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/
class QgsLabelingEngineRuleRegistry
{
%Docstring(signature="appended")
A registry 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.
This registry stores available rules and is responsible for creating rules.
:py:class:`QgsLabelingEngineRuleRegistry` is not usually directly created, but rather accessed through
:py:func:`QgsApplication.labelEngineRuleRegistry()`.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgslabelingengineruleregistry.h"
%End
public:
QgsLabelingEngineRuleRegistry();
%Docstring
Constructor for QgsLabelingEngineRuleRegistry, containing a set of
default rules.
%End
~QgsLabelingEngineRuleRegistry();
QStringList ruleIds() const;
%Docstring
Returns a list of the rule IDs for rules present in the registry.
%End
QgsAbstractLabelingEngineRule *create( const QString &id ) const /TransferBack/;
%Docstring
Creates a new rule from the type with matching ``id``.
Returns ``None`` if no matching rule was found in the registry.
The caller takes ownership of the returned object.
%End
bool addRule( QgsAbstractLabelingEngineRule *rule /Transfer/ );
%Docstring
Adds a new ``rule`` type to the registry.
The registry takes ownership of ``rule``.
:return: ``True`` if the rule was successfully added.
.. seealso:: :py:func:`removeRule`
%End
void removeRule( const QString &id );
%Docstring
Removes the rule with matching ``id`` from the registry.
.. seealso:: :py:func:`addRule`
%End
private:
QgsLabelingEngineRuleRegistry( const QgsLabelingEngineRuleRegistry &other );
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/labeling/rules/qgslabelingengineruleregistry.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/

View File

@ -944,6 +944,13 @@ Returns registry of available 3D symbols.
Gets the registry of available scalebar renderers.
.. versionadded:: 3.14
%End
static QgsLabelingEngineRuleRegistry *labelingEngineRuleRegistry() /KeepReference/;
%Docstring
Gets the registry of available labeling engine rules.
.. versionadded:: 3.40
%End
static QgsProjectStorageRegistry *projectStorageRegistry() /KeepReference/;

View File

@ -391,6 +391,9 @@
%Include auto_generated/labeling/qgspallabeling.sip
%Include auto_generated/labeling/qgsrulebasedlabeling.sip
%Include auto_generated/labeling/qgsvectorlayerlabeling.sip
%Include auto_generated/labeling/rules/qgslabelingenginerule.sip
%Include auto_generated/labeling/rules/qgslabelingenginerule_impl.sip
%Include auto_generated/labeling/rules/qgslabelingengineruleregistry.sip
%Include auto_generated/layertree/qgscolorramplegendnode.sip
%Include auto_generated/layertree/qgscolorramplegendnodesettings.sip
%Include auto_generated/layertree/qgslayertree.sip

View File

@ -875,6 +875,9 @@ set(QGIS_CORE_SRCS
labeling/qgstextlabelfeature.cpp
labeling/qgsvectorlayerlabeling.cpp
labeling/qgsvectorlayerlabelprovider.cpp
labeling/rules/qgslabelingenginerule.cpp
labeling/rules/qgslabelingenginerule_impl.cpp
labeling/rules/qgslabelingengineruleregistry.cpp
geometry/qgsabstractgeometry.cpp
geometry/qgsbox3d.cpp
@ -1538,6 +1541,9 @@ set(QGIS_CORE_HDRS
labeling/qgstextlabelfeature.h
labeling/qgsvectorlayerlabeling.h
labeling/qgsvectorlayerlabelprovider.h
labeling/rules/qgslabelingenginerule.h
labeling/rules/qgslabelingenginerule_impl.h
labeling/rules/qgslabelingengineruleregistry.h
layertree/qgscolorramplegendnode.h
layertree/qgscolorramplegendnodesettings.h
@ -2352,6 +2358,7 @@ target_include_directories(qgis_core PUBLIC
geocoding
gps
labeling
labeling/rules
layertree
layout
locator

View File

@ -31,6 +31,7 @@
#include "qgslabelingresults.h"
#include "qgsfillsymbol.h"
#include "qgsruntimeprofiler.h"
#include "qgslabelingenginerule.h"
// helper function for checking for job cancellation within PAL
static bool _palIsCanceled( void *ctx )
@ -100,6 +101,19 @@ void QgsLabelingEngine::setMapSettings( const QgsMapSettings &mapSettings )
mResults->setMapSettings( mapSettings );
}
bool QgsLabelingEngine::prepare( QgsRenderContext &context )
{
const QList<const QgsAbstractLabelingEngineRule *> rules = mMapSettings.labelingEngineSettings().rules();
bool res = true;
for ( const QgsAbstractLabelingEngineRule *rule : rules )
{
std::unique_ptr< QgsAbstractLabelingEngineRule > ruleClone( rule->clone() );
res = ruleClone->prepare( context ) && res;
mEngineRules.emplace_back( std::move( ruleClone ) );
}
return res;
}
QList< QgsMapLayer * > QgsLabelingEngine::participatingLayers() const
{
QList< QgsMapLayer * > layers;

View File

@ -356,6 +356,15 @@ class CORE_EXPORT QgsLabelingEngine
//! Gets associated labeling engine settings
const QgsLabelingEngineSettings &engineSettings() const { return mMapSettings.labelingEngineSettings(); }
/**
* Prepares the engine for rendering in the specified \a context.
*
* \warning This method must be called in advanced on the main rendering thread, not a background thread.
*
* \since QGIS 3.40
*/
bool prepare( QgsRenderContext &context );
/**
* Returns a list of layers with providers in the engine.
*/
@ -436,6 +445,9 @@ class CORE_EXPORT QgsLabelingEngine
QList<QgsAbstractLabelProvider *> mProviders;
QList<QgsAbstractLabelProvider *> mSubProviders;
//!< List of labeling engine rules (owned by the labeling engine)
std::vector< std::unique_ptr< QgsAbstractLabelingEngineRule > > mEngineRules;
//! Resulting labeling layout
std::unique_ptr< QgsLabelingResults > mResults;

View File

@ -17,11 +17,50 @@
#include "qgsproject.h"
#include "qgscolorutils.h"
#include "qgslabelingenginerule.h"
#include "qgsapplication.h"
#include "qgslabelingengineruleregistry.h"
QgsLabelingEngineSettings::QgsLabelingEngineSettings()
{
}
QgsLabelingEngineSettings::~QgsLabelingEngineSettings() = default;
QgsLabelingEngineSettings::QgsLabelingEngineSettings( const QgsLabelingEngineSettings &other )
: mFlags( other.mFlags )
, mSearchMethod( other.mSearchMethod )
, mMaxLineCandidatesPerCm( other.mMaxLineCandidatesPerCm )
, mMaxPolygonCandidatesPerCmSquared( other.mMaxPolygonCandidatesPerCmSquared )
, mUnplacedLabelColor( other.mUnplacedLabelColor )
, mPlacementVersion( other.mPlacementVersion )
, mDefaultTextRenderFormat( other.mDefaultTextRenderFormat )
{
mEngineRules.reserve( other.mEngineRules.size() );
for ( const auto &rule : other.mEngineRules )
{
mEngineRules.emplace_back( rule->clone() );
}
}
QgsLabelingEngineSettings &QgsLabelingEngineSettings::operator=( const QgsLabelingEngineSettings &other )
{
mFlags = other.mFlags;
mSearchMethod = other.mSearchMethod;
mMaxLineCandidatesPerCm = other.mMaxLineCandidatesPerCm;
mMaxPolygonCandidatesPerCmSquared = other.mMaxPolygonCandidatesPerCmSquared;
mUnplacedLabelColor = other.mUnplacedLabelColor;
mPlacementVersion = other.mPlacementVersion;
mDefaultTextRenderFormat = other.mDefaultTextRenderFormat;
mEngineRules.clear();
mEngineRules.reserve( other.mEngineRules.size() );
for ( const auto &rule : other.mEngineRules )
{
mEngineRules.emplace_back( rule->clone() );
}
return *this;
}
void QgsLabelingEngineSettings::clear()
{
*this = QgsLabelingEngineSettings();
@ -76,6 +115,50 @@ void QgsLabelingEngineSettings::writeSettingsToProject( QgsProject *project )
project->writeEntry( QStringLiteral( "PAL" ), QStringLiteral( "/PlacementEngineVersion" ), static_cast< int >( mPlacementVersion ) );
}
void QgsLabelingEngineSettings::writeXml( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const
{
if ( !mEngineRules.empty() )
{
QDomElement rulesElement = doc.createElement( QStringLiteral( "rules" ) );
for ( const auto &rule : mEngineRules )
{
QDomElement ruleElement = doc.createElement( QStringLiteral( "rule" ) );
ruleElement.setAttribute( QStringLiteral( "id" ), rule->id() );
rule->writeXml( doc, ruleElement, context );
rulesElement.appendChild( ruleElement );
}
element.appendChild( rulesElement );
}
}
void QgsLabelingEngineSettings::readXml( const QDomElement &element, const QgsReadWriteContext &context )
{
mEngineRules.clear();
{
const QDomElement rulesElement = element.firstChildElement( QStringLiteral( "rules" ) );
const QDomNodeList rules = rulesElement.childNodes();
for ( int i = 0; i < rules.length(); i++ )
{
const QDomElement ruleElement = rules.at( i ).toElement();
const QString id = ruleElement.attribute( QStringLiteral( "id" ) );
std::unique_ptr< QgsAbstractLabelingEngineRule > rule( QgsApplication::labelingEngineRuleRegistry()->create( id ) );
if ( rule )
{
rule->readXml( ruleElement, context );
mEngineRules.emplace_back( std::move( rule ) );
}
}
}
}
void QgsLabelingEngineSettings::resolveReferences( const QgsProject *project )
{
for ( const auto &rule : mEngineRules )
{
rule->resolveReferences( project );
}
}
QColor QgsLabelingEngineSettings::unplacedLabelColor() const
{
return mUnplacedLabelColor;
@ -96,4 +179,38 @@ void QgsLabelingEngineSettings::setPlacementVersion( Qgis::LabelPlacementEngineV
mPlacementVersion = placementVersion;
}
QList<QgsAbstractLabelingEngineRule *> QgsLabelingEngineSettings::rules()
{
QList<QgsAbstractLabelingEngineRule *> res;
for ( const auto &it : mEngineRules )
{
res << it.get();
}
return res;
}
QList<const QgsAbstractLabelingEngineRule *> QgsLabelingEngineSettings::rules() const
{
QList<const QgsAbstractLabelingEngineRule *> res;
for ( const auto &it : mEngineRules )
{
res << it.get();
}
return res;
}
void QgsLabelingEngineSettings::addRule( QgsAbstractLabelingEngineRule *rule )
{
mEngineRules.emplace_back( rule );
}
void QgsLabelingEngineSettings::setRules( const QList<QgsAbstractLabelingEngineRule *> &rules )
{
mEngineRules.clear();
for ( QgsAbstractLabelingEngineRule *rule : rules )
{
mEngineRules.emplace_back( rule );
}
}

View File

@ -21,6 +21,10 @@
#include <QColor>
class QgsProject;
class QgsAbstractLabelingEngineRule;
class QDomDocument;
class QDomElement;
class QgsReadWriteContext;
/**
* \ingroup core
@ -46,6 +50,10 @@ class CORE_EXPORT QgsLabelingEngineSettings
};
QgsLabelingEngineSettings();
~QgsLabelingEngineSettings();
QgsLabelingEngineSettings( const QgsLabelingEngineSettings &other );
QgsLabelingEngineSettings &operator=( const QgsLabelingEngineSettings &other );
//! Returns the configuration to the defaults
void clear();
@ -125,11 +133,57 @@ class CORE_EXPORT QgsLabelingEngineSettings
*/
Q_DECL_DEPRECATED Search searchMethod() const SIP_DEPRECATED { return Chain; }
//! Read configuration of the labeling engine from a project
// TODO QGIS 4.0 -- remove these, and just use read/writeXml directly:
/**
* Read configuration of the labeling engine from a project
*
* \note Both this method and readXml() must be called to completely restore the object's state from a project.
*/
void readSettingsFromProject( QgsProject *project );
//! Write configuration of the labeling engine to a project
/**
* Write configuration of the labeling engine to a project.
*
* \note Both this method and writeXml() must be called to completely store the object's state in a project.
*/
void writeSettingsToProject( QgsProject *project );
/**
* Writes the label engine settings to an XML \a element.
*
* \note Both this method and writeSettingsToProject() must be called to completely store the object's state in a project.
*
* \see readXml()
* \see writeSettingsToProject()
*
* \since QGIS 3.40
*/
void writeXml( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const;
/**
* Reads the label engine settings from an XML \a element.
*
* \note Both this method and readSettingsFromProject() must be called to completely restore the object's state from a project.
*
* \note resolveReferences() must be called following this method.
*
* \see writeXml()
* \see readSettingsFromProject()
*
* \since QGIS 3.40
*/
void readXml( const QDomElement &element, const QgsReadWriteContext &context );
/**
* Resolves reference to layers from stored layer ID.
*
* Should be called following a call readXml().
*
* \since QGIS 3.40
*/
void resolveReferences( const QgsProject *project );
// TODO QGIS 4.0: In reality the text render format settings don't only apply to labels, but also
// ANY text rendered using QgsTextRenderer (including some non-label text items in layouts).
// These methods should possibly be moved out of here and into the general QgsProject settings.
@ -188,6 +242,50 @@ class CORE_EXPORT QgsLabelingEngineSettings
*/
void setPlacementVersion( Qgis::LabelPlacementEngineVersion version );
/**
* Returns a list of labeling engine rules which must be satifisfied
* while placing labels.
*
* \see addRule()
* \see setRules()
* \since QGIS 3.40
*/
QList< QgsAbstractLabelingEngineRule * > rules();
/**
* Returns a list of labeling engine rules which must be satifisfied
* while placing labels.
*
* \see addRule()
* \see setRules()
* \since QGIS 3.40
*/
QList< const QgsAbstractLabelingEngineRule * > rules() const SIP_SKIP;
/**
* Adds a labeling engine \a rule which must be satifisfied
* while placing labels.
*
* Ownership of the rule is transferred to the settings.
*
* \see rules()
* \see setRules()
* \since QGIS 3.40
*/
void addRule( QgsAbstractLabelingEngineRule *rule SIP_TRANSFER );
/**
* Sets the labeling engine \a rules which must be satifisfied
* while placing labels.
*
* Ownership of the rules are transferred to the settings.
*
* \see addRule()
* \see rules()
* \since QGIS 3.40
*/
void setRules( const QList< QgsAbstractLabelingEngineRule * > &rules SIP_TRANSFER );
private:
//! Flags
Qgis::LabelingFlags mFlags = Qgis::LabelingFlag::UsePartialCandidates;
@ -204,6 +302,8 @@ class CORE_EXPORT QgsLabelingEngineSettings
Qgis::TextRenderFormat mDefaultTextRenderFormat = Qgis::TextRenderFormat::AlwaysOutlines;
std::vector< std::unique_ptr< QgsAbstractLabelingEngineRule > > mEngineRules;
};
#endif // QGSLABELINGENGINESETTINGS_H

View File

@ -0,0 +1,78 @@
/***************************************************************************
qgslabelingenginerule.cpp
---------------------
Date : August 2024
Copyright : (C) 2024 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgslabelingenginerule.h"
//
// QgsLabelingEngineContext
//
QgsLabelingEngineContext::QgsLabelingEngineContext( QgsRenderContext &renderContext )
: mRenderContext( renderContext )
{
}
QgsGeometry QgsLabelingEngineContext::mapBoundaryGeometry() const
{
return mMapBoundaryGeometry;
}
void QgsLabelingEngineContext::setMapBoundaryGeometry( const QgsGeometry &geometry )
{
mMapBoundaryGeometry = geometry;
}
QgsRectangle QgsLabelingEngineContext::extent() const
{
return mExtent;
}
void QgsLabelingEngineContext::setExtent( const QgsRectangle &extent )
{
mExtent = extent;
}
//
// QgsAbstractLabelingEngineRule
//
QgsAbstractLabelingEngineRule::~QgsAbstractLabelingEngineRule() = default;
void QgsAbstractLabelingEngineRule::resolveReferences( const QgsProject * )
{
}
bool QgsAbstractLabelingEngineRule::candidatesAreConflicting( const pal::LabelPosition *, const pal::LabelPosition * ) const
{
return false;
}
QgsRectangle QgsAbstractLabelingEngineRule::modifyCandidateConflictSearchBoundingBox( const QgsRectangle &candidateBounds ) const
{
return candidateBounds;
}
bool QgsAbstractLabelingEngineRule::candidateIsIllegal( const pal::LabelPosition *, QgsLabelingEngineContext & ) const
{
return false;
}
void QgsAbstractLabelingEngineRule::alterCandidateCost( pal::LabelPosition *, QgsLabelingEngineContext & ) const
{
}

View File

@ -0,0 +1,238 @@
/***************************************************************************
qgslabelingenginerule.h
---------------------
Date : August 2024
Copyright : (C) 2024 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSLABELINGENGINERULE_H
#define QGSLABELINGENGINERULE_H
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgis.h"
#include "qgsgeometry.h"
class QgsRenderContext;
class QDomDocument;
class QDomElement;
class QgsReadWriteContext;
class QgsProject;
#ifndef SIP_RUN
namespace pal
{
class LabelPosition;
}
#endif
/**
* \ingroup core
* \brief Encapsulates the context for a labeling engine run.
* \since QGIS 3.40
*/
class CORE_EXPORT QgsLabelingEngineContext
{
public:
/**
* Constructor for QgsLabelingEngineContext.
*/
QgsLabelingEngineContext( QgsRenderContext &renderContext );
#ifndef SIP_RUN
QgsLabelingEngineContext( const QgsLabelingEngineContext &other ) = delete;
QgsLabelingEngineContext &operator=( const QgsLabelingEngineContext &other ) = delete;
#endif
/**
* Returns a reference to the context's render context.
*/
QgsRenderContext &renderContext() { return mRenderContext; }
/**
* Returns a reference to the context's render context.
* \note Not available in Python bindings.
*/
const QgsRenderContext &renderContext() const { return mRenderContext; } SIP_SKIP
/**
* Returns the map extent defining the limits for labeling.
*
* \see mapBoundaryGeometry()
* \see setExtent()
*/
QgsRectangle extent() const;
/**
* Sets the map \a extent defining the limits for labeling.
*
* \see setMapBoundaryGeometry()
* \see extent()
*/
void setExtent( const QgsRectangle &extent );
/**
* 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 extent() in the case of rotated or non-rectangular
* maps.
*
* \see setMapBoundaryGeometry()
* \see extent()
*/
QgsGeometry mapBoundaryGeometry() const;
/**
* Sets the map label boundary \a 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 extent() in the case of rotated or non-rectangular
* maps.
*
* \see setExtent()
* \see mapBoundaryGeometry()
*/
void setMapBoundaryGeometry( const QgsGeometry &geometry );
private:
#ifdef SIP_RUN
QgsLabelingEngineContext( const QgsLabelingEngineContext &other );
#endif
QgsRenderContext &mRenderContext;
QgsRectangle mExtent;
QgsGeometry mMapBoundaryGeometry;
};
/**
* 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 QgsAbstractLabelingEngineRule cannot be subclassed in Python. Use one of the existing
* implementations of this class instead.
*
* \ingroup core
* \since QGIS 3.40
*/
class CORE_EXPORT QgsAbstractLabelingEngineRule
{
#ifdef SIP_RUN
SIP_CONVERT_TO_SUBCLASS_CODE
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;
}
SIP_END
#endif
public:
virtual ~QgsAbstractLabelingEngineRule();
/**
* Creates a clone of this rule.
*
* The caller takes ownership of the returned object.
*/
virtual QgsAbstractLabelingEngineRule *clone() const = 0 SIP_FACTORY;
/**
* Returns a string uniquely identifying the rule subclass.
*/
virtual QString id() const = 0;
/**
* 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.
*/
virtual bool prepare( QgsRenderContext &context ) = 0;
/**
* Writes the rule properties to an XML \a element.
*
* \see readXml()
*/
virtual void writeXml( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const = 0;
/**
* Reads the rule properties from an XML \a element.
*
* \see resolveReferences()
* \see writeXml()
*/
virtual void readXml( const QDomElement &element, const QgsReadWriteContext &context ) = 0;
/**
* Resolves reference to layers from stored layer ID.
*
* Should be called following a call readXml().
*/
virtual void resolveReferences( const QgsProject *project );
/**
* Returns TRUE if a labeling candidate \a lp1 conflicts with \a lp2 after applying the rule.
*
* The default implementation returns FALSE.
*/
virtual bool candidatesAreConflicting( const pal::LabelPosition *lp1, const pal::LabelPosition *lp2 ) const SIP_SKIP;
/**
* Returns a (possibly expanded) bounding box to use when searching for conflicts for a candidate.
*
* The return value is permitted to grow the bounding box, but may NOT shrink it.
*
* The default implementation returns the same bounds.
*/
virtual QgsRectangle modifyCandidateConflictSearchBoundingBox( const QgsRectangle &candidateBounds ) const SIP_SKIP;
/**
* Returns TRUE if a labeling \a candidate violates the rule and should be eliminated.
*
* The default implementation returns FALSE.
*/
virtual bool candidateIsIllegal( const pal::LabelPosition *candidate, QgsLabelingEngineContext &context ) const SIP_SKIP;
/**
* Provides an opportunity for the rule to alter the cost for a \a candidate.
*
* The default implementation does nothing.
*/
virtual void alterCandidateCost( pal::LabelPosition *candidate, QgsLabelingEngineContext &context ) const SIP_SKIP;
};
#endif // QGSLABELINGENGINESETTINGS_H

View File

@ -0,0 +1,548 @@
/***************************************************************************
qgslabelingenginerule_impl.cpp
---------------------
Date : August 2024
Copyright : (C) 2024 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgslabelingenginerule_impl.h"
#include "qgsunittypes.h"
#include "qgssymbollayerutils.h"
#include "labelposition.h"
#include "feature.h"
#include "qgsvectorlayerfeatureiterator.h"
#include "qgsthreadingutils.h"
#include "qgsspatialindex.h"
#include "qgsgeos.h"
//
// QgsAbstractLabelingEngineRuleDistanceFromFeature
//
QgsAbstractLabelingEngineRuleDistanceFromFeature::QgsAbstractLabelingEngineRuleDistanceFromFeature() = default;
QgsAbstractLabelingEngineRuleDistanceFromFeature::~QgsAbstractLabelingEngineRuleDistanceFromFeature() = default;
bool QgsAbstractLabelingEngineRuleDistanceFromFeature::prepare( QgsRenderContext &context )
{
if ( !mTargetLayer )
return false;
QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS( mTargetLayer );
mTargetLayerSource = std::make_unique< QgsVectorLayerFeatureSource >( mTargetLayer.get() );
mDistanceMapUnits = context.convertToMapUnits( mDistance, mDistanceUnit, mDistanceUnitScale );
return true;
}
void QgsAbstractLabelingEngineRuleDistanceFromFeature::writeXml( QDomDocument &, QDomElement &element, const QgsReadWriteContext & ) const
{
element.setAttribute( QStringLiteral( "distance" ), mDistance );
element.setAttribute( QStringLiteral( "distanceUnit" ), QgsUnitTypes::encodeUnit( mDistanceUnit ) );
element.setAttribute( QStringLiteral( "distanceUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceUnitScale ) );
element.setAttribute( QStringLiteral( "cost" ), mCost );
if ( mLabeledLayer )
{
element.setAttribute( QStringLiteral( "labeledLayer" ), mLabeledLayer.layerId );
element.setAttribute( QStringLiteral( "labeledLayerName" ), mLabeledLayer.name );
element.setAttribute( QStringLiteral( "labeledLayerSource" ), mLabeledLayer.source );
element.setAttribute( QStringLiteral( "labeledLayerProvider" ), mLabeledLayer.provider );
}
if ( mTargetLayer )
{
element.setAttribute( QStringLiteral( "targetLayer" ), mTargetLayer.layerId );
element.setAttribute( QStringLiteral( "targetLayerName" ), mTargetLayer.name );
element.setAttribute( QStringLiteral( "targetLayerSource" ), mTargetLayer.source );
element.setAttribute( QStringLiteral( "targetLayerProvider" ), mTargetLayer.provider );
}
}
void QgsAbstractLabelingEngineRuleDistanceFromFeature::readXml( const QDomElement &element, const QgsReadWriteContext & )
{
mDistance = element.attribute( QStringLiteral( "distance" ), QStringLiteral( "0" ) ).toDouble();
mDistanceUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "distanceUnit" ) ) );
mDistanceUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( element.attribute( QStringLiteral( "distanceUnitScale" ) ) );
mCost = element.attribute( QStringLiteral( "cost" ), QStringLiteral( "0" ) ).toDouble();
{
const QString layerId = element.attribute( QStringLiteral( "labeledLayer" ) );
const QString layerName = element.attribute( QStringLiteral( "labeledLayerName" ) );
const QString layerSource = element.attribute( QStringLiteral( "labeledLayerSource" ) );
const QString layerProvider = element.attribute( QStringLiteral( "labeledLayerProvider" ) );
mLabeledLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
}
{
const QString layerId = element.attribute( QStringLiteral( "targetLayer" ) );
const QString layerName = element.attribute( QStringLiteral( "targetLayerName" ) );
const QString layerSource = element.attribute( QStringLiteral( "targetLayerSource" ) );
const QString layerProvider = element.attribute( QStringLiteral( "targetLayerProvider" ) );
mTargetLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
}
}
void QgsAbstractLabelingEngineRuleDistanceFromFeature::resolveReferences( const QgsProject *project )
{
mLabeledLayer.resolve( project );
mTargetLayer.resolve( project );
}
bool QgsAbstractLabelingEngineRuleDistanceFromFeature::candidateIsIllegal( const pal::LabelPosition *candidate, QgsLabelingEngineContext &context ) const
{
// hard blocks on candidates only apply when cost == 10
if ( mCost < 10 )
return false;
if ( candidate->getFeaturePart()->feature()->provider()->layerId() != mLabeledLayer.layerId )
{
return false;
}
if ( !mTargetLayerSource )
return false;
return candidateExceedsTolerance( candidate, context );
}
void QgsAbstractLabelingEngineRuleDistanceFromFeature::alterCandidateCost( pal::LabelPosition *candidate, QgsLabelingEngineContext &context ) const
{
// cost of 10 = hard block, handled in candidateIsIllegal
if ( mCost >= 10 )
return;
if ( candidate->getFeaturePart()->feature()->provider()->layerId() != mLabeledLayer.layerId )
{
return;
}
if ( !mTargetLayerSource )
return;
if ( candidateExceedsTolerance( candidate, context ) )
{
// magic number alert! / 1000 here is completely arbitrary, an attempt to balance against the cost scaling of other factors
// assigned by the inscrutible logic of the pal engine internals
candidate->setCost( candidate->cost() + mCost / 1000 );
}
}
QgsVectorLayer *QgsAbstractLabelingEngineRuleDistanceFromFeature::labeledLayer()
{
return mLabeledLayer.get();
}
void QgsAbstractLabelingEngineRuleDistanceFromFeature::setLabeledLayer( QgsVectorLayer *layer )
{
mLabeledLayer = layer;
}
QgsVectorLayer *QgsAbstractLabelingEngineRuleDistanceFromFeature::targetLayer()
{
return mTargetLayer.get();
}
void QgsAbstractLabelingEngineRuleDistanceFromFeature::setTargetLayer( QgsVectorLayer *layer )
{
mTargetLayer = layer;
}
void QgsAbstractLabelingEngineRuleDistanceFromFeature::copyCommonProperties( QgsAbstractLabelingEngineRuleDistanceFromFeature *other ) const
{
other->mLabeledLayer = mLabeledLayer;
other->mTargetLayer = mTargetLayer;
other->mDistance = mDistance;
other->mDistanceUnit = mDistanceUnit;
other->mDistanceUnitScale = mDistanceUnitScale;
other->mCost = mCost;
}
void QgsAbstractLabelingEngineRuleDistanceFromFeature::initialize( QgsLabelingEngineContext &context )
{
QgsFeatureRequest req;
req.setDestinationCrs( context.renderContext().coordinateTransform().destinationCrs(), context.renderContext().transformContext() );
req.setFilterRect( context.extent() );
req.setNoAttributes();
QgsFeatureIterator it = mTargetLayerSource->getFeatures( req );
mIndex = std::make_unique< QgsSpatialIndex >( it, context.renderContext().feedback(), QgsSpatialIndex::Flag::FlagStoreFeatureGeometries );
mInitialized = true;
}
bool QgsAbstractLabelingEngineRuleDistanceFromFeature::candidateExceedsTolerance( const pal::LabelPosition *candidate, QgsLabelingEngineContext &context ) const
{
if ( !mInitialized )
const_cast< QgsAbstractLabelingEngineRuleDistanceFromFeature * >( this )->initialize( context );
const QgsRectangle candidateBounds = candidate->boundingBox();
const QgsRectangle expandedBounds = candidateBounds.buffered( mDistanceMapUnits );
const QList<QgsFeatureId> overlapCandidates = mIndex->intersects( expandedBounds );
if ( overlapCandidates.empty() )
return !mMustBeDistant;
GEOSContextHandle_t geosctxt = QgsGeosContext::get();
const GEOSPreparedGeometry *candidateGeos = candidate->preparedMultiPartGeom();
for ( const QgsFeatureId overlapCandidateId : overlapCandidates )
{
if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
break;
try
{
geos::unique_ptr featureCandidate = QgsGeos::asGeos( mIndex->geometry( overlapCandidateId ).constGet() );
#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
if ( GEOSPreparedDistanceWithin_r( geosctxt, candidateGeos, featureCandidate.get(), mDistanceMapUnits ) )
{
return mMustBeDistant;
}
#else
QgsDebugError( QStringLiteral( "This rule requires GEOS 3.10+" ) );
return false;
#endif
}
catch ( GEOSException &e )
{
QgsDebugError( QStringLiteral( "GEOS exception: %1" ).arg( e.what() ) );
}
}
return !mMustBeDistant;
}
//
// QgsLabelingEngineRuleMinimumDistanceLabelToFeature
//
QgsLabelingEngineRuleMinimumDistanceLabelToFeature::QgsLabelingEngineRuleMinimumDistanceLabelToFeature() = default;
QgsLabelingEngineRuleMinimumDistanceLabelToFeature::~QgsLabelingEngineRuleMinimumDistanceLabelToFeature() = default;
QgsLabelingEngineRuleMinimumDistanceLabelToFeature *QgsLabelingEngineRuleMinimumDistanceLabelToFeature::clone() const
{
std::unique_ptr< QgsLabelingEngineRuleMinimumDistanceLabelToFeature> res = std::make_unique< QgsLabelingEngineRuleMinimumDistanceLabelToFeature >();
copyCommonProperties( res.get() );
return res.release();
}
QString QgsLabelingEngineRuleMinimumDistanceLabelToFeature::id() const
{
return QStringLiteral( "minimumDistanceLabelToFeature" );
}
//
// QgsLabelingEngineRuleMaximumDistanceLabelToFeature
//
QgsLabelingEngineRuleMaximumDistanceLabelToFeature::QgsLabelingEngineRuleMaximumDistanceLabelToFeature()
{
mMustBeDistant = false;
}
QgsLabelingEngineRuleMaximumDistanceLabelToFeature::~QgsLabelingEngineRuleMaximumDistanceLabelToFeature() = default;
QgsLabelingEngineRuleMaximumDistanceLabelToFeature *QgsLabelingEngineRuleMaximumDistanceLabelToFeature::clone() const
{
std::unique_ptr< QgsLabelingEngineRuleMaximumDistanceLabelToFeature > res = std::make_unique< QgsLabelingEngineRuleMaximumDistanceLabelToFeature >();
copyCommonProperties( res.get() );
return res.release();
}
QString QgsLabelingEngineRuleMaximumDistanceLabelToFeature::id() const
{
return QStringLiteral( "maximumDistanceLabelToFeature" );
}
//
// QgsLabelingEngineRuleMinimumDistanceLabelToLabel
//
QgsLabelingEngineRuleMinimumDistanceLabelToLabel::QgsLabelingEngineRuleMinimumDistanceLabelToLabel() = default;
QgsLabelingEngineRuleMinimumDistanceLabelToLabel::~QgsLabelingEngineRuleMinimumDistanceLabelToLabel() = default;
QgsLabelingEngineRuleMinimumDistanceLabelToLabel *QgsLabelingEngineRuleMinimumDistanceLabelToLabel::clone() const
{
std::unique_ptr< QgsLabelingEngineRuleMinimumDistanceLabelToLabel> res = std::make_unique< QgsLabelingEngineRuleMinimumDistanceLabelToLabel >();
res->mLabeledLayer = mLabeledLayer;
res->mTargetLayer = mTargetLayer;
res->mDistance = mDistance;
res->mDistanceUnit = mDistanceUnit;
res->mDistanceUnitScale = mDistanceUnitScale;
return res.release();
}
QString QgsLabelingEngineRuleMinimumDistanceLabelToLabel::id() const
{
return QStringLiteral( "minimumDistanceLabelToLabel" );
}
void QgsLabelingEngineRuleMinimumDistanceLabelToLabel::writeXml( QDomDocument &, QDomElement &element, const QgsReadWriteContext & ) const
{
element.setAttribute( QStringLiteral( "distance" ), mDistance );
element.setAttribute( QStringLiteral( "distanceUnit" ), QgsUnitTypes::encodeUnit( mDistanceUnit ) );
element.setAttribute( QStringLiteral( "distanceUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceUnitScale ) );
if ( mLabeledLayer )
{
element.setAttribute( QStringLiteral( "labeledLayer" ), mLabeledLayer.layerId );
element.setAttribute( QStringLiteral( "labeledLayerName" ), mLabeledLayer.name );
element.setAttribute( QStringLiteral( "labeledLayerSource" ), mLabeledLayer.source );
element.setAttribute( QStringLiteral( "labeledLayerProvider" ), mLabeledLayer.provider );
}
if ( mTargetLayer )
{
element.setAttribute( QStringLiteral( "targetLayer" ), mTargetLayer.layerId );
element.setAttribute( QStringLiteral( "targetLayerName" ), mTargetLayer.name );
element.setAttribute( QStringLiteral( "targetLayerSource" ), mTargetLayer.source );
element.setAttribute( QStringLiteral( "targetLayerProvider" ), mTargetLayer.provider );
}
}
void QgsLabelingEngineRuleMinimumDistanceLabelToLabel::readXml( const QDomElement &element, const QgsReadWriteContext & )
{
mDistance = element.attribute( QStringLiteral( "distance" ), QStringLiteral( "0" ) ).toDouble();
mDistanceUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "distanceUnit" ) ) );
mDistanceUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( element.attribute( QStringLiteral( "distanceUnitScale" ) ) );
{
const QString layerId = element.attribute( QStringLiteral( "labeledLayer" ) );
const QString layerName = element.attribute( QStringLiteral( "labeledLayerName" ) );
const QString layerSource = element.attribute( QStringLiteral( "labeledLayerSource" ) );
const QString layerProvider = element.attribute( QStringLiteral( "labeledLayerProvider" ) );
mLabeledLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
}
{
const QString layerId = element.attribute( QStringLiteral( "targetLayer" ) );
const QString layerName = element.attribute( QStringLiteral( "targetLayerName" ) );
const QString layerSource = element.attribute( QStringLiteral( "targetLayerSource" ) );
const QString layerProvider = element.attribute( QStringLiteral( "targetLayerProvider" ) );
mTargetLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
}
}
void QgsLabelingEngineRuleMinimumDistanceLabelToLabel::resolveReferences( const QgsProject *project )
{
mLabeledLayer.resolve( project );
mTargetLayer.resolve( project );
}
bool QgsLabelingEngineRuleMinimumDistanceLabelToLabel::prepare( QgsRenderContext &context )
{
mDistanceMapUnits = context.convertToMapUnits( mDistance, mDistanceUnit, mDistanceUnitScale );
return true;
}
QgsRectangle QgsLabelingEngineRuleMinimumDistanceLabelToLabel::modifyCandidateConflictSearchBoundingBox( const QgsRectangle &candidateBounds ) const
{
return candidateBounds.buffered( mDistanceMapUnits );
}
bool QgsLabelingEngineRuleMinimumDistanceLabelToLabel::candidatesAreConflicting( const pal::LabelPosition *lp1, const pal::LabelPosition *lp2 ) const
{
// conflicts are commutative -- we need to check both layers
if (
( lp1->getFeaturePart()->feature()->provider()->layerId() == mLabeledLayer.layerId
&& lp2->getFeaturePart()->feature()->provider()->layerId() == mTargetLayer.layerId )
||
( lp2->getFeaturePart()->feature()->provider()->layerId() == mLabeledLayer.layerId
&& lp1->getFeaturePart()->feature()->provider()->layerId() == mTargetLayer.layerId )
)
{
GEOSContextHandle_t geosctxt = QgsGeosContext::get();
try
{
#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
if ( GEOSPreparedDistanceWithin_r( geosctxt, lp1->preparedMultiPartGeom(), lp2->multiPartGeom(), mDistanceMapUnits ) )
{
return true;
}
#else
QgsDebugError( QStringLiteral( "This rule requires GEOS 3.10+" ) );
return false;
#endif
}
catch ( GEOSException &e )
{
QgsDebugError( QStringLiteral( "GEOS exception: %1" ).arg( e.what() ) );
}
}
return false;
}
QgsVectorLayer *QgsLabelingEngineRuleMinimumDistanceLabelToLabel::labeledLayer()
{
return mLabeledLayer.get();
}
void QgsLabelingEngineRuleMinimumDistanceLabelToLabel::setLabeledLayer( QgsVectorLayer *layer )
{
mLabeledLayer = layer;
}
QgsVectorLayer *QgsLabelingEngineRuleMinimumDistanceLabelToLabel::targetLayer()
{
return mTargetLayer.get();
}
void QgsLabelingEngineRuleMinimumDistanceLabelToLabel::setTargetLayer( QgsVectorLayer *layer )
{
mTargetLayer = layer;
}
//
// QgsLabelingEngineRuleAvoidLabelOverlapWithFeature
//
QgsLabelingEngineRuleAvoidLabelOverlapWithFeature::QgsLabelingEngineRuleAvoidLabelOverlapWithFeature() = default;
QgsLabelingEngineRuleAvoidLabelOverlapWithFeature::~QgsLabelingEngineRuleAvoidLabelOverlapWithFeature() = default;
QgsLabelingEngineRuleAvoidLabelOverlapWithFeature *QgsLabelingEngineRuleAvoidLabelOverlapWithFeature::clone() const
{
std::unique_ptr< QgsLabelingEngineRuleAvoidLabelOverlapWithFeature> res = std::make_unique< QgsLabelingEngineRuleAvoidLabelOverlapWithFeature >();
res->mLabeledLayer = mLabeledLayer;
res->mTargetLayer = mTargetLayer;
return res.release();
}
QString QgsLabelingEngineRuleAvoidLabelOverlapWithFeature::id() const
{
return QStringLiteral( "avoidLabelOverlapWithFeature" );
}
bool QgsLabelingEngineRuleAvoidLabelOverlapWithFeature::prepare( QgsRenderContext & )
{
if ( !mTargetLayer )
return false;
QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS( mTargetLayer );
mTargetLayerSource = std::make_unique< QgsVectorLayerFeatureSource >( mTargetLayer.get() );
return true;
}
void QgsLabelingEngineRuleAvoidLabelOverlapWithFeature::writeXml( QDomDocument &, QDomElement &element, const QgsReadWriteContext & ) const
{
if ( mLabeledLayer )
{
element.setAttribute( QStringLiteral( "labeledLayer" ), mLabeledLayer.layerId );
element.setAttribute( QStringLiteral( "labeledLayerName" ), mLabeledLayer.name );
element.setAttribute( QStringLiteral( "labeledLayerSource" ), mLabeledLayer.source );
element.setAttribute( QStringLiteral( "labeledLayerProvider" ), mLabeledLayer.provider );
}
if ( mTargetLayer )
{
element.setAttribute( QStringLiteral( "targetLayer" ), mTargetLayer.layerId );
element.setAttribute( QStringLiteral( "targetLayerName" ), mTargetLayer.name );
element.setAttribute( QStringLiteral( "targetLayerSource" ), mTargetLayer.source );
element.setAttribute( QStringLiteral( "targetLayerProvider" ), mTargetLayer.provider );
}
}
void QgsLabelingEngineRuleAvoidLabelOverlapWithFeature::readXml( const QDomElement &element, const QgsReadWriteContext & )
{
{
const QString layerId = element.attribute( QStringLiteral( "labeledLayer" ) );
const QString layerName = element.attribute( QStringLiteral( "labeledLayerName" ) );
const QString layerSource = element.attribute( QStringLiteral( "labeledLayerSource" ) );
const QString layerProvider = element.attribute( QStringLiteral( "labeledLayerProvider" ) );
mLabeledLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
}
{
const QString layerId = element.attribute( QStringLiteral( "targetLayer" ) );
const QString layerName = element.attribute( QStringLiteral( "targetLayerName" ) );
const QString layerSource = element.attribute( QStringLiteral( "targetLayerSource" ) );
const QString layerProvider = element.attribute( QStringLiteral( "targetLayerProvider" ) );
mTargetLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
}
}
void QgsLabelingEngineRuleAvoidLabelOverlapWithFeature::resolveReferences( const QgsProject *project )
{
mLabeledLayer.resolve( project );
mTargetLayer.resolve( project );
}
bool QgsLabelingEngineRuleAvoidLabelOverlapWithFeature::candidateIsIllegal( const pal::LabelPosition *candidate, QgsLabelingEngineContext &context ) const
{
if ( candidate->getFeaturePart()->feature()->provider()->layerId() != mLabeledLayer.layerId )
{
return false;
}
if ( !mTargetLayerSource )
return false;
if ( !mInitialized )
const_cast< QgsLabelingEngineRuleAvoidLabelOverlapWithFeature * >( this )->initialize( context );
const QList<QgsFeatureId> overlapCandidates = mIndex->intersects( candidate->boundingBox() );
if ( overlapCandidates.empty() )
return false;
GEOSContextHandle_t geosctxt = QgsGeosContext::get();
const GEOSPreparedGeometry *candidateGeos = candidate->preparedMultiPartGeom();
for ( const QgsFeatureId overlapCandidateId : overlapCandidates )
{
if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
break;
try
{
geos::unique_ptr featureCandidate = QgsGeos::asGeos( mIndex->geometry( overlapCandidateId ).constGet() );
if ( GEOSPreparedIntersects_r( geosctxt, candidateGeos, featureCandidate.get() ) == 1 )
return true;
}
catch ( GEOSException &e )
{
QgsDebugError( QStringLiteral( "GEOS exception: %1" ).arg( e.what() ) );
}
}
return false;
}
QgsVectorLayer *QgsLabelingEngineRuleAvoidLabelOverlapWithFeature::labeledLayer()
{
return mLabeledLayer.get();
}
void QgsLabelingEngineRuleAvoidLabelOverlapWithFeature::setLabeledLayer( QgsVectorLayer *layer )
{
mLabeledLayer = layer;
}
QgsVectorLayer *QgsLabelingEngineRuleAvoidLabelOverlapWithFeature::targetLayer()
{
return mTargetLayer.get();
}
void QgsLabelingEngineRuleAvoidLabelOverlapWithFeature::setTargetLayer( QgsVectorLayer *layer )
{
mTargetLayer = layer;
}
void QgsLabelingEngineRuleAvoidLabelOverlapWithFeature::initialize( QgsLabelingEngineContext &context )
{
QgsFeatureRequest req;
req.setDestinationCrs( context.renderContext().coordinateTransform().destinationCrs(), context.renderContext().transformContext() );
req.setFilterRect( context.extent() );
req.setNoAttributes();
QgsFeatureIterator it = mTargetLayerSource->getFeatures( req );
mIndex = std::make_unique< QgsSpatialIndex >( it, context.renderContext().feedback(), QgsSpatialIndex::Flag::FlagStoreFeatureGeometries );
mInitialized = true;
}

View File

@ -0,0 +1,416 @@
/***************************************************************************
qgslabelingenginerule_impl.h
---------------------
Date : August 2024
Copyright : (C) 2024 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSLABELINGENGINERULEIMPL_H
#define QGSLABELINGENGINERULEIMPL_H
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgis.h"
#include "qgslabelingenginerule.h"
#include "qgsvectorlayerref.h"
#include "qgsmapunitscale.h"
class QgsSpatialIndex;
/**
* Base class for labeling engine rules which prevents labels being placed too close or to far from features from a different layer.
*
* \ingroup core
* \since QGIS 3.40
*/
class CORE_EXPORT QgsAbstractLabelingEngineRuleDistanceFromFeature : public QgsAbstractLabelingEngineRule
{
public:
QgsAbstractLabelingEngineRuleDistanceFromFeature();
~QgsAbstractLabelingEngineRuleDistanceFromFeature() override;
bool prepare( QgsRenderContext &context ) override;
void writeXml( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const override;
void readXml( const QDomElement &element, const QgsReadWriteContext &context ) override;
void resolveReferences( const QgsProject *project ) override;
bool candidateIsIllegal( const pal::LabelPosition *candidate, QgsLabelingEngineContext &context ) const override SIP_SKIP;
void alterCandidateCost( pal::LabelPosition *candidate, QgsLabelingEngineContext &context ) const override SIP_SKIP;
/**
* Returns the layer providing the labels.
*
* \see setLabeledLayer()
*/
QgsVectorLayer *labeledLayer();
/**
* Sets the \a layer providing the labels.
*
* \see labeledLayer()
*/
void setLabeledLayer( QgsVectorLayer *layer );
/**
* Returns the layer providing the features which labels must be distant from (or close to).
*
* \see setTargetLayer()
*/
QgsVectorLayer *targetLayer();
/**
* Sets the \a layer providing the features which labels must be distant from (or close to).
*
* \see targetLayer()
*/
void setTargetLayer( QgsVectorLayer *layer );
/**
* Returns the acceptable distance threshold between labels and the features
* from the targetLayer().
*
* \see setDistance()
* \see distanceUnits()
*/
double distance() const { return mDistance; }
/**
* Sets the acceptable \a distance threshold between labels and the features
* from the targetLayer().
*
* \see distance()
* \see setDistanceUnits()
*/
void setDistance( double distance ) { mDistance = distance; }
/**
* Returns the units for the distance between labels and the features
* from the targetLayer().
*
* \see setDistanceUnit()
* \see distance()
*/
Qgis::RenderUnit distanceUnit() const { return mDistanceUnit; }
/**
* Sets the \a unit for the distance between labels and the features
* from the targetLayer().
*
* \see distanceUnit()
* \see setDistance()
*/
void setDistanceUnit( Qgis::RenderUnit unit ) { mDistanceUnit = unit; }
/**
* Returns the scaling for the distance between labels and the features
* from the targetLayer().
*
* \see setDistanceUnitScale()
* \see distance()
*/
const QgsMapUnitScale &distanceUnitScale() const { return mDistanceUnitScale; }
/**
* Sets the \a scale for the distance between labels and the features
* from the targetLayer().
*
* \see distanceUnitScale()
* \see setDistance()
*/
void setDistanceUnitScale( const QgsMapUnitScale &scale ) { mDistanceUnitScale = scale; }
/**
* Returns the penalty cost incurred when the rule is violated.
*
* This is a value between 0 and 10, where 10 indicates that the rule must never be violated,
* and 1-9 = nice to have if possible, where higher numbers will try harder to avoid violating the rule.
*
* \see setCost()
*/
double cost() const { return mCost; }
/**
* Sets the penalty \a cost incurred when the rule is violated.
*
* This is a value between 0 and 10, where 10 indicates that the rule must never be violated,
* and 1-9 = nice to have if possible, where higher numbers will try harder to avoid violating the rule.
*
* \see cost()
*/
void setCost( double cost ) { mCost = cost; }
protected:
/**
* Copies common properties from this object to an \a other.
*/
void copyCommonProperties( QgsAbstractLabelingEngineRuleDistanceFromFeature *other ) const;
//! TRUE if labels must be distant from features, FALSE if they must be close
bool mMustBeDistant = true;
private:
#ifdef SIP_RUN
QgsAbstractLabelingEngineRuleDistanceFromFeature( const QgsAbstractLabelingEngineRuleDistanceFromFeature &other );
#endif
void initialize( QgsLabelingEngineContext &context );
//! Returns TRUE if \a candidate is too close / too far from features from target layer
bool candidateExceedsTolerance( const pal::LabelPosition *candidate, QgsLabelingEngineContext &context ) const;
//! Labeled layer
QgsVectorLayerRef mLabeledLayer;
//! Target layer
QgsVectorLayerRef mTargetLayer;
//! Distance threshold
double mDistance = 0;
//! Distance threshold unit
Qgis::RenderUnit mDistanceUnit = Qgis::RenderUnit::Millimeters;
//! Distance threshold map unit scale
QgsMapUnitScale mDistanceUnitScale;
//! Associated cost
double mCost = 0;
// cached variables
double mDistanceMapUnits = 0;
std::unique_ptr< QgsAbstractFeatureSource > mTargetLayerSource;
std::unique_ptr< QgsSpatialIndex > mIndex;
bool mInitialized = false;
};
/**
* A labeling engine rule which prevents labels being placed too close to features from a different layer.
*
* \ingroup core
* \since QGIS 3.40
*/
class CORE_EXPORT QgsLabelingEngineRuleMinimumDistanceLabelToFeature : public QgsAbstractLabelingEngineRuleDistanceFromFeature
{
public:
QgsLabelingEngineRuleMinimumDistanceLabelToFeature();
~QgsLabelingEngineRuleMinimumDistanceLabelToFeature() override;
QgsLabelingEngineRuleMinimumDistanceLabelToFeature *clone() const override SIP_FACTORY;
QString id() const override;
private:
#ifdef SIP_RUN
QgsLabelingEngineRuleMinimumDistanceLabelToFeature( const QgsLabelingEngineRuleMinimumDistanceLabelToFeature & );
#endif
};
/**
* A labeling engine rule which prevents labels being placed too far from features from a different layer.
*
* \ingroup core
* \since QGIS 3.40
*/
class CORE_EXPORT QgsLabelingEngineRuleMaximumDistanceLabelToFeature : public QgsAbstractLabelingEngineRuleDistanceFromFeature
{
public:
QgsLabelingEngineRuleMaximumDistanceLabelToFeature();
~QgsLabelingEngineRuleMaximumDistanceLabelToFeature() override;
QgsLabelingEngineRuleMaximumDistanceLabelToFeature *clone() const override SIP_FACTORY;
QString id() const override;
private:
#ifdef SIP_RUN
QgsLabelingEngineRuleMaximumDistanceLabelToFeature( const QgsLabelingEngineRuleMaximumDistanceLabelToFeature & );
#endif
};
/**
* A labeling engine rule which prevents labels being placed too close to labels from a different layer.
*
* \ingroup core
* \since QGIS 3.40
*/
class CORE_EXPORT QgsLabelingEngineRuleMinimumDistanceLabelToLabel : public QgsAbstractLabelingEngineRule
{
public:
QgsLabelingEngineRuleMinimumDistanceLabelToLabel();
~QgsLabelingEngineRuleMinimumDistanceLabelToLabel() override;
QgsLabelingEngineRuleMinimumDistanceLabelToLabel *clone() const override SIP_FACTORY;
QString id() const override;
void writeXml( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const override;
void readXml( const QDomElement &element, const QgsReadWriteContext &context ) override;
void resolveReferences( const QgsProject *project ) override;
bool prepare( QgsRenderContext &context ) override;
QgsRectangle modifyCandidateConflictSearchBoundingBox( const QgsRectangle &candidateBounds ) const override SIP_SKIP;
bool candidatesAreConflicting( const pal::LabelPosition *lp1, const pal::LabelPosition *lp2 ) const override SIP_SKIP;
/**
* Returns the layer providing the labels.
*
* \see setLabeledLayer()
*/
QgsVectorLayer *labeledLayer();
/**
* Sets the \a layer providing the labels.
*
* \see labeledLayer()
*/
void setLabeledLayer( QgsVectorLayer *layer );
/**
* Returns the layer providing the labels which labels must be distant from.
*
* \see setTargetLayer()
*/
QgsVectorLayer *targetLayer();
/**
* Sets the \a layer providing the labels which labels must be distant from.
*
* \see targetLayer()
*/
void setTargetLayer( QgsVectorLayer *layer );
/**
* Returns the minimum permitted distance between labels from the labeledLayer() and the labels
* from the targetLayer().
*
* \see setDistance()
* \see distanceUnits()
*/
double distance() const { return mDistance; }
/**
* Sets the minimum permitted \a distance between labels from the labeledLayer() and the labels
* from the targetLayer().
*
* \see distance()
* \see setDistanceUnits()
*/
void setDistance( double distance ) { mDistance = distance; }
/**
* Returns the units for the distance between labels from the labeledLayer() and the labels
* from the targetLayer().
*
* \see setDistanceUnit()
* \see distance()
*/
Qgis::RenderUnit distanceUnit() const { return mDistanceUnit; }
/**
* Sets the \a unit for the distance between labels from the labeledLayer() and the labels
* from the targetLayer().
*
* \see distanceUnit()
* \see setDistance()
*/
void setDistanceUnit( Qgis::RenderUnit unit ) { mDistanceUnit = unit; }
/**
* Returns the scaling for the distance between labels from the labeledLayer() and the labels
* from the targetLayer().
*
* \see setDistanceUnitScale()
* \see distance()
*/
const QgsMapUnitScale &distanceUnitScale() const { return mDistanceUnitScale; }
/**
* Sets the \a scale for the distance between labels from the labeledLayer() and the labels
* from the targetLayer().
*
* \see distanceUnitScale()
* \see setDistance()
*/
void setDistanceUnitScale( const QgsMapUnitScale &scale ) { mDistanceUnitScale = scale; }
private:
#ifdef SIP_RUN
QgsLabelingEngineRuleMinimumDistanceLabelToLabel( const QgsLabelingEngineRuleMinimumDistanceLabelToLabel & );
#endif
QgsVectorLayerRef mLabeledLayer;
QgsVectorLayerRef mTargetLayer;
double mDistance = 0;
Qgis::RenderUnit mDistanceUnit = Qgis::RenderUnit::Millimeters;
QgsMapUnitScale mDistanceUnitScale;
// cached variables
double mDistanceMapUnits = 0;
};
/**
* A labeling engine rule which prevents labels being placed overlapping features from a different layer.
*
* \ingroup core
* \since QGIS 3.40
*/
class CORE_EXPORT QgsLabelingEngineRuleAvoidLabelOverlapWithFeature : public QgsAbstractLabelingEngineRule
{
public:
QgsLabelingEngineRuleAvoidLabelOverlapWithFeature();
~QgsLabelingEngineRuleAvoidLabelOverlapWithFeature() override;
QgsLabelingEngineRuleAvoidLabelOverlapWithFeature *clone() const override SIP_FACTORY;
QString id() const override;
bool prepare( QgsRenderContext &context ) override;
void writeXml( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const override;
void readXml( const QDomElement &element, const QgsReadWriteContext &context ) override;
void resolveReferences( const QgsProject *project ) override;
bool candidateIsIllegal( const pal::LabelPosition *candidate, QgsLabelingEngineContext &context ) const override SIP_SKIP;
/**
* Returns the layer providing the labels.
*
* \see setLabeledLayer()
*/
QgsVectorLayer *labeledLayer();
/**
* Sets the \a layer providing the labels.
*
* \see labeledLayer()
*/
void setLabeledLayer( QgsVectorLayer *layer );
/**
* Returns the layer providing the features which labels must not overlap.
*
* \see setTargetLayer()
*/
QgsVectorLayer *targetLayer();
/**
* Sets the \a layer providing the features which labels must not overlap.
*
* \see targetLayer()
*/
void setTargetLayer( QgsVectorLayer *layer );
private:
#ifdef SIP_RUN
QgsLabelingEngineRuleAvoidLabelOverlapWithFeature( const QgsLabelingEngineRuleAvoidLabelOverlapWithFeature & );
#endif
void initialize( QgsLabelingEngineContext &context );
QgsVectorLayerRef mLabeledLayer;
QgsVectorLayerRef mTargetLayer;
// cached variables
std::unique_ptr< QgsAbstractFeatureSource > mTargetLayerSource;
std::unique_ptr< QgsSpatialIndex > mIndex;
bool mInitialized = false;
};
#endif // QGSLABELINGENGINERULEIMPL_H

View File

@ -0,0 +1,72 @@
/***************************************************************************
qgslabelingengineruleregistry.cpp
---------------------
Date : August 2024
Copyright : (C) 2024 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgslabelingengineruleregistry.h"
#include "qgslabelingenginerule.h"
#include "qgslabelingenginerule_impl.h"
#include <geos_c.h>
QgsLabelingEngineRuleRegistry::QgsLabelingEngineRuleRegistry()
{
#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
addRule( new QgsLabelingEngineRuleMinimumDistanceLabelToFeature() );
addRule( new QgsLabelingEngineRuleMaximumDistanceLabelToFeature() );
addRule( new QgsLabelingEngineRuleMinimumDistanceLabelToLabel() );
#endif
addRule( new QgsLabelingEngineRuleAvoidLabelOverlapWithFeature() );
}
QgsLabelingEngineRuleRegistry::~QgsLabelingEngineRuleRegistry() = default;
QStringList QgsLabelingEngineRuleRegistry::ruleIds() const
{
QStringList res;
res.reserve( static_cast< int >( mRules.size() ) );
for ( auto &it : mRules )
{
res.append( it.first );
}
return res;
}
QgsAbstractLabelingEngineRule *QgsLabelingEngineRuleRegistry::create( const QString &id ) const
{
auto it = mRules.find( id );
if ( it == mRules.end() )
return nullptr;
return it->second->clone();
}
bool QgsLabelingEngineRuleRegistry::addRule( QgsAbstractLabelingEngineRule *rule )
{
if ( !rule )
return false;
if ( mRules.find( rule->id() ) != mRules.end() )
{
delete rule;
return false;
}
mRules[ rule->id() ] = std::unique_ptr< QgsAbstractLabelingEngineRule >( rule );
return true;
}
void QgsLabelingEngineRuleRegistry::removeRule( const QString &id )
{
mRules.erase( id );
}

View File

@ -0,0 +1,96 @@
/***************************************************************************
qgslabelingengineruleregistry.h
---------------------
Date : August 2024
Copyright : (C) 2024 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSLABELINGENGINERULEREGISTRY_H
#define QGSLABELINGENGINERULEREGISTRY_H
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgis.h"
class QgsAbstractLabelingEngineRule;
/**
* A registry 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.
*
* This registry stores available rules and is responsible for creating rules.
*
* QgsLabelingEngineRuleRegistry is not usually directly created, but rather accessed through
* QgsApplication::labelEngineRuleRegistry().
*
* \ingroup core
* \since QGIS 3.40
*/
class CORE_EXPORT QgsLabelingEngineRuleRegistry
{
public:
/**
* Constructor for QgsLabelingEngineRuleRegistry, containing a set of
* default rules.
*/
QgsLabelingEngineRuleRegistry();
~QgsLabelingEngineRuleRegistry();
//! QgsLabelingEngineRuleRegistry cannot be copied
QgsLabelingEngineRuleRegistry( const QgsLabelingEngineRuleRegistry &other ) = delete;
//! QgsLabelingEngineRuleRegistry cannot be copied
QgsLabelingEngineRuleRegistry &operator=( const QgsLabelingEngineRuleRegistry &other ) = delete;
/**
* Returns a list of the rule IDs for rules present in the registry.
*/
QStringList ruleIds() const;
/**
* Creates a new rule from the type with matching \a id.
*
* Returns NULLPTR if no matching rule was found in the registry.
*
* The caller takes ownership of the returned object.
*/
QgsAbstractLabelingEngineRule *create( const QString &id ) const SIP_TRANSFERBACK;
/**
* Adds a new \a rule type to the registry.
*
* The registry takes ownership of \a rule.
*
* \returns TRUE if the rule was successfully added.
*
* \see removeRule()
*/
bool addRule( QgsAbstractLabelingEngineRule *rule SIP_TRANSFER );
/**
* Removes the rule with matching \a id from the registry.
*
* \see addRule()
*/
void removeRule( const QString &id );
private:
#ifdef SIP_RUN
QgsLabelingEngineRuleRegistry( const QgsLabelingEngineRuleRegistry &other );
#endif
std::map< QString, std::unique_ptr< QgsAbstractLabelingEngineRule > > mRules;
};
#endif // QGSLABELINGENGINERULEREGISTRY_H

View File

@ -1041,6 +1041,8 @@ LabelRenderJob QgsMapRendererJob::prepareLabelingJob( QPainter *painter, QgsLabe
job.context.setPainter( painter );
job.context.setLabelingEngine( labelingEngine2 );
job.context.setFeedback( mLabelingEngineFeedback );
if ( labelingEngine2 )
job.context.labelingEngine()->prepare( job.context );
QgsRectangle r1 = mSettings.visibleExtent();
r1.grow( mSettings.extentBuffer() );

View File

@ -2496,6 +2496,12 @@ bool QgsProject::readProjectFile( const QString &filename, Qgis::ProjectReadFlag
profile.switchTask( tr( "Loading label settings" ) );
mLabelingEngineSettings->readSettingsFromProject( this );
{
const QDomElement labelEngineSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "labelEngineSettings" ) );
mLabelingEngineSettings->readXml( labelEngineSettingsElement, context );
}
mLabelingEngineSettings->resolveReferences( this );
emit labelingEngineSettingsChanged();
profile.switchTask( tr( "Loading annotations" ) );
@ -3365,6 +3371,11 @@ bool QgsProject::writeProjectFile( const QString &filename )
qgisNode.appendChild( layerOrderNode );
mLabelingEngineSettings->writeSettingsToProject( this );
{
QDomElement labelEngineSettingsElement = doc->createElement( QStringLiteral( "labelEngineSettings" ) );
mLabelingEngineSettings->writeXml( *doc, labelEngineSettingsElement, context );
qgisNode.appendChild( labelEngineSettingsElement );
}
writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), mBackgroundColor.red() );
writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), mBackgroundColor.green() );

View File

@ -36,6 +36,7 @@
#include "qgsnumericformatregistry.h"
#include "qgsfieldformatterregistry.h"
#include "qgsscalebarrendererregistry.h"
#include "qgslabelingengineruleregistry.h"
#include "qgssvgcache.h"
#include "qgsimagecache.h"
#include "qgssourcecache.h"
@ -2631,6 +2632,11 @@ QgsScaleBarRendererRegistry *QgsApplication::scaleBarRendererRegistry()
return members()->mScaleBarRendererRegistry;
}
QgsLabelingEngineRuleRegistry *QgsApplication::labelingEngineRuleRegistry()
{
return members()->mLabelingEngineRuleRegistry;
}
QgsProjectStorageRegistry *QgsApplication::projectStorageRegistry()
{
return members()->mProjectStorageRegistry;
@ -2808,6 +2814,11 @@ QgsApplication::ApplicationMembers::ApplicationMembers()
mAnnotationItemRegistry->populate();
profiler->end();
}
{
profiler->start( tr( "Setup labeling engine rule registry" ) );
mLabelingEngineRuleRegistry = new QgsLabelingEngineRuleRegistry();
profiler->end();
}
{
profiler->start( tr( "Setup sensor registry" ) );
mSensorRegistry = new QgsSensorRegistry();
@ -2897,6 +2908,7 @@ QgsApplication::ApplicationMembers::~ApplicationMembers()
delete mSourceCache;
delete mCalloutRegistry;
delete mRecentStyleHandler;
delete mLabelingEngineRuleRegistry;
delete mSymbolLayerRegistry;
delete mExternalStorageRegistry;
delete mProfileSourceRegistry;

View File

@ -78,6 +78,7 @@ class QgsDatabaseQueryLog;
class QgsFontManager;
class QgsSensorRegistry;
class QgsProfileSourceRegistry;
class QgsLabelingEngineRuleRegistry;
/**
* \ingroup core
@ -930,6 +931,13 @@ class CORE_EXPORT QgsApplication : public QApplication
*/
static QgsScaleBarRendererRegistry *scaleBarRendererRegistry() SIP_KEEPREFERENCE;
/**
* Gets the registry of available labeling engine rules.
*
* \since QGIS 3.40
*/
static QgsLabelingEngineRuleRegistry *labelingEngineRuleRegistry() SIP_KEEPREFERENCE;
/**
* Returns registry of available project storage implementations.
* \since QGIS 3.2
@ -1146,6 +1154,7 @@ class CORE_EXPORT QgsApplication : public QApplication
QgsBabelFormatRegistry *mGpsBabelFormatRegistry = nullptr;
QgsNetworkContentFetcherRegistry *mNetworkContentFetcherRegistry = nullptr;
QgsScaleBarRendererRegistry *mScaleBarRendererRegistry = nullptr;
QgsLabelingEngineRuleRegistry *mLabelingEngineRuleRegistry = nullptr;
QgsValidityCheckRegistry *mValidityCheckRegistry = nullptr;
QgsMessageLog *mMessageLog = nullptr;
QgsPaintEffectRegistry *mPaintEffectRegistry = nullptr;

View File

@ -117,6 +117,7 @@ ADD_PYTHON_TEST(PyQgsImageCache test_qgsimagecache.py)
ADD_PYTHON_TEST(PyQgsInterpolatedLineSymbolLayer test_qgsinterpolatedlinesymbollayers.py)
ADD_PYTHON_TEST(PyQgsInterval test_qgsinterval.py)
ADD_PYTHON_TEST(PyQgsJsonUtils test_qgsjsonutils.py)
ADD_PYTHON_TEST(PyQgsLabelingEngineRule test_qgslabelingenginerule.py)
ADD_PYTHON_TEST(PyQgsLabelLineSettings test_qgslabellinesettings.py)
ADD_PYTHON_TEST(PyQgsLabelObstacleSettings test_qgslabelobstaclesettings.py)
ADD_PYTHON_TEST(PyQgsLabelPlacementSettings test_qgslabelplacementsettings.py)

View File

@ -0,0 +1,313 @@
"""QGIS Unit tests for labeling engine rules
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
import os
import tempfile
from qgis.PyQt.QtXml import QDomDocument
from qgis.core import (
Qgis,
QgsLabelingEngineRuleRegistry,
QgsAbstractLabelingEngineRule,
QgsLabelingEngineRuleMinimumDistanceLabelToFeature,
QgsLabelingEngineRuleMinimumDistanceLabelToLabel,
QgsLabelingEngineRuleMaximumDistanceLabelToFeature,
QgsLabelingEngineRuleAvoidLabelOverlapWithFeature,
QgsProject,
QgsVectorLayer,
QgsMapUnitScale,
QgsReadWriteContext,
QgsLabelingEngineSettings
)
import unittest
from qgis.testing import start_app, QgisTestCase
start_app()
class TestRule(QgsAbstractLabelingEngineRule):
def id(self):
return 'test'
def prepare(self, context):
pass
def writeXml(self, doc, element, context):
pass
def modifyProblem(self):
pass
def readXml(self, element, context):
pass
def clone(self):
return TestRule()
class TestQgsLabelingEngineRule(QgisTestCase):
def testRegistry(self):
registry = QgsLabelingEngineRuleRegistry()
self.assertTrue(registry.ruleIds())
for rule_id in registry.ruleIds():
self.assertEqual(registry.create(rule_id).id(), rule_id)
self.assertIsNone(registry.create('bad'))
self.assertIn('minimumDistanceLabelToFeature', registry.ruleIds())
self.assertFalse(registry.addRule(None))
self.assertTrue(registry.addRule(TestRule()))
self.assertIn('test', registry.ruleIds())
self.assertIsInstance(registry.create('test'), TestRule)
# no duplicates
self.assertFalse(registry.addRule(TestRule()))
registry.removeRule('test')
self.assertNotIn('test', registry.ruleIds())
self.assertIsNone(registry.create('test'))
registry.removeRule('test')
def testMinimumDistanceLabelToFeature(self):
p = QgsProject()
vl = QgsVectorLayer('Point', 'layer 1', 'memory')
vl2 = QgsVectorLayer('Point', 'layer 2', 'memory')
p.addMapLayers([vl, vl2])
rule = QgsLabelingEngineRuleMinimumDistanceLabelToFeature()
rule.setLabeledLayer(vl)
rule.setTargetLayer(vl2)
rule.setDistance(14)
rule.setDistanceUnit(Qgis.RenderUnit.Inches)
rule.setDistanceUnitScale(QgsMapUnitScale(15, 25))
rule.setCost(6.6)
self.assertEqual(rule.labeledLayer(), vl)
self.assertEqual(rule.targetLayer(), vl2)
self.assertEqual(rule.distance(), 14)
self.assertEqual(rule.distanceUnit(), Qgis.RenderUnit.Inches)
self.assertEqual(rule.distanceUnitScale().minScale, 15)
self.assertEqual(rule.distanceUnitScale().maxScale, 25)
self.assertEqual(rule.cost(), 6.6)
rule2 = rule.clone()
self.assertEqual(rule2.labeledLayer(), vl)
self.assertEqual(rule2.targetLayer(), vl2)
self.assertEqual(rule2.distance(), 14)
self.assertEqual(rule2.distanceUnit(), Qgis.RenderUnit.Inches)
self.assertEqual(rule2.distanceUnitScale().minScale, 15)
self.assertEqual(rule2.distanceUnitScale().maxScale, 25)
self.assertEqual(rule2.cost(), 6.6)
doc = QDomDocument("testdoc")
elem = doc.createElement("test")
rule.writeXml(doc, elem, QgsReadWriteContext())
rule3 = QgsLabelingEngineRuleMinimumDistanceLabelToFeature()
rule3.readXml(elem, QgsReadWriteContext())
rule3.resolveReferences(p)
self.assertEqual(rule3.labeledLayer(), vl)
self.assertEqual(rule3.targetLayer(), vl2)
self.assertEqual(rule3.distance(), 14)
self.assertEqual(rule3.distanceUnit(), Qgis.RenderUnit.Inches)
self.assertEqual(rule3.distanceUnitScale().minScale, 15)
self.assertEqual(rule3.distanceUnitScale().maxScale, 25)
self.assertEqual(rule3.cost(), 6.6)
def testMinimumDistanceLabelToLabel(self):
p = QgsProject()
vl = QgsVectorLayer('Point', 'layer 1', 'memory')
vl2 = QgsVectorLayer('Point', 'layer 2', 'memory')
p.addMapLayers([vl, vl2])
rule = QgsLabelingEngineRuleMinimumDistanceLabelToLabel()
rule.setLabeledLayer(vl)
rule.setTargetLayer(vl2)
rule.setDistance(14)
rule.setDistanceUnit(Qgis.RenderUnit.Inches)
rule.setDistanceUnitScale(QgsMapUnitScale(15, 25))
self.assertEqual(rule.labeledLayer(), vl)
self.assertEqual(rule.targetLayer(), vl2)
self.assertEqual(rule.distance(), 14)
self.assertEqual(rule.distanceUnit(), Qgis.RenderUnit.Inches)
self.assertEqual(rule.distanceUnitScale().minScale, 15)
self.assertEqual(rule.distanceUnitScale().maxScale, 25)
rule2 = rule.clone()
self.assertEqual(rule2.labeledLayer(), vl)
self.assertEqual(rule2.targetLayer(), vl2)
self.assertEqual(rule2.distance(), 14)
self.assertEqual(rule2.distanceUnit(), Qgis.RenderUnit.Inches)
self.assertEqual(rule2.distanceUnitScale().minScale, 15)
self.assertEqual(rule2.distanceUnitScale().maxScale, 25)
doc = QDomDocument("testdoc")
elem = doc.createElement("test")
rule.writeXml(doc, elem, QgsReadWriteContext())
rule3 = QgsLabelingEngineRuleMinimumDistanceLabelToLabel()
rule3.readXml(elem, QgsReadWriteContext())
rule3.resolveReferences(p)
self.assertEqual(rule3.labeledLayer(), vl)
self.assertEqual(rule3.targetLayer(), vl2)
self.assertEqual(rule3.distance(), 14)
self.assertEqual(rule3.distanceUnit(), Qgis.RenderUnit.Inches)
self.assertEqual(rule3.distanceUnitScale().minScale, 15)
self.assertEqual(rule3.distanceUnitScale().maxScale, 25)
def testMaximumDistanceLabelToFeature(self):
p = QgsProject()
vl = QgsVectorLayer('Point', 'layer 1', 'memory')
vl2 = QgsVectorLayer('Point', 'layer 2', 'memory')
p.addMapLayers([vl, vl2])
rule = QgsLabelingEngineRuleMaximumDistanceLabelToFeature()
rule.setLabeledLayer(vl)
rule.setTargetLayer(vl2)
rule.setDistance(14)
rule.setDistanceUnit(Qgis.RenderUnit.Inches)
rule.setDistanceUnitScale(QgsMapUnitScale(15, 25))
rule.setCost(6.6)
self.assertEqual(rule.labeledLayer(), vl)
self.assertEqual(rule.targetLayer(), vl2)
self.assertEqual(rule.distance(), 14)
self.assertEqual(rule.distanceUnit(), Qgis.RenderUnit.Inches)
self.assertEqual(rule.distanceUnitScale().minScale, 15)
self.assertEqual(rule.distanceUnitScale().maxScale, 25)
self.assertEqual(rule.cost(), 6.6)
rule2 = rule.clone()
self.assertEqual(rule2.labeledLayer(), vl)
self.assertEqual(rule2.targetLayer(), vl2)
self.assertEqual(rule2.distance(), 14)
self.assertEqual(rule2.distanceUnit(), Qgis.RenderUnit.Inches)
self.assertEqual(rule2.distanceUnitScale().minScale, 15)
self.assertEqual(rule2.distanceUnitScale().maxScale, 25)
self.assertEqual(rule2.cost(), 6.6)
doc = QDomDocument("testdoc")
elem = doc.createElement("test")
rule.writeXml(doc, elem, QgsReadWriteContext())
rule3 = QgsLabelingEngineRuleMaximumDistanceLabelToFeature()
rule3.readXml(elem, QgsReadWriteContext())
rule3.resolveReferences(p)
self.assertEqual(rule3.labeledLayer(), vl)
self.assertEqual(rule3.targetLayer(), vl2)
self.assertEqual(rule3.distance(), 14)
self.assertEqual(rule3.distanceUnit(), Qgis.RenderUnit.Inches)
self.assertEqual(rule3.distanceUnitScale().minScale, 15)
self.assertEqual(rule3.distanceUnitScale().maxScale, 25)
self.assertEqual(rule3.cost(), 6.6)
def testAvoidLabelOverlapWithFeature(self):
p = QgsProject()
vl = QgsVectorLayer('Point', 'layer 1', 'memory')
vl2 = QgsVectorLayer('Point', 'layer 2', 'memory')
p.addMapLayers([vl, vl2])
rule = QgsLabelingEngineRuleAvoidLabelOverlapWithFeature()
rule.setLabeledLayer(vl)
rule.setTargetLayer(vl2)
self.assertEqual(rule.labeledLayer(), vl)
self.assertEqual(rule.targetLayer(), vl2)
rule2 = rule.clone()
self.assertEqual(rule2.labeledLayer(), vl)
self.assertEqual(rule2.targetLayer(), vl2)
doc = QDomDocument("testdoc")
elem = doc.createElement("test")
rule.writeXml(doc, elem, QgsReadWriteContext())
rule3 = QgsLabelingEngineRuleAvoidLabelOverlapWithFeature()
rule3.readXml(elem, QgsReadWriteContext())
rule3.resolveReferences(p)
self.assertEqual(rule3.labeledLayer(), vl)
self.assertEqual(rule3.targetLayer(), vl2)
def test_settings(self):
"""
Test attaching rules to QgsLabelingEngineSettings
"""
p = QgsProject()
vl = QgsVectorLayer('Point', 'layer 1', 'memory')
vl2 = QgsVectorLayer('Point', 'layer 2', 'memory')
p.addMapLayers([vl, vl2])
self.assertFalse(p.labelingEngineSettings().rules())
rule = QgsLabelingEngineRuleMaximumDistanceLabelToFeature()
rule.setLabeledLayer(vl)
rule.setTargetLayer(vl2)
rule.setCost(6.6)
label_engine_settings = p.labelingEngineSettings()
label_engine_settings.addRule(rule)
self.assertEqual([r.id() for r in label_engine_settings.rules()], ['maximumDistanceLabelToFeature'])
rule2 = QgsLabelingEngineRuleAvoidLabelOverlapWithFeature()
rule2.setLabeledLayer(vl2)
rule2.setTargetLayer(vl)
label_engine_settings.addRule(rule2)
self.assertEqual([r.id() for r in label_engine_settings.rules()], ['maximumDistanceLabelToFeature', 'avoidLabelOverlapWithFeature'])
p.setLabelingEngineSettings(label_engine_settings)
label_engine_settings = p.labelingEngineSettings()
self.assertEqual([r.id() for r in label_engine_settings.rules()],
['maximumDistanceLabelToFeature',
'avoidLabelOverlapWithFeature'])
# save, restore project
with tempfile.TemporaryDirectory() as temp_dir:
self.assertTrue(p.write(os.path.join(temp_dir, 'p.qgs')))
p2 = QgsProject()
self.assertTrue(p2.read(os.path.join(temp_dir, 'p.qgs')))
label_engine_settings = p2.labelingEngineSettings()
self.assertEqual([r.id() for r in label_engine_settings.rules()],
['maximumDistanceLabelToFeature',
'avoidLabelOverlapWithFeature'])
# check layers, settings
rule1 = label_engine_settings.rules()[0]
self.assertIsInstance(rule1, QgsLabelingEngineRuleMaximumDistanceLabelToFeature)
self.assertEqual(rule1.cost(), 6.6)
self.assertEqual(rule1.labeledLayer().name(), 'layer 1')
self.assertEqual(rule1.targetLayer().name(), 'layer 2')
rule2 = label_engine_settings.rules()[1]
self.assertIsInstance(rule2, QgsLabelingEngineRuleAvoidLabelOverlapWithFeature)
self.assertEqual(rule2.labeledLayer().name(), 'layer 2')
self.assertEqual(rule2.targetLayer().name(), 'layer 1')
# test setRules
rule = QgsLabelingEngineRuleMinimumDistanceLabelToFeature()
rule.setLabeledLayer(vl)
rule.setTargetLayer(vl2)
rule.setCost(6.6)
label_engine_settings.setRules([rule])
self.assertEqual([r.id() for r in label_engine_settings.rules()],
['minimumDistanceLabelToFeature'])
if __name__ == '__main__':
unittest.main()