[feature] Add "Linear Referencing" symbol layer type

This new symbol layer type allows placing text labels at regular
intervals along a line (or at positions corresponding to
existing vertices). Positions can be calculated using
Cartesian distances, or interpolated from z/m values.

Functionality includes:

- Labels can be placed using fixed cartesian 2d distances,
at regular linearly interpolated spacing calculated using
the Z or M values in geometries, or at existing vertices
- Labels can show either the running total distance, or
the linearly interpolated Z/M value
- Uses text rendered to draw labels, so the full range
of functionality is available for the labels (including
buffers, shadows, etc)
- Uses the QGIS numeric format classes to format numbers
as strings, so users have full range of customisation
options for eg decimal places
- An optional "skip multiples of" setting. If set, then
labels which are a multiple of this value will be skipped
over. This allows construction of complex referencing labels,
eg where a symbol has two linear referencing symbol layers,
one set to label every 100m in a small font, skipping multiples
of 1000, and a second set to label every 1000m in a big
bold font
- Labels are rendered using an angle calculated by averaging
the linestring, so sharp tiny jaggies don't result in
unslightly label rotation
- Optionally, markers can be placed at referenced points
in the line string, using a full QGIS marker symbol (this allows
eg showing a cross-hatch at the labeled point, for a "ruler"
style line)
- Data defined control over the placement intervals, skip
multiples setting, marker visibility and average angle
calculation length

Notes:

- When using the distance-based placement or labels, the
distances are calculated using 2D only, Cartesian calculations
based on the original layer CRS. This could potentially be
extended in future to expose options for 3D Cartesian distances,
or ellipsoidal distance calculations.

Sponsored by the Swiss QGIS User Group
This commit is contained in:
Nyall Dawson 2024-08-21 15:02:00 +10:00
parent 459d1a183e
commit 05003ba2fa
51 changed files with 3746 additions and 3 deletions

View File

@ -5366,6 +5366,38 @@ Qgis.MarkerLinePlacement.baseClass = Qgis
Qgis.MarkerLinePlacements = lambda flags=0: Qgis.MarkerLinePlacement(flags)
Qgis.MarkerLinePlacements.baseClass = Qgis
MarkerLinePlacements = Qgis # dirty hack since SIP seems to introduce the flags in module
# monkey patching scoped based enum
Qgis.LinearReferencingPlacement.IntervalCartesian2D.__doc__ = "Place labels at regular intervals, using Cartesian distance calculations on a 2D plane"
Qgis.LinearReferencingPlacement.IntervalZ.__doc__ = "Place labels at regular intervals, linearly interpolated using Z values"
Qgis.LinearReferencingPlacement.IntervalM.__doc__ = "Place labels at regular intervals, linearly interpolated using M values"
Qgis.LinearReferencingPlacement.Vertex.__doc__ = "Place labels on every vertex in the line"
Qgis.LinearReferencingPlacement.__doc__ = """Defines how/where the labels should be placed in a linear referencing symbol layer.
.. versionadded:: 3.40
* ``IntervalCartesian2D``: Place labels at regular intervals, using Cartesian distance calculations on a 2D plane
* ``IntervalZ``: Place labels at regular intervals, linearly interpolated using Z values
* ``IntervalM``: Place labels at regular intervals, linearly interpolated using M values
* ``Vertex``: Place labels on every vertex in the line
"""
# --
Qgis.LinearReferencingPlacement.baseClass = Qgis
# monkey patching scoped based enum
Qgis.LinearReferencingLabelSource.CartesianDistance2D.__doc__ = "Distance along line, calculated using Cartesian calculations on a 2D plane."
Qgis.LinearReferencingLabelSource.Z.__doc__ = "Z values"
Qgis.LinearReferencingLabelSource.M.__doc__ = "M values"
Qgis.LinearReferencingLabelSource.__doc__ = """Defines what quantity to use for the labels shown in a linear referencing symbol layer.
.. versionadded:: 3.40
* ``CartesianDistance2D``: Distance along line, calculated using Cartesian calculations on a 2D plane.
* ``Z``: Z values
* ``M``: M values
"""
# --
Qgis.LinearReferencingLabelSource.baseClass = Qgis
QgsGradientFillSymbolLayer.GradientColorType = Qgis.GradientColorSource
# monkey patching scoped based enum
QgsGradientFillSymbolLayer.SimpleTwoColor = Qgis.GradientColorSource.SimpleTwoColor

View File

@ -0,0 +1,6 @@
# The following has been generated automatically from src/core/symbology/qgslinearreferencingsymbollayer.h
QgsLinearReferencingSymbolLayer.create = staticmethod(QgsLinearReferencingSymbolLayer.create)
try:
QgsLinearReferencingSymbolLayer.__group__ = ['symbology']
except NameError:
pass

View File

