From 48d2a370570e43cd6c9dedf802ca4549d74f7af8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 19 Mar 2019 17:08:15 +1000 Subject: [PATCH] [FEATURE] New line symbol type: Hash line This line symbol type is designed to replicate the ArcGIS Hash Line symbol layer type. It allows for a repeating line segment to be drawn over the length of a feature, with a line-sub symbol used to render each individual segment. To reduce code duplication, this is heavily based off the current line marker symbol layer, since the functionality is almost identical (draw some sub symbol at some interval along a line). Accordingly, I've split off QgsMarkerLineSymbolLayer to move as much of the common functionality as possible to a new abstract base class, so that only the actual marker/line segment rendering occurs in the marker line/hash line subclasses. This also gives the hash line all the existing placement options permissible for marker lines -- e.g. first/last vertex, mid points, regular intervals, etc. The hash line length and angle can have data defined overrides, which are evaluated per-line segment, allowing for the hash line to change size and angle over the length of a single rendered feature. --- python/core/__init__.py.in | 1 + .../core/additions/markerlinesymbollayer.py | 31 + .../symbology/qgslinesymbollayer.sip.in | 579 ++++++++---- .../symbology/qgssymbollayer.sip.in | 145 ++- .../symbology/qgssymbollayerwidget.sip.in | 32 + src/core/symbology/qgslinesymbollayer.cpp | 876 ++++++++++++------ src/core/symbology/qgslinesymbollayer.h | 518 +++++++---- src/core/symbology/qgssymbollayer.h | 116 ++- src/core/symbology/qgssymbollayerregistry.cpp | 2 + .../symbology/qgslayerpropertieswidget.cpp | 1 + src/gui/symbology/qgssymbollayerwidget.cpp | 254 ++++- src/gui/symbology/qgssymbollayerwidget.h | 51 + src/plugins/grass/qgsgrasseditrenderer.cpp | 4 +- src/ui/symbollayer/widget_hashline.ui | 436 +++++++++ tests/src/core/testqgsmarkerlinesymbol.cpp | 6 +- tests/src/core/testqgssymbol.cpp | 2 +- .../src/python/test_qgshashlinesymbollayer.py | 234 +++++ 17 files changed, 2588 insertions(+), 700 deletions(-) create mode 100644 python/core/additions/markerlinesymbollayer.py create mode 100644 src/ui/symbollayer/widget_hashline.ui create mode 100644 tests/src/python/test_qgshashlinesymbollayer.py diff --git a/python/core/__init__.py.in b/python/core/__init__.py.in index 0a5de109653..4d6ca094bf1 100644 --- a/python/core/__init__.py.in +++ b/python/core/__init__.py.in @@ -28,6 +28,7 @@ from qgis._core import * from .additions.edit import edit, QgsEditError from .additions.fromfunction import fromFunction +from .additions.markerlinesymbollayer import * from .additions.metaenum import metaEnumFromType, metaEnumFromValue from .additions.processing import processing_output_layer_repr, processing_source_repr from .additions.projectdirtyblocker import ProjectDirtyBlocker diff --git a/python/core/additions/markerlinesymbollayer.py b/python/core/additions/markerlinesymbollayer.py new file mode 100644 index 00000000000..27539b76468 --- /dev/null +++ b/python/core/additions/markerlinesymbollayer.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + markerlinesymbollayer.py + --------------------- + Date : March 2019 + Copyright : (C) 2019 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. * +* * +*************************************************************************** +""" +from qgis._core import ( + QgsMarkerLineSymbolLayer, + QgsTemplatedLineSymbolLayerBase) + + +# monkey patch deprecated enum values to maintain API +# TODO - remove for QGIS 4.0 +QgsMarkerLineSymbolLayer.Interval = QgsTemplatedLineSymbolLayerBase.Interval +QgsMarkerLineSymbolLayer.Vertex = QgsTemplatedLineSymbolLayerBase.Vertex +QgsMarkerLineSymbolLayer.LastVertex = QgsTemplatedLineSymbolLayerBase.LastVertex +QgsMarkerLineSymbolLayer.FirstVertex = QgsTemplatedLineSymbolLayerBase.FirstVertex +QgsMarkerLineSymbolLayer.CentralPoint = QgsTemplatedLineSymbolLayerBase.CentralPoint +QgsMarkerLineSymbolLayer.CurvePoint = QgsTemplatedLineSymbolLayerBase.CurvePoint diff --git a/python/core/auto_generated/symbology/qgslinesymbollayer.sip.in b/python/core/auto_generated/symbology/qgslinesymbollayer.sip.in index ba450550051..15889530463 100644 --- a/python/core/auto_generated/symbology/qgslinesymbollayer.sip.in +++ b/python/core/auto_generated/symbology/qgslinesymbollayer.sip.in @@ -233,10 +233,14 @@ used to render polygon rings. -class QgsMarkerLineSymbolLayer : QgsLineSymbolLayer +class QgsTemplatedLineSymbolLayerBase : QgsLineSymbolLayer { %Docstring -Line symbol layer type which draws repeating marker symbols along a line feature. + +Base class for templated line symbols, e.g. line symbols which draw markers or hash +lines at intervals along the line feature. + +.. versionadded:: 3.8 %End %TypeHeaderCode @@ -244,16 +248,6 @@ Line symbol layer type which draws repeating marker symbols along a line feature %End public: - QgsMarkerLineSymbolLayer( bool rotateMarker = DEFAULT_MARKERLINE_ROTATE, - double interval = DEFAULT_MARKERLINE_INTERVAL ); -%Docstring -Constructor for QgsMarkerLineSymbolLayer. Creates a marker line -with a default marker symbol, placed at the specified ``interval`` (in millimeters). - -The ``rotateMarker`` argument specifies whether individual marker symbols -should be rotated to match the line segment alignment. -%End - enum Placement { Interval, @@ -264,82 +258,33 @@ should be rotated to match the line segment alignment. CurvePoint, }; - - static QgsSymbolLayer *create( const QgsStringMap &properties = QgsStringMap() ) /Factory/; + QgsTemplatedLineSymbolLayerBase( bool rotateSymbol = true, + double interval = 3 ); %Docstring -Creates a new QgsMarkerLineSymbolLayer, using the settings -serialized in the ``properties`` map (corresponding to the output from -QgsMarkerLineSymbolLayer.properties() ). +Constructor for QgsTemplatedLineSymbolLayerBase. Creates a template +line placed at the specified ``interval`` (in millimeters). + +The ``rotateSymbol`` argument specifies whether individual symbols +should be rotated to match the line segment alignment. %End - static QgsSymbolLayer *createFromSld( QDomElement &element ) /Factory/; + bool rotateSymbols() const; %Docstring -Creates a new QgsMarkerLineSymbolLayer from an SLD XML DOM ``element``. +Returns ``True`` if the repeating symbols be rotated to match their line segment orientation. + +.. seealso:: :py:func:`setRotateSymbols` %End - - virtual QString layerType() const; - - - virtual void startRender( QgsSymbolRenderContext &context ); - - - virtual void stopRender( QgsSymbolRenderContext &context ); - - - virtual void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ); - - - virtual void renderPolygonStroke( const QPolygonF &points, QList *rings, QgsSymbolRenderContext &context ); - - - virtual QgsStringMap properties() const; - - - virtual QgsMarkerLineSymbolLayer *clone() const /Factory/; - - - virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props ) const; - - - virtual void setColor( const QColor &color ); - - virtual QColor color() const; - - - virtual QgsSymbol *subSymbol(); - - virtual bool setSubSymbol( QgsSymbol *symbol /Transfer/ ); - - - virtual void setWidth( double width ); - - virtual double width() const; - - virtual double width( const QgsRenderContext &context ) const; - - - virtual double estimateMaxBleed( const QgsRenderContext &context ) const; - - - - bool rotateMarker() const; -%Docstring -Returns ``True`` if the repeating symbols will be rotated to match their line segment orientation. - -.. seealso:: :py:func:`setRotateMarker` -%End - - void setRotateMarker( bool rotate ); + void setRotateSymbols( bool rotate ); %Docstring Sets whether the repeating symbols should be rotated to match their line segment orientation. -.. seealso:: :py:func:`rotateMarker` +.. seealso:: :py:func:`rotateSymbols` %End double interval() const; %Docstring -Returns the interval between individual markers. Units are specified through intervalUnits(). +Returns the interval between individual symbols. Units are specified through intervalUnits(). .. seealso:: :py:func:`setInterval` @@ -348,100 +293,13 @@ Returns the interval between individual markers. Units are specified through int void setInterval( double interval ); %Docstring -Sets the interval between individual markers. +Sets the interval between individual symbols. :param interval: interval size. Units are specified through setIntervalUnit() .. seealso:: :py:func:`interval` .. seealso:: :py:func:`setIntervalUnit` -%End - - Placement placement() const; -%Docstring -Returns the placement of the symbols. - -.. seealso:: :py:func:`setPlacement` -%End - - void setPlacement( Placement p ); -%Docstring -Sets the ``placement`` of the symbols. - -.. seealso:: :py:func:`placement` -%End - - double offsetAlongLine() const; -%Docstring -Returns the offset along the line for the marker placement. For Interval placements, this is the distance -between the start of the line and the first marker. For FirstVertex and LastVertex placements, this is the -distance between the marker and the start of the line or the end of the line respectively. -This setting has no effect for Vertex or CentralPoint placements. - -:return: The offset along the line. The unit for the offset is retrievable via offsetAlongLineUnit. - -.. seealso:: :py:func:`setOffsetAlongLine` - -.. seealso:: :py:func:`offsetAlongLineUnit` - -.. seealso:: :py:func:`placement` - -.. versionadded:: 2.3 -%End - - void setOffsetAlongLine( double offsetAlongLine ); -%Docstring -Sets the the offset along the line for the marker placement. For Interval placements, this is the distance -between the start of the line and the first marker. For FirstVertex and LastVertex placements, this is the -distance between the marker and the start of the line or the end of the line respectively. -This setting has no effect for Vertex or CentralPoint placements. - -:param offsetAlongLine: Distance to offset markers along the line. The offset - unit is set via setOffsetAlongLineUnit. - -.. seealso:: :py:func:`offsetAlongLine` - -.. seealso:: :py:func:`setOffsetAlongLineUnit` - -.. seealso:: :py:func:`setPlacement` - -.. versionadded:: 2.3 -%End - - QgsUnitTypes::RenderUnit offsetAlongLineUnit() const; -%Docstring -Returns the unit used for calculating the offset along line for markers. - -:return: Offset along line unit type. - -.. seealso:: :py:func:`setOffsetAlongLineUnit` - -.. seealso:: :py:func:`offsetAlongLine` -%End - - void setOffsetAlongLineUnit( QgsUnitTypes::RenderUnit unit ); -%Docstring -Sets the unit used for calculating the offset along line for markers. - -:param unit: Offset along line unit type. - -.. seealso:: :py:func:`offsetAlongLineUnit` - -.. seealso:: :py:func:`setOffsetAlongLine` -%End - - const QgsMapUnitScale &offsetAlongLineMapUnitScale() const; -%Docstring -Returns the map unit scale used for calculating the offset in map units along line for symbols. - -.. seealso:: :py:func:`setOffsetAlongLineMapUnitScale` -%End - - void setOffsetAlongLineMapUnitScale( const QgsMapUnitScale &scale ); -%Docstring -Sets the map unit ``scale`` used for calculating the offset in map units along line for symbols. - -.. seealso:: :py:func:`offsetAlongLineMapUnitScale` %End void setIntervalUnit( QgsUnitTypes::RenderUnit unit ); @@ -486,31 +344,246 @@ Returns the map unit scale for the interval between symbols. .. seealso:: :py:func:`interval` %End + Placement placement() const; +%Docstring +Returns the placement of the symbols. + +.. seealso:: :py:func:`setPlacement` +%End + + void setPlacement( Placement placement ); +%Docstring +Sets the ``placement`` of the symbols. + +.. seealso:: :py:func:`placement` +%End + + double offsetAlongLine() const; +%Docstring +Returns the offset along the line for the symbol placement. For Interval placements, this is the distance +between the start of the line and the first symbol. For FirstVertex and LastVertex placements, this is the +distance between the symbol and the start of the line or the end of the line respectively. +This setting has no effect for Vertex or CentralPoint placements. + +:return: The offset along the line. The unit for the offset is retrievable via offsetAlongLineUnit. + +.. seealso:: :py:func:`setOffsetAlongLine` + +.. seealso:: :py:func:`offsetAlongLineUnit` + +.. seealso:: :py:func:`placement` +%End + + void setOffsetAlongLine( double offsetAlongLine ); +%Docstring +Sets the the offset along the line for the symbol placement. For Interval placements, this is the distance +between the start of the line and the first symbol. For FirstVertex and LastVertex placements, this is the +distance between the symbol and the start of the line or the end of the line respectively. +This setting has no effect for Vertex or CentralPoint placements. + +:param offsetAlongLine: Distance to offset markers along the line. The offset + unit is set via setOffsetAlongLineUnit. + +.. seealso:: :py:func:`offsetAlongLine` + +.. seealso:: :py:func:`setOffsetAlongLineUnit` + +.. seealso:: :py:func:`setPlacement` +%End + + QgsUnitTypes::RenderUnit offsetAlongLineUnit() const; +%Docstring +Returns the unit used for calculating the offset along line for symbols. + +:return: Offset along line unit type. + +.. seealso:: :py:func:`setOffsetAlongLineUnit` + +.. seealso:: :py:func:`offsetAlongLine` +%End + + void setOffsetAlongLineUnit( QgsUnitTypes::RenderUnit unit ); +%Docstring +Sets the unit used for calculating the offset along line for symbols. + +:param unit: Offset along line unit type. + +.. seealso:: :py:func:`offsetAlongLineUnit` + +.. seealso:: :py:func:`setOffsetAlongLine` +%End + + const QgsMapUnitScale &offsetAlongLineMapUnitScale() const; +%Docstring +Returns the map unit scale used for calculating the offset in map units along line for symbols. + +.. seealso:: :py:func:`setOffsetAlongLineMapUnitScale` +%End + + void setOffsetAlongLineMapUnitScale( const QgsMapUnitScale &scale ); +%Docstring +Sets the map unit ``scale`` used for calculating the offset in map units along line for symbols. + +.. seealso:: :py:func:`offsetAlongLineMapUnitScale` +%End + + virtual void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) ${SIP_FINAL}; + + virtual void renderPolygonStroke( const QPolygonF &points, QList *rings, QgsSymbolRenderContext &context ) ${SIP_FINAL}; + + virtual QgsUnitTypes::RenderUnit outputUnit() const ${SIP_FINAL}; + + virtual void setMapUnitScale( const QgsMapUnitScale &scale ) ${SIP_FINAL}; + + virtual QgsMapUnitScale mapUnitScale() const ${SIP_FINAL}; + + virtual QgsStringMap properties() const; + + + protected: + + virtual void setSymbolLineAngle( double angle ) = 0; +%Docstring +Sets the line ``angle`` modification for the symbol's angle. This angle is added to +the symbol's rotation and data defined rotation before rendering the symbol, and +is used for orienting symbols to match the line's angle. + +:param angle: Angle in degrees, valid values are between 0 and 360 +%End + + virtual double symbolAngle() const = 0; +%Docstring +Returns the symbol's current angle, in degrees clockwise. +%End + + virtual void setSymbolAngle( double angle ) = 0; +%Docstring +Sets the symbol's ``angle``, in degrees clockwise. +%End + + virtual void renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer = -1, bool selected = false ) = 0; +%Docstring +Renders the templated symbol at the specified ``point``, using the given render ``context``. + +The ``feature`` argument is used to pass the feature currently being rendered (when available). + +If only a single symbol layer from the symbol should be rendered, it should be specified +in the ``layer`` argument. A ``layer`` of -1 indicates that all symbol layers should be +rendered. + +If ``selected`` is true then the symbol will be drawn using the "selected feature" +style and colors instead of the symbol's normal style. +%End + + void copyTemplateSymbolProperties( QgsTemplatedLineSymbolLayerBase *destLayer ) const; +%Docstring +Copies all common properties of this layer to another templated symbol layer. +%End + + static void setCommonProperties( QgsTemplatedLineSymbolLayerBase *destLayer, const QgsStringMap &properties ); +%Docstring +Sets all common symbol properties in the ``destLayer``, using the settings +serialized in the ``properties`` map. +%End + +}; + +class QgsMarkerLineSymbolLayer : QgsTemplatedLineSymbolLayerBase +{ +%Docstring +Line symbol layer type which draws repeating marker symbols along a line feature. +%End + +%TypeHeaderCode +#include "qgslinesymbollayer.h" +%End + public: + + QgsMarkerLineSymbolLayer( bool rotateMarker = DEFAULT_MARKERLINE_ROTATE, + double interval = DEFAULT_MARKERLINE_INTERVAL ); +%Docstring +Constructor for QgsMarkerLineSymbolLayer. Creates a marker line +with a default marker symbol, placed at the specified ``interval`` (in millimeters). + +The ``rotateMarker`` argument specifies whether individual marker symbols +should be rotated to match the line segment alignment. +%End + + + static QgsSymbolLayer *create( const QgsStringMap &properties = QgsStringMap() ) /Factory/; +%Docstring +Creates a new QgsMarkerLineSymbolLayer, using the settings +serialized in the ``properties`` map (corresponding to the output from +QgsMarkerLineSymbolLayer.properties() ). +%End + + static QgsSymbolLayer *createFromSld( QDomElement &element ) /Factory/; +%Docstring +Creates a new QgsMarkerLineSymbolLayer from an SLD XML DOM ``element``. +%End + + + virtual QString layerType() const; + + virtual void startRender( QgsSymbolRenderContext &context ); + + virtual void stopRender( QgsSymbolRenderContext &context ); + + virtual QgsMarkerLineSymbolLayer *clone() const /Factory/; + + virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props ) const; + + virtual void setColor( const QColor &color ); + + virtual QColor color() const; + + virtual QgsSymbol *subSymbol(); + + virtual bool setSubSymbol( QgsSymbol *symbol /Transfer/ ); + + virtual void setWidth( double width ); + + virtual double width() const; + + virtual double width( const QgsRenderContext &context ) const; + + virtual double estimateMaxBleed( const QgsRenderContext &context ) const; + virtual void setOutputUnit( QgsUnitTypes::RenderUnit unit ); - virtual QgsUnitTypes::RenderUnit outputUnit() const; - - - virtual void setMapUnitScale( const QgsMapUnitScale &scale ); - - virtual QgsMapUnitScale mapUnitScale() const; - - virtual QSet usedAttributes( const QgsRenderContext &context ) const; virtual bool hasDataDefinedProperties() const; - virtual void setDataDefinedProperty( QgsSymbolLayer::Property key, const QgsProperty &property ); + bool rotateMarker() const /Deprecated/; +%Docstring +Shall the marker be rotated. + +:return: ``True`` if the marker should be rotated. + +.. deprecated:: Use rotateSymbols() instead. +%End + + void setRotateMarker( bool rotate ) /Deprecated/; +%Docstring +Shall the marker be rotated. + +.. deprecated:: Use setRotateSymbols() instead. +%End protected: - void renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context ); - void renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, Placement placement = Vertex ); - void renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context ); - double markerAngle( const QPolygonF &points, bool isRing, int vertex ); + + virtual void setSymbolLineAngle( double angle ); + + virtual double symbolAngle() const; + + virtual void setSymbolAngle( double angle ); + + virtual void renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer = -1, bool selected = false ); private: @@ -518,6 +591,160 @@ Returns the map unit scale for the interval between symbols. }; +class QgsHashedLineSymbolLayer : QgsTemplatedLineSymbolLayerBase +{ +%Docstring + +Line symbol layer type which draws repeating line sections along a line feature. + +.. versionadded:: 3.8 +%End + +%TypeHeaderCode +#include "qgslinesymbollayer.h" +%End + public: + + QgsHashedLineSymbolLayer( bool rotateSymbol = true, + double interval = 3 ); +%Docstring +Constructor for QgsHashedLineSymbolLayer. Creates a line +with a default hash symbol, placed at the specified ``interval`` (in millimeters). + +The ``rotateSymbol`` argument specifies whether individual hash symbols +should be rotated to match the line segment alignment. +%End + + static QgsSymbolLayer *create( const QgsStringMap &properties = QgsStringMap() ) /Factory/; +%Docstring +Creates a new QgsHashedLineSymbolLayer, using the settings +serialized in the ``properties`` map (corresponding to the output from +QgsHashedLineSymbolLayer.properties() ). +%End + + virtual QString layerType() const; + + virtual void startRender( QgsSymbolRenderContext &context ); + + virtual void stopRender( QgsSymbolRenderContext &context ); + + virtual QgsStringMap properties() const; + + virtual QgsHashedLineSymbolLayer *clone() const /Factory/; + + virtual void setColor( const QColor &color ); + + virtual QColor color() const; + + virtual QgsSymbol *subSymbol(); + + virtual bool setSubSymbol( QgsSymbol *symbol /Transfer/ ); + + virtual void setWidth( double width ); + + virtual double width() const; + + virtual double width( const QgsRenderContext &context ) const; + + virtual double estimateMaxBleed( const QgsRenderContext &context ) const; + + virtual void setOutputUnit( QgsUnitTypes::RenderUnit unit ); + + virtual QSet usedAttributes( const QgsRenderContext &context ) const; + + virtual bool hasDataDefinedProperties() const; + + virtual void setDataDefinedProperty( QgsSymbolLayer::Property key, const QgsProperty &property ); + + + double hashAngle() const; +%Docstring +Returns the angle to use when drawing the hashed lines sections, in degrees clockwise. + +.. seealso:: :py:func:`setHashAngle` +%End + + void setHashAngle( double angle ); +%Docstring +Sets the ``angle`` to use when drawing the hashed lines sections, in degrees clockwise. + +.. seealso:: :py:func:`hashAngle` +%End + + double hashLength() const; +%Docstring +Returns the length of hash symbols. Units are specified through hashLengthUnits(). + +.. seealso:: :py:func:`setHashLength` + +.. seealso:: :py:func:`hashLengthUnit` +%End + + void setHashLength( double length ); +%Docstring +Sets the ``length`` of hash symbols. Units are specified through setHashLengthUnit() + +.. seealso:: :py:func:`hashLength` + +.. seealso:: :py:func:`setHashLengthUnit` +%End + + void setHashLengthUnit( QgsUnitTypes::RenderUnit unit ); +%Docstring +Sets the ``unit`` for the length of hash symbols. + +.. seealso:: :py:func:`hashLengthUnit` + +.. seealso:: :py:func:`setHashLength` +%End + + QgsUnitTypes::RenderUnit hashLengthUnit() const; +%Docstring +Returns the units for the length of hash symbols. + +.. seealso:: :py:func:`setHashLengthUnit` + +.. seealso:: :py:func:`hashLength` +%End + + void setHashLengthMapUnitScale( const QgsMapUnitScale &scale ); +%Docstring +Sets the map unit ``scale`` for the hash length. + +.. seealso:: :py:func:`hashLengthMapUnitScale` + +.. seealso:: :py:func:`setHashLengthUnit` + +.. seealso:: :py:func:`setHashLength` +%End + + const QgsMapUnitScale &hashLengthMapUnitScale() const; +%Docstring +Returns the map unit scale for the hash length. + +.. seealso:: :py:func:`setHashLengthMapUnitScale` + +.. seealso:: :py:func:`hashLengthUnit` + +.. seealso:: :py:func:`hashLength` +%End + + protected: + + virtual void setSymbolLineAngle( double angle ); + + virtual double symbolAngle() const; + + virtual void setSymbolAngle( double angle ); + + virtual void renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer = -1, bool selected = false ); + + + private: + QgsHashedLineSymbolLayer( const QgsHashedLineSymbolLayer &other ); +}; + + /************************************************************************ * This file has been generated automatically from * diff --git a/python/core/auto_generated/symbology/qgssymbollayer.sip.in b/python/core/auto_generated/symbology/qgssymbollayer.sip.in index 42863887603..90da9a50907 100644 --- a/python/core/auto_generated/symbology/qgssymbollayer.sip.in +++ b/python/core/auto_generated/symbology/qgssymbollayer.sip.in @@ -835,11 +835,53 @@ class QgsLineSymbolLayer : QgsSymbolLayer InteriorRingsOnly, }; + virtual void setOutputUnit( QgsUnitTypes::RenderUnit unit ); + + virtual QgsUnitTypes::RenderUnit outputUnit() const; + + virtual void setMapUnitScale( const QgsMapUnitScale &scale ); + + virtual QgsMapUnitScale mapUnitScale() const; + + virtual void drawPreviewIcon( QgsSymbolRenderContext &context, QSize size ); + + virtual double dxfWidth( const QgsDxfExport &e, QgsSymbolRenderContext &context ) const; + + virtual void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) = 0; +%Docstring +Renders the line symbol layer along the line joining ``points``, using the given render ``context``. + +.. seealso:: :py:func:`renderPolygonStroke` +%End virtual void renderPolygonStroke( const QPolygonF &points, QList *rings, QgsSymbolRenderContext &context ); +%Docstring +Renders the line symbol layer along the outline of polygon, using the given render ``context``. + +The exterior ring of the polygon is specified in ``points``. Optionally, interior +rings are set via the ``rings`` arugment. + +.. seealso:: :py:func:`renderPolyline` +%End virtual void setWidth( double width ); +%Docstring +Sets the ``width`` of the line symbol layer. + +Calling this method updates the width of the line symbol layer, without +changing the existing width units. It has different effects depending +on the line symbol layer subclass, e.g. for a simple line layer it +changes the stroke width of the line, for a marker line layer it +changes the size of the markers used to draw the line. + +.. seealso:: :py:func:`width` + +.. warning:: + + Since the width units vary, this method is useful for changing the + relative width of a line symbol layer only. +%End virtual double width() const; %Docstring @@ -867,7 +909,75 @@ width of the symbol layer using the provided render ``context``. %End double offset() const; +%Docstring +Returns the line's offset. + +Offset units can be retrieved by calling offsetUnit(). + +.. seealso:: :py:func:`setOffset` + +.. seealso:: :py:func:`offsetUnit` + +.. seealso:: :py:func:`offsetMapUnitScale` +%End + void setOffset( double offset ); +%Docstring +Sets the line's ``offset``. + +Offset units are set via setOffsetUnit(). + +.. seealso:: :py:func:`offset` + +.. seealso:: :py:func:`setOffsetUnit` + +.. seealso:: :py:func:`setOffsetMapUnitScale` +%End + + void setOffsetUnit( QgsUnitTypes::RenderUnit unit ); +%Docstring +Sets the ``unit`` for the line's offset. + +.. seealso:: :py:func:`offsetUnit` + +.. seealso:: :py:func:`setOffset` + +.. seealso:: :py:func:`setOffsetMapUnitScale` +%End + + QgsUnitTypes::RenderUnit offsetUnit() const; +%Docstring +Returns the units for the line's offset. + +.. seealso:: :py:func:`setOffsetUnit` + +.. seealso:: :py:func:`offset` + +.. seealso:: :py:func:`offsetMapUnitScale` +%End + + void setOffsetMapUnitScale( const QgsMapUnitScale &scale ); +%Docstring +Sets the map unit ``scale`` for the line's offset. + +.. seealso:: :py:func:`offsetMapUnitScale` + +.. seealso:: :py:func:`setOffset` + +.. seealso:: :py:func:`setOffsetUnit` +%End + + const QgsMapUnitScale &offsetMapUnitScale() const; +%Docstring +Returns the map unit scale for the line's offset. + +.. seealso:: :py:func:`setOffsetMapUnitScale` + +.. seealso:: :py:func:`offset` + +.. seealso:: :py:func:`offsetUnit` +%End + void setWidthUnit( QgsUnitTypes::RenderUnit unit ); %Docstring @@ -888,41 +998,6 @@ Returns the units for the line's width. void setWidthMapUnitScale( const QgsMapUnitScale &scale ); const QgsMapUnitScale &widthMapUnitScale() const; - void setOffsetUnit( QgsUnitTypes::RenderUnit unit ); -%Docstring -Sets the units for the line's offset. - -:param unit: offset units - -.. seealso:: :py:func:`offsetUnit` -%End - - QgsUnitTypes::RenderUnit offsetUnit() const; -%Docstring -Returns the units for the line's offset. - -.. seealso:: :py:func:`setOffsetUnit` -%End - - void setOffsetMapUnitScale( const QgsMapUnitScale &scale ); - const QgsMapUnitScale &offsetMapUnitScale() const; - - virtual void setOutputUnit( QgsUnitTypes::RenderUnit unit ); - - virtual QgsUnitTypes::RenderUnit outputUnit() const; - - - virtual void setMapUnitScale( const QgsMapUnitScale &scale ); - - virtual QgsMapUnitScale mapUnitScale() const; - - - virtual void drawPreviewIcon( QgsSymbolRenderContext &context, QSize size ); - - - virtual double dxfWidth( const QgsDxfExport &e, QgsSymbolRenderContext &context ) const; - - RenderRingFilter ringFilter() const; %Docstring Returns the line symbol layer's ring filter, which controls which rings are diff --git a/python/gui/auto_generated/symbology/qgssymbollayerwidget.sip.in b/python/gui/auto_generated/symbology/qgssymbollayerwidget.sip.in index 11f0a7b80d9..2ae08411bd4 100644 --- a/python/gui/auto_generated/symbology/qgssymbollayerwidget.sip.in +++ b/python/gui/auto_generated/symbology/qgssymbollayerwidget.sip.in @@ -377,6 +377,38 @@ Creates a new QgsMarkerLineSymbolLayerWidget. +class QgsHashedLineSymbolLayerWidget : QgsSymbolLayerWidget +{ + +%TypeHeaderCode +#include "qgssymbollayerwidget.h" +%End + public: + + QgsHashedLineSymbolLayerWidget( QgsVectorLayer *vl, QWidget *parent /TransferThis/ = 0 ); +%Docstring +Constructor for QgsHashedLineSymbolLayerWidget. + +:param vl: associated vector layer +:param parent: parent widget +%End + + static QgsSymbolLayerWidget *create( QgsVectorLayer *vl ) /Factory/; +%Docstring +Creates a new QgsHashedLineSymbolLayerWidget. + +:param vl: associated vector layer +%End + + virtual void setSymbolLayer( QgsSymbolLayer *layer ); + + virtual QgsSymbolLayer *symbolLayer(); + + +}; + + + class QgsSvgMarkerSymbolLayerWidget : QgsSymbolLayerWidget { diff --git a/src/core/symbology/qgslinesymbollayer.cpp b/src/core/symbology/qgslinesymbollayer.cpp index 29788ef6ff5..c1af7119932 100644 --- a/src/core/symbology/qgslinesymbollayer.cpp +++ b/src/core/symbology/qgslinesymbollayer.cpp @@ -737,126 +737,17 @@ class MyLine ///@endcond -QgsMarkerLineSymbolLayer::QgsMarkerLineSymbolLayer( bool rotateMarker, double interval ) +// +// QgsTemplatedLineSymbolLayerBase +// +QgsTemplatedLineSymbolLayerBase::QgsTemplatedLineSymbolLayerBase( bool rotateSymbol, double interval ) + : mRotateSymbols( rotateSymbol ) + , mInterval( interval ) { - mRotateMarker = rotateMarker; - mInterval = interval; - mIntervalUnit = QgsUnitTypes::RenderMillimeters; - mMarker = nullptr; - mPlacement = Interval; - mOffsetAlongLine = 0; - mOffsetAlongLineUnit = QgsUnitTypes::RenderMillimeters; - setSubSymbol( new QgsMarkerSymbol() ); } -QgsSymbolLayer *QgsMarkerLineSymbolLayer::create( const QgsStringMap &props ) -{ - bool rotate = DEFAULT_MARKERLINE_ROTATE; - double interval = DEFAULT_MARKERLINE_INTERVAL; - - - if ( props.contains( QStringLiteral( "interval" ) ) ) - interval = props[QStringLiteral( "interval" )].toDouble(); - if ( props.contains( QStringLiteral( "rotate" ) ) ) - rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) ); - - QgsMarkerLineSymbolLayer *x = new QgsMarkerLineSymbolLayer( rotate, interval ); - if ( props.contains( QStringLiteral( "offset" ) ) ) - { - x->setOffset( props[QStringLiteral( "offset" )].toDouble() ); - } - if ( props.contains( QStringLiteral( "offset_unit" ) ) ) - { - x->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )] ) ); - } - if ( props.contains( QStringLiteral( "interval_unit" ) ) ) - { - x->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "interval_unit" )] ) ); - } - if ( props.contains( QStringLiteral( "offset_along_line" ) ) ) - { - x->setOffsetAlongLine( props[QStringLiteral( "offset_along_line" )].toDouble() ); - } - if ( props.contains( QStringLiteral( "offset_along_line_unit" ) ) ) - { - x->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_along_line_unit" )] ) ); - } - if ( props.contains( ( QStringLiteral( "offset_along_line_map_unit_scale" ) ) ) ) - { - x->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_along_line_map_unit_scale" )] ) ); - } - - if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) ) - { - x->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )] ) ); - } - if ( props.contains( QStringLiteral( "interval_map_unit_scale" ) ) ) - { - x->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "interval_map_unit_scale" )] ) ); - } - - if ( props.contains( QStringLiteral( "placement" ) ) ) - { - if ( props[QStringLiteral( "placement" )] == QLatin1String( "vertex" ) ) - x->setPlacement( Vertex ); - else if ( props[QStringLiteral( "placement" )] == QLatin1String( "lastvertex" ) ) - x->setPlacement( LastVertex ); - else if ( props[QStringLiteral( "placement" )] == QLatin1String( "firstvertex" ) ) - x->setPlacement( FirstVertex ); - else if ( props[QStringLiteral( "placement" )] == QLatin1String( "centralpoint" ) ) - x->setPlacement( CentralPoint ); - else if ( props[QStringLiteral( "placement" )] == QLatin1String( "curvepoint" ) ) - x->setPlacement( CurvePoint ); - else - x->setPlacement( Interval ); - } - - if ( props.contains( QStringLiteral( "ring_filter" ) ) ) - { - x->setRingFilter( static_cast< RenderRingFilter>( props[QStringLiteral( "ring_filter" )].toInt() ) ); - } - - x->restoreOldDataDefinedProperties( props ); - - return x; -} - -QString QgsMarkerLineSymbolLayer::layerType() const -{ - return QStringLiteral( "MarkerLine" ); -} - -void QgsMarkerLineSymbolLayer::setColor( const QColor &color ) -{ - mMarker->setColor( color ); - mColor = color; -} - -QColor QgsMarkerLineSymbolLayer::color() const -{ - return mMarker ? mMarker->color() : mColor; -} - -void QgsMarkerLineSymbolLayer::startRender( QgsSymbolRenderContext &context ) -{ - mMarker->setOpacity( context.opacity() ); - - // if being rotated, it gets initialized with every line segment - QgsSymbol::RenderHints hints = nullptr; - if ( mRotateMarker ) - hints |= QgsSymbol::DynamicRotation; - mMarker->setRenderHints( hints ); - - mMarker->startRender( context.renderContext(), context.fields() ); -} - -void QgsMarkerLineSymbolLayer::stopRender( QgsSymbolRenderContext &context ) -{ - mMarker->stopRender( context.renderContext() ); -} - -void QgsMarkerLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) +void QgsTemplatedLineSymbolLayerBase::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) { double offset = mOffset; @@ -866,7 +757,7 @@ void QgsMarkerLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbo offset = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), mOffset ); } - Placement placement = mPlacement; + QgsTemplatedLineSymbolLayerBase::Placement placement = QgsTemplatedLineSymbolLayerBase::placement(); if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyPlacement ) ) { @@ -876,31 +767,31 @@ void QgsMarkerLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbo QString placementString = exprVal.toString(); if ( placementString.compare( QLatin1String( "interval" ), Qt::CaseInsensitive ) == 0 ) { - placement = Interval; + placement = QgsTemplatedLineSymbolLayerBase::Interval; } else if ( placementString.compare( QLatin1String( "vertex" ), Qt::CaseInsensitive ) == 0 ) { - placement = Vertex; + placement = QgsTemplatedLineSymbolLayerBase::Vertex; } else if ( placementString.compare( QLatin1String( "lastvertex" ), Qt::CaseInsensitive ) == 0 ) { - placement = LastVertex; + placement = QgsTemplatedLineSymbolLayerBase::LastVertex; } else if ( placementString.compare( QLatin1String( "firstvertex" ), Qt::CaseInsensitive ) == 0 ) { - placement = FirstVertex; + placement = QgsTemplatedLineSymbolLayerBase::FirstVertex; } else if ( placementString.compare( QLatin1String( "centerpoint" ), Qt::CaseInsensitive ) == 0 ) { - placement = CentralPoint; + placement = QgsTemplatedLineSymbolLayerBase::CentralPoint; } else if ( placementString.compare( QLatin1String( "curvepoint" ), Qt::CaseInsensitive ) == 0 ) { - placement = CurvePoint; + placement = QgsTemplatedLineSymbolLayerBase::CurvePoint; } else { - placement = Interval; + placement = QgsTemplatedLineSymbolLayerBase::Interval; } } } @@ -910,12 +801,23 @@ void QgsMarkerLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbo if ( qgsDoubleNear( offset, 0.0 ) ) { - if ( placement == Interval ) - renderPolylineInterval( points, context ); - else if ( placement == CentralPoint ) - renderPolylineCentral( points, context ); - else - renderPolylineVertex( points, context, placement ); + switch ( placement ) + { + case Interval: + renderPolylineInterval( points, context ); + break; + + case CentralPoint: + renderPolylineCentral( points, context ); + break; + + case Vertex: + case LastVertex: + case FirstVertex: + case CurvePoint: + renderPolylineVertex( points, context, placement ); + break; + } } else { @@ -926,19 +828,30 @@ void QgsMarkerLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbo { const QPolygonF &points2 = mline[ part ]; - if ( placement == Interval ) - renderPolylineInterval( points2, context ); - else if ( placement == CentralPoint ) - renderPolylineCentral( points2, context ); - else - renderPolylineVertex( points2, context, placement ); + switch ( placement ) + { + case Interval: + renderPolylineInterval( points2, context ); + break; + + case CentralPoint: + renderPolylineCentral( points2, context ); + break; + + case Vertex: + case LastVertex: + case FirstVertex: + case CurvePoint: + renderPolylineVertex( points2, context, placement ); + break; + } } } context.renderContext().painter()->restore(); } -void QgsMarkerLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, QList *rings, QgsSymbolRenderContext &context ) +void QgsTemplatedLineSymbolLayerBase::renderPolygonStroke( const QPolygonF &points, QList *rings, QgsSymbolRenderContext &context ) { const QgsCurvePolygon *curvePolygon = dynamic_cast( context.renderContext().geometry() ); @@ -982,7 +895,152 @@ void QgsMarkerLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, QLi } } -void QgsMarkerLineSymbolLayer::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context ) +QgsUnitTypes::RenderUnit QgsTemplatedLineSymbolLayerBase::outputUnit() const +{ + QgsUnitTypes::RenderUnit unit = QgsLineSymbolLayer::outputUnit(); + if ( intervalUnit() != unit || mOffsetUnit != unit || offsetAlongLineUnit() != unit ) + { + return QgsUnitTypes::RenderUnknownUnit; + } + return unit; +} + +void QgsTemplatedLineSymbolLayerBase::setMapUnitScale( const QgsMapUnitScale &scale ) +{ + QgsLineSymbolLayer::setMapUnitScale( scale ); + setIntervalMapUnitScale( scale ); + mOffsetMapUnitScale = scale; + setOffsetAlongLineMapUnitScale( scale ); +} + +QgsMapUnitScale QgsTemplatedLineSymbolLayerBase::mapUnitScale() const +{ + if ( QgsLineSymbolLayer::mapUnitScale() == intervalMapUnitScale() && + intervalMapUnitScale() == mOffsetMapUnitScale && + mOffsetMapUnitScale == offsetAlongLineMapUnitScale() ) + { + return mOffsetMapUnitScale; + } + return QgsMapUnitScale(); +} + +QgsStringMap QgsTemplatedLineSymbolLayerBase::properties() const +{ + QgsStringMap map; + map[QStringLiteral( "rotate" )] = ( rotateSymbols() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); + map[QStringLiteral( "interval" )] = QString::number( interval() ); + map[QStringLiteral( "offset" )] = QString::number( mOffset ); + map[QStringLiteral( "offset_along_line" )] = QString::number( offsetAlongLine() ); + map[QStringLiteral( "offset_along_line_unit" )] = QgsUnitTypes::encodeUnit( offsetAlongLineUnit() ); + map[QStringLiteral( "offset_along_line_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( offsetAlongLineMapUnitScale() ); + map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit ); + map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale ); + map[QStringLiteral( "interval_unit" )] = QgsUnitTypes::encodeUnit( intervalUnit() ); + map[QStringLiteral( "interval_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() ); + switch ( mPlacement ) + { + case Vertex: + map[QStringLiteral( "placement" )] = QStringLiteral( "vertex" ); + break; + case LastVertex: + map[QStringLiteral( "placement" )] = QStringLiteral( "lastvertex" ); + break; + case FirstVertex: + map[QStringLiteral( "placement" )] = QStringLiteral( "firstvertex" ); + break; + case CentralPoint: + map[QStringLiteral( "placement" )] = QStringLiteral( "centralpoint" ); + break; + case CurvePoint: + map[QStringLiteral( "placement" )] = QStringLiteral( "curvepoint" ); + break; + case Interval: + map[QStringLiteral( "placement" )] = QStringLiteral( "interval" ); + break; + } + + map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) ); + return map; +} + +void QgsTemplatedLineSymbolLayerBase::copyTemplateSymbolProperties( QgsTemplatedLineSymbolLayerBase *destLayer ) const +{ + destLayer->setSubSymbol( const_cast< QgsTemplatedLineSymbolLayerBase * >( this )->subSymbol()->clone() ); + destLayer->setOffset( mOffset ); + destLayer->setPlacement( placement() ); + destLayer->setOffsetUnit( mOffsetUnit ); + destLayer->setOffsetMapUnitScale( mOffsetMapUnitScale ); + destLayer->setIntervalUnit( intervalUnit() ); + destLayer->setIntervalMapUnitScale( intervalMapUnitScale() ); + destLayer->setOffsetAlongLine( offsetAlongLine() ); + destLayer->setOffsetAlongLineMapUnitScale( offsetAlongLineMapUnitScale() ); + destLayer->setOffsetAlongLineUnit( offsetAlongLineUnit() ); + destLayer->setRingFilter( mRingFilter ); + copyDataDefinedProperties( destLayer ); + copyPaintEffect( destLayer ); +} + +void QgsTemplatedLineSymbolLayerBase::setCommonProperties( QgsTemplatedLineSymbolLayerBase *destLayer, const QgsStringMap &properties ) +{ + if ( properties.contains( QStringLiteral( "offset" ) ) ) + { + destLayer->setOffset( properties[QStringLiteral( "offset" )].toDouble() ); + } + if ( properties.contains( QStringLiteral( "offset_unit" ) ) ) + { + destLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )] ) ); + } + if ( properties.contains( QStringLiteral( "interval_unit" ) ) ) + { + destLayer->setIntervalUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "interval_unit" )] ) ); + } + if ( properties.contains( QStringLiteral( "offset_along_line" ) ) ) + { + destLayer->setOffsetAlongLine( properties[QStringLiteral( "offset_along_line" )].toDouble() ); + } + if ( properties.contains( QStringLiteral( "offset_along_line_unit" ) ) ) + { + destLayer->setOffsetAlongLineUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_along_line_unit" )] ) ); + } + if ( properties.contains( ( QStringLiteral( "offset_along_line_map_unit_scale" ) ) ) ) + { + destLayer->setOffsetAlongLineMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_along_line_map_unit_scale" )] ) ); + } + + if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) ) + { + destLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )] ) ); + } + if ( properties.contains( QStringLiteral( "interval_map_unit_scale" ) ) ) + { + destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "interval_map_unit_scale" )] ) ); + } + + if ( properties.contains( QStringLiteral( "placement" ) ) ) + { + if ( properties[QStringLiteral( "placement" )] == QLatin1String( "vertex" ) ) + destLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::Vertex ); + else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "lastvertex" ) ) + destLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::LastVertex ); + else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "firstvertex" ) ) + destLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::FirstVertex ); + else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "centralpoint" ) ) + destLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::CentralPoint ); + else if ( properties[QStringLiteral( "placement" )] == QLatin1String( "curvepoint" ) ) + destLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::CurvePoint ); + else + destLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::Interval ); + } + + if ( properties.contains( QStringLiteral( "ring_filter" ) ) ) + { + destLayer->setRingFilter( static_cast< RenderRingFilter>( properties[QStringLiteral( "ring_filter" )].toInt() ) ); + } + + destLayer->restoreOldDataDefinedProperties( properties ); +} + +void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context ) { if ( points.isEmpty() ) return; @@ -1012,8 +1070,8 @@ void QgsMarkerLineSymbolLayer::renderPolylineInterval( const QPolygonF &points, offsetAlongLine = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyOffsetAlongLine, context.renderContext().expressionContext(), mOffsetAlongLine ); } - double painterUnitInterval = rc.convertToPainterUnits( interval, mIntervalUnit, mIntervalMapUnitScale ); - lengthLeft = painterUnitInterval - rc.convertToPainterUnits( offsetAlongLine, mOffsetAlongLineUnit, mOffsetAlongLineMapUnitScale ); + double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() ); + lengthLeft = painterUnitInterval - rc.convertToPainterUnits( offsetAlongLine, offsetAlongLineUnit(), offsetAlongLineMapUnitScale() ); int pointNum = 0; for ( int i = 1; i < points.count(); ++i ) @@ -1034,12 +1092,11 @@ void QgsMarkerLineSymbolLayer::renderPolylineInterval( const QPolygonF &points, lengthLeft += l.length(); // rotate marker (if desired) - if ( mRotateMarker ) + if ( rotateSymbols() ) { - mMarker->setLineAngle( l.angle() * 180 / M_PI ); + setSymbolLineAngle( l.angle() * 180 / M_PI ); } - // while we're not at the end of line segment, draw! while ( lengthLeft > painterUnitInterval ) { @@ -1047,7 +1104,7 @@ void QgsMarkerLineSymbolLayer::renderPolylineInterval( const QPolygonF &points, lastPt += c * diff; lengthLeft -= painterUnitInterval; scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) ); - mMarker->renderPoint( lastPt, context.feature(), rc, -1, context.selected() ); + renderSymbol( lastPt, context.feature(), rc, -1, context.selected() ); c = 1; // reset c (if wasn't 1 already) } @@ -1067,14 +1124,14 @@ static double _averageAngle( QPointF prevPt, QPointF pt, QPointF nextPt ) return std::atan2( unitY, unitX ); } -void QgsMarkerLineSymbolLayer::renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, Placement placement ) +void QgsTemplatedLineSymbolLayerBase::renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, QgsTemplatedLineSymbolLayerBase::Placement placement ) { if ( points.isEmpty() ) return; QgsRenderContext &rc = context.renderContext(); - double origAngle = mMarker->angle(); + double origAngle = symbolAngle(); int i, maxCount; bool isRing = false; @@ -1091,11 +1148,11 @@ void QgsMarkerLineSymbolLayer::renderPolylineVertex( const QPolygonF &points, Qg if ( !qgsDoubleNear( offsetAlongLine, 0.0 ) ) { //scale offset along line - offsetAlongLine = rc.convertToPainterUnits( offsetAlongLine, mOffsetAlongLineUnit, mOffsetAlongLineMapUnitScale ); + offsetAlongLine = rc.convertToPainterUnits( offsetAlongLine, offsetAlongLineUnit(), offsetAlongLineMapUnitScale() ); } if ( qgsDoubleNear( offsetAlongLine, 0.0 ) && context.renderContext().geometry() - && context.renderContext().geometry()->hasCurvedSegments() && ( placement == Vertex || placement == CurvePoint ) ) + && context.renderContext().geometry()->hasCurvedSegments() && ( placement == QgsTemplatedLineSymbolLayerBase::Vertex || placement == QgsTemplatedLineSymbolLayerBase::CurvePoint ) ) { QgsCoordinateTransform ct = context.renderContext().coordinateTransform(); const QgsMapToPixel &mtp = context.renderContext().mapToPixel(); @@ -1109,8 +1166,8 @@ void QgsMarkerLineSymbolLayer::renderPolylineVertex( const QPolygonF &points, Qg { scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) ); - if ( ( placement == Vertex && vId.type == QgsVertexId::SegmentVertex ) - || ( placement == CurvePoint && vId.type == QgsVertexId::CurveVertex ) ) + if ( ( placement == QgsTemplatedLineSymbolLayerBase::Vertex && vId.type == QgsVertexId::SegmentVertex ) + || ( placement == QgsTemplatedLineSymbolLayerBase::CurvePoint && vId.type == QgsVertexId::CurveVertex ) ) { //transform x = vPoint.x(); @@ -1123,12 +1180,12 @@ void QgsMarkerLineSymbolLayer::renderPolylineVertex( const QPolygonF &points, Qg mapPoint.setX( x ); mapPoint.setY( y ); mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() ); - if ( mRotateMarker ) + if ( rotateSymbols() ) { double angle = context.renderContext().geometry()->vertexAngle( vId ); - mMarker->setAngle( angle * 180 / M_PI ); + setSymbolAngle( angle * 180 / M_PI ); } - mMarker->renderPoint( mapPoint, context.feature(), rc, -1, context.selected() ); + renderSymbol( mapPoint, context.feature(), rc, -1, context.selected() ); } } @@ -1136,36 +1193,47 @@ void QgsMarkerLineSymbolLayer::renderPolylineVertex( const QPolygonF &points, Qg return; } - if ( placement == FirstVertex ) + switch ( placement ) { - i = 0; - maxCount = 1; - } - else if ( placement == LastVertex ) - { - i = points.count() - 1; - maxCount = points.count(); - } - else if ( placement == Vertex ) - { - i = 0; - maxCount = points.count(); - if ( points.first() == points.last() ) - isRing = true; - } - else - { - delete context.renderContext().expressionContext().popScope(); - return; + case FirstVertex: + { + i = 0; + maxCount = 1; + break; + } + + case LastVertex: + { + i = points.count() - 1; + maxCount = points.count(); + break; + } + + case Vertex: + { + i = 0; + maxCount = points.count(); + if ( points.first() == points.last() ) + isRing = true; + break; + } + + case Interval: + case CentralPoint: + case CurvePoint: + { + delete context.renderContext().expressionContext().popScope(); + return; + } } - if ( offsetAlongLine > 0 && ( placement == FirstVertex || placement == LastVertex ) ) + if ( offsetAlongLine > 0 && ( placement == QgsTemplatedLineSymbolLayerBase::FirstVertex || placement == QgsTemplatedLineSymbolLayerBase::LastVertex ) ) { double distance; - distance = placement == FirstVertex ? offsetAlongLine : -offsetAlongLine; + distance = placement == QgsTemplatedLineSymbolLayerBase::FirstVertex ? offsetAlongLine : -offsetAlongLine; renderOffsetVertexAlongLine( points, i, distance, context ); // restore original rotation - mMarker->setAngle( origAngle ); + setSymbolAngle( origAngle ); delete context.renderContext().expressionContext().popScope(); return; @@ -1176,27 +1244,27 @@ void QgsMarkerLineSymbolLayer::renderPolylineVertex( const QPolygonF &points, Qg { scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) ); - if ( isRing && placement == Vertex && i == points.count() - 1 ) + if ( isRing && placement == QgsTemplatedLineSymbolLayerBase::Vertex && i == points.count() - 1 ) { continue; // don't draw the last marker - it has been drawn already } // rotate marker (if desired) - if ( mRotateMarker ) + if ( rotateSymbols() ) { double angle = markerAngle( points, isRing, i ); - mMarker->setAngle( origAngle + angle * 180 / M_PI ); + setSymbolAngle( origAngle + angle * 180 / M_PI ); } - mMarker->renderPoint( points.at( i ), context.feature(), rc, -1, context.selected() ); + renderSymbol( points.at( i ), context.feature(), rc, -1, context.selected() ); } // restore original rotation - mMarker->setAngle( origAngle ); + setSymbolAngle( origAngle ); delete context.renderContext().expressionContext().popScope(); } -double QgsMarkerLineSymbolLayer::markerAngle( const QPolygonF &points, bool isRing, int vertex ) +double QgsTemplatedLineSymbolLayerBase::markerAngle( const QPolygonF &points, bool isRing, int vertex ) { double angle = 0; const QPointF &pt = points[vertex]; @@ -1271,25 +1339,25 @@ double QgsMarkerLineSymbolLayer::markerAngle( const QPolygonF &points, bool isRi return angle; } -void QgsMarkerLineSymbolLayer::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context ) +void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context ) { if ( points.isEmpty() ) return; QgsRenderContext &rc = context.renderContext(); - double origAngle = mMarker->angle(); + double origAngle = symbolAngle(); if ( qgsDoubleNear( distance, 0.0 ) ) { // rotate marker (if desired) - if ( mRotateMarker ) + if ( rotateSymbols() ) { bool isRing = false; if ( points.first() == points.last() ) isRing = true; double angle = markerAngle( points, isRing, vertex ); - mMarker->setAngle( origAngle + angle * 180 / M_PI ); + setSymbolAngle( origAngle + angle * 180 / M_PI ); } - mMarker->renderPoint( points[vertex], context.feature(), rc, -1, context.selected() ); + renderSymbol( points[vertex], context.feature(), rc, -1, context.selected() ); return; } @@ -1314,11 +1382,11 @@ void QgsMarkerLineSymbolLayer::renderOffsetVertexAlongLine( const QPolygonF &poi //destination point is in current segment QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft ); // rotate marker (if desired) - if ( mRotateMarker ) + if ( rotateSymbols() ) { - mMarker->setAngle( origAngle + ( l.angle() * 180 / M_PI ) ); + setSymbolAngle( origAngle + ( l.angle() * 180 / M_PI ) ); } - mMarker->renderPoint( markerPoint, context.feature(), rc, -1, context.selected() ); + renderSymbol( markerPoint, context.feature(), rc, -1, context.selected() ); return; } @@ -1329,7 +1397,7 @@ void QgsMarkerLineSymbolLayer::renderOffsetVertexAlongLine( const QPolygonF &poi //didn't find point } -void QgsMarkerLineSymbolLayer::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context ) +void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context ) { if ( !points.isEmpty() ) { @@ -1368,46 +1436,15 @@ void QgsMarkerLineSymbolLayer::renderPolylineCentral( const QPolygonF &points, Q QPointF pt = last + ( next - last ) * k; // draw the marker - double origAngle = mMarker->angle(); - if ( mRotateMarker ) - mMarker->setAngle( origAngle + l.angle() * 180 / M_PI ); - mMarker->renderPoint( pt, context.feature(), context.renderContext(), -1, context.selected() ); - if ( mRotateMarker ) - mMarker->setAngle( origAngle ); + double origAngle = symbolAngle(); + if ( rotateSymbols() ) + setSymbolAngle( origAngle + l.angle() * 180 / M_PI ); + renderSymbol( pt, context.feature(), context.renderContext(), -1, context.selected() ); + if ( rotateSymbols() ) + setSymbolAngle( origAngle ); } } - -QgsStringMap QgsMarkerLineSymbolLayer::properties() const -{ - QgsStringMap map; - map[QStringLiteral( "rotate" )] = ( mRotateMarker ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); - map[QStringLiteral( "interval" )] = QString::number( mInterval ); - map[QStringLiteral( "offset" )] = QString::number( mOffset ); - map[QStringLiteral( "offset_along_line" )] = QString::number( mOffsetAlongLine ); - map[QStringLiteral( "offset_along_line_unit" )] = QgsUnitTypes::encodeUnit( mOffsetAlongLineUnit ); - map[QStringLiteral( "offset_along_line_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetAlongLineMapUnitScale ); - map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit ); - map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale ); - map[QStringLiteral( "interval_unit" )] = QgsUnitTypes::encodeUnit( mIntervalUnit ); - map[QStringLiteral( "interval_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mIntervalMapUnitScale ); - if ( mPlacement == Vertex ) - map[QStringLiteral( "placement" )] = QStringLiteral( "vertex" ); - else if ( mPlacement == LastVertex ) - map[QStringLiteral( "placement" )] = QStringLiteral( "lastvertex" ); - else if ( mPlacement == FirstVertex ) - map[QStringLiteral( "placement" )] = QStringLiteral( "firstvertex" ); - else if ( mPlacement == CentralPoint ) - map[QStringLiteral( "placement" )] = QStringLiteral( "centralpoint" ); - else if ( mPlacement == CurvePoint ) - map[QStringLiteral( "placement" )] = QStringLiteral( "curvepoint" ); - else - map[QStringLiteral( "placement" )] = QStringLiteral( "interval" ); - - map[QStringLiteral( "ring_filter" )] = QString::number( static_cast< int >( mRingFilter ) ); - return map; -} - QgsSymbol *QgsMarkerLineSymbolLayer::subSymbol() { return mMarker.get(); @@ -1426,23 +1463,73 @@ bool QgsMarkerLineSymbolLayer::setSubSymbol( QgsSymbol *symbol ) return true; } + + +// +// QgsMarkerLineSymbolLayer +// + +QgsMarkerLineSymbolLayer::QgsMarkerLineSymbolLayer( bool rotateMarker, double interval ) + : QgsTemplatedLineSymbolLayerBase( rotateMarker, interval ) +{ + setSubSymbol( new QgsMarkerSymbol() ); +} + +QgsSymbolLayer *QgsMarkerLineSymbolLayer::create( const QgsStringMap &props ) +{ + bool rotate = DEFAULT_MARKERLINE_ROTATE; + double interval = DEFAULT_MARKERLINE_INTERVAL; + + if ( props.contains( QStringLiteral( "interval" ) ) ) + interval = props[QStringLiteral( "interval" )].toDouble(); + if ( props.contains( QStringLiteral( "rotate" ) ) ) + rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) ); + + std::unique_ptr< QgsMarkerLineSymbolLayer > x = qgis::make_unique< QgsMarkerLineSymbolLayer >( rotate, interval ); + setCommonProperties( x.get(), props ); + return x.release(); +} + +QString QgsMarkerLineSymbolLayer::layerType() const +{ + return QStringLiteral( "MarkerLine" ); +} + +void QgsMarkerLineSymbolLayer::setColor( const QColor &color ) +{ + mMarker->setColor( color ); + mColor = color; +} + +QColor QgsMarkerLineSymbolLayer::color() const +{ + return mMarker ? mMarker->color() : mColor; +} + +void QgsMarkerLineSymbolLayer::startRender( QgsSymbolRenderContext &context ) +{ + mMarker->setOpacity( context.opacity() ); + + // if being rotated, it gets initialized with every line segment + QgsSymbol::RenderHints hints = nullptr; + if ( rotateSymbols() ) + hints |= QgsSymbol::DynamicRotation; + mMarker->setRenderHints( hints ); + + mMarker->startRender( context.renderContext(), context.fields() ); +} + +void QgsMarkerLineSymbolLayer::stopRender( QgsSymbolRenderContext &context ) +{ + mMarker->stopRender( context.renderContext() ); +} + + QgsMarkerLineSymbolLayer *QgsMarkerLineSymbolLayer::clone() const { - QgsMarkerLineSymbolLayer *x = new QgsMarkerLineSymbolLayer( mRotateMarker, mInterval ); - x->setSubSymbol( mMarker->clone() ); - x->setOffset( mOffset ); - x->setPlacement( mPlacement ); - x->setOffsetUnit( mOffsetUnit ); - x->setOffsetMapUnitScale( mOffsetMapUnitScale ); - x->setIntervalUnit( mIntervalUnit ); - x->setIntervalMapUnitScale( mIntervalMapUnitScale ); - x->setOffsetAlongLine( mOffsetAlongLine ); - x->setOffsetAlongLineMapUnitScale( mOffsetAlongLineMapUnitScale ); - x->setOffsetAlongLineUnit( mOffsetAlongLineUnit ); - x->setRingFilter( mRingFilter ); - copyDataDefinedProperties( x ); - copyPaintEffect( x ); - return x; + std::unique_ptr< QgsMarkerLineSymbolLayer > x = qgis::make_unique< QgsMarkerLineSymbolLayer >( rotateSymbols(), interval() ); + copyTemplateSymbolProperties( x.get() ); + return x.release(); } void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props ) const @@ -1458,28 +1545,28 @@ void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, c QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ) ); QString gap; - switch ( mPlacement ) + switch ( placement() ) { - case FirstVertex: + case QgsTemplatedLineSymbolLayerBase::FirstVertex: symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "firstPoint" ) ) ); break; - case LastVertex: + case QgsTemplatedLineSymbolLayerBase::LastVertex: symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "lastPoint" ) ) ); break; - case CentralPoint: + case QgsTemplatedLineSymbolLayerBase::CentralPoint: symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "centralPoint" ) ) ); break; - case Vertex: + case QgsTemplatedLineSymbolLayerBase::Vertex: // no way to get line/polygon's vertices, use a VendorOption symbolizerElem.appendChild( QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "placement" ), QStringLiteral( "points" ) ) ); break; default: - double interval = QgsSymbolLayerUtils::rescaleUom( mInterval, mIntervalUnit, props ); + double interval = QgsSymbolLayerUtils::rescaleUom( QgsMarkerLineSymbolLayer::interval(), intervalUnit(), props ); gap = qgsDoubleToString( interval ); break; } - if ( !mRotateMarker ) + if ( !rotateSymbols() ) { // markers in LineSymbolizer must be drawn following the line orientation, // use a VendorOption when no marker rotation @@ -1536,17 +1623,21 @@ QgsSymbolLayer *QgsMarkerLineSymbolLayer::createFromSld( QDomElement &element ) // retrieve vendor options bool rotateMarker = true; - Placement placement = Interval; + QgsTemplatedLineSymbolLayerBase::Placement placement = QgsTemplatedLineSymbolLayerBase::Interval; QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element ); for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it ) { if ( it.key() == QLatin1String( "placement" ) ) { - if ( it.value() == QLatin1String( "points" ) ) placement = Vertex; - else if ( it.value() == QLatin1String( "firstPoint" ) ) placement = FirstVertex; - else if ( it.value() == QLatin1String( "lastPoint" ) ) placement = LastVertex; - else if ( it.value() == QLatin1String( "centralPoint" ) ) placement = CentralPoint; + if ( it.value() == QLatin1String( "points" ) ) + placement = QgsTemplatedLineSymbolLayerBase::Vertex; + else if ( it.value() == QLatin1String( "firstPoint" ) ) + placement = QgsTemplatedLineSymbolLayerBase::FirstVertex; + else if ( it.value() == QLatin1String( "lastPoint" ) ) + placement = QgsTemplatedLineSymbolLayerBase::LastVertex; + else if ( it.value() == QLatin1String( "centralPoint" ) ) + placement = QgsTemplatedLineSymbolLayerBase::CentralPoint; } else if ( it.value() == QLatin1String( "rotateMarker" ) ) { @@ -1614,6 +1705,26 @@ void QgsMarkerLineSymbolLayer::setDataDefinedProperty( QgsSymbolLayer::Property QgsLineSymbolLayer::setDataDefinedProperty( key, property ); } +void QgsMarkerLineSymbolLayer::setSymbolLineAngle( double angle ) +{ + mMarker->setLineAngle( angle ); +} + +double QgsMarkerLineSymbolLayer::symbolAngle() const +{ + return mMarker->angle(); +} + +void QgsMarkerLineSymbolLayer::setSymbolAngle( double angle ) +{ + mMarker->setAngle( angle ); +} + +void QgsMarkerLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected ) +{ + mMarker->renderPoint( point, feature, context, layer, selected ); +} + double QgsMarkerLineSymbolLayer::width() const { return mMarker->size(); @@ -1628,39 +1739,11 @@ void QgsMarkerLineSymbolLayer::setOutputUnit( QgsUnitTypes::RenderUnit unit ) { QgsLineSymbolLayer::setOutputUnit( unit ); mMarker->setOutputUnit( unit ); - mIntervalUnit = unit; + setIntervalUnit( unit ); mOffsetUnit = unit; - mOffsetAlongLineUnit = unit; + setOffsetAlongLineUnit( unit ); } -QgsUnitTypes::RenderUnit QgsMarkerLineSymbolLayer::outputUnit() const -{ - QgsUnitTypes::RenderUnit unit = QgsLineSymbolLayer::outputUnit(); - if ( mIntervalUnit != unit || mOffsetUnit != unit || mOffsetAlongLineUnit != unit ) - { - return QgsUnitTypes::RenderUnknownUnit; - } - return unit; -} - -void QgsMarkerLineSymbolLayer::setMapUnitScale( const QgsMapUnitScale &scale ) -{ - QgsLineSymbolLayer::setMapUnitScale( scale ); - mIntervalMapUnitScale = scale; - mOffsetMapUnitScale = scale; - mOffsetAlongLineMapUnitScale = scale; -} - -QgsMapUnitScale QgsMarkerLineSymbolLayer::mapUnitScale() const -{ - if ( QgsLineSymbolLayer::mapUnitScale() == mIntervalMapUnitScale && - mIntervalMapUnitScale == mOffsetMapUnitScale && - mOffsetMapUnitScale == mOffsetAlongLineMapUnitScale ) - { - return mOffsetMapUnitScale; - } - return QgsMapUnitScale(); -} QSet QgsMarkerLineSymbolLayer::usedAttributes( const QgsRenderContext &context ) const { @@ -1686,4 +1769,227 @@ double QgsMarkerLineSymbolLayer::estimateMaxBleed( const QgsRenderContext &conte } +// +// QgsHashedLineSymbolLayer +// + +QgsHashedLineSymbolLayer::QgsHashedLineSymbolLayer( bool rotateSymbol, double interval ) + : QgsTemplatedLineSymbolLayerBase( rotateSymbol, interval ) +{ + setSubSymbol( new QgsLineSymbol() ); +} + +QgsSymbolLayer *QgsHashedLineSymbolLayer::create( const QgsStringMap &props ) +{ + bool rotate = DEFAULT_MARKERLINE_ROTATE; + double interval = DEFAULT_MARKERLINE_INTERVAL; + + if ( props.contains( QStringLiteral( "interval" ) ) ) + interval = props[QStringLiteral( "interval" )].toDouble(); + if ( props.contains( QStringLiteral( "rotate" ) ) ) + rotate = ( props[QStringLiteral( "rotate" )] == QLatin1String( "1" ) ); + + std::unique_ptr< QgsHashedLineSymbolLayer > x = qgis::make_unique< QgsHashedLineSymbolLayer >( rotate, interval ); + setCommonProperties( x.get(), props ); + if ( props.contains( QStringLiteral( "hash_angle" ) ) ) + { + x->setHashAngle( props[QStringLiteral( "hash_angle" )].toDouble() ); + } + + if ( props.contains( QStringLiteral( "hash_length" ) ) ) + x->setHashLength( props[QStringLiteral( "hash_length" )].toDouble() ); + + if ( props.contains( QStringLiteral( "hash_length_unit" ) ) ) + x->setHashLengthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "hash_length_unit" )] ) ); + + if ( props.contains( QStringLiteral( "hash_length_map_unit_scale" ) ) ) + x->setHashLengthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "hash_length_map_unit_scale" )] ) ); + + return x.release(); +} + +QString QgsHashedLineSymbolLayer::layerType() const +{ + return QStringLiteral( "HashLine" ); +} + +void QgsHashedLineSymbolLayer::startRender( QgsSymbolRenderContext &context ) +{ + mHashSymbol->setOpacity( context.opacity() ); + + // if being rotated, it gets initialized with every line segment + QgsSymbol::RenderHints hints = nullptr; + if ( rotateSymbols() ) + hints |= QgsSymbol::DynamicRotation; + mHashSymbol->setRenderHints( hints ); + + mHashSymbol->startRender( context.renderContext(), context.fields() ); +} + +void QgsHashedLineSymbolLayer::stopRender( QgsSymbolRenderContext &context ) +{ + mHashSymbol->stopRender( context.renderContext() ); +} + +QgsStringMap QgsHashedLineSymbolLayer::properties() const +{ + QgsStringMap map = QgsTemplatedLineSymbolLayerBase::properties(); + map[ QStringLiteral( "hash_angle" ) ] = QString::number( mHashAngle ); + + map[QStringLiteral( "hash_length" )] = QString::number( mHashLength ); + map[QStringLiteral( "hash_length_unit" )] = QgsUnitTypes::encodeUnit( mHashLengthUnit ); + map[QStringLiteral( "hash_length_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mHashLengthMapUnitScale ); + + return map; +} + +QgsHashedLineSymbolLayer *QgsHashedLineSymbolLayer::clone() const +{ + std::unique_ptr< QgsHashedLineSymbolLayer > x = qgis::make_unique< QgsHashedLineSymbolLayer >( rotateSymbols(), interval() ); + copyTemplateSymbolProperties( x.get() ); + x->setHashAngle( mHashAngle ); + x->setHashLength( mHashLength ); + x->setHashLengthUnit( mHashLengthUnit ); + x->setHashLengthMapUnitScale( mHashLengthMapUnitScale ); + return x.release(); +} + +void QgsHashedLineSymbolLayer::setColor( const QColor &color ) +{ + mHashSymbol->setColor( color ); + mColor = color; +} + +QColor QgsHashedLineSymbolLayer::color() const +{ + return mHashSymbol ? mHashSymbol->color() : mColor; +} + +QgsSymbol *QgsHashedLineSymbolLayer::subSymbol() +{ + return mHashSymbol.get(); +} + +bool QgsHashedLineSymbolLayer::setSubSymbol( QgsSymbol *symbol ) +{ + if ( !symbol || symbol->type() != QgsSymbol::Line ) + { + delete symbol; + return false; + } + + mHashSymbol.reset( static_cast( symbol ) ); + mColor = mHashSymbol->color(); + return true; +} + +void QgsHashedLineSymbolLayer::setWidth( const double width ) +{ + mHashLength = width; +} + +double QgsHashedLineSymbolLayer::width() const +{ + return mHashLength; +} + +double QgsHashedLineSymbolLayer::width( const QgsRenderContext &context ) const +{ + return context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale ); +} + +double QgsHashedLineSymbolLayer::estimateMaxBleed( const QgsRenderContext &context ) const +{ + return ( mHashSymbol->width( context ) / 2.0 ) + + context.convertToPainterUnits( mHashLength, mHashLengthUnit, mHashLengthMapUnitScale ) + + context.convertToPainterUnits( std::fabs( mOffset ), mOffsetUnit, mOffsetMapUnitScale ); +} + +void QgsHashedLineSymbolLayer::setOutputUnit( QgsUnitTypes::RenderUnit unit ) +{ + QgsLineSymbolLayer::setOutputUnit( unit ); + mHashSymbol->setOutputUnit( unit ); + setIntervalUnit( unit ); + mOffsetUnit = unit; + setOffsetAlongLineUnit( unit ); +} + +QSet QgsHashedLineSymbolLayer::usedAttributes( const QgsRenderContext &context ) const +{ + QSet attr = QgsLineSymbolLayer::usedAttributes( context ); + if ( mHashSymbol ) + attr.unite( mHashSymbol->usedAttributes( context ) ); + return attr; +} + +bool QgsHashedLineSymbolLayer::hasDataDefinedProperties() const +{ + if ( QgsSymbolLayer::hasDataDefinedProperties() ) + return true; + if ( mHashSymbol && mHashSymbol->hasDataDefinedProperties() ) + return true; + return false; +} + +void QgsHashedLineSymbolLayer::setDataDefinedProperty( QgsSymbolLayer::Property key, const QgsProperty &property ) +{ + if ( key == QgsSymbolLayer::PropertyWidth && mHashSymbol && property ) + { + mHashSymbol->setDataDefinedWidth( property ); + } + QgsLineSymbolLayer::setDataDefinedProperty( key, property ); +} + +void QgsHashedLineSymbolLayer::setSymbolLineAngle( double angle ) +{ + mSymbolLineAngle = angle; +} + +double QgsHashedLineSymbolLayer::symbolAngle() const +{ + return mSymbolAngle; +} + +void QgsHashedLineSymbolLayer::setSymbolAngle( double angle ) +{ + mSymbolAngle = angle; +} + +void QgsHashedLineSymbolLayer::renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer, bool selected ) +{ + double lineLength = mHashLength; + if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyLineDistance ) ) + { + context.expressionContext().setOriginalValueVariable( mHashLength ); + lineLength = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyLineDistance, context.expressionContext(), lineLength ); + } + const double w = context.convertToPainterUnits( lineLength, mHashLengthUnit, mHashLengthMapUnitScale ) / 2.0; + + double hashAngle = mHashAngle; + if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyLineAngle ) ) + { + context.expressionContext().setOriginalValueVariable( mHashAngle ); + hashAngle = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyLineAngle, context.expressionContext(), hashAngle ); + } + + QgsPointXY center( point ); + QgsPointXY start = center.project( w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) ); + QgsPointXY end = center.project( -w, 180 - ( mSymbolAngle + mSymbolLineAngle + hashAngle ) ); + + QPolygonF points; + points << QPointF( start.x(), start.y() ) << QPointF( end.x(), end.y() ); + + mHashSymbol->renderPolyline( points, feature, context, layer, selected ); +} + +double QgsHashedLineSymbolLayer::hashAngle() const +{ + return mHashAngle; +} + +void QgsHashedLineSymbolLayer::setHashAngle( double angle ) +{ + mHashAngle = angle; +} + diff --git a/src/core/symbology/qgslinesymbollayer.h b/src/core/symbology/qgslinesymbollayer.h index 4efe7b0a501..274b8c7632c 100644 --- a/src/core/symbology/qgslinesymbollayer.h +++ b/src/core/symbology/qgslinesymbollayer.h @@ -244,25 +244,19 @@ class CORE_EXPORT QgsSimpleLineSymbolLayer : public QgsLineSymbolLayer /** * \ingroup core - * \class QgsMarkerLineSymbolLayer - * Line symbol layer type which draws repeating marker symbols along a line feature. + * \class QgsTemplatedLineSymbolLayerBase + * + * Base class for templated line symbols, e.g. line symbols which draw markers or hash + * lines at intervals along the line feature. + * + * \since QGIS 3.8 */ -class CORE_EXPORT QgsMarkerLineSymbolLayer : public QgsLineSymbolLayer +class CORE_EXPORT QgsTemplatedLineSymbolLayerBase : public QgsLineSymbolLayer { public: /** - * Constructor for QgsMarkerLineSymbolLayer. Creates a marker line - * with a default marker symbol, placed at the specified \a interval (in millimeters). - * - * The \a rotateMarker argument specifies whether individual marker symbols - * should be rotated to match the line segment alignment. - */ - QgsMarkerLineSymbolLayer( bool rotateMarker = DEFAULT_MARKERLINE_ROTATE, - double interval = DEFAULT_MARKERLINE_INTERVAL ); - - /** - * Defines how/where the marker should be placed on the line + * Defines how/where the templated symbol should be placed on the line. */ enum Placement { @@ -274,146 +268,43 @@ class CORE_EXPORT QgsMarkerLineSymbolLayer : public QgsLineSymbolLayer CurvePoint, //!< Place symbols at every virtual curve point in the line (used when rendering curved geometry types only) }; - // static stuff + /** + * Constructor for QgsTemplatedLineSymbolLayerBase. Creates a template + * line placed at the specified \a interval (in millimeters). + * + * The \a rotateSymbol argument specifies whether individual symbols + * should be rotated to match the line segment alignment. + */ + QgsTemplatedLineSymbolLayerBase( bool rotateSymbol = true, + double interval = 3 ); /** - * Creates a new QgsMarkerLineSymbolLayer, using the settings - * serialized in the \a properties map (corresponding to the output from - * QgsMarkerLineSymbolLayer::properties() ). + * Returns TRUE if the repeating symbols be rotated to match their line segment orientation. + * \see setRotateSymbols() */ - static QgsSymbolLayer *create( const QgsStringMap &properties = QgsStringMap() ) SIP_FACTORY; - - /** - * Creates a new QgsMarkerLineSymbolLayer from an SLD XML DOM \a element. - */ - static QgsSymbolLayer *createFromSld( QDomElement &element ) SIP_FACTORY; - - // implemented from base classes - - QString layerType() const override; - - void startRender( QgsSymbolRenderContext &context ) override; - - void stopRender( QgsSymbolRenderContext &context ) override; - - void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) override; - - void renderPolygonStroke( const QPolygonF &points, QList *rings, QgsSymbolRenderContext &context ) override; - - QgsStringMap properties() const override; - - QgsMarkerLineSymbolLayer *clone() const override SIP_FACTORY; - - void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props ) const override; - - void setColor( const QColor &color ) override; - QColor color() const override; - - QgsSymbol *subSymbol() override; - bool setSubSymbol( QgsSymbol *symbol SIP_TRANSFER ) override; - - void setWidth( double width ) override; - double width() const override; - double width( const QgsRenderContext &context ) const override; - - double estimateMaxBleed( const QgsRenderContext &context ) const override; - - // new stuff - - /** - * Returns TRUE if the repeating symbols will be rotated to match their line segment orientation. - * \see setRotateMarker() - */ - bool rotateMarker() const { return mRotateMarker; } + bool rotateSymbols() const { return mRotateSymbols; } /** * Sets whether the repeating symbols should be rotated to match their line segment orientation. - * \see rotateMarker() + * \see rotateSymbols() */ - void setRotateMarker( bool rotate ) { mRotateMarker = rotate; } + void setRotateSymbols( bool rotate ) { mRotateSymbols = rotate; } /** - * Returns the interval between individual markers. Units are specified through intervalUnits(). + * Returns the interval between individual symbols. Units are specified through intervalUnits(). * \see setInterval() * \see intervalUnit() */ double interval() const { return mInterval; } /** - * Sets the interval between individual markers. + * Sets the interval between individual symbols. * \param interval interval size. Units are specified through setIntervalUnit() * \see interval() * \see setIntervalUnit() */ void setInterval( double interval ) { mInterval = interval; } - /** - * Returns the placement of the symbols. - * \see setPlacement() - */ - Placement placement() const { return mPlacement; } - - /** - * Sets the \a placement of the symbols. - * \see placement() - */ - void setPlacement( Placement p ) { mPlacement = p; } - - /** - * Returns the offset along the line for the marker placement. For Interval placements, this is the distance - * between the start of the line and the first marker. For FirstVertex and LastVertex placements, this is the - * distance between the marker and the start of the line or the end of the line respectively. - * This setting has no effect for Vertex or CentralPoint placements. - * \returns The offset along the line. The unit for the offset is retrievable via offsetAlongLineUnit. - * \see setOffsetAlongLine - * \see offsetAlongLineUnit - * \see placement - * \since QGIS 2.3 - */ - double offsetAlongLine() const { return mOffsetAlongLine; } - - /** - * Sets the the offset along the line for the marker placement. For Interval placements, this is the distance - * between the start of the line and the first marker. For FirstVertex and LastVertex placements, this is the - * distance between the marker and the start of the line or the end of the line respectively. - * This setting has no effect for Vertex or CentralPoint placements. - * \param offsetAlongLine Distance to offset markers along the line. The offset - * unit is set via setOffsetAlongLineUnit. - * \see offsetAlongLine - * \see setOffsetAlongLineUnit - * \see setPlacement - * \since QGIS 2.3 - */ - void setOffsetAlongLine( double offsetAlongLine ) { mOffsetAlongLine = offsetAlongLine; } - - /** - * Returns the unit used for calculating the offset along line for markers. - * \returns Offset along line unit type. - * \see setOffsetAlongLineUnit - * \see offsetAlongLine - */ - QgsUnitTypes::RenderUnit offsetAlongLineUnit() const { return mOffsetAlongLineUnit; } - - /** - * Sets the unit used for calculating the offset along line for markers. - * \param unit Offset along line unit type. - * \see offsetAlongLineUnit - * \see setOffsetAlongLine - */ - void setOffsetAlongLineUnit( QgsUnitTypes::RenderUnit unit ) { mOffsetAlongLineUnit = unit; } - - /** - * Returns the map unit scale used for calculating the offset in map units along line for symbols. - * \see setOffsetAlongLineMapUnitScale() - */ - const QgsMapUnitScale &offsetAlongLineMapUnitScale() const { return mOffsetAlongLineMapUnitScale; } - - /** - * Sets the map unit \a scale used for calculating the offset in map units along line for symbols. - * \see offsetAlongLineMapUnitScale() - */ - void setOffsetAlongLineMapUnitScale( const QgsMapUnitScale &scale ) { mOffsetAlongLineMapUnitScale = scale; } - /** * Sets the units for the interval between symbols. * \param unit interval units @@ -445,43 +336,132 @@ class CORE_EXPORT QgsMarkerLineSymbolLayer : public QgsLineSymbolLayer */ const QgsMapUnitScale &intervalMapUnitScale() const { return mIntervalMapUnitScale; } - void setOutputUnit( QgsUnitTypes::RenderUnit unit ) override; - QgsUnitTypes::RenderUnit outputUnit() const override; + /** + * Returns the placement of the symbols. + * \see setPlacement() + */ + Placement placement() const { return mPlacement; } - void setMapUnitScale( const QgsMapUnitScale &scale ) override; - QgsMapUnitScale mapUnitScale() const override; + /** + * Sets the \a placement of the symbols. + * \see placement() + */ + void setPlacement( Placement placement ) { mPlacement = placement; } - QSet usedAttributes( const QgsRenderContext &context ) const override; - bool hasDataDefinedProperties() const override; + /** + * Returns the offset along the line for the symbol placement. For Interval placements, this is the distance + * between the start of the line and the first symbol. For FirstVertex and LastVertex placements, this is the + * distance between the symbol and the start of the line or the end of the line respectively. + * This setting has no effect for Vertex or CentralPoint placements. + * \returns The offset along the line. The unit for the offset is retrievable via offsetAlongLineUnit. + * \see setOffsetAlongLine() + * \see offsetAlongLineUnit() + * \see placement() + */ + double offsetAlongLine() const { return mOffsetAlongLine; } - void setDataDefinedProperty( QgsSymbolLayer::Property key, const QgsProperty &property ) override; + /** + * Sets the the offset along the line for the symbol placement. For Interval placements, this is the distance + * between the start of the line and the first symbol. For FirstVertex and LastVertex placements, this is the + * distance between the symbol and the start of the line or the end of the line respectively. + * This setting has no effect for Vertex or CentralPoint placements. + * \param offsetAlongLine Distance to offset markers along the line. The offset + * unit is set via setOffsetAlongLineUnit. + * \see offsetAlongLine() + * \see setOffsetAlongLineUnit() + * \see setPlacement() + */ + void setOffsetAlongLine( double offsetAlongLine ) { mOffsetAlongLine = offsetAlongLine; } + /** + * Returns the unit used for calculating the offset along line for symbols. + * \returns Offset along line unit type. + * \see setOffsetAlongLineUnit() + * \see offsetAlongLine() + */ + QgsUnitTypes::RenderUnit offsetAlongLineUnit() const { return mOffsetAlongLineUnit; } + + /** + * Sets the unit used for calculating the offset along line for symbols. + * \param unit Offset along line unit type. + * \see offsetAlongLineUnit() + * \see setOffsetAlongLine() + */ + void setOffsetAlongLineUnit( QgsUnitTypes::RenderUnit unit ) { mOffsetAlongLineUnit = unit; } + + /** + * Returns the map unit scale used for calculating the offset in map units along line for symbols. + * \see setOffsetAlongLineMapUnitScale() + */ + const QgsMapUnitScale &offsetAlongLineMapUnitScale() const { return mOffsetAlongLineMapUnitScale; } + + /** + * Sets the map unit \a scale used for calculating the offset in map units along line for symbols. + * \see offsetAlongLineMapUnitScale() + */ + void setOffsetAlongLineMapUnitScale( const QgsMapUnitScale &scale ) { mOffsetAlongLineMapUnitScale = scale; } + + void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) FINAL; + void renderPolygonStroke( const QPolygonF &points, QList *rings, QgsSymbolRenderContext &context ) FINAL; + QgsUnitTypes::RenderUnit outputUnit() const FINAL; + void setMapUnitScale( const QgsMapUnitScale &scale ) FINAL; + QgsMapUnitScale mapUnitScale() const FINAL; + QgsStringMap properties() const override; protected: - void renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context ); - void renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, Placement placement = Vertex ); - void renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context ); - double markerAngle( const QPolygonF &points, bool isRing, int vertex ); + /** + * Sets the line \a angle modification for the symbol's angle. This angle is added to + * the symbol's rotation and data defined rotation before rendering the symbol, and + * is used for orienting symbols to match the line's angle. + * \param angle Angle in degrees, valid values are between 0 and 360 + */ + virtual void setSymbolLineAngle( double angle ) = 0; - bool mRotateMarker; - double mInterval; - QgsUnitTypes::RenderUnit mIntervalUnit; - QgsMapUnitScale mIntervalMapUnitScale; - std::unique_ptr< QgsMarkerSymbol > mMarker; - Placement mPlacement; - double mOffsetAlongLine; //distance to offset along line before marker is drawn - QgsUnitTypes::RenderUnit mOffsetAlongLineUnit; //unit for offset along line - QgsMapUnitScale mOffsetAlongLineMapUnitScale; + /** + * Returns the symbol's current angle, in degrees clockwise. + */ + virtual double symbolAngle() const = 0; + + /** + * Sets the symbol's \a angle, in degrees clockwise. + */ + virtual void setSymbolAngle( double angle ) = 0; + + /** + * Renders the templated symbol at the specified \a point, using the given render \a context. + * + * The \a feature argument is used to pass the feature currently being rendered (when available). + * + * If only a single symbol layer from the symbol should be rendered, it should be specified + * in the \a layer argument. A \a layer of -1 indicates that all symbol layers should be + * rendered. + * + * If \a selected is true then the symbol will be drawn using the "selected feature" + * style and colors instead of the symbol's normal style. + */ + virtual void renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer = -1, bool selected = false ) = 0; + + /** + * Copies all common properties of this layer to another templated symbol layer. + */ + void copyTemplateSymbolProperties( QgsTemplatedLineSymbolLayerBase *destLayer ) const; + + /** + * Sets all common symbol properties in the \a destLayer, using the settings + * serialized in the \a properties map. + */ + static void setCommonProperties( QgsTemplatedLineSymbolLayerBase *destLayer, const QgsStringMap &properties ); private: -#ifdef SIP_RUN - QgsMarkerLineSymbolLayer( const QgsMarkerLineSymbolLayer &other ); -#endif + void renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context ); + void renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, QgsTemplatedLineSymbolLayerBase::Placement placement = QgsTemplatedLineSymbolLayerBase::Vertex ); + void renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context ); + double markerAngle( const QPolygonF &points, bool isRing, int vertex ); /** - * Renders a marker by offsetting a vertex along the line by a specified distance. + * Renders a symbol by offsetting a vertex along the line by a specified distance. * \param points vertices making up the line * \param vertex vertex number to begin offset at * \param distance distance to offset from vertex. If distance is positive, offset is calculated @@ -492,6 +472,230 @@ class CORE_EXPORT QgsMarkerLineSymbolLayer : public QgsLineSymbolLayer * \see setOffsetAlongLineUnit */ void renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context ); + + bool mRotateSymbols = true; + double mInterval = 3; + QgsUnitTypes::RenderUnit mIntervalUnit = QgsUnitTypes::RenderMillimeters; + QgsMapUnitScale mIntervalMapUnitScale; + Placement mPlacement = Interval; + double mOffsetAlongLine = 0; //distance to offset along line before marker is drawn + QgsUnitTypes::RenderUnit mOffsetAlongLineUnit = QgsUnitTypes::RenderMillimeters; //unit for offset along line + QgsMapUnitScale mOffsetAlongLineMapUnitScale; +}; + +/** + * \ingroup core + * \class QgsMarkerLineSymbolLayer + * Line symbol layer type which draws repeating marker symbols along a line feature. + */ +class CORE_EXPORT QgsMarkerLineSymbolLayer : public QgsTemplatedLineSymbolLayerBase +{ + public: + + /** + * Constructor for QgsMarkerLineSymbolLayer. Creates a marker line + * with a default marker symbol, placed at the specified \a interval (in millimeters). + * + * The \a rotateMarker argument specifies whether individual marker symbols + * should be rotated to match the line segment alignment. + */ + QgsMarkerLineSymbolLayer( bool rotateMarker = DEFAULT_MARKERLINE_ROTATE, + double interval = DEFAULT_MARKERLINE_INTERVAL ); + + // static stuff + + /** + * Creates a new QgsMarkerLineSymbolLayer, using the settings + * serialized in the \a properties map (corresponding to the output from + * QgsMarkerLineSymbolLayer::properties() ). + */ + static QgsSymbolLayer *create( const QgsStringMap &properties = QgsStringMap() ) SIP_FACTORY; + + /** + * Creates a new QgsMarkerLineSymbolLayer from an SLD XML DOM \a element. + */ + static QgsSymbolLayer *createFromSld( QDomElement &element ) SIP_FACTORY; + + // implemented from base classes + + QString layerType() const override; + void startRender( QgsSymbolRenderContext &context ) override; + void stopRender( QgsSymbolRenderContext &context ) override; + QgsMarkerLineSymbolLayer *clone() const override SIP_FACTORY; + void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props ) const override; + void setColor( const QColor &color ) override; + QColor color() const override; + QgsSymbol *subSymbol() override; + bool setSubSymbol( QgsSymbol *symbol SIP_TRANSFER ) override; + void setWidth( double width ) override; + double width() const override; + double width( const QgsRenderContext &context ) const override; + double estimateMaxBleed( const QgsRenderContext &context ) const override; + void setOutputUnit( QgsUnitTypes::RenderUnit unit ) override; + QSet usedAttributes( const QgsRenderContext &context ) const override; + bool hasDataDefinedProperties() const override; + void setDataDefinedProperty( QgsSymbolLayer::Property key, const QgsProperty &property ) override; + + /** + * Shall the marker be rotated. + * + * \returns TRUE if the marker should be rotated. + * \deprecated Use rotateSymbols() instead. + */ + Q_DECL_DEPRECATED bool rotateMarker() const SIP_DEPRECATED { return rotateSymbols(); } + + /** + * Shall the marker be rotated. + * \deprecated Use setRotateSymbols() instead. + */ + Q_DECL_DEPRECATED void setRotateMarker( bool rotate ) SIP_DEPRECATED { setRotateSymbols( rotate ); } + + protected: + + std::unique_ptr< QgsMarkerSymbol > mMarker; + + void setSymbolLineAngle( double angle ) override; + double symbolAngle() const override; + void setSymbolAngle( double angle ) override; + void renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer = -1, bool selected = false ) override; + + private: + +#ifdef SIP_RUN + QgsMarkerLineSymbolLayer( const QgsMarkerLineSymbolLayer &other ); +#endif + + +}; + + +/** + * \ingroup core + * \class QgsHashedLineSymbolLayer + * + * Line symbol layer type which draws repeating line sections along a line feature. + * + * \since QGIS 3.8 + */ +class CORE_EXPORT QgsHashedLineSymbolLayer : public QgsTemplatedLineSymbolLayerBase +{ + public: + + /** + * Constructor for QgsHashedLineSymbolLayer. Creates a line + * with a default hash symbol, placed at the specified \a interval (in millimeters). + * + * The \a rotateSymbol argument specifies whether individual hash symbols + * should be rotated to match the line segment alignment. + */ + QgsHashedLineSymbolLayer( bool rotateSymbol = true, + double interval = 3 ); + + /** + * Creates a new QgsHashedLineSymbolLayer, using the settings + * serialized in the \a properties map (corresponding to the output from + * QgsHashedLineSymbolLayer::properties() ). + */ + static QgsSymbolLayer *create( const QgsStringMap &properties = QgsStringMap() ) SIP_FACTORY; + + QString layerType() const override; + void startRender( QgsSymbolRenderContext &context ) override; + void stopRender( QgsSymbolRenderContext &context ) override; + QgsStringMap properties() const override; + QgsHashedLineSymbolLayer *clone() const override SIP_FACTORY; + void setColor( const QColor &color ) override; + QColor color() const override; + QgsSymbol *subSymbol() override; + bool setSubSymbol( QgsSymbol *symbol SIP_TRANSFER ) override; + void setWidth( double width ) override; + double width() const override; + double width( const QgsRenderContext &context ) const override; + double estimateMaxBleed( const QgsRenderContext &context ) const override; + void setOutputUnit( QgsUnitTypes::RenderUnit unit ) override; + QSet usedAttributes( const QgsRenderContext &context ) const override; + bool hasDataDefinedProperties() const override; + void setDataDefinedProperty( QgsSymbolLayer::Property key, const QgsProperty &property ) override; + + /** + * Returns the angle to use when drawing the hashed lines sections, in degrees clockwise. + * + * \see setHashAngle() + */ + double hashAngle() const; + + /** + * Sets the \a angle to use when drawing the hashed lines sections, in degrees clockwise. + * + * \see hashAngle() + */ + void setHashAngle( double angle ); + + /** + * Returns the length of hash symbols. Units are specified through hashLengthUnits(). + * \see setHashLength() + * \see hashLengthUnit() + */ + double hashLength() const { return mHashLength; } + + /** + * Sets the \a length of hash symbols. Units are specified through setHashLengthUnit() + * \see hashLength() + * \see setHashLengthUnit() + */ + void setHashLength( double length ) { mHashLength = length; } + + /** + * Sets the \a unit for the length of hash symbols. + * \see hashLengthUnit() + * \see setHashLength() + */ + void setHashLengthUnit( QgsUnitTypes::RenderUnit unit ) { mHashLengthUnit = unit; } + + /** + * Returns the units for the length of hash symbols. + * \see setHashLengthUnit() + * \see hashLength() + */ + QgsUnitTypes::RenderUnit hashLengthUnit() const { return mHashLengthUnit; } + + /** + * Sets the map unit \a scale for the hash length. + * \see hashLengthMapUnitScale() + * \see setHashLengthUnit() + * \see setHashLength() + */ + void setHashLengthMapUnitScale( const QgsMapUnitScale &scale ) { mHashLengthMapUnitScale = scale; } + + /** + * Returns the map unit scale for the hash length. + * \see setHashLengthMapUnitScale() + * \see hashLengthUnit() + * \see hashLength() + */ + const QgsMapUnitScale &hashLengthMapUnitScale() const { return mHashLengthMapUnitScale; } + + protected: + + void setSymbolLineAngle( double angle ) override; + double symbolAngle() const override; + void setSymbolAngle( double angle ) override; + void renderSymbol( const QPointF &point, const QgsFeature *feature, QgsRenderContext &context, int layer = -1, bool selected = false ) override; + + private: +#ifdef SIP_RUN + QgsHashedLineSymbolLayer( const QgsHashedLineSymbolLayer &other ); +#endif + + std::unique_ptr< QgsLineSymbol > mHashSymbol; + + double mSymbolLineAngle = 0; + double mSymbolAngle = 0; + + double mHashAngle = 0; + double mHashLength = 3; + QgsUnitTypes::RenderUnit mHashLengthUnit = QgsUnitTypes::RenderMillimeters; + QgsMapUnitScale mHashLengthMapUnitScale; + }; #endif diff --git a/src/core/symbology/qgssymbollayer.h b/src/core/symbology/qgssymbollayer.h index de4da75f9f6..ebb79e344a7 100644 --- a/src/core/symbology/qgssymbollayer.h +++ b/src/core/symbology/qgssymbollayer.h @@ -139,8 +139,8 @@ class CORE_EXPORT QgsSymbolLayer PropertyFillStyle, //!< Fill style (eg solid, dots) PropertyJoinStyle, //!< Line join style PropertySecondaryColor, //!< Secondary color (eg for gradient fills) - PropertyLineAngle, //!< Line angle - PropertyLineDistance, //!< Distance between lines + PropertyLineAngle, //!< Line angle, or angle of hash lines for hash line symbols + PropertyLineDistance, //!< Distance between lines, or length of lines for hash line symbols PropertyGradientType, //!< Gradient fill type PropertyCoordinateMode, //!< Gradient coordinate mode PropertyGradientSpread, //!< Gradient spread mode @@ -786,10 +786,42 @@ class CORE_EXPORT QgsLineSymbolLayer : public QgsSymbolLayer InteriorRingsOnly, //!< Render the interior rings only }; + void setOutputUnit( QgsUnitTypes::RenderUnit unit ) override; + QgsUnitTypes::RenderUnit outputUnit() const override; + void setMapUnitScale( const QgsMapUnitScale &scale ) override; + QgsMapUnitScale mapUnitScale() const override; + void drawPreviewIcon( QgsSymbolRenderContext &context, QSize size ) override; + double dxfWidth( const QgsDxfExport &e, QgsSymbolRenderContext &context ) const override; + + /** + * Renders the line symbol layer along the line joining \a points, using the given render \a context. + * \see renderPolygonStroke() + */ virtual void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) = 0; + /** + * Renders the line symbol layer along the outline of polygon, using the given render \a context. + * + * The exterior ring of the polygon is specified in \a points. Optionally, interior + * rings are set via the \a rings arugment. + * + * \see renderPolyline() + */ virtual void renderPolygonStroke( const QPolygonF &points, QList *rings, QgsSymbolRenderContext &context ); + /** + * Sets the \a width of the line symbol layer. + * + * Calling this method updates the width of the line symbol layer, without + * changing the existing width units. It has different effects depending + * on the line symbol layer subclass, e.g. for a simple line layer it + * changes the stroke width of the line, for a marker line layer it + * changes the size of the markers used to draw the line. + * + * \see width() + * \warning Since the width units vary, this method is useful for changing the + * relative width of a line symbol layer only. + */ virtual void setWidth( double width ) { mWidth = width; } /** @@ -815,9 +847,63 @@ class CORE_EXPORT QgsLineSymbolLayer : public QgsSymbolLayer */ virtual double width( const QgsRenderContext &context ) const; + /** + * Returns the line's offset. + * + * Offset units can be retrieved by calling offsetUnit(). + * + * \see setOffset() + * \see offsetUnit() + * \see offsetMapUnitScale() + */ double offset() const { return mOffset; } + + /** + * Sets the line's \a offset. + * + * Offset units are set via setOffsetUnit(). + * + * \see offset() + * \see setOffsetUnit() + * \see setOffsetMapUnitScale() + */ void setOffset( double offset ) { mOffset = offset; } + /** + * Sets the \a unit for the line's offset. + * \see offsetUnit() + * \see setOffset() + * \see setOffsetMapUnitScale() + */ + void setOffsetUnit( QgsUnitTypes::RenderUnit unit ) { mOffsetUnit = unit; } + + /** + * Returns the units for the line's offset. + * \see setOffsetUnit() + * \see offset() + * \see offsetMapUnitScale() + */ + QgsUnitTypes::RenderUnit offsetUnit() const { return mOffsetUnit; } + + /** + * Sets the map unit \a scale for the line's offset. + * \see offsetMapUnitScale() + * \see setOffset() + * \see setOffsetUnit() + */ + void setOffsetMapUnitScale( const QgsMapUnitScale &scale ) { mOffsetMapUnitScale = scale; } + + /** + * Returns the map unit scale for the line's offset. + * \see setOffsetMapUnitScale() + * \see offset() + * \see offsetUnit() + */ + const QgsMapUnitScale &offsetMapUnitScale() const { return mOffsetMapUnitScale; } + + // TODO QGIS 4.0 - setWidthUnit(), widthUnit(), setWidthUnitScale(), widthUnitScale() + // only apply to simple line symbol layers and do not belong here. + /** * Sets the units for the line's width. * \param unit width units @@ -834,32 +920,6 @@ class CORE_EXPORT QgsLineSymbolLayer : public QgsSymbolLayer void setWidthMapUnitScale( const QgsMapUnitScale &scale ) { mWidthMapUnitScale = scale; } const QgsMapUnitScale &widthMapUnitScale() const { return mWidthMapUnitScale; } - /** - * Sets the units for the line's offset. - * \param unit offset units - * \see offsetUnit() - */ - void setOffsetUnit( QgsUnitTypes::RenderUnit unit ) { mOffsetUnit = unit; } - - /** - * Returns the units for the line's offset. - * \see setOffsetUnit() - */ - QgsUnitTypes::RenderUnit offsetUnit() const { return mOffsetUnit; } - - void setOffsetMapUnitScale( const QgsMapUnitScale &scale ) { mOffsetMapUnitScale = scale; } - const QgsMapUnitScale &offsetMapUnitScale() const { return mOffsetMapUnitScale; } - - void setOutputUnit( QgsUnitTypes::RenderUnit unit ) override; - QgsUnitTypes::RenderUnit outputUnit() const override; - - void setMapUnitScale( const QgsMapUnitScale &scale ) override; - QgsMapUnitScale mapUnitScale() const override; - - void drawPreviewIcon( QgsSymbolRenderContext &context, QSize size ) override; - - double dxfWidth( const QgsDxfExport &e, QgsSymbolRenderContext &context ) const override; - /** * Returns the line symbol layer's ring filter, which controls which rings are * rendered when the line symbol is being used to draw a polygon's rings. diff --git a/src/core/symbology/qgssymbollayerregistry.cpp b/src/core/symbology/qgssymbollayerregistry.cpp index 66792053411..918e17fc4a2 100644 --- a/src/core/symbology/qgssymbollayerregistry.cpp +++ b/src/core/symbology/qgssymbollayerregistry.cpp @@ -30,6 +30,8 @@ QgsSymbolLayerRegistry::QgsSymbolLayerRegistry() QgsSimpleLineSymbolLayer::create, QgsSimpleLineSymbolLayer::createFromSld ) ); addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "MarkerLine" ), QObject::tr( "Marker line" ), QgsSymbol::Line, QgsMarkerLineSymbolLayer::create, QgsMarkerLineSymbolLayer::createFromSld ) ); + addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "HashLine" ), QObject::tr( "Hashed line" ), QgsSymbol::Line, + QgsHashedLineSymbolLayer::create ) ); addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "ArrowLine" ), QObject::tr( "Arrow" ), QgsSymbol::Line, QgsArrowSymbolLayer::create ) ); addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "SimpleMarker" ), QObject::tr( "Simple marker" ), QgsSymbol::Marker, diff --git a/src/gui/symbology/qgslayerpropertieswidget.cpp b/src/gui/symbology/qgslayerpropertieswidget.cpp index f3bb749b0ee..dcfc5d1f71d 100644 --- a/src/gui/symbology/qgslayerpropertieswidget.cpp +++ b/src/gui/symbology/qgslayerpropertieswidget.cpp @@ -66,6 +66,7 @@ static void _initWidgetFunctions() _initWidgetFunction( QStringLiteral( "SimpleLine" ), QgsSimpleLineSymbolLayerWidget::create ); _initWidgetFunction( QStringLiteral( "MarkerLine" ), QgsMarkerLineSymbolLayerWidget::create ); + _initWidgetFunction( QStringLiteral( "HashLine" ), QgsHashedLineSymbolLayerWidget::create ); _initWidgetFunction( QStringLiteral( "ArrowLine" ), QgsArrowSymbolLayerWidget::create ); _initWidgetFunction( QStringLiteral( "SimpleMarker" ), QgsSimpleMarkerSymbolLayerWidget::create ); diff --git a/src/gui/symbology/qgssymbollayerwidget.cpp b/src/gui/symbology/qgssymbollayerwidget.cpp index c9559f10b2f..a59846d129e 100644 --- a/src/gui/symbology/qgssymbollayerwidget.cpp +++ b/src/gui/symbology/qgssymbollayerwidget.cpp @@ -1726,20 +1726,20 @@ void QgsMarkerLineSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer ) mSpinOffsetAlongLine->setValue( mLayer->offsetAlongLine() ); mSpinOffsetAlongLine->blockSignals( false ); chkRotateMarker->blockSignals( true ); - chkRotateMarker->setChecked( mLayer->rotateMarker() ); + chkRotateMarker->setChecked( mLayer->rotateSymbols() ); chkRotateMarker->blockSignals( false ); spinOffset->blockSignals( true ); spinOffset->setValue( mLayer->offset() ); spinOffset->blockSignals( false ); - if ( mLayer->placement() == QgsMarkerLineSymbolLayer::Interval ) + if ( mLayer->placement() == QgsTemplatedLineSymbolLayerBase::Interval ) radInterval->setChecked( true ); - else if ( mLayer->placement() == QgsMarkerLineSymbolLayer::Vertex ) + else if ( mLayer->placement() == QgsTemplatedLineSymbolLayerBase::Vertex ) radVertex->setChecked( true ); - else if ( mLayer->placement() == QgsMarkerLineSymbolLayer::LastVertex ) + else if ( mLayer->placement() == QgsTemplatedLineSymbolLayerBase::LastVertex ) radVertexLast->setChecked( true ); - else if ( mLayer->placement() == QgsMarkerLineSymbolLayer::CentralPoint ) + else if ( mLayer->placement() == QgsTemplatedLineSymbolLayerBase::CentralPoint ) radCentralPoint->setChecked( true ); - else if ( mLayer->placement() == QgsMarkerLineSymbolLayer::CurvePoint ) + else if ( mLayer->placement() == QgsTemplatedLineSymbolLayerBase::CurvePoint ) radCurvePoint->setChecked( true ); else radVertexFirst->setChecked( true ); @@ -1787,7 +1787,7 @@ void QgsMarkerLineSymbolLayerWidget::setOffsetAlongLine( double val ) void QgsMarkerLineSymbolLayerWidget::setRotate() { - mLayer->setRotateMarker( chkRotateMarker->isChecked() ); + mLayer->setRotateSymbols( chkRotateMarker->isChecked() ); emit changed(); } @@ -1804,17 +1804,17 @@ void QgsMarkerLineSymbolLayerWidget::setPlacement() mSpinOffsetAlongLine->setEnabled( radInterval->isChecked() || radVertexLast->isChecked() || radVertexFirst->isChecked() ); //mLayer->setPlacement( interval ? QgsMarkerLineSymbolLayer::Interval : QgsMarkerLineSymbolLayer::Vertex ); if ( radInterval->isChecked() ) - mLayer->setPlacement( QgsMarkerLineSymbolLayer::Interval ); + mLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::Interval ); else if ( radVertex->isChecked() ) - mLayer->setPlacement( QgsMarkerLineSymbolLayer::Vertex ); + mLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::Vertex ); else if ( radVertexLast->isChecked() ) - mLayer->setPlacement( QgsMarkerLineSymbolLayer::LastVertex ); + mLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::LastVertex ); else if ( radVertexFirst->isChecked() ) - mLayer->setPlacement( QgsMarkerLineSymbolLayer::FirstVertex ); + mLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::FirstVertex ); else if ( radCurvePoint->isChecked() ) - mLayer->setPlacement( QgsMarkerLineSymbolLayer::CurvePoint ); + mLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::CurvePoint ); else - mLayer->setPlacement( QgsMarkerLineSymbolLayer::CentralPoint ); + mLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::CentralPoint ); emit changed(); } @@ -1849,6 +1849,234 @@ void QgsMarkerLineSymbolLayerWidget::mOffsetAlongLineUnitWidget_changed() emit changed(); } + +/////////// + +QgsHashedLineSymbolLayerWidget::QgsHashedLineSymbolLayerWidget( QgsVectorLayer *vl, QWidget *parent ) + : QgsSymbolLayerWidget( parent, vl ) +{ + mLayer = nullptr; + + setupUi( this ); + connect( mIntervalUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsHashedLineSymbolLayerWidget::mIntervalUnitWidget_changed ); + connect( mOffsetUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsHashedLineSymbolLayerWidget::mOffsetUnitWidget_changed ); + connect( mOffsetAlongLineUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsHashedLineSymbolLayerWidget::mOffsetAlongLineUnitWidget_changed ); + connect( mHashLengthUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsHashedLineSymbolLayerWidget::hashLengthUnitWidgetChanged ); + mIntervalUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels + << QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches ); + mOffsetUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels + << QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches ); + mOffsetAlongLineUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels + << QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches ); + + mHashLengthUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels + << QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches ); + + mRingFilterComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "mIconAllRings.svg" ) ), tr( "All Rings" ), QgsLineSymbolLayer::AllRings ); + mRingFilterComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "mIconExteriorRing.svg" ) ), tr( "Exterior Ring Only" ), QgsLineSymbolLayer::ExteriorRingOnly ); + mRingFilterComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "mIconInteriorRings.svg" ) ), tr( "Interior Rings Only" ), QgsLineSymbolLayer::InteriorRingsOnly ); + connect( mRingFilterComboBox, qgis::overload< int >::of( &QComboBox::currentIndexChanged ), this, [ = ]( int ) + { + if ( mLayer ) + { + mLayer->setRingFilter( static_cast< QgsLineSymbolLayer::RenderRingFilter >( mRingFilterComboBox->currentData().toInt() ) ); + emit changed(); + } + } ); + + spinOffset->setClearValue( 0.0 ); + + + if ( vl && vl->geometryType() != QgsWkbTypes::PolygonGeometry ) + { + mRingFilterComboBox->hide(); + mRingsLabel->hide(); + } + + mHashRotationSpinBox->setClearValue( 0 ); + + connect( spinInterval, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsHashedLineSymbolLayerWidget::setInterval ); + connect( mSpinOffsetAlongLine, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsHashedLineSymbolLayerWidget::setOffsetAlongLine ); + connect( mSpinHashLength, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsHashedLineSymbolLayerWidget::setHashLength ); + connect( mHashRotationSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsHashedLineSymbolLayerWidget::setHashAngle ); + connect( chkRotateMarker, &QAbstractButton::clicked, this, &QgsHashedLineSymbolLayerWidget::setRotate ); + connect( spinOffset, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsHashedLineSymbolLayerWidget::setOffset ); + connect( radInterval, &QAbstractButton::clicked, this, &QgsHashedLineSymbolLayerWidget::setPlacement ); + connect( radVertex, &QAbstractButton::clicked, this, &QgsHashedLineSymbolLayerWidget::setPlacement ); + connect( radVertexLast, &QAbstractButton::clicked, this, &QgsHashedLineSymbolLayerWidget::setPlacement ); + connect( radVertexFirst, &QAbstractButton::clicked, this, &QgsHashedLineSymbolLayerWidget::setPlacement ); + connect( radCentralPoint, &QAbstractButton::clicked, this, &QgsHashedLineSymbolLayerWidget::setPlacement ); + connect( radCurvePoint, &QAbstractButton::clicked, this, &QgsHashedLineSymbolLayerWidget::setPlacement ); +} + +void QgsHashedLineSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer ) +{ + if ( layer->layerType() != QLatin1String( "HashLine" ) ) + return; + + // layer type is correct, we can do the cast + mLayer = static_cast( layer ); + + // set values + spinInterval->blockSignals( true ); + spinInterval->setValue( mLayer->interval() ); + spinInterval->blockSignals( false ); + mSpinOffsetAlongLine->blockSignals( true ); + mSpinOffsetAlongLine->setValue( mLayer->offsetAlongLine() ); + mSpinOffsetAlongLine->blockSignals( false ); + whileBlocking( mSpinHashLength )->setValue( mLayer->hashLength() ); + whileBlocking( mHashRotationSpinBox )->setValue( mLayer->hashAngle() ); + chkRotateMarker->blockSignals( true ); + chkRotateMarker->setChecked( mLayer->rotateSymbols() ); + chkRotateMarker->blockSignals( false ); + spinOffset->blockSignals( true ); + spinOffset->setValue( mLayer->offset() ); + spinOffset->blockSignals( false ); + if ( mLayer->placement() == QgsTemplatedLineSymbolLayerBase::Interval ) + radInterval->setChecked( true ); + else if ( mLayer->placement() == QgsTemplatedLineSymbolLayerBase::Vertex ) + radVertex->setChecked( true ); + else if ( mLayer->placement() == QgsTemplatedLineSymbolLayerBase::LastVertex ) + radVertexLast->setChecked( true ); + else if ( mLayer->placement() == QgsTemplatedLineSymbolLayerBase::CentralPoint ) + radCentralPoint->setChecked( true ); + else if ( mLayer->placement() == QgsTemplatedLineSymbolLayerBase::CurvePoint ) + radCurvePoint->setChecked( true ); + else + radVertexFirst->setChecked( true ); + + // set units + mIntervalUnitWidget->blockSignals( true ); + mIntervalUnitWidget->setUnit( mLayer->intervalUnit() ); + mIntervalUnitWidget->setMapUnitScale( mLayer->intervalMapUnitScale() ); + mIntervalUnitWidget->blockSignals( false ); + mOffsetUnitWidget->blockSignals( true ); + mOffsetUnitWidget->setUnit( mLayer->offsetUnit() ); + mOffsetUnitWidget->setMapUnitScale( mLayer->offsetMapUnitScale() ); + mOffsetUnitWidget->blockSignals( false ); + mOffsetAlongLineUnitWidget->blockSignals( true ); + mOffsetAlongLineUnitWidget->setUnit( mLayer->offsetAlongLineUnit() ); + mOffsetAlongLineUnitWidget->setMapUnitScale( mLayer->offsetAlongLineMapUnitScale() ); + mOffsetAlongLineUnitWidget->blockSignals( false ); + + whileBlocking( mHashLengthUnitWidget )->setUnit( mLayer->hashLengthUnit() ); + whileBlocking( mHashLengthUnitWidget )->setMapUnitScale( mLayer->hashLengthMapUnitScale() ); + + whileBlocking( mRingFilterComboBox )->setCurrentIndex( mRingFilterComboBox->findData( mLayer->ringFilter() ) ); + + setPlacement(); // update gui + + registerDataDefinedButton( mIntervalDDBtn, QgsSymbolLayer::PropertyInterval ); + registerDataDefinedButton( mLineOffsetDDBtn, QgsSymbolLayer::PropertyOffset ); + registerDataDefinedButton( mPlacementDDBtn, QgsSymbolLayer::PropertyPlacement ); + registerDataDefinedButton( mOffsetAlongLineDDBtn, QgsSymbolLayer::PropertyOffsetAlongLine ); + registerDataDefinedButton( mHashLengthDDBtn, QgsSymbolLayer::PropertyLineDistance ); + registerDataDefinedButton( mHashRotationDDBtn, QgsSymbolLayer::PropertyLineAngle ); +} + +QgsSymbolLayer *QgsHashedLineSymbolLayerWidget::symbolLayer() +{ + return mLayer; +} + +void QgsHashedLineSymbolLayerWidget::setInterval( double val ) +{ + mLayer->setInterval( val ); + emit changed(); +} + +void QgsHashedLineSymbolLayerWidget::setOffsetAlongLine( double val ) +{ + mLayer->setOffsetAlongLine( val ); + emit changed(); +} + +void QgsHashedLineSymbolLayerWidget::setHashLength( double val ) +{ + mLayer->setHashLength( val ); + emit changed(); +} + +void QgsHashedLineSymbolLayerWidget::setHashAngle( double val ) +{ + mLayer->setHashAngle( val ); + emit changed(); +} + +void QgsHashedLineSymbolLayerWidget::setRotate() +{ + mLayer->setRotateSymbols( chkRotateMarker->isChecked() ); + emit changed(); +} + +void QgsHashedLineSymbolLayerWidget::setOffset() +{ + mLayer->setOffset( spinOffset->value() ); + emit changed(); +} + +void QgsHashedLineSymbolLayerWidget::setPlacement() +{ + bool interval = radInterval->isChecked(); + spinInterval->setEnabled( interval ); + mSpinOffsetAlongLine->setEnabled( radInterval->isChecked() || radVertexLast->isChecked() || radVertexFirst->isChecked() ); + //mLayer->setPlacement( interval ? QgsMarkerLineSymbolLayer::Interval : QgsMarkerLineSymbolLayer::Vertex ); + if ( radInterval->isChecked() ) + mLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::Interval ); + else if ( radVertex->isChecked() ) + mLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::Vertex ); + else if ( radVertexLast->isChecked() ) + mLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::LastVertex ); + else if ( radVertexFirst->isChecked() ) + mLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::FirstVertex ); + else if ( radCurvePoint->isChecked() ) + mLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::CurvePoint ); + else + mLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::CentralPoint ); + + emit changed(); +} + +void QgsHashedLineSymbolLayerWidget::mIntervalUnitWidget_changed() +{ + if ( mLayer ) + { + mLayer->setIntervalUnit( mIntervalUnitWidget->unit() ); + mLayer->setIntervalMapUnitScale( mIntervalUnitWidget->getMapUnitScale() ); + emit changed(); + } +} + +void QgsHashedLineSymbolLayerWidget::mOffsetUnitWidget_changed() +{ + if ( mLayer ) + { + mLayer->setOffsetUnit( mOffsetUnitWidget->unit() ); + mLayer->setOffsetMapUnitScale( mOffsetUnitWidget->getMapUnitScale() ); + emit changed(); + } +} + +void QgsHashedLineSymbolLayerWidget::mOffsetAlongLineUnitWidget_changed() +{ + if ( mLayer ) + { + mLayer->setOffsetAlongLineUnit( mOffsetAlongLineUnitWidget->unit() ); + mLayer->setOffsetAlongLineMapUnitScale( mOffsetAlongLineUnitWidget->getMapUnitScale() ); + } + emit changed(); +} + +void QgsHashedLineSymbolLayerWidget::hashLengthUnitWidgetChanged() +{ + if ( mLayer ) + { + mLayer->setHashLengthUnit( mHashLengthUnitWidget->unit() ); + mLayer->setHashLengthMapUnitScale( mHashLengthUnitWidget->getMapUnitScale() ); + } + emit changed(); +} + /////////// diff --git a/src/gui/symbology/qgssymbollayerwidget.h b/src/gui/symbology/qgssymbollayerwidget.h index 36f5aba2e1e..a9ff46d2b91 100644 --- a/src/gui/symbology/qgssymbollayerwidget.h +++ b/src/gui/symbology/qgssymbollayerwidget.h @@ -502,6 +502,57 @@ class GUI_EXPORT QgsMarkerLineSymbolLayerWidget : public QgsSymbolLayerWidget, p }; +#include "ui_widget_hashline.h" + +class QgsHashedLineSymbolLayer; + +/** + * \ingroup gui + * \class QgsHashedLineSymbolLayerWidget + */ +class GUI_EXPORT QgsHashedLineSymbolLayerWidget : public QgsSymbolLayerWidget, private Ui::WidgetHashedLine +{ + Q_OBJECT + + public: + + /** + * Constructor for QgsHashedLineSymbolLayerWidget. + * \param vl associated vector layer + * \param parent parent widget + */ + QgsHashedLineSymbolLayerWidget( QgsVectorLayer *vl, QWidget *parent SIP_TRANSFERTHIS = nullptr ); + + /** + * Creates a new QgsHashedLineSymbolLayerWidget. + * \param vl associated vector layer + */ + static QgsSymbolLayerWidget *create( QgsVectorLayer *vl ) SIP_FACTORY { return new QgsHashedLineSymbolLayerWidget( vl ); } + + // from base class + void setSymbolLayer( QgsSymbolLayer *layer ) override; + QgsSymbolLayer *symbolLayer() override; + + private slots: + + void setInterval( double val ); + void setOffsetAlongLine( double val ); + void setHashLength( double val ); + void setHashAngle( double val ); + + void setRotate(); + void setOffset(); + void setPlacement(); + void mIntervalUnitWidget_changed(); + void mOffsetUnitWidget_changed(); + void mOffsetAlongLineUnitWidget_changed(); + void hashLengthUnitWidgetChanged(); + private: + QgsHashedLineSymbolLayer *mLayer = nullptr; + + +}; + /////////// #include "ui_widget_svgmarker.h" diff --git a/src/plugins/grass/qgsgrasseditrenderer.cpp b/src/plugins/grass/qgsgrasseditrenderer.cpp index 50879fd5696..b99fba09194 100644 --- a/src/plugins/grass/qgsgrasseditrenderer.cpp +++ b/src/plugins/grass/qgsgrasseditrenderer.cpp @@ -61,9 +61,9 @@ QgsGrassEditRenderer::QgsGrassEditRenderer() markerLayers << markerSymbolLayer; QgsMarkerSymbol *markerSymbol = new QgsMarkerSymbol( markerLayers ); firstVertexMarkerLine->setSubSymbol( markerSymbol ); - firstVertexMarkerLine->setPlacement( QgsMarkerLineSymbolLayer::FirstVertex ); + firstVertexMarkerLine->setPlacement( QgsTemplatedLineSymbolLayerBase::FirstVertex ); QgsMarkerLineSymbolLayer *lastVertexMarkerLine = static_cast( firstVertexMarkerLine->clone() ); - lastVertexMarkerLine->setPlacement( QgsMarkerLineSymbolLayer::LastVertex ); + lastVertexMarkerLine->setPlacement( QgsTemplatedLineSymbolLayerBase::LastVertex ); Q_FOREACH ( int value, colors.keys() ) { QgsSymbol *symbol = QgsSymbol::defaultSymbol( QgsWkbTypes::LineGeometry ); diff --git a/src/ui/symbollayer/widget_hashline.ui b/src/ui/symbollayer/widget_hashline.ui new file mode 100644 index 00000000000..396af538801 --- /dev/null +++ b/src/ui/symbollayer/widget_hashline.ui @@ -0,0 +1,436 @@ + + + WidgetHashedLine + + + + 0 + 0 + 371 + 380 + + + + Form + + + + 1 + + + 1 + + + 1 + + + + + Hash placement + + + + + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + with interval + + + true + + + + + + + + 1 + 0 + + + + 6 + + + 10000000.000000000000000 + + + 0.200000000000000 + + + 1.000000000000000 + + + false + + + + + + + + 0 + 0 + + + + Qt::TabFocus + + + + + + + + + on last vertex only + + + + + + + on first vertex only + + + + + + + on central point + + + + + + + on every curve point + + + + + + + on every vertex + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 1 + 0 + + + + 6 + + + 10000000.000000000000000 + + + 0.200000000000000 + + + 1.000000000000000 + + + + + + + + + + + + + + Hash rotation + + + + + + + + 20 + 0 + + + + Qt::TabFocus + + + + + + + Offset along line + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::TabFocus + + + + + + + Rings + + + + + + + Line offset + + + + + + + + + + + 1 + 0 + + + + 6 + + + -999999999.000000000000000 + + + 999999999.000000000000000 + + + 0.200000000000000 + + + + + + + Rotate hash to follow line direction + + + + + + + Hash length + + + + + + + + 1 + 0 + + + + 6 + + + 10000000.000000000000000 + + + 0.200000000000000 + + + 1.000000000000000 + + + + + + + + 20 + 0 + + + + Qt::TabFocus + + + + + + + + + + + + + + + 1 + 0 + + + + true + + + ° + + + 2 + + + -360.000000000000000 + + + 360.000000000000000 + + + 0.500000000000000 + + + + + + + + + + QgsPropertyOverrideButton + QToolButton +
qgspropertyoverridebutton.h
+
+ + QgsDoubleSpinBox + QDoubleSpinBox +
qgsdoublespinbox.h
+
+ + QgsUnitSelectionWidget + QWidget +
qgsunitselectionwidget.h
+ 1 +
+
+ + mPlacementDDBtn + radInterval + spinInterval + mIntervalUnitWidget + mIntervalDDBtn + radVertex + radVertexLast + radVertexFirst + radCentralPoint + radCurvePoint + + + +
diff --git a/tests/src/core/testqgsmarkerlinesymbol.cpp b/tests/src/core/testqgsmarkerlinesymbol.cpp index 85a60fd4ae8..5f2db3be72c 100644 --- a/tests/src/core/testqgsmarkerlinesymbol.cpp +++ b/tests/src/core/testqgsmarkerlinesymbol.cpp @@ -144,7 +144,7 @@ void TestQgsMarkerLineSymbol::pointNumInterval() mMapSettings->setLayers( QList() << mLinesLayer ); QgsMarkerLineSymbolLayer *ml = new QgsMarkerLineSymbolLayer(); - ml->setPlacement( QgsMarkerLineSymbolLayer::Interval ); + ml->setPlacement( QgsTemplatedLineSymbolLayerBase::Interval ); ml->setInterval( 4 ); QgsLineSymbol *lineSymbol = new QgsLineSymbol(); lineSymbol->changeSymbolLayer( 0, ml ); @@ -174,7 +174,7 @@ void TestQgsMarkerLineSymbol::pointNumVertex() mMapSettings->setLayers( QList() << mLinesLayer ); QgsMarkerLineSymbolLayer *ml = new QgsMarkerLineSymbolLayer(); - ml->setPlacement( QgsMarkerLineSymbolLayer::Vertex ); + ml->setPlacement( QgsTemplatedLineSymbolLayerBase::Vertex ); QgsLineSymbol *lineSymbol = new QgsLineSymbol(); lineSymbol->changeSymbolLayer( 0, ml ); QgsSingleSymbolRenderer *r = new QgsSingleSymbolRenderer( lineSymbol ); @@ -203,7 +203,7 @@ void TestQgsMarkerLineSymbol::ringFilter() mMapSettings->setLayers( QList() << mLinesLayer ); QgsMarkerLineSymbolLayer *ml = new QgsMarkerLineSymbolLayer(); - ml->setPlacement( QgsMarkerLineSymbolLayer::Vertex ); + ml->setPlacement( QgsTemplatedLineSymbolLayerBase::Vertex ); QgsLineSymbol *lineSymbol = new QgsLineSymbol(); lineSymbol->changeSymbolLayer( 0, ml ); QgsSingleSymbolRenderer *r = new QgsSingleSymbolRenderer( lineSymbol ); diff --git a/tests/src/core/testqgssymbol.cpp b/tests/src/core/testqgssymbol.cpp index 1b3a78e5481..7ceeb6eec40 100644 --- a/tests/src/core/testqgssymbol.cpp +++ b/tests/src/core/testqgssymbol.cpp @@ -177,7 +177,7 @@ void TestQgsSymbol::testCanvasClip() ms.setLayers( QList() << mpLinesLayer ); QgsMarkerLineSymbolLayer *markerLine = new QgsMarkerLineSymbolLayer(); - markerLine->setPlacement( QgsMarkerLineSymbolLayer:: CentralPoint ); + markerLine->setPlacement( QgsTemplatedLineSymbolLayerBase::CentralPoint ); static_cast< QgsSimpleMarkerSymbolLayer *>( markerLine->subSymbol()->symbolLayer( 0 ) )->setStrokeColor( Qt::black ); QgsLineSymbol *lineSymbol = new QgsLineSymbol(); lineSymbol->changeSymbolLayer( 0, markerLine ); diff --git a/tests/src/python/test_qgshashlinesymbollayer.py b/tests/src/python/test_qgshashlinesymbollayer.py new file mode 100644 index 00000000000..664c1619ed9 --- /dev/null +++ b/tests/src/python/test_qgshashlinesymbollayer.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + test_qgsmarkerlinesymbollayer.py + --------------------- + Date : November 2018 + Copyright : (C) 2018 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. * +* * +*************************************************************************** +""" + +__author__ = 'Nyall Dawson' +__date__ = 'November 2018' +__copyright__ = '(C) 2018, Nyall Dawson' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import qgis # NOQA + +from utilities import unitTestDataPath + +from qgis.PyQt.QtCore import QDir, Qt, QSize +from qgis.PyQt.QtGui import QImage, QColor, QPainter +from qgis.PyQt.QtXml import QDomDocument + +from qgis.core import (QgsGeometry, + QgsFillSymbol, + QgsRenderContext, + QgsFeature, + QgsMapSettings, + QgsRenderChecker, + QgsReadWriteContext, + QgsSymbolLayerUtils, + QgsSimpleMarkerSymbolLayer, + QgsLineSymbolLayer, + QgsMarkerLineSymbolLayer, + QgsMarkerSymbol, + QgsGeometryGeneratorSymbolLayer, + QgsSymbol, + QgsFontMarkerSymbolLayer, + QgsFontUtils, + QgsLineSymbol, + QgsSymbolLayer, + QgsProperty, + QgsRectangle, + QgsUnitTypes + ) + +from qgis.testing import unittest, start_app + +start_app() +TEST_DATA_DIR = unitTestDataPath() + + +class TestQgsMarkerLineSymbolLayer(unittest.TestCase): + + def setUp(self): + self.report = "

Python QgsMarkerLineSymbolLayer Tests

\n" + + def tearDown(self): + report_file_path = "%s/qgistest.html" % QDir.tempPath() + with open(report_file_path, 'a') as report_file: + report_file.write(self.report) + + def testWidth(self): + ms = QgsMapSettings() + extent = QgsRectangle(100, 200, 100, 200) + ms.setExtent(extent) + ms.setOutputSize(QSize(400, 400)) + context = QgsRenderContext.fromMapSettings(ms) + context.setScaleFactor(96 / 25.4) # 96 DPI + ms.setExtent(QgsRectangle(100, 150, 100, 150)) + ms.setOutputDpi(ms.outputDpi() * 2) + context2 = QgsRenderContext.fromMapSettings(ms) + context2.setScaleFactor(300 / 25.4) + + s = QgsFillSymbol() + s.deleteSymbolLayer(0) + + marker_line = QgsMarkerLineSymbolLayer(True) + marker_line.setPlacement(QgsMarkerLineSymbolLayer.FirstVertex) + marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 10) + marker.setColor(QColor(255, 0, 0)) + marker.setStrokeStyle(Qt.NoPen) + marker_symbol = QgsMarkerSymbol() + marker_symbol.changeSymbolLayer(0, marker) + marker_line.setSubSymbol(marker_symbol) + + self.assertEqual(marker_line.width(), 10) + self.assertAlmostEqual(marker_line.width(context), 37.795275590551185, 3) + self.assertAlmostEqual(marker_line.width(context2), 118.11023622047244, 3) + + marker_line.subSymbol().setSizeUnit(QgsUnitTypes.RenderPixels) + self.assertAlmostEqual(marker_line.width(context), 10.0, 3) + self.assertAlmostEqual(marker_line.width(context2), 10.0, 3) + + def testRingFilter(self): + # test filtering rings during rendering + s = QgsFillSymbol() + s.deleteSymbolLayer(0) + + marker_line = QgsMarkerLineSymbolLayer(True) + marker_line.setPlacement(QgsMarkerLineSymbolLayer.FirstVertex) + marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4) + marker.setColor(QColor(255, 0, 0)) + marker.setStrokeStyle(Qt.NoPen) + marker_symbol = QgsMarkerSymbol() + marker_symbol.changeSymbolLayer(0, marker) + marker_line.setSubSymbol(marker_symbol) + + s.appendSymbolLayer(marker_line.clone()) + self.assertEqual(s.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.AllRings) + s.symbolLayer(0).setRingFilter(QgsLineSymbolLayer.ExteriorRingOnly) + self.assertEqual(s.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.ExteriorRingOnly) + + s2 = s.clone() + self.assertEqual(s2.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.ExteriorRingOnly) + + doc = QDomDocument() + context = QgsReadWriteContext() + element = QgsSymbolLayerUtils.saveSymbol('test', s, doc, context) + + s2 = QgsSymbolLayerUtils.loadSymbol(element, context) + self.assertEqual(s2.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.ExteriorRingOnly) + + # rendering test + s3 = QgsFillSymbol() + s3.deleteSymbolLayer(0) + s3.appendSymbolLayer( + QgsMarkerLineSymbolLayer()) + s3.symbolLayer(0).setRingFilter(QgsLineSymbolLayer.ExteriorRingOnly) + + g = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 1 2, 2 2, 2 1, 1 1),(8 8, 9 8, 9 9, 8 9, 8 8))') + rendered_image = self.renderGeometry(s3, g) + assert self.imageCheck('markerline_exterioronly', 'markerline_exterioronly', rendered_image) + + s3.symbolLayer(0).setRingFilter(QgsLineSymbolLayer.InteriorRingsOnly) + g = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 1 2, 2 2, 2 1, 1 1),(8 8, 9 8, 9 9, 8 9, 8 8))') + rendered_image = self.renderGeometry(s3, g) + assert self.imageCheck('markerline_interioronly', 'markerline_interioronly', rendered_image) + + def testPartNum(self): + # test geometry_part_num variable + s = QgsLineSymbol() + s.deleteSymbolLayer(0) + + sym_layer = QgsGeometryGeneratorSymbolLayer.create({'geometryModifier': 'segments_to_lines($geometry)'}) + sym_layer.setSymbolType(QgsSymbol.Line) + s.appendSymbolLayer(sym_layer) + + marker_line = QgsMarkerLineSymbolLayer(False) + marker_line.setPlacement(QgsMarkerLineSymbolLayer.FirstVertex) + f = QgsFontUtils.getStandardTestFont('Bold', 24) + marker = QgsFontMarkerSymbolLayer(f.family(), 'x', 24, QColor(255, 255, 0)) + marker.setDataDefinedProperty(QgsSymbolLayer.PropertyCharacter, QgsProperty.fromExpression('@geometry_part_num')) + marker_symbol = QgsMarkerSymbol() + marker_symbol.changeSymbolLayer(0, marker) + marker_line.setSubSymbol(marker_symbol) + line_symbol = QgsLineSymbol() + line_symbol.changeSymbolLayer(0, marker_line) + sym_layer.setSubSymbol(line_symbol) + + # rendering test + g = QgsGeometry.fromWkt('LineString(0 0, 10 0, 10 10, 0 10)') + rendered_image = self.renderGeometry(s, g, buffer=4) + assert self.imageCheck('part_num_variable', 'part_num_variable', rendered_image) + + marker.setDataDefinedProperty(QgsSymbolLayer.PropertyCharacter, + QgsProperty.fromExpression('@geometry_part_count')) + + # rendering test + g = QgsGeometry.fromWkt('LineString(0 0, 10 0, 10 10, 0 10)') + rendered_image = self.renderGeometry(s, g, buffer=4) + assert self.imageCheck('part_count_variable', 'part_count_variable', rendered_image) + + def renderGeometry(self, symbol, geom, buffer=20): + f = QgsFeature() + f.setGeometry(geom) + + image = QImage(200, 200, QImage.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()) / buffer) + else: + extent = extent.buffered(buffer / 2) + + ms.setExtent(extent) + ms.setOutputSize(image.size()) + context = QgsRenderContext.fromMapSettings(ms) + context.setPainter(painter) + context.setScaleFactor(96 / 25.4) # 96 DPI + context.expressionContext().setFeature(f) + + 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 + + def imageCheck(self, name, reference_image, image): + self.report += "

Render {}

\n".format(name) + temp_dir = QDir.tempPath() + '/' + file_name = temp_dir + 'symbol_' + name + ".png" + image.save(file_name, "PNG") + checker = QgsRenderChecker() + checker.setControlPathPrefix("symbol_markerline") + checker.setControlName("expected_" + reference_image) + checker.setRenderedImage(file_name) + checker.setColorTolerance(2) + result = checker.compareImages(name, 20) + self.report += checker.report() + print((self.report)) + return result + + +if __name__ == '__main__': + unittest.main()