@ -280,6 +280,12 @@ QgsSymbolLayer.PropertyLineClipping = QgsSymbolLayer.Property.LineClipping
QgsSymbolLayer.Property.PropertyLineClipping = QgsSymbolLayer.Property.LineClipping
QgsSymbolLayer.PropertyLineClipping.is_monkey_patched = True
QgsSymbolLayer.PropertyLineClipping.__doc__ = "Line clipping mode \n.. versionadded:: 3.24"
QgsSymbolLayer.SkipMultiples = QgsSymbolLayer.Property.SkipMultiples
QgsSymbolLayer.SkipMultiples.is_monkey_patched = True
QgsSymbolLayer.SkipMultiples.__doc__ = "Skip multiples of \n.. versionadded:: 3.40"
QgsSymbolLayer.ShowMarker = QgsSymbolLayer.Property.ShowMarker
QgsSymbolLayer.ShowMarker.is_monkey_patched = True
QgsSymbolLayer.ShowMarker.__doc__ = "Show markers \n.. versionadded:: 3.40"
QgsSymbolLayer.Property.__doc__ = """Data definable properties.
* ``Size``: Symbol size
@ -592,6 +598,14 @@ QgsSymbolLayer.Property.__doc__ = """Data definable properties.
Available as ``QgsSymbolLayer.PropertyLineClipping`` in older QGIS releases.
* ``SkipMultiples``: Skip multiples of
.. versionadded:: 3.40
* ``ShowMarker``: Show markers
.. versionadded:: 3.40
"""
# --

View File

@ -1628,6 +1628,21 @@ The development version
typedef QFlags<Qgis::MarkerLinePlacement> MarkerLinePlacements;
enum class LinearReferencingPlacement /BaseType=IntFlag/
{
IntervalCartesian2D,
IntervalZ,
IntervalM,
Vertex,
};
enum class LinearReferencingLabelSource /BaseType=IntEnum/
{
CartesianDistance2D,
Z,
M,
};
enum class GradientColorSource /BaseType=IntEnum/
{
SimpleTwoColor,

View File

@ -0,0 +1,326 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/symbology/qgslinearreferencingsymbollayer.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/
class QgsLinearReferencingSymbolLayer : QgsLineSymbolLayer
{
%Docstring(signature="appended")
Line symbol layer used for decorating accordingly to linear referencing.
This symbol layer type allows placing text labels at regular intervals along
a line (or at positions corresponding to existing vertices). Positions
can be calculated using Cartesian distances, or interpolated from z or m values.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgslinearreferencingsymbollayer.h"
%End
public:
QgsLinearReferencingSymbolLayer();
~QgsLinearReferencingSymbolLayer();
static QgsSymbolLayer *create( const QVariantMap &properties = QVariantMap() ) /Factory/;
%Docstring
Creates a new QgsLinearReferencingSymbolLayer, using the specified ``properties``.
The caller takes ownership of the returned object.
%End
virtual QgsLinearReferencingSymbolLayer *clone() const /Factory/;
virtual QVariantMap properties() const;
virtual QString layerType() const;
virtual Qgis::SymbolLayerFlags flags() const;
virtual QgsSymbol *subSymbol();
virtual bool setSubSymbol( QgsSymbol *symbol /Transfer/ );
virtual void startRender( QgsSymbolRenderContext &context );
virtual void stopRender( QgsSymbolRenderContext &context );
virtual void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context );
QgsTextFormat textFormat() const;
%Docstring
Returns the text format used to render the layer.
.. seealso:: :py:func:`setTextFormat`
%End
void setTextFormat( const QgsTextFormat &format );
%Docstring
Sets the text ``format`` used to render the layer.
.. seealso:: :py:func:`textFormat`
%End
QgsNumericFormat *numericFormat() const;
%Docstring
Returns the numeric format used to format the labels for the layer.
.. seealso:: :py:func:`setNumericFormat`
%End
void setNumericFormat( QgsNumericFormat *format /Transfer/ );
%Docstring
Sets the numeric ``format`` used to format the labels for the layer.
Ownership of ``format`` is transferred to the layer.
.. seealso:: :py:func:`numericFormat`
%End
double interval() const;
%Docstring
Returns the interval between labels.
Units are always in the original layer CRS units.
.. seealso:: :py:func:`setInterval`
%End
void setInterval( double interval );
%Docstring
Sets the ``interval`` between labels.
Units are always in the original layer CRS units.
.. seealso:: :py:func:`setInterval`
%End
double skipMultiplesOf() const;
%Docstring
Returns the multiple distance to skip labels for.
If this value is non-zero, then any labels which are integer multiples of the returned
value will be skipped. This allows creation of advanced referencing styles where a single
:py:class:`QgsSymbol` has multiple :py:class:`QgsLinearReferencingSymbolLayer` symbol layers, eg allowing
labeling every 100 in a normal font and every 1000 in a bold, larger font.
.. seealso:: :py:func:`setSkipMultiplesOf`
%End
void setSkipMultiplesOf( double multiple );
%Docstring
Sets the ``multiple`` distance to skip labels for.
If this value is non-zero, then any labels which are integer multiples of the returned
value will be skipped. This allows creation of advanced referencing styles where a single
:py:class:`QgsSymbol` has multiple :py:class:`QgsLinearReferencingSymbolLayer` symbol layers, eg allowing
labeling every 100 in a normal font and every 1000 in a bold, larger font.
.. seealso:: :py:func:`skipMultiplesOf`
%End
bool rotateLabels() const;
%Docstring
Returns ``True`` if the labels and symbols are to be rotated to match their line segment orientation.
.. seealso:: :py:func:`setRotateLabels`
%End
void setRotateLabels( bool rotate );
%Docstring
Sets whether the labels and symbols should be rotated to match their line segment orientation.
.. seealso:: :py:func:`rotateLabels`
%End
QPointF labelOffset() const;
%Docstring
Returns the offset between the line and linear referencing labels.
The unit for the offset is retrievable via :py:func:`~QgsLinearReferencingSymbolLayer.labelOffsetUnit`.
.. seealso:: :py:func:`setLabelOffset`
.. seealso:: :py:func:`labelOffsetUnit`
%End
void setLabelOffset( const QPointF &offset );
%Docstring
Sets the ``offset`` between the line and linear referencing labels.
The unit for the offset is set via :py:func:`~QgsLinearReferencingSymbolLayer.setLabelOffsetUnit`.
.. seealso:: :py:func:`labelOffset`
.. seealso:: :py:func:`setLabelOffsetUnit`
%End
Qgis::RenderUnit labelOffsetUnit() const;
%Docstring
Returns the unit used for the offset between the line and linear referencing labels.
.. seealso:: :py:func:`setLabelOffsetUnit`
.. seealso:: :py:func:`labelOffset`
%End
void setLabelOffsetUnit( Qgis::RenderUnit unit );
%Docstring
Sets the ``unit`` used for the offset between the line and linear referencing labels.
.. seealso:: :py:func:`labelOffsetUnit`
.. seealso:: :py:func:`setLabelOffset`
%End
const QgsMapUnitScale &labelOffsetMapUnitScale() const;
%Docstring
Returns the map unit scale used for calculating the offset between the line and linear referencing labels.
.. seealso:: :py:func:`setLabelOffsetMapUnitScale`
%End
void setLabelOffsetMapUnitScale( const QgsMapUnitScale &scale );
%Docstring
Sets the map unit ``scale`` used for calculating the offset between the line and linear referencing labels.
.. seealso:: :py:func:`labelOffsetMapUnitScale`
%End
bool showMarker() const;
%Docstring
Returns ``True`` if a marker symbol should be shown corresponding to the labeled point on line.
The marker symbol is set using :py:func:`~QgsLinearReferencingSymbolLayer.setSubSymbol`
.. seealso:: :py:func:`setShowMarker`
%End
void setShowMarker( bool show );
%Docstring
Sets whether a marker symbol should be shown corresponding to the labeled point on line.
The marker symbol is set using :py:func:`~QgsLinearReferencingSymbolLayer.setSubSymbol`
.. seealso:: :py:func:`showMarker`
%End
Qgis::LinearReferencingPlacement placement() const;
%Docstring
Returns the placement mode for the labels.
.. seealso:: :py:func:`setPlacement`
%End
void setPlacement( Qgis::LinearReferencingPlacement placement );
%Docstring
Sets the ``placement`` mode for the labels.
.. seealso:: :py:func:`placement`
%End
Qgis::LinearReferencingLabelSource labelSource() const;
%Docstring
Returns the label source, which dictates what quantity to use for the labels shown.
.. seealso:: :py:func:`setLabelSource`
%End
void setLabelSource( Qgis::LinearReferencingLabelSource source );
%Docstring
Sets the label ``source``, which dictates what quantity to use for the labels shown.
.. seealso:: :py:func:`labelSource`
%End
double averageAngleLength() const;
%Docstring
Returns the length of line over which the line's direction is averaged when
calculating individual label angles. Longer lengths smooth out angles from jagged lines to a greater extent.
Units are retrieved through :py:func:`~QgsLinearReferencingSymbolLayer.averageAngleUnit`
.. seealso:: :py:func:`setAverageAngleLength`
.. seealso:: :py:func:`averageAngleUnit`
.. seealso:: :py:func:`averageAngleMapUnitScale`
%End
void setAverageAngleLength( double length );
%Docstring
Sets the ``length`` of line over which the line's direction is averaged when
calculating individual label angles. Longer lengths smooth out angles from jagged lines to a greater extent.
Units are set through :py:func:`~QgsLinearReferencingSymbolLayer.setAverageAngleUnit`
.. seealso:: :py:func:`averageAngleLength`
.. seealso:: :py:func:`setAverageAngleUnit`
.. seealso:: :py:func:`setAverageAngleMapUnitScale`
%End
void setAverageAngleUnit( Qgis::RenderUnit unit );
%Docstring
Sets the ``unit`` for the length over which the line's direction is averaged when
calculating individual label angles.
.. seealso:: :py:func:`averageAngleUnit`
.. seealso:: :py:func:`setAverageAngleLength`
.. seealso:: :py:func:`setAverageAngleMapUnitScale`
%End
Qgis::RenderUnit averageAngleUnit() const;
%Docstring
Returns the unit for the length over which the line's direction is averaged when
calculating individual label angles.
.. seealso:: :py:func:`setAverageAngleUnit`
.. seealso:: :py:func:`averageAngleLength`
.. seealso:: :py:func:`averageAngleMapUnitScale`
%End
void setAverageAngleMapUnitScale( const QgsMapUnitScale &scale );
%Docstring
Sets the map unit ``scale`` for the length over which the line's direction is averaged when
calculating individual label angles.
.. seealso:: :py:func:`averageAngleMapUnitScale`
.. seealso:: :py:func:`setAverageAngleLength`
.. seealso:: :py:func:`setAverageAngleUnit`
%End
const QgsMapUnitScale &averageAngleMapUnitScale() const;
%Docstring
Returns the map unit scale for the length over which the line's direction is averaged when
calculating individual label angles.
.. seealso:: :py:func:`setAverageAngleMapUnitScale`
.. seealso:: :py:func:`averageAngleLength`
.. seealso:: :py:func:`averageAngleUnit`
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/symbology/qgslinearreferencingsymbollayer.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/

View File

@ -60,6 +60,8 @@ class QgsSymbolLayer
sipType = sipType_QgsRasterLineSymbolLayer;
else if ( sipCpp->layerType() == "Lineburst" )
sipType = sipType_QgsLineburstSymbolLayer;
else if ( sipCpp->layerType() == "LinearReferencing" )
sipType = sipType_QgsLinearReferencingSymbolLayer;
else
sipType = sipType_QgsLineSymbolLayer;
break;
@ -167,6 +169,8 @@ class QgsSymbolLayer
RandomOffsetX,
RandomOffsetY,
LineClipping,
SkipMultiples,
ShowMarker,
};
static const QgsPropertiesDefinition &propertyDefinitions();

View File

@ -690,6 +690,7 @@
%Include auto_generated/symbology/qgsinterpolatedlinerenderer.sip
%Include auto_generated/symbology/qgsinvertedpolygonrenderer.sip
%Include auto_generated/symbology/qgslegendsymbolitem.sip
%Include auto_generated/symbology/qgslinearreferencingsymbollayer.sip
%Include auto_generated/symbology/qgslinesymbol.sip
%Include auto_generated/symbology/qgslinesymbollayer.sip
%Include auto_generated/symbology/qgsmapinfosymbolconverter.sip

View File

@ -24,6 +24,7 @@ QgsPointPatternFillSymbolLayerWidget.create = staticmethod(QgsPointPatternFillSy
QgsRandomMarkerFillSymbolLayerWidget.create = staticmethod(QgsRandomMarkerFillSymbolLayerWidget.create)
QgsFontMarkerSymbolLayerWidget.create = staticmethod(QgsFontMarkerSymbolLayerWidget.create)
QgsCentroidFillSymbolLayerWidget.create = staticmethod(QgsCentroidFillSymbolLayerWidget.create)
QgsLinearReferencingSymbolLayerWidget.create = staticmethod(QgsLinearReferencingSymbolLayerWidget.create)
QgsGeometryGeneratorSymbolLayerWidget.create = staticmethod(QgsGeometryGeneratorSymbolLayerWidget.create)
try:
QgsSymbolLayerWidget.__group__ = ['symbology']
@ -113,6 +114,10 @@ try:
QgsCentroidFillSymbolLayerWidget.__group__ = ['symbology']
except NameError:
pass
try:
QgsLinearReferencingSymbolLayerWidget.__group__ = ['symbology']
except NameError:
pass
try:
QgsGeometryGeneratorSymbolLayerWidget.__group__ = ['symbology']
except NameError:

View File

@ -990,6 +990,46 @@ Creates a new QgsCentroidFillSymbolLayerWidget.
class QgsLinearReferencingSymbolLayerWidget : QgsSymbolLayerWidget
{
%Docstring(signature="appended")
Widget for controlling the properties of a :py:class:`QgsLinearReferencingSymbolLayer`.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgssymbollayerwidget.h"
%End
public:
QgsLinearReferencingSymbolLayerWidget( QgsVectorLayer *vl, QWidget *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsLinearReferencingSymbolLayerWidget.
%End
~QgsLinearReferencingSymbolLayerWidget();
static QgsSymbolLayerWidget *create( QgsVectorLayer *vl ) /Factory/;
%Docstring
Creates a new QgsLinearReferencingSymbolLayerWidget.
:param vl: associated vector layer
%End
virtual void setSymbolLayer( QgsSymbolLayer *layer );
virtual QgsSymbolLayer *symbolLayer();
virtual void setContext( const QgsSymbolWidgetContext &context );
};
class QgsGeometryGeneratorSymbolLayerWidget : QgsSymbolLayerWidget
{

View File

@ -5316,6 +5316,38 @@ Qgis.MarkerLinePlacement.__doc__ = """Defines how/where the symbols should be pl
Qgis.MarkerLinePlacement.baseClass = Qgis
Qgis.MarkerLinePlacements.baseClass = Qgis
MarkerLinePlacements = Qgis # dirty hack since SIP seems to introduce the flags in module
# monkey patching scoped based enum
Qgis.LinearReferencingPlacement.IntervalCartesian2D.__doc__ = "Place labels at regular intervals, using Cartesian distance calculations on a 2D plane"
Qgis.LinearReferencingPlacement.IntervalZ.__doc__ = "Place labels at regular intervals, linearly interpolated using Z values"
Qgis.LinearReferencingPlacement.IntervalM.__doc__ = "Place labels at regular intervals, linearly interpolated using M values"
Qgis.LinearReferencingPlacement.Vertex.__doc__ = "Place labels on every vertex in the line"
Qgis.LinearReferencingPlacement.__doc__ = """Defines how/where the labels should be placed in a linear referencing symbol layer.
.. versionadded:: 3.40
* ``IntervalCartesian2D``: Place labels at regular intervals, using Cartesian distance calculations on a 2D plane
* ``IntervalZ``: Place labels at regular intervals, linearly interpolated using Z values
* ``IntervalM``: Place labels at regular intervals, linearly interpolated using M values
* ``Vertex``: Place labels on every vertex in the line
"""
# --
Qgis.LinearReferencingPlacement.baseClass = Qgis
# monkey patching scoped based enum
Qgis.LinearReferencingLabelSource.CartesianDistance2D.__doc__ = "Distance along line, calculated using Cartesian calculations on a 2D plane."
Qgis.LinearReferencingLabelSource.Z.__doc__ = "Z values"
Qgis.LinearReferencingLabelSource.M.__doc__ = "M values"
Qgis.LinearReferencingLabelSource.__doc__ = """Defines what quantity to use for the labels shown in a linear referencing symbol layer.
.. versionadded:: 3.40
* ``CartesianDistance2D``: Distance along line, calculated using Cartesian calculations on a 2D plane.
* ``Z``: Z values
* ``M``: M values
"""
# --
Qgis.LinearReferencingLabelSource.baseClass = Qgis
QgsGradientFillSymbolLayer.GradientColorType = Qgis.GradientColorSource
# monkey patching scoped based enum
QgsGradientFillSymbolLayer.SimpleTwoColor = Qgis.GradientColorSource.SimpleTwoColor

View File

@ -0,0 +1,6 @@
# The following has been generated automatically from src/core/symbology/qgslinearreferencingsymbollayer.h
QgsLinearReferencingSymbolLayer.create = staticmethod(QgsLinearReferencingSymbolLayer.create)
try:
QgsLinearReferencingSymbolLayer.__group__ = ['symbology']
except NameError:
pass

View File

@ -280,6 +280,12 @@ QgsSymbolLayer.PropertyLineClipping = QgsSymbolLayer.Property.LineClipping
QgsSymbolLayer.Property.PropertyLineClipping = QgsSymbolLayer.Property.LineClipping
QgsSymbolLayer.PropertyLineClipping.is_monkey_patched = True
QgsSymbolLayer.PropertyLineClipping.__doc__ = "Line clipping mode \n.. versionadded:: 3.24"
QgsSymbolLayer.SkipMultiples = QgsSymbolLayer.Property.SkipMultiples
QgsSymbolLayer.SkipMultiples.is_monkey_patched = True
QgsSymbolLayer.SkipMultiples.__doc__ = "Skip multiples of \n.. versionadded:: 3.40"
QgsSymbolLayer.ShowMarker = QgsSymbolLayer.Property.ShowMarker
QgsSymbolLayer.ShowMarker.is_monkey_patched = True
QgsSymbolLayer.ShowMarker.__doc__ = "Show markers \n.. versionadded:: 3.40"
QgsSymbolLayer.Property.__doc__ = """Data definable properties.
* ``Size``: Symbol size
@ -592,6 +598,14 @@ QgsSymbolLayer.Property.__doc__ = """Data definable properties.
Available as ``QgsSymbolLayer.PropertyLineClipping`` in older QGIS releases.
* ``SkipMultiples``: Skip multiples of
.. versionadded:: 3.40
* ``ShowMarker``: Show markers
.. versionadded:: 3.40
"""
# --

View File

@ -1628,6 +1628,21 @@ The development version
typedef QFlags<Qgis::MarkerLinePlacement> MarkerLinePlacements;
enum class LinearReferencingPlacement
{
IntervalCartesian2D,
IntervalZ,
IntervalM,
Vertex,
};
enum class LinearReferencingLabelSource
{
CartesianDistance2D,
Z,
M,
};
enum class GradientColorSource
{
SimpleTwoColor,

View File

@ -0,0 +1,326 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/symbology/qgslinearreferencingsymbollayer.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/
class QgsLinearReferencingSymbolLayer : QgsLineSymbolLayer
{
%Docstring(signature="appended")
Line symbol layer used for decorating accordingly to linear referencing.
This symbol layer type allows placing text labels at regular intervals along
a line (or at positions corresponding to existing vertices). Positions
can be calculated using Cartesian distances, or interpolated from z or m values.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgslinearreferencingsymbollayer.h"
%End
public:
QgsLinearReferencingSymbolLayer();
~QgsLinearReferencingSymbolLayer();
static QgsSymbolLayer *create( const QVariantMap &properties = QVariantMap() ) /Factory/;
%Docstring
Creates a new QgsLinearReferencingSymbolLayer, using the specified ``properties``.
The caller takes ownership of the returned object.
%End
virtual QgsLinearReferencingSymbolLayer *clone() const /Factory/;
virtual QVariantMap properties() const;
virtual QString layerType() const;
virtual Qgis::SymbolLayerFlags flags() const;
virtual QgsSymbol *subSymbol();
virtual bool setSubSymbol( QgsSymbol *symbol /Transfer/ );
virtual void startRender( QgsSymbolRenderContext &context );
virtual void stopRender( QgsSymbolRenderContext &context );
virtual void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context );
QgsTextFormat textFormat() const;
%Docstring
Returns the text format used to render the layer.
.. seealso:: :py:func:`setTextFormat`
%End
void setTextFormat( const QgsTextFormat &format );
%Docstring
Sets the text ``format`` used to render the layer.
.. seealso:: :py:func:`textFormat`
%End
QgsNumericFormat *numericFormat() const;
%Docstring
Returns the numeric format used to format the labels for the layer.
.. seealso:: :py:func:`setNumericFormat`
%End
void setNumericFormat( QgsNumericFormat *format /Transfer/ );
%Docstring
Sets the numeric ``format`` used to format the labels for the layer.
Ownership of ``format`` is transferred to the layer.
.. seealso:: :py:func:`numericFormat`
%End
double interval() const;
%Docstring
Returns the interval between labels.
Units are always in the original layer CRS units.
.. seealso:: :py:func:`setInterval`
%End
void setInterval( double interval );
%Docstring
Sets the ``interval`` between labels.
Units are always in the original layer CRS units.
.. seealso:: :py:func:`setInterval`
%End
double skipMultiplesOf() const;
%Docstring
Returns the multiple distance to skip labels for.
If this value is non-zero, then any labels which are integer multiples of the returned
value will be skipped. This allows creation of advanced referencing styles where a single
:py:class:`QgsSymbol` has multiple :py:class:`QgsLinearReferencingSymbolLayer` symbol layers, eg allowing
labeling every 100 in a normal font and every 1000 in a bold, larger font.
.. seealso:: :py:func:`setSkipMultiplesOf`
%End
void setSkipMultiplesOf( double multiple );
%Docstring
Sets the ``multiple`` distance to skip labels for.
If this value is non-zero, then any labels which are integer multiples of the returned
value will be skipped. This allows creation of advanced referencing styles where a single
:py:class:`QgsSymbol` has multiple :py:class:`QgsLinearReferencingSymbolLayer` symbol layers, eg allowing
labeling every 100 in a normal font and every 1000 in a bold, larger font.
.. seealso:: :py:func:`skipMultiplesOf`
%End
bool rotateLabels() const;
%Docstring
Returns ``True`` if the labels and symbols are to be rotated to match their line segment orientation.
.. seealso:: :py:func:`setRotateLabels`
%End
void setRotateLabels( bool rotate );
%Docstring
Sets whether the labels and symbols should be rotated to match their line segment orientation.
.. seealso:: :py:func:`rotateLabels`
%End
QPointF labelOffset() const;
%Docstring
Returns the offset between the line and linear referencing labels.
The unit for the offset is retrievable via :py:func:`~QgsLinearReferencingSymbolLayer.labelOffsetUnit`.
.. seealso:: :py:func:`setLabelOffset`
.. seealso:: :py:func:`labelOffsetUnit`
%End
void setLabelOffset( const QPointF &offset );
%Docstring
Sets the ``offset`` between the line and linear referencing labels.
The unit for the offset is set via :py:func:`~QgsLinearReferencingSymbolLayer.setLabelOffsetUnit`.
.. seealso:: :py:func:`labelOffset`
.. seealso:: :py:func:`setLabelOffsetUnit`
%End
Qgis::RenderUnit labelOffsetUnit() const;
%Docstring
Returns the unit used for the offset between the line and linear referencing labels.
.. seealso:: :py:func:`setLabelOffsetUnit`
.. seealso:: :py:func:`labelOffset`
%End
void setLabelOffsetUnit( Qgis::RenderUnit unit );
%Docstring
Sets the ``unit`` used for the offset between the line and linear referencing labels.
.. seealso:: :py:func:`labelOffsetUnit`
.. seealso:: :py:func:`setLabelOffset`
%End
const QgsMapUnitScale &labelOffsetMapUnitScale() const;
%Docstring
Returns the map unit scale used for calculating the offset between the line and linear referencing labels.
.. seealso:: :py:func:`setLabelOffsetMapUnitScale`
%End
void setLabelOffsetMapUnitScale( const QgsMapUnitScale &scale );
%Docstring
Sets the map unit ``scale`` used for calculating the offset between the line and linear referencing labels.
.. seealso:: :py:func:`labelOffsetMapUnitScale`
%End
bool showMarker() const;
%Docstring
Returns ``True`` if a marker symbol should be shown corresponding to the labeled point on line.
The marker symbol is set using :py:func:`~QgsLinearReferencingSymbolLayer.setSubSymbol`
.. seealso:: :py:func:`setShowMarker`
%End
void setShowMarker( bool show );
%Docstring
Sets whether a marker symbol should be shown corresponding to the labeled point on line.
The marker symbol is set using :py:func:`~QgsLinearReferencingSymbolLayer.setSubSymbol`
.. seealso:: :py:func:`showMarker`
%End
Qgis::LinearReferencingPlacement placement() const;
%Docstring
Returns the placement mode for the labels.
.. seealso:: :py:func:`setPlacement`
%End
void setPlacement( Qgis::LinearReferencingPlacement placement );
%Docstring
Sets the ``placement`` mode for the labels.
.. seealso:: :py:func:`placement`
%End
Qgis::LinearReferencingLabelSource labelSource() const;
%Docstring
Returns the label source, which dictates what quantity to use for the labels shown.
.. seealso:: :py:func:`setLabelSource`
%End
void setLabelSource( Qgis::LinearReferencingLabelSource source );
%Docstring
Sets the label ``source``, which dictates what quantity to use for the labels shown.
.. seealso:: :py:func:`labelSource`
%End
double averageAngleLength() const;
%Docstring
Returns the length of line over which the line's direction is averaged when
calculating individual label angles. Longer lengths smooth out angles from jagged lines to a greater extent.
Units are retrieved through :py:func:`~QgsLinearReferencingSymbolLayer.averageAngleUnit`
.. seealso:: :py:func:`setAverageAngleLength`
.. seealso:: :py:func:`averageAngleUnit`
.. seealso:: :py:func:`averageAngleMapUnitScale`
%End
void setAverageAngleLength( double length );
%Docstring
Sets the ``length`` of line over which the line's direction is averaged when
calculating individual label angles. Longer lengths smooth out angles from jagged lines to a greater extent.
Units are set through :py:func:`~QgsLinearReferencingSymbolLayer.setAverageAngleUnit`
.. seealso:: :py:func:`averageAngleLength`
.. seealso:: :py:func:`setAverageAngleUnit`
.. seealso:: :py:func:`setAverageAngleMapUnitScale`
%End
void setAverageAngleUnit( Qgis::RenderUnit unit );
%Docstring
Sets the ``unit`` for the length over which the line's direction is averaged when
calculating individual label angles.
.. seealso:: :py:func:`averageAngleUnit`
.. seealso:: :py:func:`setAverageAngleLength`
.. seealso:: :py:func:`setAverageAngleMapUnitScale`
%End
Qgis::RenderUnit averageAngleUnit() const;
%Docstring
Returns the unit for the length over which the line's direction is averaged when
calculating individual label angles.
.. seealso:: :py:func:`setAverageAngleUnit`
.. seealso:: :py:func:`averageAngleLength`
.. seealso:: :py:func:`averageAngleMapUnitScale`
%End
void setAverageAngleMapUnitScale( const QgsMapUnitScale &scale );
%Docstring
Sets the map unit ``scale`` for the length over which the line's direction is averaged when
calculating individual label angles.
.. seealso:: :py:func:`averageAngleMapUnitScale`
.. seealso:: :py:func:`setAverageAngleLength`
.. seealso:: :py:func:`setAverageAngleUnit`
%End
const QgsMapUnitScale &averageAngleMapUnitScale() const;
%Docstring
Returns the map unit scale for the length over which the line's direction is averaged when
calculating individual label angles.
.. seealso:: :py:func:`setAverageAngleMapUnitScale`
.. seealso:: :py:func:`averageAngleLength`
.. seealso:: :py:func:`averageAngleUnit`
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/symbology/qgslinearreferencingsymbollayer.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/

View File

@ -60,6 +60,8 @@ class QgsSymbolLayer
sipType = sipType_QgsRasterLineSymbolLayer;
else if ( sipCpp->layerType() == "Lineburst" )
sipType = sipType_QgsLineburstSymbolLayer;
else if ( sipCpp->layerType() == "LinearReferencing" )
sipType = sipType_QgsLinearReferencingSymbolLayer;
else
sipType = sipType_QgsLineSymbolLayer;
break;
@ -167,6 +169,8 @@ class QgsSymbolLayer
RandomOffsetX,
RandomOffsetY,
LineClipping,
SkipMultiples,
ShowMarker,
};
static const QgsPropertiesDefinition &propertyDefinitions();

View File

@ -690,6 +690,7 @@
%Include auto_generated/symbology/qgsinterpolatedlinerenderer.sip
%Include auto_generated/symbology/qgsinvertedpolygonrenderer.sip
%Include auto_generated/symbology/qgslegendsymbolitem.sip
%Include auto_generated/symbology/qgslinearreferencingsymbollayer.sip
%Include auto_generated/symbology/qgslinesymbol.sip
%Include auto_generated/symbology/qgslinesymbollayer.sip
%Include auto_generated/symbology/qgsmapinfosymbolconverter.sip

View File

@ -24,6 +24,7 @@ QgsPointPatternFillSymbolLayerWidget.create = staticmethod(QgsPointPatternFillSy
QgsRandomMarkerFillSymbolLayerWidget.create = staticmethod(QgsRandomMarkerFillSymbolLayerWidget.create)
QgsFontMarkerSymbolLayerWidget.create = staticmethod(QgsFontMarkerSymbolLayerWidget.create)
QgsCentroidFillSymbolLayerWidget.create = staticmethod(QgsCentroidFillSymbolLayerWidget.create)
QgsLinearReferencingSymbolLayerWidget.create = staticmethod(QgsLinearReferencingSymbolLayerWidget.create)
QgsGeometryGeneratorSymbolLayerWidget.create = staticmethod(QgsGeometryGeneratorSymbolLayerWidget.create)
try:
QgsSymbolLayerWidget.__group__ = ['symbology']
@ -113,6 +114,10 @@ try:
QgsCentroidFillSymbolLayerWidget.__group__ = ['symbology']
except NameError:
pass
try:
QgsLinearReferencingSymbolLayerWidget.__group__ = ['symbology']
except NameError:
pass
try:
QgsGeometryGeneratorSymbolLayerWidget.__group__ = ['symbology']
except NameError:

View File

@ -990,6 +990,46 @@ Creates a new QgsCentroidFillSymbolLayerWidget.
class QgsLinearReferencingSymbolLayerWidget : QgsSymbolLayerWidget
{
%Docstring(signature="appended")
Widget for controlling the properties of a :py:class:`QgsLinearReferencingSymbolLayer`.
.. versionadded:: 3.40
%End
%TypeHeaderCode
#include "qgssymbollayerwidget.h"
%End
public:
QgsLinearReferencingSymbolLayerWidget( QgsVectorLayer *vl, QWidget *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsLinearReferencingSymbolLayerWidget.
%End
~QgsLinearReferencingSymbolLayerWidget();
static QgsSymbolLayerWidget *create( QgsVectorLayer *vl ) /Factory/;
%Docstring
Creates a new QgsLinearReferencingSymbolLayerWidget.
:param vl: associated vector layer
%End
virtual void setSymbolLayer( QgsSymbolLayer *layer );
virtual QgsSymbolLayer *symbolLayer();
virtual void setContext( const QgsSymbolWidgetContext &context );
};
class QgsGeometryGeneratorSymbolLayerWidget : QgsSymbolLayerWidget
{

View File

@ -107,6 +107,7 @@ set(QGIS_CORE_SRCS
symbology/qgsinterpolatedlinerenderer.cpp
symbology/qgsinvertedpolygonrenderer.cpp
symbology/qgslegendsymbolitem.cpp
symbology/qgslinearreferencingsymbollayer.cpp
symbology/qgslinesymbol.cpp
symbology/qgslinesymbollayer.cpp
symbology/qgsmapinfosymbolconverter.cpp
@ -1938,6 +1939,7 @@ set(QGIS_CORE_HDRS
symbology/qgsinterpolatedlinerenderer.h
symbology/qgsinvertedpolygonrenderer.h
symbology/qgslegendsymbolitem.h
symbology/qgslinearreferencingsymbollayer.h
symbology/qgslinesymbol.h
symbology/qgslinesymbollayer.h
symbology/qgsmapinfosymbolconverter.h

View File

@ -2791,6 +2791,33 @@ class CORE_EXPORT Qgis
Q_DECLARE_FLAGS( MarkerLinePlacements, MarkerLinePlacement )
Q_FLAG( MarkerLinePlacements )
/**
* Defines how/where the labels should be placed in a linear referencing symbol layer.
*
* \since QGIS 3.40
*/
enum class LinearReferencingPlacement : int SIP_ENUM_BASETYPE( IntFlag )
{
IntervalCartesian2D = 1 << 0, //!< Place labels at regular intervals, using Cartesian distance calculations on a 2D plane
IntervalZ = 1 << 1, //!< Place labels at regular intervals, linearly interpolated using Z values
IntervalM = 1 << 2, //!< Place labels at regular intervals, linearly interpolated using M values
Vertex = 1 << 3, //!< Place labels on every vertex in the line
};
Q_ENUM( LinearReferencingPlacement )
/**
* Defines what quantity to use for the labels shown in a linear referencing symbol layer.
*
* \since QGIS 3.40
*/
enum class LinearReferencingLabelSource : int
{
CartesianDistance2D, //!< Distance along line, calculated using Cartesian calculations on a 2D plane.
Z, //!< Z values
M, //!< M values
};
Q_ENUM( LinearReferencingLabelSource )
/**
* Gradient color sources.
*

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,334 @@
/***************************************************************************
qgslinearreferencingsymbollayer.h
---------------------
begin : 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 QGSLINEARREFERENCINGSYMBOLLAYER_H
#define QGSLINEARREFERENCINGSYMBOLLAYER_H
#include "qgis_core.h"
#include "qgis.h"
#include "qgssymbollayer.h"
#include "qgstextformat.h"
class QgsNumericFormat;
/**
* \ingroup core
* \brief Line symbol layer used for decorating accordingly to linear referencing.
*
* This symbol layer type allows placing text labels at regular intervals along
* a line (or at positions corresponding to existing vertices). Positions
* can be calculated using Cartesian distances, or interpolated from z or m values.
*
* \since QGIS 3.40
*/
class CORE_EXPORT QgsLinearReferencingSymbolLayer : public QgsLineSymbolLayer
{
public:
QgsLinearReferencingSymbolLayer();
~QgsLinearReferencingSymbolLayer() override;
/**
* Creates a new QgsLinearReferencingSymbolLayer, using the specified \a properties.
*
* The caller takes ownership of the returned object.
*/
static QgsSymbolLayer *create( const QVariantMap &properties = QVariantMap() ) SIP_FACTORY;
QgsLinearReferencingSymbolLayer *clone() const override SIP_FACTORY;
QVariantMap properties() const override;
QString layerType() const override;
Qgis::SymbolLayerFlags flags() const override;
QgsSymbol *subSymbol() override;
bool setSubSymbol( QgsSymbol *symbol SIP_TRANSFER ) override;
void startRender( QgsSymbolRenderContext &context ) override;
void stopRender( QgsSymbolRenderContext &context ) override;
void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) override;
/**
* Returns the text format used to render the layer.
*
* \see setTextFormat()
*/
QgsTextFormat textFormat() const;
/**
* Sets the text \a format used to render the layer.
*
* \see textFormat()
*/
void setTextFormat( const QgsTextFormat &format );
/**
* Returns the numeric format used to format the labels for the layer.
*
* \see setNumericFormat()
*/
QgsNumericFormat *numericFormat() const;
/**
* Sets the numeric \a format used to format the labels for the layer.
*
* Ownership of \a format is transferred to the layer.
*
* \see numericFormat()
*/
void setNumericFormat( QgsNumericFormat *format SIP_TRANSFER );
/**
* Returns the interval between labels.
*
* Units are always in the original layer CRS units.
*
* \see setInterval()
*/
double interval() const;
/**
* Sets the \a interval between labels.
*
* Units are always in the original layer CRS units.
*
* \see setInterval()
*/
void setInterval( double interval );
/**
* Returns the multiple distance to skip labels for.
*
* If this value is non-zero, then any labels which are integer multiples of the returned
* value will be skipped. This allows creation of advanced referencing styles where a single
* QgsSymbol has multiple QgsLinearReferencingSymbolLayer symbol layers, eg allowing
* labeling every 100 in a normal font and every 1000 in a bold, larger font.
*
* \see setSkipMultiplesOf()
*/
double skipMultiplesOf() const;
/**
* Sets the \a multiple distance to skip labels for.
*
* If this value is non-zero, then any labels which are integer multiples of the returned
* value will be skipped. This allows creation of advanced referencing styles where a single
* QgsSymbol has multiple QgsLinearReferencingSymbolLayer symbol layers, eg allowing
* labeling every 100 in a normal font and every 1000 in a bold, larger font.
*
* \see skipMultiplesOf()
*/
void setSkipMultiplesOf( double multiple );
/**
* Returns TRUE if the labels and symbols are to be rotated to match their line segment orientation.
*
* \see setRotateLabels()
*/
bool rotateLabels() const { return mRotateLabels; }
/**
* Sets whether the labels and symbols should be rotated to match their line segment orientation.
*
* \see rotateLabels()
*/
void setRotateLabels( bool rotate ) { mRotateLabels = rotate; }
/**
* Returns the offset between the line and linear referencing labels.
*
* The unit for the offset is retrievable via labelOffsetUnit().
*
* \see setLabelOffset()
* \see labelOffsetUnit()
*/
QPointF labelOffset() const { return mLabelOffset; }
/**
* Sets the \a offset between the line and linear referencing labels.
*
* The unit for the offset is set via setLabelOffsetUnit().
*
* \see labelOffset()
* \see setLabelOffsetUnit()
*/
void setLabelOffset( const QPointF &offset ) { mLabelOffset = offset; }
/**
* Returns the unit used for the offset between the line and linear referencing labels.
*
* \see setLabelOffsetUnit()
* \see labelOffset()
*/
Qgis::RenderUnit labelOffsetUnit() const { return mLabelOffsetUnit; }
/**
* Sets the \a unit used for the offset between the line and linear referencing labels.
*
* \see labelOffsetUnit()
* \see setLabelOffset()
*/
void setLabelOffsetUnit( Qgis::RenderUnit unit ) { mLabelOffsetUnit = unit; }
/**
* Returns the map unit scale used for calculating the offset between the line and linear referencing labels.
*
* \see setLabelOffsetMapUnitScale()
*/
const QgsMapUnitScale &labelOffsetMapUnitScale() const { return mLabelOffsetMapUnitScale; }
/**
* Sets the map unit \a scale used for calculating the offset between the line and linear referencing labels.
*
* \see labelOffsetMapUnitScale()
*/
void setLabelOffsetMapUnitScale( const QgsMapUnitScale &scale ) { mLabelOffsetMapUnitScale = scale; }
/**
* Returns TRUE if a marker symbol should be shown corresponding to the labeled point on line.
*
* The marker symbol is set using setSubSymbol()
*
* \see setShowMarker()
*/
bool showMarker() const;
/**
* Sets whether a marker symbol should be shown corresponding to the labeled point on line.
*
* The marker symbol is set using setSubSymbol()
*
* \see showMarker()
*/
void setShowMarker( bool show );
/**
* Returns the placement mode for the labels.
*
* \see setPlacement()
*/
Qgis::LinearReferencingPlacement placement() const;
/**
* Sets the \a placement mode for the labels.
*
* \see placement()
*/
void setPlacement( Qgis::LinearReferencingPlacement placement );
/**
* Returns the label source, which dictates what quantity to use for the labels shown.
*
* \see setLabelSource()
*/
Qgis::LinearReferencingLabelSource labelSource() const;
/**
* Sets the label \a source, which dictates what quantity to use for the labels shown.
*
* \see labelSource()
*/
void setLabelSource( Qgis::LinearReferencingLabelSource source );
/**
* Returns the length of line over which the line's direction is averaged when
* calculating individual label angles. Longer lengths smooth out angles from jagged lines to a greater extent.
*
* Units are retrieved through averageAngleUnit()
*
* \see setAverageAngleLength()
* \see averageAngleUnit()
* \see averageAngleMapUnitScale()
*/
double averageAngleLength() const { return mAverageAngleLength; }
/**
* Sets the \a length of line over which the line's direction is averaged when
* calculating individual label angles. Longer lengths smooth out angles from jagged lines to a greater extent.
*
* Units are set through setAverageAngleUnit()
*
* \see averageAngleLength()
* \see setAverageAngleUnit()
* \see setAverageAngleMapUnitScale()
*/
void setAverageAngleLength( double length ) { mAverageAngleLength = length; }
/**
* Sets the \a unit for the length over which the line's direction is averaged when
* calculating individual label angles.
*
* \see averageAngleUnit()
* \see setAverageAngleLength()
* \see setAverageAngleMapUnitScale()
*/
void setAverageAngleUnit( Qgis::RenderUnit unit ) { mAverageAngleLengthUnit = unit; }
/**
* Returns the unit for the length over which the line's direction is averaged when
* calculating individual label angles.
*
* \see setAverageAngleUnit()
* \see averageAngleLength()
* \see averageAngleMapUnitScale()
*/
Qgis::RenderUnit averageAngleUnit() const { return mAverageAngleLengthUnit; }
/**
* Sets the map unit \a scale for the length over which the line's direction is averaged when
* calculating individual label angles.
*
* \see averageAngleMapUnitScale()
* \see setAverageAngleLength()
* \see setAverageAngleUnit()
*/
void setAverageAngleMapUnitScale( const QgsMapUnitScale &scale ) { mAverageAngleLengthMapUnitScale = scale; }
/**
* Returns the map unit scale for the length over which the line's direction is averaged when
* calculating individual label angles.
*
* \see setAverageAngleMapUnitScale()
* \see averageAngleLength()
* \see averageAngleUnit()
*/
const QgsMapUnitScale &averageAngleMapUnitScale() const { return mAverageAngleLengthMapUnitScale; }
private:
void renderPolylineInterval( const QgsLineString *line, QgsSymbolRenderContext &context, double skipMultiples, const QPointF &labelOffsetPainterUnits, double averageAngleLengthPainterUnits, bool showMarker );
void renderPolylineVertex( const QgsLineString *line, QgsSymbolRenderContext &context, double skipMultiples, const QPointF &labelOffsetPainterUnits, double averageAngleLengthPainterUnits, bool showMarker );
static QPointF pointToPainter( QgsSymbolRenderContext &context, double x, double y, double z );
Qgis::LinearReferencingPlacement mPlacement = Qgis::LinearReferencingPlacement::IntervalCartesian2D;
Qgis::LinearReferencingLabelSource mLabelSource = Qgis::LinearReferencingLabelSource::CartesianDistance2D;
double mInterval = 1000;
double mSkipMultiplesOf = 0;
bool mRotateLabels = true;
QPointF mLabelOffset{ 1, 0 };
Qgis::RenderUnit mLabelOffsetUnit = Qgis::RenderUnit::Millimeters;
QgsMapUnitScale mLabelOffsetMapUnitScale;
QgsTextFormat mTextFormat;
std::unique_ptr<QgsNumericFormat> mNumericFormat;
bool mShowMarker = false;
std::unique_ptr<QgsMarkerSymbol> mMarkerSymbol;
double mAverageAngleLength = 4;
Qgis::RenderUnit mAverageAngleLengthUnit = Qgis::RenderUnit::Millimeters;
QgsMapUnitScale mAverageAngleLengthMapUnitScale;
void renderGeometryPart( QgsSymbolRenderContext &context, const QgsAbstractGeometry *geometry, double labelOffsetPainterUnitsX, double labelOffsetPainterUnitsY, double skipMultiples, double averageAngleDistancePainterUnits, bool showMarker );
void renderLineString( QgsSymbolRenderContext &context, const QgsLineString *line, double labelOffsetPainterUnitsX, double labelOffsetPainterUnitsY, double skipMultiples, double averageAngleDistancePainterUnits, bool showMarker );
};
#endif // QGSLINEARREFERENCINGSYMBOLLAYER_H

View File

@ -116,6 +116,8 @@ void QgsSymbolLayer::initPropertyDefinitions()
{ static_cast< int >( QgsSymbolLayer::Property::RandomOffsetX ), QgsPropertyDefinition( "randomOffsetX", QObject::tr( "Horizontal random offset" ), QgsPropertyDefinition::Double, origin )},
{ static_cast< int >( QgsSymbolLayer::Property::RandomOffsetY ), QgsPropertyDefinition( "randomOffsetY", QObject::tr( "Vertical random offset" ), QgsPropertyDefinition::Double, origin )},
{ static_cast< int >( QgsSymbolLayer::Property::LineClipping ), QgsPropertyDefinition( "lineClipping", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line clipping mode" ), QObject::tr( "string " ) + QLatin1String( "[<b>no</b>|<b>during_render</b>|<b>before_render</b>]" ), origin )},
{ static_cast< int >( QgsSymbolLayer::Property::SkipMultiples ), QgsPropertyDefinition( "skipMultiples", QObject::tr( "Skip multiples of" ), QgsPropertyDefinition::DoublePositive, origin )},
{ static_cast< int >( QgsSymbolLayer::Property::ShowMarker ), QgsPropertyDefinition( "showMarker", QObject::tr( "Show marker" ), QgsPropertyDefinition::Boolean, origin )},
};
}

View File

@ -100,6 +100,8 @@ class CORE_EXPORT QgsSymbolLayer
sipType = sipType_QgsRasterLineSymbolLayer;
else if ( sipCpp->layerType() == "Lineburst" )
sipType = sipType_QgsLineburstSymbolLayer;
else if ( sipCpp->layerType() == "LinearReferencing" )
sipType = sipType_QgsLinearReferencingSymbolLayer;
else
sipType = sipType_QgsLineSymbolLayer;
break;
@ -212,6 +214,8 @@ class CORE_EXPORT QgsSymbolLayer
RandomOffsetX SIP_MONKEYPATCH_COMPAT_NAME( PropertyRandomOffsetX ), //!< Random offset X \since QGIS 3.24
RandomOffsetY SIP_MONKEYPATCH_COMPAT_NAME( PropertyRandomOffsetY ), //!< Random offset Y \since QGIS 3.24
LineClipping SIP_MONKEYPATCH_COMPAT_NAME( PropertyLineClipping ), //!< Line clipping mode \since QGIS 3.24
SkipMultiples, //!< Skip multiples of \since QGIS 3.40
ShowMarker, //!< Show markers \since QGIS 3.40
};
// *INDENT-ON*

View File

@ -24,6 +24,7 @@
#include "qgsmasksymbollayer.h"
#include "qgsgeometrygeneratorsymbollayer.h"
#include "qgsinterpolatedlinerenderer.h"
#include "qgslinearreferencingsymbollayer.h"
QgsSymbolLayerRegistry::QgsSymbolLayerRegistry()
{
@ -44,6 +45,8 @@ QgsSymbolLayerRegistry::QgsSymbolLayerRegistry()
QgsLineburstSymbolLayer::create ) );
addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "FilledLine" ), QObject::tr( "Filled Line" ), Qgis::SymbolType::Line,
QgsFilledLineSymbolLayer::create ) );
addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "LinearReferencing" ), QObject::tr( "Linear Referencing" ), Qgis::SymbolType::Line,
QgsLinearReferencingSymbolLayer::create ) );
addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "SimpleMarker" ), QObject::tr( "Simple Marker" ), Qgis::SymbolType::Marker,
QgsSimpleMarkerSymbolLayer::create, QgsSimpleMarkerSymbolLayer::createFromSld ) );

View File

@ -83,6 +83,7 @@ static void _initWidgetFunctions()
_initWidgetFunction( QStringLiteral( "RasterLine" ), QgsRasterLineSymbolLayerWidget::create );
_initWidgetFunction( QStringLiteral( "Lineburst" ), QgsLineburstSymbolLayerWidget::create );
_initWidgetFunction( QStringLiteral( "FilledLine" ), QgsFilledLineSymbolLayerWidget::create );
_initWidgetFunction( QStringLiteral( "LinearReferencing" ), QgsLinearReferencingSymbolLayerWidget::create );
_initWidgetFunction( QStringLiteral( "SimpleMarker" ), QgsSimpleMarkerSymbolLayerWidget::create );
_initWidgetFunction( QStringLiteral( "FilledMarker" ), QgsFilledMarkerSymbolLayerWidget::create );

View File

@ -42,6 +42,8 @@
#include "qgsmarkersymbol.h"
#include "qgsfillsymbol.h"
#include "qgsiconutils.h"
#include "qgslinearreferencingsymbollayer.h"
#include "qgsnumericformatselectorwidget.h"
#include <QAbstractButton>
#include <QButtonGroup>
@ -5458,3 +5460,242 @@ QgsSymbolLayer *QgsFilledLineSymbolLayerWidget::symbolLayer()
{
return mLayer;
}
//
// QgsLinearReferencingSymbolLayerWidget
//
QgsLinearReferencingSymbolLayerWidget::QgsLinearReferencingSymbolLayerWidget( QgsVectorLayer *vl, QWidget *parent )
: QgsSymbolLayerWidget( parent, vl )
{
mLayer = nullptr;
setupUi( this );
mComboPlacement->addItem( tr( "Interval (Cartesian 2D Distances)" ), QVariant::fromValue( Qgis::LinearReferencingPlacement::IntervalCartesian2D ) );
mComboPlacement->addItem( tr( "Interval (Z Values)" ), QVariant::fromValue( Qgis::LinearReferencingPlacement::IntervalZ ) );
mComboPlacement->addItem( tr( "Interval (M Values)" ), QVariant::fromValue( Qgis::LinearReferencingPlacement::IntervalM ) );
mComboPlacement->addItem( tr( "On Every Vertex" ), QVariant::fromValue( Qgis::LinearReferencingPlacement::Vertex ) );
mComboQuantity->addItem( tr( "Cartesian 2D Distance" ), QVariant::fromValue( Qgis::LinearReferencingLabelSource::CartesianDistance2D ) );
mComboQuantity->addItem( tr( "Z Values" ), QVariant::fromValue( Qgis::LinearReferencingLabelSource::Z ) );
mComboQuantity->addItem( tr( "M Values" ), QVariant::fromValue( Qgis::LinearReferencingLabelSource::M ) );
mSpinSkipMultiples->setClearValue( 0, tr( "Not set" ) );
mSpinLabelOffsetX->setClearValue( 0 );
mSpinLabelOffsetY->setClearValue( 0 );
mSpinAverageAngleLength->setClearValue( 4.0 );
mLabelOffsetUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << Qgis::RenderUnit::Millimeters << Qgis::RenderUnit::MetersInMapUnits << Qgis::RenderUnit::MapUnits << Qgis::RenderUnit::Pixels
<< Qgis::RenderUnit::Points << Qgis::RenderUnit::Inches );
mAverageAngleUnit->setUnits( QgsUnitTypes::RenderUnitList() << Qgis::RenderUnit::Millimeters << Qgis::RenderUnit::MetersInMapUnits << Qgis::RenderUnit::MapUnits << Qgis::RenderUnit::Pixels
<< Qgis::RenderUnit::Points << Qgis::RenderUnit::Inches );
connect( mComboQuantity, qOverload< int >( &QComboBox::currentIndexChanged ), this, [ = ]
{
if ( mLayer && !mBlockChangesSignal )
{
mLayer->setLabelSource( mComboQuantity->currentData().value< Qgis::LinearReferencingLabelSource >() );
emit changed();
}
} );
connect( mTextFormatButton, &QgsFontButton::changed, this, [ = ]
{
if ( mLayer && !mBlockChangesSignal )
{
mLayer->setTextFormat( mTextFormatButton->textFormat() );
emit changed();
}
} );
connect( spinInterval, qOverload< double >( &QgsDoubleSpinBox::valueChanged ), this, [ = ]
{
if ( mLayer && !mBlockChangesSignal )
{
mLayer->setInterval( spinInterval->value() );
emit changed();
}
} );
connect( mSpinSkipMultiples, qOverload< double >( &QgsDoubleSpinBox::valueChanged ), this, [ = ]
{
if ( mLayer && !mBlockChangesSignal )
{
mLayer->setSkipMultiplesOf( mSpinSkipMultiples->value() );
emit changed();
}
} );
connect( mCheckRotate, &QCheckBox::toggled, this, [ = ]( bool checked )
{
if ( mLayer && !mBlockChangesSignal )
{
mLayer->setRotateLabels( checked );
emit changed();
}
mSpinAverageAngleLength->setEnabled( checked );
mAverageAngleUnit->setEnabled( mSpinAverageAngleLength->isEnabled() );
} );
connect( mCheckShowMarker, &QCheckBox::toggled, this, [ = ]( bool checked )
{
if ( mLayer && !mBlockChangesSignal )
{
mLayer->setShowMarker( checked );
emit symbolChanged();
}
} );
connect( mSpinLabelOffsetX, qOverload< double >( &QgsDoubleSpinBox::valueChanged ), this, [ = ]
{
if ( mLayer && !mBlockChangesSignal )
{
mLayer->setLabelOffset( QPointF( mSpinLabelOffsetX->value(), mSpinLabelOffsetY->value() ) );
emit changed();
}
} );
connect( mSpinLabelOffsetY, qOverload< double >( &QgsDoubleSpinBox::valueChanged ), this, [ = ]
{
if ( mLayer && !mBlockChangesSignal )
{
mLayer->setLabelOffset( QPointF( mSpinLabelOffsetX->value(), mSpinLabelOffsetY->value() ) );
emit changed();
}
} );
connect( mLabelOffsetUnitWidget, &QgsUnitSelectionWidget::changed, this, [ = ]
{
if ( mLayer && !mBlockChangesSignal )
{
mLayer->setLabelOffsetUnit( mLabelOffsetUnitWidget->unit() );
mLayer->setLabelOffsetMapUnitScale( mLabelOffsetUnitWidget->getMapUnitScale() );
emit changed();
}
} );
connect( mComboPlacement, qOverload< int>( &QComboBox::currentIndexChanged ), this, [ = ]
{
if ( mLayer && !mBlockChangesSignal )
{
const Qgis::LinearReferencingPlacement placement = mComboPlacement->currentData().value< Qgis::LinearReferencingPlacement >();
mLayer->setPlacement( placement );
switch ( placement )
{
case Qgis::LinearReferencingPlacement::IntervalCartesian2D:
case Qgis::LinearReferencingPlacement::IntervalZ:
case Qgis::LinearReferencingPlacement::IntervalM:
mIntervalWidget->show();
break;
case Qgis::LinearReferencingPlacement::Vertex:
mIntervalWidget->hide();
break;
}
emit changed();
}
} );
connect( mSpinAverageAngleLength, qOverload< double >( &QgsDoubleSpinBox::valueChanged ), this, [ = ]
{
if ( mLayer && !mBlockChangesSignal )
{
mLayer->setAverageAngleLength( mSpinAverageAngleLength->value() );
emit changed();
}
} );
connect( mAverageAngleUnit, &QgsUnitSelectionWidget::changed, this, [ = ]
{
if ( mLayer && !mBlockChangesSignal )
{
mLayer->setAverageAngleUnit( mAverageAngleUnit->unit() );
mLayer->setAverageAngleMapUnitScale( mAverageAngleUnit->getMapUnitScale() );
emit changed();
}
} );
connect( mNumberFormatPushButton, &QPushButton::clicked, this, &QgsLinearReferencingSymbolLayerWidget::changeNumberFormat );
mTextFormatButton->registerExpressionContextGenerator( this );
}
QgsLinearReferencingSymbolLayerWidget::~QgsLinearReferencingSymbolLayerWidget() = default;
void QgsLinearReferencingSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
{
if ( !layer || layer->layerType() != QLatin1String( "LinearReferencing" ) )
return;
// layer type is correct, we can do the cast
mLayer = qgis::down_cast<QgsLinearReferencingSymbolLayer *>( layer );
mBlockChangesSignal = true;
mComboPlacement->setCurrentIndex( mComboPlacement->findData( QVariant::fromValue( mLayer->placement() ) ) );
switch ( mLayer->placement() )
{
case Qgis::LinearReferencingPlacement::IntervalCartesian2D:
case Qgis::LinearReferencingPlacement::IntervalZ:
case Qgis::LinearReferencingPlacement::IntervalM:
mIntervalWidget->show();
break;
case Qgis::LinearReferencingPlacement::Vertex:
mIntervalWidget->hide();
break;
}
mComboQuantity->setCurrentIndex( mComboQuantity->findData( QVariant::fromValue( mLayer->labelSource() ) ) );
mTextFormatButton->setTextFormat( mLayer->textFormat() );
spinInterval->setValue( mLayer->interval() );
mSpinSkipMultiples->setValue( mLayer->skipMultiplesOf() );
mCheckRotate->setChecked( mLayer->rotateLabels() );
mCheckShowMarker->setChecked( mLayer->showMarker() );
mSpinLabelOffsetX->setValue( mLayer->labelOffset().x() );
mSpinLabelOffsetY->setValue( mLayer->labelOffset().y() );
mLabelOffsetUnitWidget->setUnit( mLayer->labelOffsetUnit() );
mLabelOffsetUnitWidget->setMapUnitScale( mLayer->labelOffsetMapUnitScale() );
mAverageAngleUnit->setUnit( mLayer->averageAngleUnit() );
mAverageAngleUnit->setMapUnitScale( mLayer->averageAngleMapUnitScale() );
mSpinAverageAngleLength->setValue( mLayer->averageAngleLength() );
mSpinAverageAngleLength->setEnabled( mCheckRotate->isChecked() );
mAverageAngleUnit->setEnabled( mSpinAverageAngleLength->isEnabled() );
registerDataDefinedButton( mIntervalDDBtn, QgsSymbolLayer::Property::Interval );
registerDataDefinedButton( mAverageAngleDDBtn, QgsSymbolLayer::Property::AverageAngleLength );
registerDataDefinedButton( mSkipMultiplesDDBtn, QgsSymbolLayer::Property::SkipMultiples );
registerDataDefinedButton( mShowMarkerDDBtn, QgsSymbolLayer::Property::ShowMarker );
mBlockChangesSignal = false;
}
QgsSymbolLayer *QgsLinearReferencingSymbolLayerWidget::symbolLayer()
{
return mLayer;
}
void QgsLinearReferencingSymbolLayerWidget::setContext( const QgsSymbolWidgetContext &context )
{
QgsSymbolLayerWidget::setContext( context );
mTextFormatButton->setMapCanvas( context.mapCanvas() );
mTextFormatButton->setMessageBar( context.messageBar() );
}
void QgsLinearReferencingSymbolLayerWidget::changeNumberFormat()
{
QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this );
if ( panel && panel->dockMode() )
{
QgsNumericFormatSelectorWidget *widget = new QgsNumericFormatSelectorWidget( this );
widget->setPanelTitle( tr( "Number Format" ) );
widget->setFormat( mLayer->numericFormat() );
connect( widget, &QgsNumericFormatSelectorWidget::changed, this, [ = ]
{
if ( !mBlockChangesSignal )
{
mLayer->setNumericFormat( widget->format() );
emit changed();
}
} );
panel->openPanel( widget );
}
else
{
// TODO!! dialog mode
}
}

View File

@ -144,7 +144,6 @@ class GUI_EXPORT QgsSimpleLineSymbolLayerWidget : public QgsSymbolLayerWidget, p
*/
static QgsSymbolLayerWidget *create( QgsVectorLayer *vl ) SIP_FACTORY { return new QgsSimpleLineSymbolLayerWidget( vl ); }
// from base class
void setSymbolLayer( QgsSymbolLayer *layer ) override;
QgsSymbolLayer *symbolLayer() override;
void setContext( const QgsSymbolWidgetContext &context ) override;
@ -1291,6 +1290,51 @@ class GUI_EXPORT QgsCentroidFillSymbolLayerWidget : public QgsSymbolLayerWidget,
};
///////////
#include "ui_qgslinearreferencingsymbollayerwidgetbase.h"
class QgsLinearReferencingSymbolLayer;
/**
* \ingroup gui
* \class QgsLinearReferencingSymbolLayerWidget
* \brief Widget for controlling the properties of a QgsLinearReferencingSymbolLayer.
* \since QGIS 3.40
*/
class GUI_EXPORT QgsLinearReferencingSymbolLayerWidget : public QgsSymbolLayerWidget, private Ui::QgsLinearReferencingSymbolLayerWidgetBase
{
Q_OBJECT
public:
/**
* Constructor for QgsLinearReferencingSymbolLayerWidget.
*/
QgsLinearReferencingSymbolLayerWidget( QgsVectorLayer *vl, QWidget *parent SIP_TRANSFERTHIS = nullptr );
~QgsLinearReferencingSymbolLayerWidget() override;
/**
* Creates a new QgsLinearReferencingSymbolLayerWidget.
* \param vl associated vector layer
*/
static QgsSymbolLayerWidget *create( QgsVectorLayer *vl ) SIP_FACTORY { return new QgsLinearReferencingSymbolLayerWidget( vl ); }
void setSymbolLayer( QgsSymbolLayer *layer ) override;
QgsSymbolLayer *symbolLayer() override;
void setContext( const QgsSymbolWidgetContext &context ) override;
private slots:
void changeNumberFormat();
private:
QgsLinearReferencingSymbolLayer *mLayer = nullptr;
bool mBlockChangesSignal = false;
};
#include "ui_qgsgeometrygeneratorwidgetbase.h"
#include "qgis_gui.h"

View File

@ -814,9 +814,8 @@ void QgsSymbolSelectorWidget::duplicateLayer()
void QgsSymbolSelectorWidget::changeLayer( QgsSymbolLayer *newLayer )
{
SymbolLayerItem *item = currentLayerItem();
QgsSymbolLayer *layer = item->layer();
if ( layer->subSymbol() )
if ( item->rowCount() > 0 )
{
item->removeRow( 0 );
}

View File

@ -0,0 +1,439 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsLinearReferencingSymbolLayerWidgetBase</class>
<widget class="QWidget" name="QgsLinearReferencingSymbolLayerWidgetBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>388</width>
<height>419</height>
</rect>
</property>
<property name="focusPolicy">
<enum>Qt::FocusPolicy::WheelFocus</enum>
</property>
<property name="windowTitle">
<string notr="true">Linear Referencing Symbol Layer</string>
</property>
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,0,0,0,0,0,0,0,0,1" columnstretch="1,2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item row="5" column="0">
<widget class="QLabel" name="mUnitLabelLabel_2">
<property name="text">
<string>Number format</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="1">
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>x</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QgsDoubleSpinBox" name="mSpinLabelOffsetX">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-99999999.989999994635582</double>
</property>
<property name="maximum">
<double>99999999.989999994635582</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>y</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QgsDoubleSpinBox" name="mSpinLabelOffsetY">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-99999999.989999994635582</double>
</property>
<property name="maximum">
<double>99999999.989999994635582</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
</widget>
</item>
<item row="0" column="2" rowspan="2">
<widget class="QgsUnitSelectionWidget" name="mLabelOffsetUnitWidget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::FocusPolicy::StrongFocus</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Label offset</string>
</property>
</widget>
</item>
<item row="13" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="1">
<widget class="QgsFontButton" name="mTextFormatButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Text format</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Average angle over</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QWidget" name="mIntervalWidget" native="true">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_9">
<property name="text">
<string>Interval</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="rightMargin">
<number>0</number>
</property>
<item>
<widget class="QgsDoubleSpinBox" name="spinInterval">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>10000000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
<property name="showClearButton" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QgsPropertyOverrideButton" name="mIntervalDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QgsDoubleSpinBox" name="mSpinSkipMultiples">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>10000000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
<property name="showClearButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QgsPropertyOverrideButton" name="mSkipMultiplesDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="1">
<widget class="QPushButton" name="mNumberFormatPushButton">
<property name="text">
<string>Customize</string>
</property>
</widget>
</item>
<item row="12" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="rightMargin">
<number>0</number>
</property>
<item>
<widget class="QgsDoubleSpinBox" name="mSpinAverageAngleLength">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>10000000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QgsUnitSelectionWidget" name="mAverageAngleUnit" native="true">
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::FocusPolicy::TabFocus</enum>
</property>
</widget>
</item>
<item>
<widget class="QgsPropertyOverrideButton" name="mAverageAngleDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Measure placement</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Skip multiples of</string>
</property>
</widget>
</item>
<item row="11" column="0" colspan="2">
<widget class="QCheckBox" name="mCheckRotate">
<property name="text">
<string>Rotate labels to follow line direction</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Quantity</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="mComboPlacement"/>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="mComboQuantity"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Text format</string>
</property>
</widget>
</item>
<item row="10" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="mCheckShowMarker">
<property name="text">
<string>Show marker symbols</string>
</property>
</widget>
</item>
<item>
<widget class="QgsPropertyOverrideButton" name="mShowMarkerDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsPropertyOverrideButton</class>
<extends>QToolButton</extends>
<header>qgspropertyoverridebutton.h</header>
</customwidget>
<customwidget>
<class>QgsDoubleSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>qgsdoublespinbox.h</header>
</customwidget>
<customwidget>
<class>QgsUnitSelectionWidget</class>
<extends>QWidget</extends>
<header>qgsunitselectionwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsFontButton</class>
<extends>QToolButton</extends>
<header>qgsfontbutton.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>mComboPlacement</tabstop>
<tabstop>spinInterval</tabstop>
<tabstop>mIntervalDDBtn</tabstop>
<tabstop>mComboQuantity</tabstop>
<tabstop>mTextFormatButton</tabstop>
<tabstop>mNumberFormatPushButton</tabstop>
<tabstop>mSpinSkipMultiples</tabstop>
<tabstop>mSkipMultiplesDDBtn</tabstop>
<tabstop>mSpinLabelOffsetX</tabstop>
<tabstop>mLabelOffsetUnitWidget</tabstop>
<tabstop>mSpinLabelOffsetY</tabstop>
<tabstop>mCheckShowMarker</tabstop>
<tabstop>mShowMarkerDDBtn</tabstop>
<tabstop>mCheckRotate</tabstop>
<tabstop>mSpinAverageAngleLength</tabstop>
<tabstop>mAverageAngleUnit</tabstop>
<tabstop>mAverageAngleDDBtn</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@ -158,6 +158,7 @@ ADD_PYTHON_TEST(PyQgsLayoutSnapper test_qgslayoutsnapper.py)
ADD_PYTHON_TEST(PyQgsLayoutTable test_qgslayouttable.py)
ADD_PYTHON_TEST(PyQgsLegendPatchShape test_qgslegendpatchshape.py)
ADD_PYTHON_TEST(PyQgsLegendRenderer test_qgslegendrenderer.py)
ADD_PYTHON_TEST(PyQgsLinearReferencingSymbolLayer test_qgslinearreferencingsymbollayer.py)
ADD_PYTHON_TEST(PyQgsLineSegment test_qgslinesegment.py)
ADD_PYTHON_TEST(PyQgsLineString test_qgslinestring.py)
ADD_PYTHON_TEST(PyQgsLineSymbolLayers test_qgslinesymbollayers.py)

View File

@ -0,0 +1,759 @@
"""
***************************************************************************
test_qgslinearreferencingsymbollayer.py
---------------------
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. *
* *
***************************************************************************
"""
import unittest
from qgis.PyQt.QtCore import QPointF
from qgis.PyQt.QtGui import QColor, QImage, QPainter
from qgis.core import (
Qgis,
QgsFeature,
QgsGeometry,
QgsLineSymbol,
QgsMapSettings,
QgsRenderContext,
QgsLinearReferencingSymbolLayer,
QgsTextFormat,
QgsFontUtils,
QgsBasicNumericFormat,
QgsMarkerSymbol,
QgsFillSymbol
)
from qgis.testing import start_app, QgisTestCase
from utilities import unitTestDataPath
start_app()
TEST_DATA_DIR = unitTestDataPath()
class TestQgsSimpleLineSymbolLayer(QgisTestCase):
@classmethod
def control_path_prefix(cls):
return 'symbol_linearref'
def test_distance_2d(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalCartesian2D)
linear_ref.setInterval(1)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'distance_2d',
'distance_2d',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_distance_2d_with_z(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalCartesian2D)
linear_ref.setInterval(1)
linear_ref.setLabelSource(Qgis.LinearReferencingLabelSource.Z)
number_format = QgsBasicNumericFormat()
number_format.setNumberDecimalPlaces(1)
number_format.setShowTrailingZeros(False)
linear_ref.setNumericFormat(number_format)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'distance_with_z',
'distance_with_z',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_distance_2d_with_m(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalCartesian2D)
linear_ref.setInterval(1)
linear_ref.setLabelSource(Qgis.LinearReferencingLabelSource.M)
number_format = QgsBasicNumericFormat()
number_format.setNumberDecimalPlaces(1)
number_format.setShowTrailingZeros(False)
linear_ref.setNumericFormat(number_format)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'distance_with_m',
'distance_with_m',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_interpolate_by_z_with_distance(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalZ)
linear_ref.setInterval(0.3)
linear_ref.setLabelSource(Qgis.LinearReferencingLabelSource.CartesianDistance2D)
number_format = QgsBasicNumericFormat()
number_format.setNumberDecimalPlaces(1)
number_format.setShowTrailingZeros(False)
linear_ref.setNumericFormat(number_format)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'placement_by_z_distance',
'placement_by_z_distance',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_interpolate_by_z_with_z(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalZ)
linear_ref.setInterval(0.3)
linear_ref.setLabelSource(Qgis.LinearReferencingLabelSource.Z)
number_format = QgsBasicNumericFormat()
number_format.setNumberDecimalPlaces(1)
number_format.setShowTrailingZeros(False)
linear_ref.setNumericFormat(number_format)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'placement_by_z_z',
'placement_by_z_z',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_interpolate_by_z_with_m(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalZ)
linear_ref.setInterval(0.3)
linear_ref.setLabelSource(Qgis.LinearReferencingLabelSource.M)
number_format = QgsBasicNumericFormat()
number_format.setNumberDecimalPlaces(1)
number_format.setShowTrailingZeros(False)
linear_ref.setNumericFormat(number_format)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'placement_by_z_m',
'placement_by_z_m',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_interpolate_by_m_with_distance(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalM)
linear_ref.setInterval(0.3)
linear_ref.setLabelSource(Qgis.LinearReferencingLabelSource.CartesianDistance2D)
number_format = QgsBasicNumericFormat()
number_format.setNumberDecimalPlaces(1)
number_format.setShowTrailingZeros(False)
linear_ref.setNumericFormat(number_format)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'placement_by_m_distance',
'placement_by_m_distance',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_interpolate_by_m_with_z(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalM)
linear_ref.setInterval(0.3)
linear_ref.setLabelSource(Qgis.LinearReferencingLabelSource.Z)
number_format = QgsBasicNumericFormat()
number_format.setNumberDecimalPlaces(1)
number_format.setShowTrailingZeros(False)
linear_ref.setNumericFormat(number_format)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'placement_by_m_z',
'placement_by_m_z',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_interpolate_by_m_with_m(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalM)
linear_ref.setInterval(0.3)
linear_ref.setLabelSource(Qgis.LinearReferencingLabelSource.M)
number_format = QgsBasicNumericFormat()
number_format.setNumberDecimalPlaces(1)
number_format.setShowTrailingZeros(False)
linear_ref.setNumericFormat(number_format)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'placement_by_m_m',
'placement_by_m_m',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_at_vertex_with_distance(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.Vertex)
linear_ref.setLabelSource(Qgis.LinearReferencingLabelSource.CartesianDistance2D)
number_format = QgsBasicNumericFormat()
number_format.setNumberDecimalPlaces(1)
number_format.setShowTrailingZeros(False)
linear_ref.setNumericFormat(number_format)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'vertex_distance',
'vertex_distance',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_at_vertex_with_z(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.Vertex)
linear_ref.setLabelSource(Qgis.LinearReferencingLabelSource.Z)
number_format = QgsBasicNumericFormat()
number_format.setNumberDecimalPlaces(1)
number_format.setShowTrailingZeros(False)
linear_ref.setNumericFormat(number_format)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'vertex_z',
'vertex_z',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_at_vertex_with_m(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.Vertex)
linear_ref.setLabelSource(Qgis.LinearReferencingLabelSource.M)
number_format = QgsBasicNumericFormat()
number_format.setNumberDecimalPlaces(1)
number_format.setShowTrailingZeros(False)
linear_ref.setNumericFormat(number_format)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'vertex_m',
'vertex_m',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_distance_2d_skip_multiples(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalCartesian2D)
linear_ref.setInterval(1)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
linear_ref.setSkipMultiplesOf(2)
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'skip_multiples',
'skip_multiples',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_distance_2d_numeric_format(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalCartesian2D)
linear_ref.setInterval(1)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
number_format = QgsBasicNumericFormat()
number_format.setNumberDecimalPlaces(2)
number_format.setShowTrailingZeros(True)
linear_ref.setNumericFormat(number_format)
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'numeric_format',
'numeric_format',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_distance_2d_no_rotate(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalCartesian2D)
linear_ref.setInterval(1)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
linear_ref.setRotateLabels(False)
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'no_rotate',
'no_rotate',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_distance_2d_marker(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalCartesian2D)
linear_ref.setInterval(1)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
linear_ref.setShowMarker(True)
linear_ref.setSubSymbol(
QgsMarkerSymbol.createSimple(
{'color': '#00ff00', 'outline_style': 'no', 'size': '8', 'name': 'arrow'}
)
)
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'marker',
'marker',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_distance_2d_marker_no_rotate(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalCartesian2D)
linear_ref.setInterval(1)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
linear_ref.setShowMarker(True)
linear_ref.setSubSymbol(
QgsMarkerSymbol.createSimple(
{'color': '#00ff00', 'outline_style': 'no', 'size': '8', 'name': 'arrow'}
)
)
linear_ref.setRotateLabels(False)
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4))'))
self.assertTrue(
self.image_check(
'marker_no_rotate',
'marker_no_rotate',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_multiline(self):
s = QgsLineSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalCartesian2D)
linear_ref.setInterval(1)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'MultiLineStringZM ((6 2 0.2 1.2, 9 2 0.7 0.2, 9 3 0.4 0, 11 5 0.8 0.4),'
'(16 12 0.2 1.2, 19 12 0.7 0.2))'))
self.assertTrue(
self.image_check(
'multiline',
'multiline',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def test_polygon(self):
s = QgsFillSymbol.createSimple(
{'outline_color': '#ff0000', 'outline_width': '2'})
linear_ref = QgsLinearReferencingSymbolLayer()
linear_ref.setPlacement(
Qgis.LinearReferencingPlacement.IntervalCartesian2D)
linear_ref.setInterval(1)
font = QgsFontUtils.getStandardTestFont('Bold', 18)
text_format = QgsTextFormat.fromQFont(font)
text_format.setColor(QColor(255, 255, 255))
linear_ref.setTextFormat(text_format)
linear_ref.setLabelOffset(QPointF(3, -1))
s.appendSymbolLayer(linear_ref)
rendered_image = self.renderGeometry(s,
QgsGeometry.fromWkt(
'Polygon ((6 1, 10 1, 10 -3, 6 -3, 6 1),(7 0, 7 -2, 9 -2, 9 0, 7 0))'))
self.assertTrue(
self.image_check(
'polygon',
'polygon',
rendered_image,
color_tolerance=2,
allowed_mismatch=20
)
)
def renderGeometry(self, symbol, geom):
f = QgsFeature()
f.setGeometry(geom)
image = QImage(800, 800, QImage.Format.Format_RGB32)
painter = QPainter()
ms = QgsMapSettings()
extent = geom.get().boundingBox()
# buffer extent by 10%
if extent.width() > 0:
extent = extent.buffered((extent.height() + extent.width()) / 20.0)
else:
extent = extent.buffered(10)
ms.setExtent(extent)
ms.setOutputSize(image.size())
context = QgsRenderContext.fromMapSettings(ms)
context.setPainter(painter)
context.setScaleFactor(96 / 25.4) # 96 DPI
painter.begin(image)
try:
image.fill(QColor(0, 0, 0))
symbol.startRender(context)
symbol.renderFeature(f, context)
symbol.stopRender(context)
finally:
painter.end()
return image
if __name__ == '__main__':
unittest.main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB