From 26ab9b928552024a3ea76248b47255d3fa6500ee Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 21 Aug 2024 15:02:00 +1000 Subject: [PATCH] [feature] Add "Linear Referencing" symbol layer type This new symbol layer type allows placing text labels at regular intervals along a line (or at positions corresponding to existing vertices). Positions can be calculated using Cartesian distances, or interpolated from z/m values. Functionality includes: - Labels can be placed using fixed cartesian 2d distances, at regular linearly interpolated spacing calculated using the Z or M values in geometries, or at existing vertices - Labels can show either the running total distance, or the linearly interpolated Z/M value - Uses text rendered to draw labels, so the full range of functionality is available for the labels (including buffers, shadows, etc) - Uses the QGIS numeric format classes to format numbers as strings, so users have full range of customisation options for eg decimal places - An optional "skip multiples of" setting. If set, then labels which are a multiple of this value will be skipped over. This allows construction of complex referencing labels, eg where a symbol has two linear referencing symbol layers, one set to label every 100m in a small font, skipping multiples of 1000, and a second set to label every 1000m in a big bold font - Labels are rendered using an angle calculated by averaging the linestring, so sharp tiny jaggies don't result in unslightly label rotation - Optionally, markers can be placed at referenced points in the line string, using a full QGIS marker symbol (this allows eg showing a cross-hatch at the labeled point, for a "ruler" style line) - Data defined control over the placement intervals, skip multiples setting, marker visibility and average angle calculation length Notes: - When using the distance-based placement or labels, the distances are calculated using 2D only, Cartesian calculations based on the original layer CRS. This could potentially be extended in future to expose options for 3D Cartesian distances, or ellipsoidal distance calculations. Sponsored by the Swiss QGIS User Group --- CMakeLists.txt | 2 +- python/PyQt6/core/auto_additions/qgis.py | 15 + .../qgslinearreferencingsymbollayer.py | 6 + .../core/auto_additions/qgssymbollayer.py | 8 +- python/PyQt6/core/auto_generated/qgis.sip.in | 15 + .../qgslinearreferencingsymbollayer.sip.in | 326 +++++ .../symbology/qgssymbollayer.sip.in | 4 + python/PyQt6/core/core_auto.sip | 1 + .../auto_additions/qgssymbollayerwidget.py | 5 + .../symbology/qgssymbollayerwidget.sip.in | 40 + python/core/auto_additions/qgis.py | 15 + .../qgslinearreferencingsymbollayer.py | 6 + python/core/auto_additions/qgssymbollayer.py | 8 +- python/core/auto_generated/qgis.sip.in | 15 + .../qgslinearreferencingsymbollayer.sip.in | 326 +++++ .../symbology/qgssymbollayer.sip.in | 4 + python/core/core_auto.sip | 1 + .../auto_additions/qgssymbollayerwidget.py | 5 + .../symbology/qgssymbollayerwidget.sip.in | 40 + src/core/CMakeLists.txt | 2 + src/core/qgis.h | 27 + .../qgslinearreferencingsymbollayer.cpp | 1061 +++++++++++++++++ .../qgslinearreferencingsymbollayer.h | 334 ++++++ src/core/symbology/qgssymbollayer.cpp | 2 + src/core/symbology/qgssymbollayer.h | 4 + src/core/symbology/qgssymbollayerregistry.cpp | 3 + .../symbology/qgslayerpropertieswidget.cpp | 1 + src/gui/symbology/qgssymbollayerwidget.cpp | 241 ++++ src/gui/symbology/qgssymbollayerwidget.h | 46 +- src/gui/symbology/qgssymbolselectordialog.cpp | 3 +- ...slinearreferencingsymbollayerwidgetbase.ui | 439 +++++++ 31 files changed, 2999 insertions(+), 6 deletions(-) create mode 100644 python/PyQt6/core/auto_additions/qgslinearreferencingsymbollayer.py create mode 100644 python/PyQt6/core/auto_generated/symbology/qgslinearreferencingsymbollayer.sip.in create mode 100644 python/core/auto_additions/qgslinearreferencingsymbollayer.py create mode 100644 python/core/auto_generated/symbology/qgslinearreferencingsymbollayer.sip.in create mode 100644 src/core/symbology/qgslinearreferencingsymbollayer.cpp create mode 100644 src/core/symbology/qgslinearreferencingsymbollayer.h create mode 100644 src/ui/symbollayer/qgslinearreferencingsymbollayerwidgetbase.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index 698a0424cb031..93e8e8e94daef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1103,7 +1103,7 @@ if (WITH_CORE AND WITH_BINDINGS) include(SIPMacros) set(SIP_INCLUDES ${PYQT_SIP_DIR} ${CMAKE_SOURCE_DIR}/python) - set(SIP_CONCAT_PARTS 25) + set(SIP_CONCAT_PARTS 20) if (NOT BINDINGS_GLOBAL_INSTALL) set(Python_SITEARCH ${QGIS_DATA_DIR}/python) diff --git a/python/PyQt6/core/auto_additions/qgis.py b/python/PyQt6/core/auto_additions/qgis.py index f039a5737eaea..8611cc485da86 100644 --- a/python/PyQt6/core/auto_additions/qgis.py +++ b/python/PyQt6/core/auto_additions/qgis.py @@ -2911,6 +2911,21 @@ Qgis.MarkerLinePlacements = lambda flags=0: Qgis.MarkerLinePlacement(flags) Qgis.MarkerLinePlacements.baseClass = Qgis MarkerLinePlacements = Qgis # dirty hack since SIP seems to introduce the flags in module +# monkey patching scoped based enum +Qgis.LinearReferencingPlacement.IntervalCartesian2D.__doc__ = "Place labels at regular intervals, using Cartesian distance calculations on a 2D plane" +Qgis.LinearReferencingPlacement.IntervalZ.__doc__ = "Place labels at regular intervals, linearly interpolated using Z values" +Qgis.LinearReferencingPlacement.IntervalM.__doc__ = "Place labels at regular intervals, linearly interpolated using M values" +Qgis.LinearReferencingPlacement.Vertex.__doc__ = "Place labels on every vertex in the line" +Qgis.LinearReferencingPlacement.__doc__ = "Defines how/where the labels should be placed in a linear referencing symbol layer.\n\n.. versionadded:: 3.40\n\n" + '* ``IntervalCartesian2D``: ' + Qgis.LinearReferencingPlacement.IntervalCartesian2D.__doc__ + '\n' + '* ``IntervalZ``: ' + Qgis.LinearReferencingPlacement.IntervalZ.__doc__ + '\n' + '* ``IntervalM``: ' + Qgis.LinearReferencingPlacement.IntervalM.__doc__ + '\n' + '* ``Vertex``: ' + Qgis.LinearReferencingPlacement.Vertex.__doc__ +# -- +Qgis.LinearReferencingPlacement.baseClass = Qgis +# monkey patching scoped based enum +Qgis.LinearReferencingLabelSource.CartesianDistance2D.__doc__ = "Distance along line, calculated using Cartesian calculations on a 2D plane." +Qgis.LinearReferencingLabelSource.Z.__doc__ = "Z values" +Qgis.LinearReferencingLabelSource.M.__doc__ = "M values" +Qgis.LinearReferencingLabelSource.__doc__ = "Defines what quantity to use for the labels shown in a linear referencing symbol layer.\n\n.. versionadded:: 3.40\n\n" + '* ``CartesianDistance2D``: ' + Qgis.LinearReferencingLabelSource.CartesianDistance2D.__doc__ + '\n' + '* ``Z``: ' + Qgis.LinearReferencingLabelSource.Z.__doc__ + '\n' + '* ``M``: ' + Qgis.LinearReferencingLabelSource.M.__doc__ +# -- +Qgis.LinearReferencingLabelSource.baseClass = Qgis QgsGradientFillSymbolLayer.GradientColorType = Qgis.GradientColorSource # monkey patching scoped based enum QgsGradientFillSymbolLayer.SimpleTwoColor = Qgis.GradientColorSource.SimpleTwoColor diff --git a/python/PyQt6/core/auto_additions/qgslinearreferencingsymbollayer.py b/python/PyQt6/core/auto_additions/qgslinearreferencingsymbollayer.py new file mode 100644 index 0000000000000..c876d0fe5649d --- /dev/null +++ b/python/PyQt6/core/auto_additions/qgslinearreferencingsymbollayer.py @@ -0,0 +1,6 @@ +# The following has been generated automatically from src/core/symbology/qgslinearreferencingsymbollayer.h +QgsLinearReferencingSymbolLayer.create = staticmethod(QgsLinearReferencingSymbolLayer.create) +try: + QgsLinearReferencingSymbolLayer.__group__ = ['symbology'] +except NameError: + pass diff --git a/python/PyQt6/core/auto_additions/qgssymbollayer.py b/python/PyQt6/core/auto_additions/qgssymbollayer.py index a34d30347882e..b4a1c42fee331 100644 --- a/python/PyQt6/core/auto_additions/qgssymbollayer.py +++ b/python/PyQt6/core/auto_additions/qgssymbollayer.py @@ -280,7 +280,13 @@ QgsSymbolLayer.Property.PropertyLineClipping = QgsSymbolLayer.Property.LineClipping QgsSymbolLayer.PropertyLineClipping.is_monkey_patched = True QgsSymbolLayer.PropertyLineClipping.__doc__ = "Line clipping mode (since QGIS 3.24)" -QgsSymbolLayer.Property.__doc__ = "Data definable properties.\n\n" + '* ``PropertySize``: ' + QgsSymbolLayer.Property.Size.__doc__ + '\n' + '* ``PropertyAngle``: ' + QgsSymbolLayer.Property.Angle.__doc__ + '\n' + '* ``PropertyName``: ' + QgsSymbolLayer.Property.Name.__doc__ + '\n' + '* ``PropertyFillColor``: ' + QgsSymbolLayer.Property.FillColor.__doc__ + '\n' + '* ``PropertyStrokeColor``: ' + QgsSymbolLayer.Property.StrokeColor.__doc__ + '\n' + '* ``PropertyStrokeWidth``: ' + QgsSymbolLayer.Property.StrokeWidth.__doc__ + '\n' + '* ``PropertyStrokeStyle``: ' + QgsSymbolLayer.Property.StrokeStyle.__doc__ + '\n' + '* ``PropertyOffset``: ' + QgsSymbolLayer.Property.Offset.__doc__ + '\n' + '* ``PropertyCharacter``: ' + QgsSymbolLayer.Property.Character.__doc__ + '\n' + '* ``PropertyWidth``: ' + QgsSymbolLayer.Property.Width.__doc__ + '\n' + '* ``PropertyHeight``: ' + QgsSymbolLayer.Property.Height.__doc__ + '\n' + '* ``PropertyPreserveAspectRatio``: ' + QgsSymbolLayer.Property.PreserveAspectRatio.__doc__ + '\n' + '* ``PropertyFillStyle``: ' + QgsSymbolLayer.Property.FillStyle.__doc__ + '\n' + '* ``PropertyJoinStyle``: ' + QgsSymbolLayer.Property.JoinStyle.__doc__ + '\n' + '* ``PropertySecondaryColor``: ' + QgsSymbolLayer.Property.SecondaryColor.__doc__ + '\n' + '* ``PropertyLineAngle``: ' + QgsSymbolLayer.Property.LineAngle.__doc__ + '\n' + '* ``PropertyLineDistance``: ' + QgsSymbolLayer.Property.LineDistance.__doc__ + '\n' + '* ``PropertyGradientType``: ' + QgsSymbolLayer.Property.GradientType.__doc__ + '\n' + '* ``PropertyCoordinateMode``: ' + QgsSymbolLayer.Property.CoordinateMode.__doc__ + '\n' + '* ``PropertyGradientSpread``: ' + QgsSymbolLayer.Property.GradientSpread.__doc__ + '\n' + '* ``PropertyGradientReference1X``: ' + QgsSymbolLayer.Property.GradientReference1X.__doc__ + '\n' + '* ``PropertyGradientReference1Y``: ' + QgsSymbolLayer.Property.GradientReference1Y.__doc__ + '\n' + '* ``PropertyGradientReference2X``: ' + QgsSymbolLayer.Property.GradientReference2X.__doc__ + '\n' + '* ``PropertyGradientReference2Y``: ' + QgsSymbolLayer.Property.GradientReference2Y.__doc__ + '\n' + '* ``PropertyGradientReference1IsCentroid``: ' + QgsSymbolLayer.Property.GradientReference1IsCentroid.__doc__ + '\n' + '* ``PropertyGradientReference2IsCentroid``: ' + QgsSymbolLayer.Property.GradientReference2IsCentroid.__doc__ + '\n' + '* ``PropertyBlurRadius``: ' + QgsSymbolLayer.Property.BlurRadius.__doc__ + '\n' + '* ``PropertyShapeburstUseWholeShape``: ' + QgsSymbolLayer.Property.ShapeburstUseWholeShape.__doc__ + '\n' + '* ``PropertyShapeburstMaxDistance``: ' + QgsSymbolLayer.Property.ShapeburstMaxDistance.__doc__ + '\n' + '* ``PropertyShapeburstIgnoreRings``: ' + QgsSymbolLayer.Property.ShapeburstIgnoreRings.__doc__ + '\n' + '* ``PropertyFile``: ' + QgsSymbolLayer.Property.File.__doc__ + '\n' + '* ``PropertyDistanceX``: ' + QgsSymbolLayer.Property.DistanceX.__doc__ + '\n' + '* ``PropertyDistanceY``: ' + QgsSymbolLayer.Property.DistanceY.__doc__ + '\n' + '* ``PropertyDisplacementX``: ' + QgsSymbolLayer.Property.DisplacementX.__doc__ + '\n' + '* ``PropertyDisplacementY``: ' + QgsSymbolLayer.Property.DisplacementY.__doc__ + '\n' + '* ``PropertyOpacity``: ' + QgsSymbolLayer.Property.Opacity.__doc__ + '\n' + '* ``PropertyCustomDash``: ' + QgsSymbolLayer.Property.CustomDash.__doc__ + '\n' + '* ``PropertyCapStyle``: ' + QgsSymbolLayer.Property.CapStyle.__doc__ + '\n' + '* ``PropertyPlacement``: ' + QgsSymbolLayer.Property.Placement.__doc__ + '\n' + '* ``PropertyInterval``: ' + QgsSymbolLayer.Property.Interval.__doc__ + '\n' + '* ``PropertyOffsetAlongLine``: ' + QgsSymbolLayer.Property.OffsetAlongLine.__doc__ + '\n' + '* ``PropertyAverageAngleLength``: ' + QgsSymbolLayer.Property.AverageAngleLength.__doc__ + '\n' + '* ``PropertyHorizontalAnchor``: ' + QgsSymbolLayer.Property.HorizontalAnchor.__doc__ + '\n' + '* ``PropertyVerticalAnchor``: ' + QgsSymbolLayer.Property.VerticalAnchor.__doc__ + '\n' + '* ``PropertyLayerEnabled``: ' + QgsSymbolLayer.Property.LayerEnabled.__doc__ + '\n' + '* ``PropertyArrowWidth``: ' + QgsSymbolLayer.Property.ArrowWidth.__doc__ + '\n' + '* ``PropertyArrowStartWidth``: ' + QgsSymbolLayer.Property.ArrowStartWidth.__doc__ + '\n' + '* ``PropertyArrowHeadLength``: ' + QgsSymbolLayer.Property.ArrowHeadLength.__doc__ + '\n' + '* ``PropertyArrowHeadThickness``: ' + QgsSymbolLayer.Property.ArrowHeadThickness.__doc__ + '\n' + '* ``PropertyArrowHeadType``: ' + QgsSymbolLayer.Property.ArrowHeadType.__doc__ + '\n' + '* ``PropertyArrowType``: ' + QgsSymbolLayer.Property.ArrowType.__doc__ + '\n' + '* ``PropertyOffsetX``: ' + QgsSymbolLayer.Property.OffsetX.__doc__ + '\n' + '* ``PropertyOffsetY``: ' + QgsSymbolLayer.Property.OffsetY.__doc__ + '\n' + '* ``PropertyPointCount``: ' + QgsSymbolLayer.Property.PointCount.__doc__ + '\n' + '* ``PropertyRandomSeed``: ' + QgsSymbolLayer.Property.RandomSeed.__doc__ + '\n' + '* ``PropertyClipPoints``: ' + QgsSymbolLayer.Property.ClipPoints.__doc__ + '\n' + '* ``PropertyDensityArea``: ' + QgsSymbolLayer.Property.DensityArea.__doc__ + '\n' + '* ``PropertyFontFamily``: ' + QgsSymbolLayer.Property.FontFamily.__doc__ + '\n' + '* ``PropertyFontStyle``: ' + QgsSymbolLayer.Property.FontStyle.__doc__ + '\n' + '* ``PropertyDashPatternOffset``: ' + QgsSymbolLayer.Property.DashPatternOffset.__doc__ + '\n' + '* ``PropertyTrimStart``: ' + QgsSymbolLayer.Property.TrimStart.__doc__ + '\n' + '* ``PropertyTrimEnd``: ' + QgsSymbolLayer.Property.TrimEnd.__doc__ + '\n' + '* ``PropertyLineStartWidthValue``: ' + QgsSymbolLayer.Property.LineStartWidthValue.__doc__ + '\n' + '* ``PropertyLineEndWidthValue``: ' + QgsSymbolLayer.Property.LineEndWidthValue.__doc__ + '\n' + '* ``PropertyLineStartColorValue``: ' + QgsSymbolLayer.Property.LineStartColorValue.__doc__ + '\n' + '* ``PropertyLineEndColorValue``: ' + QgsSymbolLayer.Property.LineEndColorValue.__doc__ + '\n' + '* ``PropertyMarkerClipping``: ' + QgsSymbolLayer.Property.MarkerClipping.__doc__ + '\n' + '* ``PropertyRandomOffsetX``: ' + QgsSymbolLayer.Property.RandomOffsetX.__doc__ + '\n' + '* ``PropertyRandomOffsetY``: ' + QgsSymbolLayer.Property.RandomOffsetY.__doc__ + '\n' + '* ``PropertyLineClipping``: ' + QgsSymbolLayer.Property.LineClipping.__doc__ +QgsSymbolLayer.SkipMultiples = QgsSymbolLayer.Property.SkipMultiples +QgsSymbolLayer.SkipMultiples.is_monkey_patched = True +QgsSymbolLayer.SkipMultiples.__doc__ = "Skip multiples of (since QGIS 3.40)" +QgsSymbolLayer.ShowMarker = QgsSymbolLayer.Property.ShowMarker +QgsSymbolLayer.ShowMarker.is_monkey_patched = True +QgsSymbolLayer.ShowMarker.__doc__ = "Show markers (since QGIS 3.40)" +QgsSymbolLayer.Property.__doc__ = "Data definable properties.\n\n" + '* ``PropertySize``: ' + QgsSymbolLayer.Property.Size.__doc__ + '\n' + '* ``PropertyAngle``: ' + QgsSymbolLayer.Property.Angle.__doc__ + '\n' + '* ``PropertyName``: ' + QgsSymbolLayer.Property.Name.__doc__ + '\n' + '* ``PropertyFillColor``: ' + QgsSymbolLayer.Property.FillColor.__doc__ + '\n' + '* ``PropertyStrokeColor``: ' + QgsSymbolLayer.Property.StrokeColor.__doc__ + '\n' + '* ``PropertyStrokeWidth``: ' + QgsSymbolLayer.Property.StrokeWidth.__doc__ + '\n' + '* ``PropertyStrokeStyle``: ' + QgsSymbolLayer.Property.StrokeStyle.__doc__ + '\n' + '* ``PropertyOffset``: ' + QgsSymbolLayer.Property.Offset.__doc__ + '\n' + '* ``PropertyCharacter``: ' + QgsSymbolLayer.Property.Character.__doc__ + '\n' + '* ``PropertyWidth``: ' + QgsSymbolLayer.Property.Width.__doc__ + '\n' + '* ``PropertyHeight``: ' + QgsSymbolLayer.Property.Height.__doc__ + '\n' + '* ``PropertyPreserveAspectRatio``: ' + QgsSymbolLayer.Property.PreserveAspectRatio.__doc__ + '\n' + '* ``PropertyFillStyle``: ' + QgsSymbolLayer.Property.FillStyle.__doc__ + '\n' + '* ``PropertyJoinStyle``: ' + QgsSymbolLayer.Property.JoinStyle.__doc__ + '\n' + '* ``PropertySecondaryColor``: ' + QgsSymbolLayer.Property.SecondaryColor.__doc__ + '\n' + '* ``PropertyLineAngle``: ' + QgsSymbolLayer.Property.LineAngle.__doc__ + '\n' + '* ``PropertyLineDistance``: ' + QgsSymbolLayer.Property.LineDistance.__doc__ + '\n' + '* ``PropertyGradientType``: ' + QgsSymbolLayer.Property.GradientType.__doc__ + '\n' + '* ``PropertyCoordinateMode``: ' + QgsSymbolLayer.Property.CoordinateMode.__doc__ + '\n' + '* ``PropertyGradientSpread``: ' + QgsSymbolLayer.Property.GradientSpread.__doc__ + '\n' + '* ``PropertyGradientReference1X``: ' + QgsSymbolLayer.Property.GradientReference1X.__doc__ + '\n' + '* ``PropertyGradientReference1Y``: ' + QgsSymbolLayer.Property.GradientReference1Y.__doc__ + '\n' + '* ``PropertyGradientReference2X``: ' + QgsSymbolLayer.Property.GradientReference2X.__doc__ + '\n' + '* ``PropertyGradientReference2Y``: ' + QgsSymbolLayer.Property.GradientReference2Y.__doc__ + '\n' + '* ``PropertyGradientReference1IsCentroid``: ' + QgsSymbolLayer.Property.GradientReference1IsCentroid.__doc__ + '\n' + '* ``PropertyGradientReference2IsCentroid``: ' + QgsSymbolLayer.Property.GradientReference2IsCentroid.__doc__ + '\n' + '* ``PropertyBlurRadius``: ' + QgsSymbolLayer.Property.BlurRadius.__doc__ + '\n' + '* ``PropertyShapeburstUseWholeShape``: ' + QgsSymbolLayer.Property.ShapeburstUseWholeShape.__doc__ + '\n' + '* ``PropertyShapeburstMaxDistance``: ' + QgsSymbolLayer.Property.ShapeburstMaxDistance.__doc__ + '\n' + '* ``PropertyShapeburstIgnoreRings``: ' + QgsSymbolLayer.Property.ShapeburstIgnoreRings.__doc__ + '\n' + '* ``PropertyFile``: ' + QgsSymbolLayer.Property.File.__doc__ + '\n' + '* ``PropertyDistanceX``: ' + QgsSymbolLayer.Property.DistanceX.__doc__ + '\n' + '* ``PropertyDistanceY``: ' + QgsSymbolLayer.Property.DistanceY.__doc__ + '\n' + '* ``PropertyDisplacementX``: ' + QgsSymbolLayer.Property.DisplacementX.__doc__ + '\n' + '* ``PropertyDisplacementY``: ' + QgsSymbolLayer.Property.DisplacementY.__doc__ + '\n' + '* ``PropertyOpacity``: ' + QgsSymbolLayer.Property.Opacity.__doc__ + '\n' + '* ``PropertyCustomDash``: ' + QgsSymbolLayer.Property.CustomDash.__doc__ + '\n' + '* ``PropertyCapStyle``: ' + QgsSymbolLayer.Property.CapStyle.__doc__ + '\n' + '* ``PropertyPlacement``: ' + QgsSymbolLayer.Property.Placement.__doc__ + '\n' + '* ``PropertyInterval``: ' + QgsSymbolLayer.Property.Interval.__doc__ + '\n' + '* ``PropertyOffsetAlongLine``: ' + QgsSymbolLayer.Property.OffsetAlongLine.__doc__ + '\n' + '* ``PropertyAverageAngleLength``: ' + QgsSymbolLayer.Property.AverageAngleLength.__doc__ + '\n' + '* ``PropertyHorizontalAnchor``: ' + QgsSymbolLayer.Property.HorizontalAnchor.__doc__ + '\n' + '* ``PropertyVerticalAnchor``: ' + QgsSymbolLayer.Property.VerticalAnchor.__doc__ + '\n' + '* ``PropertyLayerEnabled``: ' + QgsSymbolLayer.Property.LayerEnabled.__doc__ + '\n' + '* ``PropertyArrowWidth``: ' + QgsSymbolLayer.Property.ArrowWidth.__doc__ + '\n' + '* ``PropertyArrowStartWidth``: ' + QgsSymbolLayer.Property.ArrowStartWidth.__doc__ + '\n' + '* ``PropertyArrowHeadLength``: ' + QgsSymbolLayer.Property.ArrowHeadLength.__doc__ + '\n' + '* ``PropertyArrowHeadThickness``: ' + QgsSymbolLayer.Property.ArrowHeadThickness.__doc__ + '\n' + '* ``PropertyArrowHeadType``: ' + QgsSymbolLayer.Property.ArrowHeadType.__doc__ + '\n' + '* ``PropertyArrowType``: ' + QgsSymbolLayer.Property.ArrowType.__doc__ + '\n' + '* ``PropertyOffsetX``: ' + QgsSymbolLayer.Property.OffsetX.__doc__ + '\n' + '* ``PropertyOffsetY``: ' + QgsSymbolLayer.Property.OffsetY.__doc__ + '\n' + '* ``PropertyPointCount``: ' + QgsSymbolLayer.Property.PointCount.__doc__ + '\n' + '* ``PropertyRandomSeed``: ' + QgsSymbolLayer.Property.RandomSeed.__doc__ + '\n' + '* ``PropertyClipPoints``: ' + QgsSymbolLayer.Property.ClipPoints.__doc__ + '\n' + '* ``PropertyDensityArea``: ' + QgsSymbolLayer.Property.DensityArea.__doc__ + '\n' + '* ``PropertyFontFamily``: ' + QgsSymbolLayer.Property.FontFamily.__doc__ + '\n' + '* ``PropertyFontStyle``: ' + QgsSymbolLayer.Property.FontStyle.__doc__ + '\n' + '* ``PropertyDashPatternOffset``: ' + QgsSymbolLayer.Property.DashPatternOffset.__doc__ + '\n' + '* ``PropertyTrimStart``: ' + QgsSymbolLayer.Property.TrimStart.__doc__ + '\n' + '* ``PropertyTrimEnd``: ' + QgsSymbolLayer.Property.TrimEnd.__doc__ + '\n' + '* ``PropertyLineStartWidthValue``: ' + QgsSymbolLayer.Property.LineStartWidthValue.__doc__ + '\n' + '* ``PropertyLineEndWidthValue``: ' + QgsSymbolLayer.Property.LineEndWidthValue.__doc__ + '\n' + '* ``PropertyLineStartColorValue``: ' + QgsSymbolLayer.Property.LineStartColorValue.__doc__ + '\n' + '* ``PropertyLineEndColorValue``: ' + QgsSymbolLayer.Property.LineEndColorValue.__doc__ + '\n' + '* ``PropertyMarkerClipping``: ' + QgsSymbolLayer.Property.MarkerClipping.__doc__ + '\n' + '* ``PropertyRandomOffsetX``: ' + QgsSymbolLayer.Property.RandomOffsetX.__doc__ + '\n' + '* ``PropertyRandomOffsetY``: ' + QgsSymbolLayer.Property.RandomOffsetY.__doc__ + '\n' + '* ``PropertyLineClipping``: ' + QgsSymbolLayer.Property.LineClipping.__doc__ + '\n' + '* ``SkipMultiples``: ' + QgsSymbolLayer.Property.SkipMultiples.__doc__ + '\n' + '* ``ShowMarker``: ' + QgsSymbolLayer.Property.ShowMarker.__doc__ # -- QgsMarkerSymbolLayer.Left = QgsMarkerSymbolLayer.HorizontalAnchorPoint.Left QgsMarkerSymbolLayer.HCenter = QgsMarkerSymbolLayer.HorizontalAnchorPoint.HCenter diff --git a/python/PyQt6/core/auto_generated/qgis.sip.in b/python/PyQt6/core/auto_generated/qgis.sip.in index 3906b737d0cfa..6361d796e3100 100644 --- a/python/PyQt6/core/auto_generated/qgis.sip.in +++ b/python/PyQt6/core/auto_generated/qgis.sip.in @@ -1628,6 +1628,21 @@ The development version typedef QFlags MarkerLinePlacements; + enum class LinearReferencingPlacement /BaseType=IntFlag/ + { + IntervalCartesian2D, + IntervalZ, + IntervalM, + Vertex, + }; + + enum class LinearReferencingLabelSource /BaseType=IntEnum/ + { + CartesianDistance2D, + Z, + M, + }; + enum class GradientColorSource /BaseType=IntEnum/ { SimpleTwoColor, diff --git a/python/PyQt6/core/auto_generated/symbology/qgslinearreferencingsymbollayer.sip.in b/python/PyQt6/core/auto_generated/symbology/qgslinearreferencingsymbollayer.sip.in new file mode 100644 index 0000000000000..9bd10e6ba0062 --- /dev/null +++ b/python/PyQt6/core/auto_generated/symbology/qgslinearreferencingsymbollayer.sip.in @@ -0,0 +1,326 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/symbology/qgslinearreferencingsymbollayer.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.py again * + ************************************************************************/ + + + +class QgsLinearReferencingSymbolLayer : QgsLineSymbolLayer +{ +%Docstring(signature="appended") +Line symbol layer used for decorating accordingly to linear referencing. + +This symbol layer type allows placing text labels at regular intervals along +a line (or at positions corresponding to existing vertices). Positions +can be calculated using Cartesian distances, or interpolated from z or m values. + +.. versionadded:: 3.40 +%End + +%TypeHeaderCode +#include "qgslinearreferencingsymbollayer.h" +%End + public: + QgsLinearReferencingSymbolLayer(); + ~QgsLinearReferencingSymbolLayer(); + + static QgsSymbolLayer *create( const QVariantMap &properties = QVariantMap() ) /Factory/; +%Docstring +Creates a new QgsLinearReferencingSymbolLayer, using the specified ``properties``. + +The caller takes ownership of the returned object. +%End + + virtual QgsLinearReferencingSymbolLayer *clone() const /Factory/; + + virtual QVariantMap properties() const; + + virtual QString layerType() const; + + virtual Qgis::SymbolLayerFlags flags() const; + + virtual QgsSymbol *subSymbol(); + + virtual bool setSubSymbol( QgsSymbol *symbol /Transfer/ ); + + virtual void startRender( QgsSymbolRenderContext &context ); + + virtual void stopRender( QgsSymbolRenderContext &context ); + + virtual void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ); + + + QgsTextFormat textFormat() const; +%Docstring +Returns the text format used to render the layer. + +.. seealso:: :py:func:`setTextFormat` +%End + + void setTextFormat( const QgsTextFormat &format ); +%Docstring +Sets the text ``format`` used to render the layer. + +.. seealso:: :py:func:`textFormat` +%End + + QgsNumericFormat *numericFormat() const; +%Docstring +Returns the numeric format used to format the labels for the layer. + +.. seealso:: :py:func:`setNumericFormat` +%End + + void setNumericFormat( QgsNumericFormat *format /Transfer/ ); +%Docstring +Sets the numeric ``format`` used to format the labels for the layer. + +Ownership of ``format`` is transferred to the layer. + +.. seealso:: :py:func:`numericFormat` +%End + + double interval() const; +%Docstring +Returns the interval between labels. + +Units are always in the original layer CRS units. + +.. seealso:: :py:func:`setInterval` +%End + + void setInterval( double interval ); +%Docstring +Sets the ``interval`` between labels. + +Units are always in the original layer CRS units. + +.. seealso:: :py:func:`setInterval` +%End + + double skipMultiplesOf() const; +%Docstring +Returns the multiple distance to skip labels for. + +If this value is non-zero, then any labels which are integer multiples of the returned +value will be skipped. This allows creation of advanced referencing styles where a single +:py:class:`QgsSymbol` has multiple :py:class:`QgsLinearReferencingSymbolLayer` symbol layers, eg allowing +labeling every 100 in a normal font and every 1000 in a bold, larger font. + +.. seealso:: :py:func:`setSkipMultiplesOf` +%End + + void setSkipMultiplesOf( double multiple ); +%Docstring +Sets the ``multiple`` distance to skip labels for. + +If this value is non-zero, then any labels which are integer multiples of the returned +value will be skipped. This allows creation of advanced referencing styles where a single +:py:class:`QgsSymbol` has multiple :py:class:`QgsLinearReferencingSymbolLayer` symbol layers, eg allowing +labeling every 100 in a normal font and every 1000 in a bold, larger font. + +.. seealso:: :py:func:`skipMultiplesOf` +%End + + bool rotateLabels() const; +%Docstring +Returns ``True`` if the labels and symbols are to be rotated to match their line segment orientation. + +.. seealso:: :py:func:`setRotateLabels` +%End + + void setRotateLabels( bool rotate ); +%Docstring +Sets whether the labels and symbols should be rotated to match their line segment orientation. + +.. seealso:: :py:func:`rotateLabels` +%End + + QPointF labelOffset() const; +%Docstring +Returns the offset between the line and linear referencing labels. + +The unit for the offset is retrievable via :py:func:`~QgsLinearReferencingSymbolLayer.labelOffsetUnit`. + +.. seealso:: :py:func:`setLabelOffset` + +.. seealso:: :py:func:`labelOffsetUnit` +%End + + void setLabelOffset( const QPointF &offset ); +%Docstring +Sets the ``offset`` between the line and linear referencing labels. + +The unit for the offset is set via :py:func:`~QgsLinearReferencingSymbolLayer.setLabelOffsetUnit`. + +.. seealso:: :py:func:`labelOffset` + +.. seealso:: :py:func:`setLabelOffsetUnit` +%End + + Qgis::RenderUnit labelOffsetUnit() const; +%Docstring +Returns the unit used for the offset between the line and linear referencing labels. + +.. seealso:: :py:func:`setLabelOffsetUnit` + +.. seealso:: :py:func:`labelOffset` +%End + + void setLabelOffsetUnit( Qgis::RenderUnit unit ); +%Docstring +Sets the ``unit`` used for the offset between the line and linear referencing labels. + +.. seealso:: :py:func:`labelOffsetUnit` + +.. seealso:: :py:func:`setLabelOffset` +%End + + const QgsMapUnitScale &labelOffsetMapUnitScale() const; +%Docstring +Returns the map unit scale used for calculating the offset between the line and linear referencing labels. + +.. seealso:: :py:func:`setLabelOffsetMapUnitScale` +%End + + void setLabelOffsetMapUnitScale( const QgsMapUnitScale &scale ); +%Docstring +Sets the map unit ``scale`` used for calculating the offset between the line and linear referencing labels. + +.. seealso:: :py:func:`labelOffsetMapUnitScale` +%End + + bool showMarker() const; +%Docstring +Returns ``True`` if a marker symbol should be shown corresponding to the labeled point on line. + +The marker symbol is set using :py:func:`~QgsLinearReferencingSymbolLayer.setSubSymbol` + +.. seealso:: :py:func:`setShowMarker` +%End + + void setShowMarker( bool show ); +%Docstring +Sets whether a marker symbol should be shown corresponding to the labeled point on line. + +The marker symbol is set using :py:func:`~QgsLinearReferencingSymbolLayer.setSubSymbol` + +.. seealso:: :py:func:`showMarker` +%End + + Qgis::LinearReferencingPlacement placement() const; +%Docstring +Returns the placement mode for the labels. + +.. seealso:: :py:func:`setPlacement` +%End + + void setPlacement( Qgis::LinearReferencingPlacement placement ); +%Docstring +Sets the ``placement`` mode for the labels. + +.. seealso:: :py:func:`placement` +%End + + Qgis::LinearReferencingLabelSource labelSource() const; +%Docstring +Returns the label source, which dictates what quantity to use for the labels shown. + +.. seealso:: :py:func:`setLabelSource` +%End + + void setLabelSource( Qgis::LinearReferencingLabelSource source ); +%Docstring +Sets the label ``source``, which dictates what quantity to use for the labels shown. + +.. seealso:: :py:func:`labelSource` +%End + + double averageAngleLength() const; +%Docstring +Returns the length of line over which the line's direction is averaged when +calculating individual label angles. Longer lengths smooth out angles from jagged lines to a greater extent. + +Units are retrieved through :py:func:`~QgsLinearReferencingSymbolLayer.averageAngleUnit` + +.. seealso:: :py:func:`setAverageAngleLength` + +.. seealso:: :py:func:`averageAngleUnit` + +.. seealso:: :py:func:`averageAngleMapUnitScale` +%End + + void setAverageAngleLength( double length ); +%Docstring +Sets the ``length`` of line over which the line's direction is averaged when +calculating individual label angles. Longer lengths smooth out angles from jagged lines to a greater extent. + +Units are set through :py:func:`~QgsLinearReferencingSymbolLayer.setAverageAngleUnit` + +.. seealso:: :py:func:`averageAngleLength` + +.. seealso:: :py:func:`setAverageAngleUnit` + +.. seealso:: :py:func:`setAverageAngleMapUnitScale` +%End + + void setAverageAngleUnit( Qgis::RenderUnit unit ); +%Docstring +Sets the ``unit`` for the length over which the line's direction is averaged when +calculating individual label angles. + +.. seealso:: :py:func:`averageAngleUnit` + +.. seealso:: :py:func:`setAverageAngleLength` + +.. seealso:: :py:func:`setAverageAngleMapUnitScale` +%End + + Qgis::RenderUnit averageAngleUnit() const; +%Docstring +Returns the unit for the length over which the line's direction is averaged when +calculating individual label angles. + +.. seealso:: :py:func:`setAverageAngleUnit` + +.. seealso:: :py:func:`averageAngleLength` + +.. seealso:: :py:func:`averageAngleMapUnitScale` +%End + + void setAverageAngleMapUnitScale( const QgsMapUnitScale &scale ); +%Docstring +Sets the map unit ``scale`` for the length over which the line's direction is averaged when +calculating individual label angles. + +.. seealso:: :py:func:`averageAngleMapUnitScale` + +.. seealso:: :py:func:`setAverageAngleLength` + +.. seealso:: :py:func:`setAverageAngleUnit` +%End + + const QgsMapUnitScale &averageAngleMapUnitScale() const; +%Docstring +Returns the map unit scale for the length over which the line's direction is averaged when +calculating individual label angles. + +.. seealso:: :py:func:`setAverageAngleMapUnitScale` + +.. seealso:: :py:func:`averageAngleLength` + +.. seealso:: :py:func:`averageAngleUnit` +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/symbology/qgslinearreferencingsymbollayer.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.py again * + ************************************************************************/ diff --git a/python/PyQt6/core/auto_generated/symbology/qgssymbollayer.sip.in b/python/PyQt6/core/auto_generated/symbology/qgssymbollayer.sip.in index 0e06ee544cd16..38767f5942ec6 100644 --- a/python/PyQt6/core/auto_generated/symbology/qgssymbollayer.sip.in +++ b/python/PyQt6/core/auto_generated/symbology/qgssymbollayer.sip.in @@ -60,6 +60,8 @@ class QgsSymbolLayer sipType = sipType_QgsRasterLineSymbolLayer; else if ( sipCpp->layerType() == "Lineburst" ) sipType = sipType_QgsLineburstSymbolLayer; + else if ( sipCpp->layerType() == "LinearReferencing" ) + sipType = sipType_QgsLinearReferencingSymbolLayer; else sipType = sipType_QgsLineSymbolLayer; break; @@ -167,6 +169,8 @@ class QgsSymbolLayer RandomOffsetX, RandomOffsetY, LineClipping, + SkipMultiples, + ShowMarker, }; static const QgsPropertiesDefinition &propertyDefinitions(); diff --git a/python/PyQt6/core/core_auto.sip b/python/PyQt6/core/core_auto.sip index 0b01ea61216c8..3e7db7ae6645c 100644 --- a/python/PyQt6/core/core_auto.sip +++ b/python/PyQt6/core/core_auto.sip @@ -689,6 +689,7 @@ %Include auto_generated/symbology/qgsinterpolatedlinerenderer.sip %Include auto_generated/symbology/qgsinvertedpolygonrenderer.sip %Include auto_generated/symbology/qgslegendsymbolitem.sip +%Include auto_generated/symbology/qgslinearreferencingsymbollayer.sip %Include auto_generated/symbology/qgslinesymbol.sip %Include auto_generated/symbology/qgslinesymbollayer.sip %Include auto_generated/symbology/qgsmapinfosymbolconverter.sip diff --git a/python/PyQt6/gui/auto_additions/qgssymbollayerwidget.py b/python/PyQt6/gui/auto_additions/qgssymbollayerwidget.py index c13cebff625ca..ed1060b48c095 100644 --- a/python/PyQt6/gui/auto_additions/qgssymbollayerwidget.py +++ b/python/PyQt6/gui/auto_additions/qgssymbollayerwidget.py @@ -24,6 +24,7 @@ QgsRandomMarkerFillSymbolLayerWidget.create = staticmethod(QgsRandomMarkerFillSymbolLayerWidget.create) QgsFontMarkerSymbolLayerWidget.create = staticmethod(QgsFontMarkerSymbolLayerWidget.create) QgsCentroidFillSymbolLayerWidget.create = staticmethod(QgsCentroidFillSymbolLayerWidget.create) +QgsLinearReferencingSymbolLayerWidget.create = staticmethod(QgsLinearReferencingSymbolLayerWidget.create) QgsGeometryGeneratorSymbolLayerWidget.create = staticmethod(QgsGeometryGeneratorSymbolLayerWidget.create) try: QgsSymbolLayerWidget.__group__ = ['symbology'] @@ -113,6 +114,10 @@ QgsCentroidFillSymbolLayerWidget.__group__ = ['symbology'] except NameError: pass +try: + QgsLinearReferencingSymbolLayerWidget.__group__ = ['symbology'] +except NameError: + pass try: QgsGeometryGeneratorSymbolLayerWidget.__group__ = ['symbology'] except NameError: diff --git a/python/PyQt6/gui/auto_generated/symbology/qgssymbollayerwidget.sip.in b/python/PyQt6/gui/auto_generated/symbology/qgssymbollayerwidget.sip.in index 5f98f173c8191..4efa85fd9cf6c 100644 --- a/python/PyQt6/gui/auto_generated/symbology/qgssymbollayerwidget.sip.in +++ b/python/PyQt6/gui/auto_generated/symbology/qgssymbollayerwidget.sip.in @@ -990,6 +990,46 @@ Creates a new QgsCentroidFillSymbolLayerWidget. + +class QgsLinearReferencingSymbolLayerWidget : QgsSymbolLayerWidget +{ +%Docstring(signature="appended") +Widget for controlling the properties of a :py:class:`QgsLinearReferencingSymbolLayer`. + +.. versionadded:: 3.40 +%End + +%TypeHeaderCode +#include "qgssymbollayerwidget.h" +%End + public: + + QgsLinearReferencingSymbolLayerWidget( QgsVectorLayer *vl, QWidget *parent /TransferThis/ = 0 ); +%Docstring +Constructor for QgsLinearReferencingSymbolLayerWidget. +%End + + ~QgsLinearReferencingSymbolLayerWidget(); + + static QgsSymbolLayerWidget *create( QgsVectorLayer *vl ) /Factory/; +%Docstring +Creates a new QgsLinearReferencingSymbolLayerWidget. + +:param vl: associated vector layer +%End + + virtual void setSymbolLayer( QgsSymbolLayer *layer ); + + virtual QgsSymbolLayer *symbolLayer(); + + virtual void setContext( const QgsSymbolWidgetContext &context ); + + +}; + + + + class QgsGeometryGeneratorSymbolLayerWidget : QgsSymbolLayerWidget { diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index 20a3670393af6..de80d92cd82fa 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -2861,6 +2861,21 @@ Qgis.MarkerLinePlacement.baseClass = Qgis Qgis.MarkerLinePlacements.baseClass = Qgis MarkerLinePlacements = Qgis # dirty hack since SIP seems to introduce the flags in module +# monkey patching scoped based enum +Qgis.LinearReferencingPlacement.IntervalCartesian2D.__doc__ = "Place labels at regular intervals, using Cartesian distance calculations on a 2D plane" +Qgis.LinearReferencingPlacement.IntervalZ.__doc__ = "Place labels at regular intervals, linearly interpolated using Z values" +Qgis.LinearReferencingPlacement.IntervalM.__doc__ = "Place labels at regular intervals, linearly interpolated using M values" +Qgis.LinearReferencingPlacement.Vertex.__doc__ = "Place labels on every vertex in the line" +Qgis.LinearReferencingPlacement.__doc__ = "Defines how/where the labels should be placed in a linear referencing symbol layer.\n\n.. versionadded:: 3.40\n\n" + '* ``IntervalCartesian2D``: ' + Qgis.LinearReferencingPlacement.IntervalCartesian2D.__doc__ + '\n' + '* ``IntervalZ``: ' + Qgis.LinearReferencingPlacement.IntervalZ.__doc__ + '\n' + '* ``IntervalM``: ' + Qgis.LinearReferencingPlacement.IntervalM.__doc__ + '\n' + '* ``Vertex``: ' + Qgis.LinearReferencingPlacement.Vertex.__doc__ +# -- +Qgis.LinearReferencingPlacement.baseClass = Qgis +# monkey patching scoped based enum +Qgis.LinearReferencingLabelSource.CartesianDistance2D.__doc__ = "Distance along line, calculated using Cartesian calculations on a 2D plane." +Qgis.LinearReferencingLabelSource.Z.__doc__ = "Z values" +Qgis.LinearReferencingLabelSource.M.__doc__ = "M values" +Qgis.LinearReferencingLabelSource.__doc__ = "Defines what quantity to use for the labels shown in a linear referencing symbol layer.\n\n.. versionadded:: 3.40\n\n" + '* ``CartesianDistance2D``: ' + Qgis.LinearReferencingLabelSource.CartesianDistance2D.__doc__ + '\n' + '* ``Z``: ' + Qgis.LinearReferencingLabelSource.Z.__doc__ + '\n' + '* ``M``: ' + Qgis.LinearReferencingLabelSource.M.__doc__ +# -- +Qgis.LinearReferencingLabelSource.baseClass = Qgis QgsGradientFillSymbolLayer.GradientColorType = Qgis.GradientColorSource # monkey patching scoped based enum QgsGradientFillSymbolLayer.SimpleTwoColor = Qgis.GradientColorSource.SimpleTwoColor diff --git a/python/core/auto_additions/qgslinearreferencingsymbollayer.py b/python/core/auto_additions/qgslinearreferencingsymbollayer.py new file mode 100644 index 0000000000000..c876d0fe5649d --- /dev/null +++ b/python/core/auto_additions/qgslinearreferencingsymbollayer.py @@ -0,0 +1,6 @@ +# The following has been generated automatically from src/core/symbology/qgslinearreferencingsymbollayer.h +QgsLinearReferencingSymbolLayer.create = staticmethod(QgsLinearReferencingSymbolLayer.create) +try: + QgsLinearReferencingSymbolLayer.__group__ = ['symbology'] +except NameError: + pass diff --git a/python/core/auto_additions/qgssymbollayer.py b/python/core/auto_additions/qgssymbollayer.py index 4986de2a34d3a..e489b67767a54 100644 --- a/python/core/auto_additions/qgssymbollayer.py +++ b/python/core/auto_additions/qgssymbollayer.py @@ -280,7 +280,13 @@ QgsSymbolLayer.Property.PropertyLineClipping = QgsSymbolLayer.Property.LineClipping QgsSymbolLayer.PropertyLineClipping.is_monkey_patched = True QgsSymbolLayer.PropertyLineClipping.__doc__ = "Line clipping mode (since QGIS 3.24)" -QgsSymbolLayer.Property.__doc__ = "Data definable properties.\n\n" + '* ``PropertySize``: ' + QgsSymbolLayer.Property.Size.__doc__ + '\n' + '* ``PropertyAngle``: ' + QgsSymbolLayer.Property.Angle.__doc__ + '\n' + '* ``PropertyName``: ' + QgsSymbolLayer.Property.Name.__doc__ + '\n' + '* ``PropertyFillColor``: ' + QgsSymbolLayer.Property.FillColor.__doc__ + '\n' + '* ``PropertyStrokeColor``: ' + QgsSymbolLayer.Property.StrokeColor.__doc__ + '\n' + '* ``PropertyStrokeWidth``: ' + QgsSymbolLayer.Property.StrokeWidth.__doc__ + '\n' + '* ``PropertyStrokeStyle``: ' + QgsSymbolLayer.Property.StrokeStyle.__doc__ + '\n' + '* ``PropertyOffset``: ' + QgsSymbolLayer.Property.Offset.__doc__ + '\n' + '* ``PropertyCharacter``: ' + QgsSymbolLayer.Property.Character.__doc__ + '\n' + '* ``PropertyWidth``: ' + QgsSymbolLayer.Property.Width.__doc__ + '\n' + '* ``PropertyHeight``: ' + QgsSymbolLayer.Property.Height.__doc__ + '\n' + '* ``PropertyPreserveAspectRatio``: ' + QgsSymbolLayer.Property.PreserveAspectRatio.__doc__ + '\n' + '* ``PropertyFillStyle``: ' + QgsSymbolLayer.Property.FillStyle.__doc__ + '\n' + '* ``PropertyJoinStyle``: ' + QgsSymbolLayer.Property.JoinStyle.__doc__ + '\n' + '* ``PropertySecondaryColor``: ' + QgsSymbolLayer.Property.SecondaryColor.__doc__ + '\n' + '* ``PropertyLineAngle``: ' + QgsSymbolLayer.Property.LineAngle.__doc__ + '\n' + '* ``PropertyLineDistance``: ' + QgsSymbolLayer.Property.LineDistance.__doc__ + '\n' + '* ``PropertyGradientType``: ' + QgsSymbolLayer.Property.GradientType.__doc__ + '\n' + '* ``PropertyCoordinateMode``: ' + QgsSymbolLayer.Property.CoordinateMode.__doc__ + '\n' + '* ``PropertyGradientSpread``: ' + QgsSymbolLayer.Property.GradientSpread.__doc__ + '\n' + '* ``PropertyGradientReference1X``: ' + QgsSymbolLayer.Property.GradientReference1X.__doc__ + '\n' + '* ``PropertyGradientReference1Y``: ' + QgsSymbolLayer.Property.GradientReference1Y.__doc__ + '\n' + '* ``PropertyGradientReference2X``: ' + QgsSymbolLayer.Property.GradientReference2X.__doc__ + '\n' + '* ``PropertyGradientReference2Y``: ' + QgsSymbolLayer.Property.GradientReference2Y.__doc__ + '\n' + '* ``PropertyGradientReference1IsCentroid``: ' + QgsSymbolLayer.Property.GradientReference1IsCentroid.__doc__ + '\n' + '* ``PropertyGradientReference2IsCentroid``: ' + QgsSymbolLayer.Property.GradientReference2IsCentroid.__doc__ + '\n' + '* ``PropertyBlurRadius``: ' + QgsSymbolLayer.Property.BlurRadius.__doc__ + '\n' + '* ``PropertyShapeburstUseWholeShape``: ' + QgsSymbolLayer.Property.ShapeburstUseWholeShape.__doc__ + '\n' + '* ``PropertyShapeburstMaxDistance``: ' + QgsSymbolLayer.Property.ShapeburstMaxDistance.__doc__ + '\n' + '* ``PropertyShapeburstIgnoreRings``: ' + QgsSymbolLayer.Property.ShapeburstIgnoreRings.__doc__ + '\n' + '* ``PropertyFile``: ' + QgsSymbolLayer.Property.File.__doc__ + '\n' + '* ``PropertyDistanceX``: ' + QgsSymbolLayer.Property.DistanceX.__doc__ + '\n' + '* ``PropertyDistanceY``: ' + QgsSymbolLayer.Property.DistanceY.__doc__ + '\n' + '* ``PropertyDisplacementX``: ' + QgsSymbolLayer.Property.DisplacementX.__doc__ + '\n' + '* ``PropertyDisplacementY``: ' + QgsSymbolLayer.Property.DisplacementY.__doc__ + '\n' + '* ``PropertyOpacity``: ' + QgsSymbolLayer.Property.Opacity.__doc__ + '\n' + '* ``PropertyCustomDash``: ' + QgsSymbolLayer.Property.CustomDash.__doc__ + '\n' + '* ``PropertyCapStyle``: ' + QgsSymbolLayer.Property.CapStyle.__doc__ + '\n' + '* ``PropertyPlacement``: ' + QgsSymbolLayer.Property.Placement.__doc__ + '\n' + '* ``PropertyInterval``: ' + QgsSymbolLayer.Property.Interval.__doc__ + '\n' + '* ``PropertyOffsetAlongLine``: ' + QgsSymbolLayer.Property.OffsetAlongLine.__doc__ + '\n' + '* ``PropertyAverageAngleLength``: ' + QgsSymbolLayer.Property.AverageAngleLength.__doc__ + '\n' + '* ``PropertyHorizontalAnchor``: ' + QgsSymbolLayer.Property.HorizontalAnchor.__doc__ + '\n' + '* ``PropertyVerticalAnchor``: ' + QgsSymbolLayer.Property.VerticalAnchor.__doc__ + '\n' + '* ``PropertyLayerEnabled``: ' + QgsSymbolLayer.Property.LayerEnabled.__doc__ + '\n' + '* ``PropertyArrowWidth``: ' + QgsSymbolLayer.Property.ArrowWidth.__doc__ + '\n' + '* ``PropertyArrowStartWidth``: ' + QgsSymbolLayer.Property.ArrowStartWidth.__doc__ + '\n' + '* ``PropertyArrowHeadLength``: ' + QgsSymbolLayer.Property.ArrowHeadLength.__doc__ + '\n' + '* ``PropertyArrowHeadThickness``: ' + QgsSymbolLayer.Property.ArrowHeadThickness.__doc__ + '\n' + '* ``PropertyArrowHeadType``: ' + QgsSymbolLayer.Property.ArrowHeadType.__doc__ + '\n' + '* ``PropertyArrowType``: ' + QgsSymbolLayer.Property.ArrowType.__doc__ + '\n' + '* ``PropertyOffsetX``: ' + QgsSymbolLayer.Property.OffsetX.__doc__ + '\n' + '* ``PropertyOffsetY``: ' + QgsSymbolLayer.Property.OffsetY.__doc__ + '\n' + '* ``PropertyPointCount``: ' + QgsSymbolLayer.Property.PointCount.__doc__ + '\n' + '* ``PropertyRandomSeed``: ' + QgsSymbolLayer.Property.RandomSeed.__doc__ + '\n' + '* ``PropertyClipPoints``: ' + QgsSymbolLayer.Property.ClipPoints.__doc__ + '\n' + '* ``PropertyDensityArea``: ' + QgsSymbolLayer.Property.DensityArea.__doc__ + '\n' + '* ``PropertyFontFamily``: ' + QgsSymbolLayer.Property.FontFamily.__doc__ + '\n' + '* ``PropertyFontStyle``: ' + QgsSymbolLayer.Property.FontStyle.__doc__ + '\n' + '* ``PropertyDashPatternOffset``: ' + QgsSymbolLayer.Property.DashPatternOffset.__doc__ + '\n' + '* ``PropertyTrimStart``: ' + QgsSymbolLayer.Property.TrimStart.__doc__ + '\n' + '* ``PropertyTrimEnd``: ' + QgsSymbolLayer.Property.TrimEnd.__doc__ + '\n' + '* ``PropertyLineStartWidthValue``: ' + QgsSymbolLayer.Property.LineStartWidthValue.__doc__ + '\n' + '* ``PropertyLineEndWidthValue``: ' + QgsSymbolLayer.Property.LineEndWidthValue.__doc__ + '\n' + '* ``PropertyLineStartColorValue``: ' + QgsSymbolLayer.Property.LineStartColorValue.__doc__ + '\n' + '* ``PropertyLineEndColorValue``: ' + QgsSymbolLayer.Property.LineEndColorValue.__doc__ + '\n' + '* ``PropertyMarkerClipping``: ' + QgsSymbolLayer.Property.MarkerClipping.__doc__ + '\n' + '* ``PropertyRandomOffsetX``: ' + QgsSymbolLayer.Property.RandomOffsetX.__doc__ + '\n' + '* ``PropertyRandomOffsetY``: ' + QgsSymbolLayer.Property.RandomOffsetY.__doc__ + '\n' + '* ``PropertyLineClipping``: ' + QgsSymbolLayer.Property.LineClipping.__doc__ +QgsSymbolLayer.SkipMultiples = QgsSymbolLayer.Property.SkipMultiples +QgsSymbolLayer.SkipMultiples.is_monkey_patched = True +QgsSymbolLayer.SkipMultiples.__doc__ = "Skip multiples of (since QGIS 3.40)" +QgsSymbolLayer.ShowMarker = QgsSymbolLayer.Property.ShowMarker +QgsSymbolLayer.ShowMarker.is_monkey_patched = True +QgsSymbolLayer.ShowMarker.__doc__ = "Show markers (since QGIS 3.40)" +QgsSymbolLayer.Property.__doc__ = "Data definable properties.\n\n" + '* ``PropertySize``: ' + QgsSymbolLayer.Property.Size.__doc__ + '\n' + '* ``PropertyAngle``: ' + QgsSymbolLayer.Property.Angle.__doc__ + '\n' + '* ``PropertyName``: ' + QgsSymbolLayer.Property.Name.__doc__ + '\n' + '* ``PropertyFillColor``: ' + QgsSymbolLayer.Property.FillColor.__doc__ + '\n' + '* ``PropertyStrokeColor``: ' + QgsSymbolLayer.Property.StrokeColor.__doc__ + '\n' + '* ``PropertyStrokeWidth``: ' + QgsSymbolLayer.Property.StrokeWidth.__doc__ + '\n' + '* ``PropertyStrokeStyle``: ' + QgsSymbolLayer.Property.StrokeStyle.__doc__ + '\n' + '* ``PropertyOffset``: ' + QgsSymbolLayer.Property.Offset.__doc__ + '\n' + '* ``PropertyCharacter``: ' + QgsSymbolLayer.Property.Character.__doc__ + '\n' + '* ``PropertyWidth``: ' + QgsSymbolLayer.Property.Width.__doc__ + '\n' + '* ``PropertyHeight``: ' + QgsSymbolLayer.Property.Height.__doc__ + '\n' + '* ``PropertyPreserveAspectRatio``: ' + QgsSymbolLayer.Property.PreserveAspectRatio.__doc__ + '\n' + '* ``PropertyFillStyle``: ' + QgsSymbolLayer.Property.FillStyle.__doc__ + '\n' + '* ``PropertyJoinStyle``: ' + QgsSymbolLayer.Property.JoinStyle.__doc__ + '\n' + '* ``PropertySecondaryColor``: ' + QgsSymbolLayer.Property.SecondaryColor.__doc__ + '\n' + '* ``PropertyLineAngle``: ' + QgsSymbolLayer.Property.LineAngle.__doc__ + '\n' + '* ``PropertyLineDistance``: ' + QgsSymbolLayer.Property.LineDistance.__doc__ + '\n' + '* ``PropertyGradientType``: ' + QgsSymbolLayer.Property.GradientType.__doc__ + '\n' + '* ``PropertyCoordinateMode``: ' + QgsSymbolLayer.Property.CoordinateMode.__doc__ + '\n' + '* ``PropertyGradientSpread``: ' + QgsSymbolLayer.Property.GradientSpread.__doc__ + '\n' + '* ``PropertyGradientReference1X``: ' + QgsSymbolLayer.Property.GradientReference1X.__doc__ + '\n' + '* ``PropertyGradientReference1Y``: ' + QgsSymbolLayer.Property.GradientReference1Y.__doc__ + '\n' + '* ``PropertyGradientReference2X``: ' + QgsSymbolLayer.Property.GradientReference2X.__doc__ + '\n' + '* ``PropertyGradientReference2Y``: ' + QgsSymbolLayer.Property.GradientReference2Y.__doc__ + '\n' + '* ``PropertyGradientReference1IsCentroid``: ' + QgsSymbolLayer.Property.GradientReference1IsCentroid.__doc__ + '\n' + '* ``PropertyGradientReference2IsCentroid``: ' + QgsSymbolLayer.Property.GradientReference2IsCentroid.__doc__ + '\n' + '* ``PropertyBlurRadius``: ' + QgsSymbolLayer.Property.BlurRadius.__doc__ + '\n' + '* ``PropertyShapeburstUseWholeShape``: ' + QgsSymbolLayer.Property.ShapeburstUseWholeShape.__doc__ + '\n' + '* ``PropertyShapeburstMaxDistance``: ' + QgsSymbolLayer.Property.ShapeburstMaxDistance.__doc__ + '\n' + '* ``PropertyShapeburstIgnoreRings``: ' + QgsSymbolLayer.Property.ShapeburstIgnoreRings.__doc__ + '\n' + '* ``PropertyFile``: ' + QgsSymbolLayer.Property.File.__doc__ + '\n' + '* ``PropertyDistanceX``: ' + QgsSymbolLayer.Property.DistanceX.__doc__ + '\n' + '* ``PropertyDistanceY``: ' + QgsSymbolLayer.Property.DistanceY.__doc__ + '\n' + '* ``PropertyDisplacementX``: ' + QgsSymbolLayer.Property.DisplacementX.__doc__ + '\n' + '* ``PropertyDisplacementY``: ' + QgsSymbolLayer.Property.DisplacementY.__doc__ + '\n' + '* ``PropertyOpacity``: ' + QgsSymbolLayer.Property.Opacity.__doc__ + '\n' + '* ``PropertyCustomDash``: ' + QgsSymbolLayer.Property.CustomDash.__doc__ + '\n' + '* ``PropertyCapStyle``: ' + QgsSymbolLayer.Property.CapStyle.__doc__ + '\n' + '* ``PropertyPlacement``: ' + QgsSymbolLayer.Property.Placement.__doc__ + '\n' + '* ``PropertyInterval``: ' + QgsSymbolLayer.Property.Interval.__doc__ + '\n' + '* ``PropertyOffsetAlongLine``: ' + QgsSymbolLayer.Property.OffsetAlongLine.__doc__ + '\n' + '* ``PropertyAverageAngleLength``: ' + QgsSymbolLayer.Property.AverageAngleLength.__doc__ + '\n' + '* ``PropertyHorizontalAnchor``: ' + QgsSymbolLayer.Property.HorizontalAnchor.__doc__ + '\n' + '* ``PropertyVerticalAnchor``: ' + QgsSymbolLayer.Property.VerticalAnchor.__doc__ + '\n' + '* ``PropertyLayerEnabled``: ' + QgsSymbolLayer.Property.LayerEnabled.__doc__ + '\n' + '* ``PropertyArrowWidth``: ' + QgsSymbolLayer.Property.ArrowWidth.__doc__ + '\n' + '* ``PropertyArrowStartWidth``: ' + QgsSymbolLayer.Property.ArrowStartWidth.__doc__ + '\n' + '* ``PropertyArrowHeadLength``: ' + QgsSymbolLayer.Property.ArrowHeadLength.__doc__ + '\n' + '* ``PropertyArrowHeadThickness``: ' + QgsSymbolLayer.Property.ArrowHeadThickness.__doc__ + '\n' + '* ``PropertyArrowHeadType``: ' + QgsSymbolLayer.Property.ArrowHeadType.__doc__ + '\n' + '* ``PropertyArrowType``: ' + QgsSymbolLayer.Property.ArrowType.__doc__ + '\n' + '* ``PropertyOffsetX``: ' + QgsSymbolLayer.Property.OffsetX.__doc__ + '\n' + '* ``PropertyOffsetY``: ' + QgsSymbolLayer.Property.OffsetY.__doc__ + '\n' + '* ``PropertyPointCount``: ' + QgsSymbolLayer.Property.PointCount.__doc__ + '\n' + '* ``PropertyRandomSeed``: ' + QgsSymbolLayer.Property.RandomSeed.__doc__ + '\n' + '* ``PropertyClipPoints``: ' + QgsSymbolLayer.Property.ClipPoints.__doc__ + '\n' + '* ``PropertyDensityArea``: ' + QgsSymbolLayer.Property.DensityArea.__doc__ + '\n' + '* ``PropertyFontFamily``: ' + QgsSymbolLayer.Property.FontFamily.__doc__ + '\n' + '* ``PropertyFontStyle``: ' + QgsSymbolLayer.Property.FontStyle.__doc__ + '\n' + '* ``PropertyDashPatternOffset``: ' + QgsSymbolLayer.Property.DashPatternOffset.__doc__ + '\n' + '* ``PropertyTrimStart``: ' + QgsSymbolLayer.Property.TrimStart.__doc__ + '\n' + '* ``PropertyTrimEnd``: ' + QgsSymbolLayer.Property.TrimEnd.__doc__ + '\n' + '* ``PropertyLineStartWidthValue``: ' + QgsSymbolLayer.Property.LineStartWidthValue.__doc__ + '\n' + '* ``PropertyLineEndWidthValue``: ' + QgsSymbolLayer.Property.LineEndWidthValue.__doc__ + '\n' + '* ``PropertyLineStartColorValue``: ' + QgsSymbolLayer.Property.LineStartColorValue.__doc__ + '\n' + '* ``PropertyLineEndColorValue``: ' + QgsSymbolLayer.Property.LineEndColorValue.__doc__ + '\n' + '* ``PropertyMarkerClipping``: ' + QgsSymbolLayer.Property.MarkerClipping.__doc__ + '\n' + '* ``PropertyRandomOffsetX``: ' + QgsSymbolLayer.Property.RandomOffsetX.__doc__ + '\n' + '* ``PropertyRandomOffsetY``: ' + QgsSymbolLayer.Property.RandomOffsetY.__doc__ + '\n' + '* ``PropertyLineClipping``: ' + QgsSymbolLayer.Property.LineClipping.__doc__ + '\n' + '* ``SkipMultiples``: ' + QgsSymbolLayer.Property.SkipMultiples.__doc__ + '\n' + '* ``ShowMarker``: ' + QgsSymbolLayer.Property.ShowMarker.__doc__ # -- QgsMarkerSymbolLayer._rotatedOffset = staticmethod(QgsMarkerSymbolLayer._rotatedOffset) try: diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index effc72c91ebdd..1c2170dc22c60 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -1628,6 +1628,21 @@ The development version typedef QFlags MarkerLinePlacements; + enum class LinearReferencingPlacement + { + IntervalCartesian2D, + IntervalZ, + IntervalM, + Vertex, + }; + + enum class LinearReferencingLabelSource + { + CartesianDistance2D, + Z, + M, + }; + enum class GradientColorSource { SimpleTwoColor, diff --git a/python/core/auto_generated/symbology/qgslinearreferencingsymbollayer.sip.in b/python/core/auto_generated/symbology/qgslinearreferencingsymbollayer.sip.in new file mode 100644 index 0000000000000..9bd10e6ba0062 --- /dev/null +++ b/python/core/auto_generated/symbology/qgslinearreferencingsymbollayer.sip.in @@ -0,0 +1,326 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/symbology/qgslinearreferencingsymbollayer.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.py again * + ************************************************************************/ + + + +class QgsLinearReferencingSymbolLayer : QgsLineSymbolLayer +{ +%Docstring(signature="appended") +Line symbol layer used for decorating accordingly to linear referencing. + +This symbol layer type allows placing text labels at regular intervals along +a line (or at positions corresponding to existing vertices). Positions +can be calculated using Cartesian distances, or interpolated from z or m values. + +.. versionadded:: 3.40 +%End + +%TypeHeaderCode +#include "qgslinearreferencingsymbollayer.h" +%End + public: + QgsLinearReferencingSymbolLayer(); + ~QgsLinearReferencingSymbolLayer(); + + static QgsSymbolLayer *create( const QVariantMap &properties = QVariantMap() ) /Factory/; +%Docstring +Creates a new QgsLinearReferencingSymbolLayer, using the specified ``properties``. + +The caller takes ownership of the returned object. +%End + + virtual QgsLinearReferencingSymbolLayer *clone() const /Factory/; + + virtual QVariantMap properties() const; + + virtual QString layerType() const; + + virtual Qgis::SymbolLayerFlags flags() const; + + virtual QgsSymbol *subSymbol(); + + virtual bool setSubSymbol( QgsSymbol *symbol /Transfer/ ); + + virtual void startRender( QgsSymbolRenderContext &context ); + + virtual void stopRender( QgsSymbolRenderContext &context ); + + virtual void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ); + + + QgsTextFormat textFormat() const; +%Docstring +Returns the text format used to render the layer. + +.. seealso:: :py:func:`setTextFormat` +%End + + void setTextFormat( const QgsTextFormat &format ); +%Docstring +Sets the text ``format`` used to render the layer. + +.. seealso:: :py:func:`textFormat` +%End + + QgsNumericFormat *numericFormat() const; +%Docstring +Returns the numeric format used to format the labels for the layer. + +.. seealso:: :py:func:`setNumericFormat` +%End + + void setNumericFormat( QgsNumericFormat *format /Transfer/ ); +%Docstring +Sets the numeric ``format`` used to format the labels for the layer. + +Ownership of ``format`` is transferred to the layer. + +.. seealso:: :py:func:`numericFormat` +%End + + double interval() const; +%Docstring +Returns the interval between labels. + +Units are always in the original layer CRS units. + +.. seealso:: :py:func:`setInterval` +%End + + void setInterval( double interval ); +%Docstring +Sets the ``interval`` between labels. + +Units are always in the original layer CRS units. + +.. seealso:: :py:func:`setInterval` +%End + + double skipMultiplesOf() const; +%Docstring +Returns the multiple distance to skip labels for. + +If this value is non-zero, then any labels which are integer multiples of the returned +value will be skipped. This allows creation of advanced referencing styles where a single +:py:class:`QgsSymbol` has multiple :py:class:`QgsLinearReferencingSymbolLayer` symbol layers, eg allowing +labeling every 100 in a normal font and every 1000 in a bold, larger font. + +.. seealso:: :py:func:`setSkipMultiplesOf` +%End + + void setSkipMultiplesOf( double multiple ); +%Docstring +Sets the ``multiple`` distance to skip labels for. + +If this value is non-zero, then any labels which are integer multiples of the returned +value will be skipped. This allows creation of advanced referencing styles where a single +:py:class:`QgsSymbol` has multiple :py:class:`QgsLinearReferencingSymbolLayer` symbol layers, eg allowing +labeling every 100 in a normal font and every 1000 in a bold, larger font. + +.. seealso:: :py:func:`skipMultiplesOf` +%End + + bool rotateLabels() const; +%Docstring +Returns ``True`` if the labels and symbols are to be rotated to match their line segment orientation. + +.. seealso:: :py:func:`setRotateLabels` +%End + + void setRotateLabels( bool rotate ); +%Docstring +Sets whether the labels and symbols should be rotated to match their line segment orientation. + +.. seealso:: :py:func:`rotateLabels` +%End + + QPointF labelOffset() const; +%Docstring +Returns the offset between the line and linear referencing labels. + +The unit for the offset is retrievable via :py:func:`~QgsLinearReferencingSymbolLayer.labelOffsetUnit`. + +.. seealso:: :py:func:`setLabelOffset` + +.. seealso:: :py:func:`labelOffsetUnit` +%End + + void setLabelOffset( const QPointF &offset ); +%Docstring +Sets the ``offset`` between the line and linear referencing labels. + +The unit for the offset is set via :py:func:`~QgsLinearReferencingSymbolLayer.setLabelOffsetUnit`. + +.. seealso:: :py:func:`labelOffset` + +.. seealso:: :py:func:`setLabelOffsetUnit` +%End + + Qgis::RenderUnit labelOffsetUnit() const; +%Docstring +Returns the unit used for the offset between the line and linear referencing labels. + +.. seealso:: :py:func:`setLabelOffsetUnit` + +.. seealso:: :py:func:`labelOffset` +%End + + void setLabelOffsetUnit( Qgis::RenderUnit unit ); +%Docstring +Sets the ``unit`` used for the offset between the line and linear referencing labels. + +.. seealso:: :py:func:`labelOffsetUnit` + +.. seealso:: :py:func:`setLabelOffset` +%End + + const QgsMapUnitScale &labelOffsetMapUnitScale() const; +%Docstring +Returns the map unit scale used for calculating the offset between the line and linear referencing labels. + +.. seealso:: :py:func:`setLabelOffsetMapUnitScale` +%End + + void setLabelOffsetMapUnitScale( const QgsMapUnitScale &scale ); +%Docstring +Sets the map unit ``scale`` used for calculating the offset between the line and linear referencing labels. + +.. seealso:: :py:func:`labelOffsetMapUnitScale` +%End + + bool showMarker() const; +%Docstring +Returns ``True`` if a marker symbol should be shown corresponding to the labeled point on line. + +The marker symbol is set using :py:func:`~QgsLinearReferencingSymbolLayer.setSubSymbol` + +.. seealso:: :py:func:`setShowMarker` +%End + + void setShowMarker( bool show ); +%Docstring +Sets whether a marker symbol should be shown corresponding to the labeled point on line. + +The marker symbol is set using :py:func:`~QgsLinearReferencingSymbolLayer.setSubSymbol` + +.. seealso:: :py:func:`showMarker` +%End + + Qgis::LinearReferencingPlacement placement() const; +%Docstring +Returns the placement mode for the labels. + +.. seealso:: :py:func:`setPlacement` +%End + + void setPlacement( Qgis::LinearReferencingPlacement placement ); +%Docstring +Sets the ``placement`` mode for the labels. + +.. seealso:: :py:func:`placement` +%End + + Qgis::LinearReferencingLabelSource labelSource() const; +%Docstring +Returns the label source, which dictates what quantity to use for the labels shown. + +.. seealso:: :py:func:`setLabelSource` +%End + + void setLabelSource( Qgis::LinearReferencingLabelSource source ); +%Docstring +Sets the label ``source``, which dictates what quantity to use for the labels shown. + +.. seealso:: :py:func:`labelSource` +%End + + double averageAngleLength() const; +%Docstring +Returns the length of line over which the line's direction is averaged when +calculating individual label angles. Longer lengths smooth out angles from jagged lines to a greater extent. + +Units are retrieved through :py:func:`~QgsLinearReferencingSymbolLayer.averageAngleUnit` + +.. seealso:: :py:func:`setAverageAngleLength` + +.. seealso:: :py:func:`averageAngleUnit` + +.. seealso:: :py:func:`averageAngleMapUnitScale` +%End + + void setAverageAngleLength( double length ); +%Docstring +Sets the ``length`` of line over which the line's direction is averaged when +calculating individual label angles. Longer lengths smooth out angles from jagged lines to a greater extent. + +Units are set through :py:func:`~QgsLinearReferencingSymbolLayer.setAverageAngleUnit` + +.. seealso:: :py:func:`averageAngleLength` + +.. seealso:: :py:func:`setAverageAngleUnit` + +.. seealso:: :py:func:`setAverageAngleMapUnitScale` +%End + + void setAverageAngleUnit( Qgis::RenderUnit unit ); +%Docstring +Sets the ``unit`` for the length over which the line's direction is averaged when +calculating individual label angles. + +.. seealso:: :py:func:`averageAngleUnit` + +.. seealso:: :py:func:`setAverageAngleLength` + +.. seealso:: :py:func:`setAverageAngleMapUnitScale` +%End + + Qgis::RenderUnit averageAngleUnit() const; +%Docstring +Returns the unit for the length over which the line's direction is averaged when +calculating individual label angles. + +.. seealso:: :py:func:`setAverageAngleUnit` + +.. seealso:: :py:func:`averageAngleLength` + +.. seealso:: :py:func:`averageAngleMapUnitScale` +%End + + void setAverageAngleMapUnitScale( const QgsMapUnitScale &scale ); +%Docstring +Sets the map unit ``scale`` for the length over which the line's direction is averaged when +calculating individual label angles. + +.. seealso:: :py:func:`averageAngleMapUnitScale` + +.. seealso:: :py:func:`setAverageAngleLength` + +.. seealso:: :py:func:`setAverageAngleUnit` +%End + + const QgsMapUnitScale &averageAngleMapUnitScale() const; +%Docstring +Returns the map unit scale for the length over which the line's direction is averaged when +calculating individual label angles. + +.. seealso:: :py:func:`setAverageAngleMapUnitScale` + +.. seealso:: :py:func:`averageAngleLength` + +.. seealso:: :py:func:`averageAngleUnit` +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/symbology/qgslinearreferencingsymbollayer.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.py again * + ************************************************************************/ diff --git a/python/core/auto_generated/symbology/qgssymbollayer.sip.in b/python/core/auto_generated/symbology/qgssymbollayer.sip.in index 88eeb881c7236..a5de52ff9a136 100644 --- a/python/core/auto_generated/symbology/qgssymbollayer.sip.in +++ b/python/core/auto_generated/symbology/qgssymbollayer.sip.in @@ -60,6 +60,8 @@ class QgsSymbolLayer sipType = sipType_QgsRasterLineSymbolLayer; else if ( sipCpp->layerType() == "Lineburst" ) sipType = sipType_QgsLineburstSymbolLayer; + else if ( sipCpp->layerType() == "LinearReferencing" ) + sipType = sipType_QgsLinearReferencingSymbolLayer; else sipType = sipType_QgsLineSymbolLayer; break; @@ -167,6 +169,8 @@ class QgsSymbolLayer RandomOffsetX, RandomOffsetY, LineClipping, + SkipMultiples, + ShowMarker, }; static const QgsPropertiesDefinition &propertyDefinitions(); diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 0b01ea61216c8..3e7db7ae6645c 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -689,6 +689,7 @@ %Include auto_generated/symbology/qgsinterpolatedlinerenderer.sip %Include auto_generated/symbology/qgsinvertedpolygonrenderer.sip %Include auto_generated/symbology/qgslegendsymbolitem.sip +%Include auto_generated/symbology/qgslinearreferencingsymbollayer.sip %Include auto_generated/symbology/qgslinesymbol.sip %Include auto_generated/symbology/qgslinesymbollayer.sip %Include auto_generated/symbology/qgsmapinfosymbolconverter.sip diff --git a/python/gui/auto_additions/qgssymbollayerwidget.py b/python/gui/auto_additions/qgssymbollayerwidget.py index c13cebff625ca..ed1060b48c095 100644 --- a/python/gui/auto_additions/qgssymbollayerwidget.py +++ b/python/gui/auto_additions/qgssymbollayerwidget.py @@ -24,6 +24,7 @@ QgsRandomMarkerFillSymbolLayerWidget.create = staticmethod(QgsRandomMarkerFillSymbolLayerWidget.create) QgsFontMarkerSymbolLayerWidget.create = staticmethod(QgsFontMarkerSymbolLayerWidget.create) QgsCentroidFillSymbolLayerWidget.create = staticmethod(QgsCentroidFillSymbolLayerWidget.create) +QgsLinearReferencingSymbolLayerWidget.create = staticmethod(QgsLinearReferencingSymbolLayerWidget.create) QgsGeometryGeneratorSymbolLayerWidget.create = staticmethod(QgsGeometryGeneratorSymbolLayerWidget.create) try: QgsSymbolLayerWidget.__group__ = ['symbology'] @@ -113,6 +114,10 @@ QgsCentroidFillSymbolLayerWidget.__group__ = ['symbology'] except NameError: pass +try: + QgsLinearReferencingSymbolLayerWidget.__group__ = ['symbology'] +except NameError: + pass try: QgsGeometryGeneratorSymbolLayerWidget.__group__ = ['symbology'] except NameError: diff --git a/python/gui/auto_generated/symbology/qgssymbollayerwidget.sip.in b/python/gui/auto_generated/symbology/qgssymbollayerwidget.sip.in index 5f98f173c8191..4efa85fd9cf6c 100644 --- a/python/gui/auto_generated/symbology/qgssymbollayerwidget.sip.in +++ b/python/gui/auto_generated/symbology/qgssymbollayerwidget.sip.in @@ -990,6 +990,46 @@ Creates a new QgsCentroidFillSymbolLayerWidget. + +class QgsLinearReferencingSymbolLayerWidget : QgsSymbolLayerWidget +{ +%Docstring(signature="appended") +Widget for controlling the properties of a :py:class:`QgsLinearReferencingSymbolLayer`. + +.. versionadded:: 3.40 +%End + +%TypeHeaderCode +#include "qgssymbollayerwidget.h" +%End + public: + + QgsLinearReferencingSymbolLayerWidget( QgsVectorLayer *vl, QWidget *parent /TransferThis/ = 0 ); +%Docstring +Constructor for QgsLinearReferencingSymbolLayerWidget. +%End + + ~QgsLinearReferencingSymbolLayerWidget(); + + static QgsSymbolLayerWidget *create( QgsVectorLayer *vl ) /Factory/; +%Docstring +Creates a new QgsLinearReferencingSymbolLayerWidget. + +:param vl: associated vector layer +%End + + virtual void setSymbolLayer( QgsSymbolLayer *layer ); + + virtual QgsSymbolLayer *symbolLayer(); + + virtual void setContext( const QgsSymbolWidgetContext &context ); + + +}; + + + + class QgsGeometryGeneratorSymbolLayerWidget : QgsSymbolLayerWidget { diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index fecf4fb64dbdb..21c09ef5c146b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -107,6 +107,7 @@ set(QGIS_CORE_SRCS symbology/qgsinterpolatedlinerenderer.cpp symbology/qgsinvertedpolygonrenderer.cpp symbology/qgslegendsymbolitem.cpp + symbology/qgslinearreferencingsymbollayer.cpp symbology/qgslinesymbol.cpp symbology/qgslinesymbollayer.cpp symbology/qgsmapinfosymbolconverter.cpp @@ -1936,6 +1937,7 @@ set(QGIS_CORE_HDRS symbology/qgsinterpolatedlinerenderer.h symbology/qgsinvertedpolygonrenderer.h symbology/qgslegendsymbolitem.h + symbology/qgslinearreferencingsymbollayer.h symbology/qgslinesymbol.h symbology/qgslinesymbollayer.h symbology/qgsmapinfosymbolconverter.h diff --git a/src/core/qgis.h b/src/core/qgis.h index 506b69e284c41..337671e20b540 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -2791,6 +2791,33 @@ class CORE_EXPORT Qgis Q_DECLARE_FLAGS( MarkerLinePlacements, MarkerLinePlacement ) Q_FLAG( MarkerLinePlacements ) + /** + * Defines how/where the labels should be placed in a linear referencing symbol layer. + * + * \since QGIS 3.40 + */ + enum class LinearReferencingPlacement : int SIP_ENUM_BASETYPE( IntFlag ) + { + IntervalCartesian2D = 1 << 0, //!< Place labels at regular intervals, using Cartesian distance calculations on a 2D plane + IntervalZ = 1 << 1, //!< Place labels at regular intervals, linearly interpolated using Z values + IntervalM = 1 << 2, //!< Place labels at regular intervals, linearly interpolated using M values + Vertex = 1 << 3, //!< Place labels on every vertex in the line + }; + Q_ENUM( LinearReferencingPlacement ) + + /** + * Defines what quantity to use for the labels shown in a linear referencing symbol layer. + * + * \since QGIS 3.40 + */ + enum class LinearReferencingLabelSource : int + { + CartesianDistance2D, //!< Distance along line, calculated using Cartesian calculations on a 2D plane. + Z, //!< Z values + M, //!< M values + }; + Q_ENUM( LinearReferencingLabelSource ) + /** * Gradient color sources. * diff --git a/src/core/symbology/qgslinearreferencingsymbollayer.cpp b/src/core/symbology/qgslinearreferencingsymbollayer.cpp new file mode 100644 index 0000000000000..3d64ff1b850aa --- /dev/null +++ b/src/core/symbology/qgslinearreferencingsymbollayer.cpp @@ -0,0 +1,1061 @@ +/*************************************************************************** + qgslinearreferencingsymbollayer.h + --------------------- + begin : August 2024 + copyright : (C) 2024 by Nyall Dawson + email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgslinearreferencingsymbollayer.h" +#include "qgsrendercontext.h" +#include "qgstextrenderer.h" +#include "qgslinestring.h" +#include "qgspolygon.h" +#include "qgsmarkersymbol.h" +#include "qgsnumericformatregistry.h" +#include "qgsapplication.h" +#include "qgsbasicnumericformat.h" +#include "qgsgeometryutils.h" +#include "qgsunittypes.h" +#include "qgssymbollayerutils.h" + +QgsLinearReferencingSymbolLayer::QgsLinearReferencingSymbolLayer() + : QgsLineSymbolLayer() +{ + mNumericFormat = std::make_unique< QgsBasicNumericFormat >(); +} + +QgsLinearReferencingSymbolLayer::~QgsLinearReferencingSymbolLayer() = default; + +QgsSymbolLayer *QgsLinearReferencingSymbolLayer::create( const QVariantMap &properties ) +{ + std::unique_ptr< QgsLinearReferencingSymbolLayer > res = std::make_unique< QgsLinearReferencingSymbolLayer >(); + res->setPlacement( qgsEnumKeyToValue( properties.value( QStringLiteral( "placement" ) ).toString(), Qgis::LinearReferencingPlacement::IntervalCartesian2D ) ); + res->setLabelSource( qgsEnumKeyToValue( properties.value( QStringLiteral( "source" ) ).toString(), Qgis::LinearReferencingLabelSource::CartesianDistance2D ) ); + bool ok = false; + const double interval = properties.value( QStringLiteral( "interval" ) ).toDouble( &ok ); + if ( ok ) + res->setInterval( interval ); + const double skipMultiples = properties.value( QStringLiteral( "skip_multiples" ) ).toDouble( &ok ); + if ( ok ) + res->setSkipMultiplesOf( skipMultiples ); + res->setRotateLabels( properties.value( QStringLiteral( "rotate" ), true ).toBool() ); + res->setShowMarker( properties.value( QStringLiteral( "show_marker" ), false ).toBool() ); + + // it's impossible to get the project's path resolver here :( + // TODO QGIS 4.0 -- force use of QgsReadWriteContext in create methods + QgsReadWriteContext rwContext; + //rwContext.setPathResolver( QgsProject::instance()->pathResolver() ); + + const QString textFormatXml = properties.value( QStringLiteral( "text_format" ) ).toString(); + if ( !textFormatXml.isEmpty() ) + { + QDomDocument doc; + QDomElement elem; + doc.setContent( textFormatXml ); + elem = doc.documentElement(); + + QgsTextFormat textFormat; + textFormat.readXml( elem, rwContext ); + res->setTextFormat( textFormat ); + } + + const QString numericFormatXml = properties.value( QStringLiteral( "numeric_format" ) ).toString(); + if ( !numericFormatXml.isEmpty() ) + { + QDomDocument doc; + doc.setContent( numericFormatXml ); + res->setNumericFormat( QgsApplication::numericFormatRegistry()->createFromXml( doc.documentElement(), rwContext ) ); + } + + if ( properties.contains( QStringLiteral( "label_offset" ) ) ) + { + res->setLabelOffset( QgsSymbolLayerUtils::decodePoint( properties[QStringLiteral( "label_offset" )].toString() ) ); + } + if ( properties.contains( QStringLiteral( "label_offset_unit" ) ) ) + { + res->setLabelOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "label_offset_unit" )].toString() ) ); + } + if ( properties.contains( ( QStringLiteral( "label_offset_map_unit_scale" ) ) ) ) + { + res->setLabelOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "label_offset_map_unit_scale" )].toString() ) ); + } + if ( properties.contains( QStringLiteral( "average_angle_length" ) ) ) + { + res->setAverageAngleLength( properties[QStringLiteral( "average_angle_length" )].toDouble() ); + } + if ( properties.contains( QStringLiteral( "average_angle_unit" ) ) ) + { + res->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "average_angle_unit" )].toString() ) ); + } + if ( properties.contains( ( QStringLiteral( "average_angle_map_unit_scale" ) ) ) ) + { + res->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "average_angle_map_unit_scale" )].toString() ) ); + } + + return res.release(); +} + +QgsLinearReferencingSymbolLayer *QgsLinearReferencingSymbolLayer::clone() const +{ + std::unique_ptr< QgsLinearReferencingSymbolLayer > res = std::make_unique< QgsLinearReferencingSymbolLayer >(); + res->setPlacement( mPlacement ); + res->setLabelSource( mLabelSource ); + res->setInterval( mInterval ); + res->setSkipMultiplesOf( mSkipMultiplesOf ); + res->setRotateLabels( mRotateLabels ); + res->setLabelOffset( mLabelOffset ); + res->setLabelOffsetUnit( mLabelOffsetUnit ); + res->setLabelOffsetMapUnitScale( mLabelOffsetMapUnitScale ); + res->setShowMarker( mShowMarker ); + res->setAverageAngleLength( mAverageAngleLength ); + res->setAverageAngleUnit( mAverageAngleLengthUnit ); + res->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale ); + + res->mTextFormat = mTextFormat; + res->mMarkerSymbol.reset( mMarkerSymbol ? mMarkerSymbol->clone() : nullptr ); + if ( mNumericFormat ) + res->mNumericFormat.reset( mNumericFormat->clone() ); + + copyDataDefinedProperties( res.get() ); + copyPaintEffect( res.get() ); + + return res.release(); +} + +QVariantMap QgsLinearReferencingSymbolLayer::properties() const +{ + QDomDocument textFormatDoc; + // it's impossible to get the project's path resolver here :( + // TODO QGIS 4.0 -- force use of QgsReadWriteContext in properties methods + QgsReadWriteContext rwContext; + // rwContext.setPathResolver( QgsProject::instance()->pathResolver() ); + const QDomElement textElem = mTextFormat.writeXml( textFormatDoc, rwContext ); + textFormatDoc.appendChild( textElem ); + + QDomDocument numericFormatDoc; + QDomElement numericFormatElem = numericFormatDoc.createElement( QStringLiteral( "numericFormat" ) ); + mNumericFormat->writeXml( numericFormatElem, numericFormatDoc, rwContext ); + numericFormatDoc.appendChild( numericFormatElem ); + + QVariantMap res + { + { + QStringLiteral( "placement" ), qgsEnumValueToKey( mPlacement ) + }, + { + QStringLiteral( "source" ), qgsEnumValueToKey( mLabelSource ) + }, + { + QStringLiteral( "interval" ), mInterval + }, + { + QStringLiteral( "rotate" ), mRotateLabels + }, + { + QStringLiteral( "show_marker" ), mShowMarker + }, + { + QStringLiteral( "text_format" ), textFormatDoc.toString() + }, + { + QStringLiteral( "numeric_format" ), numericFormatDoc.toString() + }, + { + QStringLiteral( "label_offset" ), QgsSymbolLayerUtils::encodePoint( mLabelOffset ) + }, + { + QStringLiteral( "label_offset_unit" ), QgsUnitTypes::encodeUnit( mLabelOffsetUnit ) + }, + { + QStringLiteral( "label_offset_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mLabelOffsetMapUnitScale ) + }, + { + QStringLiteral( "average_angle_length" ), mAverageAngleLength + }, + { + QStringLiteral( "average_angle_unit" ), QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit ) + }, + { + QStringLiteral( "average_angle_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale ) + }, + }; + + if ( mSkipMultiplesOf >= 0 ) + { + res.insert( QStringLiteral( "skip_multiples" ), mSkipMultiplesOf ); + } + + return res; +} + +QString QgsLinearReferencingSymbolLayer::layerType() const +{ + return QStringLiteral( "LinearReferencing" ); +} + +Qgis::SymbolLayerFlags QgsLinearReferencingSymbolLayer::flags() const +{ + return Qgis::SymbolLayerFlag::DisableFeatureClipping; +} + +QgsSymbol *QgsLinearReferencingSymbolLayer::subSymbol() +{ + return mShowMarker ? mMarkerSymbol.get() : nullptr; +} + +bool QgsLinearReferencingSymbolLayer::setSubSymbol( QgsSymbol *symbol ) +{ + if ( symbol && symbol->type() == Qgis::SymbolType::Marker ) + { + mMarkerSymbol.reset( qgis::down_cast( symbol ) ); + return true; + } + delete symbol; + return false; +} + +void QgsLinearReferencingSymbolLayer::startRender( QgsSymbolRenderContext &context ) +{ + if ( mMarkerSymbol ) + { + Qgis::SymbolRenderHints hints = mMarkerSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol; + if ( mRotateLabels ) + hints |= Qgis::SymbolRenderHint::DynamicRotation; + mMarkerSymbol->setRenderHints( hints ); + + mMarkerSymbol->startRender( context.renderContext(), context.fields() ); + } +} + +void QgsLinearReferencingSymbolLayer::stopRender( QgsSymbolRenderContext &context ) +{ + if ( mMarkerSymbol ) + { + mMarkerSymbol->stopRender( context.renderContext() ); + } +} + +void QgsLinearReferencingSymbolLayer::renderGeometryPart( QgsSymbolRenderContext &context, const QgsAbstractGeometry *geometry, double labelOffsetPainterUnitsX, double labelOffsetPainterUnitsY, double skipMultiples, double averageAngleDistancePainterUnits, bool showMarker ) +{ + if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( geometry ) ) + { + renderLineString( context, line, labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker ); + } + else if ( const QgsPolygon *polygon = qgsgeometry_cast< const QgsPolygon * >( geometry ) ) + { + renderLineString( context, qgsgeometry_cast< const QgsLineString *>( polygon->exteriorRing() ), labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker ); + for ( int i = 0; i < polygon->numInteriorRings(); ++i ) + { + renderLineString( context, qgsgeometry_cast< const QgsLineString *>( polygon->interiorRing( i ) ), labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker ); + } + } +} + +void QgsLinearReferencingSymbolLayer::renderLineString( QgsSymbolRenderContext &context, const QgsLineString *line, double labelOffsetPainterUnitsX, double labelOffsetPainterUnitsY, double skipMultiples, double averageAngleDistancePainterUnits, bool showMarker ) +{ + if ( !line ) + return; + + switch ( mPlacement ) + { + case Qgis::LinearReferencingPlacement::IntervalCartesian2D: + case Qgis::LinearReferencingPlacement::IntervalZ: + case Qgis::LinearReferencingPlacement::IntervalM: + renderPolylineInterval( line, context, skipMultiples, QPointF( labelOffsetPainterUnitsX, labelOffsetPainterUnitsY ), averageAngleDistancePainterUnits, showMarker ); + break; + + case Qgis::LinearReferencingPlacement::Vertex: + renderPolylineVertex( line, context, skipMultiples, QPointF( labelOffsetPainterUnitsX, labelOffsetPainterUnitsY ), averageAngleDistancePainterUnits, showMarker ); + break; + } +} + +void QgsLinearReferencingSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) +{ + QPainter *p = context.renderContext().painter(); + if ( !p ) + { + return; + } + + double skipMultiples = mSkipMultiplesOf; + if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::SkipMultiples ) ) + { + context.setOriginalValueVariable( mSkipMultiplesOf ); + skipMultiples = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::SkipMultiples, context.renderContext().expressionContext(), mSkipMultiplesOf ); + } + + double labelOffsetX = mLabelOffset.x(); + double labelOffsetY = mLabelOffset.y(); + + double averageOver = mAverageAngleLength; + if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::AverageAngleLength ) ) + { + context.setOriginalValueVariable( mAverageAngleLength ); + averageOver = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::AverageAngleLength, context.renderContext().expressionContext(), mAverageAngleLength ); + } + + bool showMarker = mShowMarker; + if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::ShowMarker ) ) + { + context.setOriginalValueVariable( showMarker ); + showMarker = mDataDefinedProperties.valueAsBool( QgsSymbolLayer::Property::ShowMarker, context.renderContext().expressionContext(), mShowMarker ); + } + + const double labelOffsetPainterUnitsX = context.renderContext().convertToPainterUnits( labelOffsetX, mLabelOffsetUnit, mLabelOffsetMapUnitScale ); + const double labelOffsetPainterUnitsY = context.renderContext().convertToPainterUnits( labelOffsetY, mLabelOffsetUnit, mLabelOffsetMapUnitScale ); + const double averageAngleDistancePainterUnits = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2; + + // TODO (maybe?): if we don't have an original geometry, convert points to linestring and scale distance to painter units? + // in reality this line type makes no sense for rendering non-real feature geometries... + ( void )points; + const QgsAbstractGeometry *geometry = context.renderContext().geometry(); + if ( !geometry ) + return; + + for ( auto partIt = geometry->const_parts_begin(); partIt != geometry->const_parts_end(); ++partIt ) + { + renderGeometryPart( context, *partIt, labelOffsetPainterUnitsX, labelOffsetPainterUnitsY, skipMultiples, averageAngleDistancePainterUnits, showMarker ); + } +} + +typedef std::function VisitPointFunction; +typedef std::function< void( const QgsLineString *, const QgsLineString *, bool, double, double, const VisitPointFunction & ) > VisitPointAtDistanceFunction; + +void visitPointsByRegularDistance( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint ) +{ + if ( distance < 0 ) + return; + + double distanceTraversed = 0; + const int totalPoints = line->numPoints(); + if ( totalPoints == 0 ) + return; + + const double *x = line->xData(); + const double *y = line->yData(); + const double *z = line->is3D() ? line->zData() : nullptr; + const double *m = line->isMeasure() ? line->mData() : nullptr; + + const double *xPainterUnits = linePainterUnits->xData(); + const double *yPainterUnits = linePainterUnits->yData(); + + double prevX = *x++; + double prevY = *y++; + double prevZ = z ? *z++ : 0.0; + double prevM = m ? *m++ : 0.0; + + double prevXPainterUnits = *xPainterUnits++; + double prevYPainterUnits = *yPainterUnits++; + + if ( qgsDoubleNear( distance, 0.0 ) ) + { + visitPoint( prevX, prevY, prevZ, prevM, 0, 0 ); + return; + } + + double pZ = std::numeric_limits::quiet_NaN(); + double pM = std::numeric_limits::quiet_NaN(); + double nextPointDistance = emitFirstPoint ? 0 : distance; + for ( int i = 1; i < totalPoints; ++i ) + { + double thisX = *x++; + double thisY = *y++; + double thisZ = z ? *z++ : 0.0; + double thisM = m ? *m++ : 0.0; + double thisXPainterUnits = *xPainterUnits++; + double thisYPainterUnits = *yPainterUnits++; + + double angle = std::fmod( QgsGeometryUtilsBase::azimuth( prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits ) + 360, 360 ); + if ( angle > 90 && angle < 270 ) + angle += 180; + + const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY ); + const double segmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( thisXPainterUnits, thisYPainterUnits, prevXPainterUnits, prevYPainterUnits ); + + while ( nextPointDistance < distanceTraversed + segmentLength || qgsDoubleNear( nextPointDistance, distanceTraversed + segmentLength ) ) + { + // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length + const double distanceToPoint = std::min( nextPointDistance - distanceTraversed, segmentLength ); + double pX, pY; + QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY, + z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &pZ : nullptr, + m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &pM : nullptr ); + + double calculatedAngle = angle; + if ( averageAngleLengthPainterUnits > 0 ) + { + const double targetPointFractionAlongSegment = distanceToPoint / segmentLength; + const double targetPointDistanceAlongSegment = targetPointFractionAlongSegment * segmentLengthPainterUnits; + + // track forward by averageAngleLengthPainterUnits + double painterDistRemaining = averageAngleLengthPainterUnits + targetPointDistanceAlongSegment; + double startAverageSegmentX = prevXPainterUnits; + double startAverageSegmentY = prevYPainterUnits; + double endAverageSegmentX = thisXPainterUnits; + double endAverageSegmentY = thisYPainterUnits; + double averagingSegmentLengthPainterUnits = segmentLengthPainterUnits; + const double *xAveragingData = xPainterUnits; + const double *yAveragingData = yPainterUnits; + + int j = i; + while ( painterDistRemaining > averagingSegmentLengthPainterUnits ) + { + if ( j >= totalPoints - 1 ) + break; + + painterDistRemaining -= averagingSegmentLengthPainterUnits; + startAverageSegmentX = endAverageSegmentX; + startAverageSegmentY = endAverageSegmentY; + + endAverageSegmentX = *xAveragingData++; + endAverageSegmentY = *yAveragingData++; + j++; + averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY ); + } + // fits on this same segment + double endAverageXPainterUnits; + double endAverageYPainterUnits; + if ( painterDistRemaining < averagingSegmentLengthPainterUnits ) + { + QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, endAverageXPainterUnits, endAverageYPainterUnits, + nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr ); + } + else + { + endAverageXPainterUnits = endAverageSegmentX; + endAverageYPainterUnits = endAverageSegmentY; + } + + // also track back by averageAngleLengthPainterUnits + j = i; + painterDistRemaining = ( segmentLengthPainterUnits - targetPointDistanceAlongSegment ) + averageAngleLengthPainterUnits; + startAverageSegmentX = thisXPainterUnits; + startAverageSegmentY = thisYPainterUnits; + endAverageSegmentX = prevXPainterUnits; + endAverageSegmentY = prevYPainterUnits; + averagingSegmentLengthPainterUnits = segmentLengthPainterUnits; + xAveragingData = xPainterUnits - 2; + yAveragingData = yPainterUnits - 2; + while ( painterDistRemaining > averagingSegmentLengthPainterUnits ) + { + if ( j < 1 ) + break; + + painterDistRemaining -= averagingSegmentLengthPainterUnits; + startAverageSegmentX = endAverageSegmentX; + startAverageSegmentY = endAverageSegmentY; + + endAverageSegmentX = *xAveragingData--; + endAverageSegmentY = *yAveragingData--; + j--; + averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY ); + } + // fits on this same segment + double startAverageXPainterUnits; + double startAverageYPainterUnits; + if ( painterDistRemaining < averagingSegmentLengthPainterUnits ) + { + QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, startAverageXPainterUnits, startAverageYPainterUnits, + nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr ); + } + else + { + startAverageXPainterUnits = endAverageSegmentX; + startAverageYPainterUnits = endAverageSegmentY; + } + + calculatedAngle = std::fmod( QgsGeometryUtilsBase::azimuth( startAverageXPainterUnits, startAverageYPainterUnits, endAverageXPainterUnits, endAverageYPainterUnits ) + 360, 360 ); + if ( calculatedAngle > 90 && calculatedAngle < 270 ) + calculatedAngle += 180; + } + + if ( !visitPoint( pX, pY, pZ, pM, nextPointDistance, calculatedAngle ) ) + return; + + nextPointDistance += distance; + } + + distanceTraversed += segmentLength; + prevX = thisX; + prevY = thisY; + prevZ = thisZ; + prevM = thisM; + prevXPainterUnits = thisXPainterUnits; + prevYPainterUnits = thisYPainterUnits; + } +} + +double interpolateValue( double a, double b, double fraction ) +{ + return a + ( b - a ) * fraction; +} + +void visitPointsByInterpolatedZM( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double step, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint, bool useZ ) +{ + if ( step < 0 ) + return; + + double distanceTraversed = 0; + const int totalPoints = line->numPoints(); + if ( totalPoints < 2 ) + return; + + const double *x = line->xData(); + const double *y = line->yData(); + const double *z = line->is3D() ? line->zData() : nullptr; + const double *m = line->isMeasure() ? line->mData() : nullptr; + + const double *xPainterUnits = linePainterUnits->xData(); + const double *yPainterUnits = linePainterUnits->yData(); + + double prevX = *x++; + double prevY = *y++; + double prevZ = z ? *z++ : 0.0; + double prevM = m ? *m++ : 0.0; + + double prevXPainterUnits = *xPainterUnits++; + double prevYPainterUnits = *yPainterUnits++; + + if ( qgsDoubleNear( step, 0.0 ) ) + { + visitPoint( prevX, prevY, prevZ, prevM, 0, 0 ); + return; + } + + double prevValue = useZ ? prevZ : prevM; + bool isFirstPoint = true; + for ( int i = 1; i < totalPoints; ++i ) + { + double thisX = *x++; + double thisY = *y++; + double thisZ = z ? *z++ : 0.0; + double thisM = m ? *m++ : 0.0; + const double thisValue = useZ ? thisZ : thisM; + double thisXPainterUnits = *xPainterUnits++; + double thisYPainterUnits = *yPainterUnits++; + + double angle = std::fmod( QgsGeometryUtilsBase::azimuth( prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits ) + 360, 360 ); + if ( angle > 90 && angle < 270 ) + angle += 180; + + const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY ); + const double segmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( thisXPainterUnits, thisYPainterUnits, prevXPainterUnits, prevYPainterUnits ); + + // direction for this segment + const int direction = ( thisValue > prevValue ) ? 1 : ( thisValue < prevValue ) ? -1 : 0; + if ( direction != 0 ) + { + // non-constant segment + double nextStepValue = direction > 0 ? std::ceil( prevValue / step ) * step + : std::floor( prevValue / step ) * step; + + while ( ( direction > 0 && ( nextStepValue <= thisValue || qgsDoubleNear( nextStepValue, thisValue ) ) ) || + ( direction < 0 && ( nextStepValue >= thisValue || qgsDoubleNear( nextStepValue, thisValue ) ) ) ) + { + const double targetPointFractionAlongSegment = ( nextStepValue - prevValue ) / ( thisValue - prevValue ); + const double targetPointDistanceAlongSegment = targetPointFractionAlongSegment * segmentLengthPainterUnits; + + double pX, pY; + QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, targetPointFractionAlongSegment * segmentLength, pX, pY ); + + const double pZ = useZ ? nextStepValue : interpolateValue( prevZ, thisZ, targetPointFractionAlongSegment ); + const double pM = useZ ? interpolateValue( prevM, thisM, targetPointFractionAlongSegment ) : nextStepValue; + + double calculatedAngle = angle; + if ( averageAngleLengthPainterUnits > 0 ) + { + // track forward by averageAngleLengthPainterUnits + double painterDistRemaining = averageAngleLengthPainterUnits + targetPointDistanceAlongSegment; + double startAverageSegmentX = prevXPainterUnits; + double startAverageSegmentY = prevYPainterUnits; + double endAverageSegmentX = thisXPainterUnits; + double endAverageSegmentY = thisYPainterUnits; + double averagingSegmentLengthPainterUnits = segmentLengthPainterUnits; + const double *xAveragingData = xPainterUnits; + const double *yAveragingData = yPainterUnits; + + int j = i; + while ( painterDistRemaining > averagingSegmentLengthPainterUnits ) + { + if ( j >= totalPoints - 1 ) + break; + + painterDistRemaining -= averagingSegmentLengthPainterUnits; + startAverageSegmentX = endAverageSegmentX; + startAverageSegmentY = endAverageSegmentY; + + endAverageSegmentX = *xAveragingData++; + endAverageSegmentY = *yAveragingData++; + j++; + averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY ); + } + // fits on this same segment + double endAverageXPainterUnits; + double endAverageYPainterUnits; + if ( painterDistRemaining < averagingSegmentLengthPainterUnits ) + { + QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, endAverageXPainterUnits, endAverageYPainterUnits, + nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr ); + } + else + { + endAverageXPainterUnits = endAverageSegmentX; + endAverageYPainterUnits = endAverageSegmentY; + } + + // also track back by averageAngleLengthPainterUnits + j = i; + painterDistRemaining = ( segmentLengthPainterUnits - targetPointDistanceAlongSegment ) + averageAngleLengthPainterUnits; + startAverageSegmentX = thisXPainterUnits; + startAverageSegmentY = thisYPainterUnits; + endAverageSegmentX = prevXPainterUnits; + endAverageSegmentY = prevYPainterUnits; + averagingSegmentLengthPainterUnits = segmentLengthPainterUnits; + xAveragingData = xPainterUnits - 2; + yAveragingData = yPainterUnits - 2; + while ( painterDistRemaining > averagingSegmentLengthPainterUnits ) + { + if ( j < 1 ) + break; + + painterDistRemaining -= averagingSegmentLengthPainterUnits; + startAverageSegmentX = endAverageSegmentX; + startAverageSegmentY = endAverageSegmentY; + + endAverageSegmentX = *xAveragingData--; + endAverageSegmentY = *yAveragingData--; + j--; + averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY ); + } + // fits on this same segment + double startAverageXPainterUnits; + double startAverageYPainterUnits; + if ( painterDistRemaining < averagingSegmentLengthPainterUnits ) + { + QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, startAverageXPainterUnits, startAverageYPainterUnits, + nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr ); + } + else + { + startAverageXPainterUnits = endAverageSegmentX; + startAverageYPainterUnits = endAverageSegmentY; + } + + calculatedAngle = std::fmod( QgsGeometryUtilsBase::azimuth( startAverageXPainterUnits, startAverageYPainterUnits, endAverageXPainterUnits, endAverageYPainterUnits ) + 360, 360 ); + if ( calculatedAngle > 90 && calculatedAngle < 270 ) + calculatedAngle += 180; + } + + if ( !qgsDoubleNear( targetPointFractionAlongSegment, 0 ) || isFirstPoint ) + { + if ( !visitPoint( pX, pY, pZ, pM, distanceTraversed + segmentLength * targetPointFractionAlongSegment, calculatedAngle ) ) + return; + } + + nextStepValue += direction * step; + } + } + else if ( isFirstPoint && emitFirstPoint ) + { + if ( !visitPoint( prevX, prevY, prevZ, prevM, distanceTraversed, + std::fmod( QgsGeometryUtilsBase::azimuth( prevXPainterUnits, prevYPainterUnits, thisXPainterUnits, thisYPainterUnits ) + 360, 360 ) ) ) + return; + } + isFirstPoint = false; + + prevX = thisX; + prevY = thisY; + prevZ = thisZ; + prevM = thisM; + prevXPainterUnits = thisXPainterUnits; + prevYPainterUnits = thisYPainterUnits; + prevValue = thisValue; + distanceTraversed += segmentLength; + } +} + +void visitPointsByInterpolatedZ( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint ) +{ + visitPointsByInterpolatedZM( line, linePainterUnits, emitFirstPoint, distance, averageAngleLengthPainterUnits, visitPoint, true ); +} + +void visitPointsByInterpolatedM( const QgsLineString *line, const QgsLineString *linePainterUnits, bool emitFirstPoint, const double distance, const double averageAngleLengthPainterUnits, const VisitPointFunction &visitPoint ) +{ + visitPointsByInterpolatedZM( line, linePainterUnits, emitFirstPoint, distance, averageAngleLengthPainterUnits, visitPoint, false ); +} + +QPointF QgsLinearReferencingSymbolLayer::pointToPainter( QgsSymbolRenderContext &context, double x, double y, double z ) +{ + QPointF pt; + if ( context.renderContext().coordinateTransform().isValid() ) + { + context.renderContext().coordinateTransform().transformInPlace( x, y, z ); + pt = QPointF( x, y ); + + } + else + { + pt = QPointF( x, y ); + } + + context.renderContext().mapToPixel().transformInPlace( pt.rx(), pt.ry() ); + return pt; +} + +void QgsLinearReferencingSymbolLayer::renderPolylineInterval( const QgsLineString *line, QgsSymbolRenderContext &context, double skipMultiples, const QPointF &labelOffsetPainterUnits, double averageAngleLengthPainterUnits, bool showMarker ) +{ + double distance = mInterval; + if ( mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Interval ) ) + { + context.setOriginalValueVariable( mInterval ); + distance = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::Interval, context.renderContext().expressionContext(), mInterval ); + } + + QgsNumericFormatContext numericContext; + + std::unique_ptr< QgsLineString > painterUnitsGeometry( line->clone() ); + if ( context.renderContext().coordinateTransform().isValid() ) + { + painterUnitsGeometry->transform( context.renderContext().coordinateTransform() ); + } + painterUnitsGeometry->transform( context.renderContext().mapToPixel().transform() ); + + const bool hasZ = line->is3D(); + const bool hasM = line->isMeasure(); + const bool emitFirstPoint = mLabelSource != Qgis::LinearReferencingLabelSource::CartesianDistance2D; + + VisitPointAtDistanceFunction func = nullptr; + + switch ( mPlacement ) + { + case Qgis::LinearReferencingPlacement::IntervalCartesian2D: + func = visitPointsByRegularDistance; + break; + + case Qgis::LinearReferencingPlacement::IntervalZ: + func = visitPointsByInterpolatedZ; + break; + + case Qgis::LinearReferencingPlacement::IntervalM: + func = visitPointsByInterpolatedM; + break; + + case Qgis::LinearReferencingPlacement::Vertex: + return; + } + + func( line, painterUnitsGeometry.get(), emitFirstPoint, distance, averageAngleLengthPainterUnits, [&context, &numericContext, skipMultiples, showMarker, + labelOffsetPainterUnits, hasZ, hasM, this]( double x, double y, double z, double m, double distanceFromStart, double angle ) -> bool + { + if ( context.renderContext().renderingStopped() ) + return false; + + double labelValue = 0; + bool labelVertex = true; + switch ( mLabelSource ) + { + case Qgis::LinearReferencingLabelSource::CartesianDistance2D: + labelValue = distanceFromStart; + break; + case Qgis::LinearReferencingLabelSource::Z: + labelValue = z; + labelVertex = hasZ && !std::isnan( labelValue ); + break; + case Qgis::LinearReferencingLabelSource::M: + labelValue = m; + labelVertex = hasM && !std::isnan( labelValue ); + break; + } + + if ( !labelVertex ) + return true; + + if ( skipMultiples > 0 && qgsDoubleNear( std::fmod( labelValue, skipMultiples ), 0 ) ) + return true; + + const QPointF pt = pointToPainter( context, x, y, z ); + + if ( mMarkerSymbol && showMarker ) + { + if ( mRotateLabels ) + mMarkerSymbol->setLineAngle( 90 - angle ); + mMarkerSymbol->renderPoint( pt, context.feature(), context.renderContext() ); + } + + const double angleRadians = ( mRotateLabels ? angle : 0 ) * M_PI / 180.0; + const double dx = labelOffsetPainterUnits.x() * std::sin( angleRadians + M_PI_2 ) + + labelOffsetPainterUnits.y() * std::sin( angleRadians ); + const double dy = labelOffsetPainterUnits.x() * std::cos( angleRadians + M_PI_2 ) + + labelOffsetPainterUnits.y() * std::cos( angleRadians ); + + QgsTextRenderer::drawText( QPointF( pt.x() + dx, pt.y() + dy ), angleRadians, Qgis::TextHorizontalAlignment::Left, { mNumericFormat->formatDouble( labelValue, numericContext ) }, context.renderContext(), mTextFormat ); + + return true; + } ); +} + +void QgsLinearReferencingSymbolLayer::renderPolylineVertex( const QgsLineString *line, QgsSymbolRenderContext &context, double skipMultiples, const QPointF &labelOffsetPainterUnits, double averageAngleLengthPainterUnits, bool showMarker ) +{ + // let's simplify the logic by ALWAYS using the averaging approach for angles, and just + // use a very small distance if the user actually set this to 0. It'll be identical + // results anyway... + averageAngleLengthPainterUnits = std::max( averageAngleLengthPainterUnits, 0.1 ); + + QgsNumericFormatContext numericContext; + + const double *xData = line->xData(); + const double *yData = line->yData(); + const double *zData = line->is3D() ? line->zData() : nullptr; + const double *mData = line->isMeasure() ? line->mData() : nullptr; + const int size = line->numPoints(); + if ( size < 2 ) + return; + + std::unique_ptr< QgsLineString > painterUnitsGeometry( line->clone() ); + if ( context.renderContext().coordinateTransform().isValid() ) + { + painterUnitsGeometry->transform( context.renderContext().coordinateTransform() ); + } + painterUnitsGeometry->transform( context.renderContext().mapToPixel().transform() ); + const double *xPainterUnits = painterUnitsGeometry->xData(); + const double *yPainterUnits = painterUnitsGeometry->yData(); + + double currentDistance = 0; + double prevX = *xData; + double prevY = *yData; + + for ( int i = 0; i < size; ++i ) + { + if ( context.renderContext().renderingStopped() ) + break; + + double thisX = *xData++; + double thisY = *yData++; + double thisZ = zData ? *zData++ : 0; + double thisM = mData ? *mData++ : 0; + double thisXPainterUnits = *xPainterUnits++; + double thisYPainterUnits = *yPainterUnits++; + + const double thisSegmentLength = QgsGeometryUtilsBase::distance2D( prevX, prevY, thisX, thisY ); + currentDistance += thisSegmentLength; + + if ( skipMultiples > 0 && qgsDoubleNear( std::fmod( currentDistance, skipMultiples ), 0 ) ) + { + prevX = thisX; + prevY = thisY; + continue; + } + + const QPointF pt = pointToPainter( context, thisX, thisY, thisZ ); + + double calculatedAngle = 0; + + // track forward by averageAngleLengthPainterUnits + double painterDistRemaining = averageAngleLengthPainterUnits; + double startAverageSegmentX = thisXPainterUnits; + double startAverageSegmentY = thisYPainterUnits; + + const double *xAveragingData = xPainterUnits; + const double *yAveragingData = yPainterUnits; + double endAverageSegmentX = *xAveragingData; + double endAverageSegmentY = *yAveragingData; + double averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY ); + + int j = i; + while ( ( j < size - 1 ) && ( painterDistRemaining > averagingSegmentLengthPainterUnits ) ) + { + painterDistRemaining -= averagingSegmentLengthPainterUnits; + startAverageSegmentX = endAverageSegmentX; + startAverageSegmentY = endAverageSegmentY; + + endAverageSegmentX = *xAveragingData++; + endAverageSegmentY = *yAveragingData++; + j++; + averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY ); + } + // fits on this same segment + double endAverageXPainterUnits = thisXPainterUnits; + double endAverageYPainterUnits = thisYPainterUnits; + if ( ( j < size - 1 ) && painterDistRemaining < averagingSegmentLengthPainterUnits ) + { + QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, endAverageXPainterUnits, endAverageYPainterUnits, + nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr ); + } + else if ( i < size - 2 ) + { + endAverageXPainterUnits = endAverageSegmentX; + endAverageYPainterUnits = endAverageSegmentY; + } + + // also track back by averageAngleLengthPainterUnits + j = i; + painterDistRemaining = averageAngleLengthPainterUnits; + startAverageSegmentX = thisXPainterUnits; + startAverageSegmentY = thisYPainterUnits; + + xAveragingData = xPainterUnits - 2; + yAveragingData = yPainterUnits - 2; + + endAverageSegmentX = *xAveragingData; + endAverageSegmentY = *yAveragingData; + averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY ); + + while ( j > 0 && painterDistRemaining > averagingSegmentLengthPainterUnits ) + { + painterDistRemaining -= averagingSegmentLengthPainterUnits; + startAverageSegmentX = endAverageSegmentX; + startAverageSegmentY = endAverageSegmentY; + + endAverageSegmentX = *xAveragingData--; + endAverageSegmentY = *yAveragingData--; + j--; + averagingSegmentLengthPainterUnits = QgsGeometryUtilsBase::distance2D( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY ); + } + // fits on this same segment + double startAverageXPainterUnits = thisXPainterUnits; + double startAverageYPainterUnits = thisYPainterUnits; + if ( j > 0 && painterDistRemaining < averagingSegmentLengthPainterUnits ) + { + QgsGeometryUtilsBase::pointOnLineWithDistance( startAverageSegmentX, startAverageSegmentY, endAverageSegmentX, endAverageSegmentY, painterDistRemaining, startAverageXPainterUnits, startAverageYPainterUnits, + nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr ); + } + else if ( j > 1 ) + { + startAverageXPainterUnits = endAverageSegmentX; + startAverageYPainterUnits = endAverageSegmentY; + } + + calculatedAngle = std::fmod( QgsGeometryUtilsBase::azimuth( startAverageXPainterUnits, startAverageYPainterUnits, endAverageXPainterUnits, endAverageYPainterUnits ) + 360, 360 ); + + if ( calculatedAngle > 90 && calculatedAngle < 270 ) + calculatedAngle += 180; + + if ( mMarkerSymbol && showMarker ) + { + if ( mRotateLabels ) + mMarkerSymbol->setLineAngle( 90 - calculatedAngle ); + mMarkerSymbol->renderPoint( pt, context.feature(), context.renderContext() ); + } + + const double angleRadians = mRotateLabels ? ( calculatedAngle * M_PI / 180.0 ) : 0; + const double dx = labelOffsetPainterUnits.x() * std::sin( angleRadians + M_PI_2 ) + + labelOffsetPainterUnits.y() * std::sin( angleRadians ); + const double dy = labelOffsetPainterUnits.x() * std::cos( angleRadians + M_PI_2 ) + + labelOffsetPainterUnits.y() * std::cos( angleRadians ); + + double labelValue = 0; + bool labelVertex = true; + switch ( mLabelSource ) + { + case Qgis::LinearReferencingLabelSource::CartesianDistance2D: + labelValue = currentDistance; + break; + case Qgis::LinearReferencingLabelSource::Z: + labelValue = thisZ; + labelVertex = static_cast< bool >( zData ) && !std::isnan( labelValue ); + break; + case Qgis::LinearReferencingLabelSource::M: + labelValue = thisM; + labelVertex = static_cast< bool >( mData ) && !std::isnan( labelValue ); + break; + } + + if ( !labelVertex ) + continue; + + QgsTextRenderer::drawText( QPointF( pt.x() + dx, pt.y() + dy ), angleRadians, Qgis::TextHorizontalAlignment::Left, { mNumericFormat->formatDouble( labelValue, numericContext ) }, context.renderContext(), mTextFormat ); + + prevX = thisX; + prevY = thisY; + } +} + +QgsTextFormat QgsLinearReferencingSymbolLayer::textFormat() const +{ + return mTextFormat; +} + +void QgsLinearReferencingSymbolLayer::setTextFormat( const QgsTextFormat &format ) +{ + mTextFormat = format; +} + +QgsNumericFormat *QgsLinearReferencingSymbolLayer::numericFormat() const +{ + return mNumericFormat.get(); +} + +void QgsLinearReferencingSymbolLayer::setNumericFormat( QgsNumericFormat *format ) +{ + mNumericFormat.reset( format ); +} + +double QgsLinearReferencingSymbolLayer::interval() const +{ + return mInterval; +} + +void QgsLinearReferencingSymbolLayer::setInterval( double interval ) +{ + mInterval = interval; +} + +double QgsLinearReferencingSymbolLayer::skipMultiplesOf() const +{ + return mSkipMultiplesOf; +} + +void QgsLinearReferencingSymbolLayer::setSkipMultiplesOf( double skipMultiplesOf ) +{ + mSkipMultiplesOf = skipMultiplesOf; +} + +bool QgsLinearReferencingSymbolLayer::showMarker() const +{ + return mShowMarker; +} + +void QgsLinearReferencingSymbolLayer::setShowMarker( bool show ) +{ + mShowMarker = show; + if ( show && !mMarkerSymbol ) + { + mMarkerSymbol.reset( QgsMarkerSymbol::createSimple( {} ) ); + } +} + +Qgis::LinearReferencingPlacement QgsLinearReferencingSymbolLayer::placement() const +{ + return mPlacement; +} + +void QgsLinearReferencingSymbolLayer::setPlacement( Qgis::LinearReferencingPlacement placement ) +{ + mPlacement = placement; +} + +Qgis::LinearReferencingLabelSource QgsLinearReferencingSymbolLayer::labelSource() const +{ + return mLabelSource; +} + +void QgsLinearReferencingSymbolLayer::setLabelSource( Qgis::LinearReferencingLabelSource source ) +{ + mLabelSource = source; +} + diff --git a/src/core/symbology/qgslinearreferencingsymbollayer.h b/src/core/symbology/qgslinearreferencingsymbollayer.h new file mode 100644 index 0000000000000..20baac175dd2f --- /dev/null +++ b/src/core/symbology/qgslinearreferencingsymbollayer.h @@ -0,0 +1,334 @@ +/*************************************************************************** + qgslinearreferencingsymbollayer.h + --------------------- + begin : August 2024 + copyright : (C) 2024 by Nyall Dawson + email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSLINEARREFERENCINGSYMBOLLAYER_H +#define QGSLINEARREFERENCINGSYMBOLLAYER_H + +#include "qgis_core.h" +#include "qgis.h" +#include "qgssymbollayer.h" +#include "qgstextformat.h" + +class QgsNumericFormat; + +/** + * \ingroup core + * \brief Line symbol layer used for decorating accordingly to linear referencing. + * + * This symbol layer type allows placing text labels at regular intervals along + * a line (or at positions corresponding to existing vertices). Positions + * can be calculated using Cartesian distances, or interpolated from z or m values. + * + * \since QGIS 3.40 + */ +class CORE_EXPORT QgsLinearReferencingSymbolLayer : public QgsLineSymbolLayer +{ + public: + QgsLinearReferencingSymbolLayer(); + ~QgsLinearReferencingSymbolLayer() override; + + /** + * Creates a new QgsLinearReferencingSymbolLayer, using the specified \a properties. + * + * The caller takes ownership of the returned object. + */ + static QgsSymbolLayer *create( const QVariantMap &properties = QVariantMap() ) SIP_FACTORY; + + QgsLinearReferencingSymbolLayer *clone() const override SIP_FACTORY; + QVariantMap properties() const override; + QString layerType() const override; + Qgis::SymbolLayerFlags flags() const override; + QgsSymbol *subSymbol() override; + bool setSubSymbol( QgsSymbol *symbol SIP_TRANSFER ) override; + void startRender( QgsSymbolRenderContext &context ) override; + void stopRender( QgsSymbolRenderContext &context ) override; + void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) override; + + /** + * Returns the text format used to render the layer. + * + * \see setTextFormat() + */ + QgsTextFormat textFormat() const; + + /** + * Sets the text \a format used to render the layer. + * + * \see textFormat() + */ + void setTextFormat( const QgsTextFormat &format ); + + /** + * Returns the numeric format used to format the labels for the layer. + * + * \see setNumericFormat() + */ + QgsNumericFormat *numericFormat() const; + + /** + * Sets the numeric \a format used to format the labels for the layer. + * + * Ownership of \a format is transferred to the layer. + * + * \see numericFormat() + */ + void setNumericFormat( QgsNumericFormat *format SIP_TRANSFER ); + + /** + * Returns the interval between labels. + * + * Units are always in the original layer CRS units. + * + * \see setInterval() + */ + double interval() const; + + /** + * Sets the \a interval between labels. + * + * Units are always in the original layer CRS units. + * + * \see setInterval() + */ + void setInterval( double interval ); + + /** + * Returns the multiple distance to skip labels for. + * + * If this value is non-zero, then any labels which are integer multiples of the returned + * value will be skipped. This allows creation of advanced referencing styles where a single + * QgsSymbol has multiple QgsLinearReferencingSymbolLayer symbol layers, eg allowing + * labeling every 100 in a normal font and every 1000 in a bold, larger font. + * + * \see setSkipMultiplesOf() + */ + double skipMultiplesOf() const; + + /** + * Sets the \a multiple distance to skip labels for. + * + * If this value is non-zero, then any labels which are integer multiples of the returned + * value will be skipped. This allows creation of advanced referencing styles where a single + * QgsSymbol has multiple QgsLinearReferencingSymbolLayer symbol layers, eg allowing + * labeling every 100 in a normal font and every 1000 in a bold, larger font. + * + * \see skipMultiplesOf() + */ + void setSkipMultiplesOf( double multiple ); + + /** + * Returns TRUE if the labels and symbols are to be rotated to match their line segment orientation. + * + * \see setRotateLabels() + */ + bool rotateLabels() const { return mRotateLabels; } + + /** + * Sets whether the labels and symbols should be rotated to match their line segment orientation. + * + * \see rotateLabels() + */ + void setRotateLabels( bool rotate ) { mRotateLabels = rotate; } + + /** + * Returns the offset between the line and linear referencing labels. + * + * The unit for the offset is retrievable via labelOffsetUnit(). + * + * \see setLabelOffset() + * \see labelOffsetUnit() + */ + QPointF labelOffset() const { return mLabelOffset; } + + /** + * Sets the \a offset between the line and linear referencing labels. + * + * The unit for the offset is set via setLabelOffsetUnit(). + * + * \see labelOffset() + * \see setLabelOffsetUnit() + */ + void setLabelOffset( const QPointF &offset ) { mLabelOffset = offset; } + + /** + * Returns the unit used for the offset between the line and linear referencing labels. + * + * \see setLabelOffsetUnit() + * \see labelOffset() + */ + Qgis::RenderUnit labelOffsetUnit() const { return mLabelOffsetUnit; } + + /** + * Sets the \a unit used for the offset between the line and linear referencing labels. + * + * \see labelOffsetUnit() + * \see setLabelOffset() + */ + void setLabelOffsetUnit( Qgis::RenderUnit unit ) { mLabelOffsetUnit = unit; } + + /** + * Returns the map unit scale used for calculating the offset between the line and linear referencing labels. + * + * \see setLabelOffsetMapUnitScale() + */ + const QgsMapUnitScale &labelOffsetMapUnitScale() const { return mLabelOffsetMapUnitScale; } + + /** + * Sets the map unit \a scale used for calculating the offset between the line and linear referencing labels. + * + * \see labelOffsetMapUnitScale() + */ + void setLabelOffsetMapUnitScale( const QgsMapUnitScale &scale ) { mLabelOffsetMapUnitScale = scale; } + + /** + * Returns TRUE if a marker symbol should be shown corresponding to the labeled point on line. + * + * The marker symbol is set using setSubSymbol() + * + * \see setShowMarker() + */ + bool showMarker() const; + + /** + * Sets whether a marker symbol should be shown corresponding to the labeled point on line. + * + * The marker symbol is set using setSubSymbol() + * + * \see showMarker() + */ + void setShowMarker( bool show ); + + /** + * Returns the placement mode for the labels. + * + * \see setPlacement() + */ + Qgis::LinearReferencingPlacement placement() const; + + /** + * Sets the \a placement mode for the labels. + * + * \see placement() + */ + void setPlacement( Qgis::LinearReferencingPlacement placement ); + + /** + * Returns the label source, which dictates what quantity to use for the labels shown. + * + * \see setLabelSource() + */ + Qgis::LinearReferencingLabelSource labelSource() const; + + /** + * Sets the label \a source, which dictates what quantity to use for the labels shown. + * + * \see labelSource() + */ + void setLabelSource( Qgis::LinearReferencingLabelSource source ); + + /** + * Returns the length of line over which the line's direction is averaged when + * calculating individual label angles. Longer lengths smooth out angles from jagged lines to a greater extent. + * + * Units are retrieved through averageAngleUnit() + * + * \see setAverageAngleLength() + * \see averageAngleUnit() + * \see averageAngleMapUnitScale() + */ + double averageAngleLength() const { return mAverageAngleLength; } + + /** + * Sets the \a length of line over which the line's direction is averaged when + * calculating individual label angles. Longer lengths smooth out angles from jagged lines to a greater extent. + * + * Units are set through setAverageAngleUnit() + * + * \see averageAngleLength() + * \see setAverageAngleUnit() + * \see setAverageAngleMapUnitScale() + */ + void setAverageAngleLength( double length ) { mAverageAngleLength = length; } + + /** + * Sets the \a unit for the length over which the line's direction is averaged when + * calculating individual label angles. + * + * \see averageAngleUnit() + * \see setAverageAngleLength() + * \see setAverageAngleMapUnitScale() + */ + void setAverageAngleUnit( Qgis::RenderUnit unit ) { mAverageAngleLengthUnit = unit; } + + /** + * Returns the unit for the length over which the line's direction is averaged when + * calculating individual label angles. + * + * \see setAverageAngleUnit() + * \see averageAngleLength() + * \see averageAngleMapUnitScale() + */ + Qgis::RenderUnit averageAngleUnit() const { return mAverageAngleLengthUnit; } + + /** + * Sets the map unit \a scale for the length over which the line's direction is averaged when + * calculating individual label angles. + * + * \see averageAngleMapUnitScale() + * \see setAverageAngleLength() + * \see setAverageAngleUnit() + */ + void setAverageAngleMapUnitScale( const QgsMapUnitScale &scale ) { mAverageAngleLengthMapUnitScale = scale; } + + /** + * Returns the map unit scale for the length over which the line's direction is averaged when + * calculating individual label angles. + * + * \see setAverageAngleMapUnitScale() + * \see averageAngleLength() + * \see averageAngleUnit() + */ + const QgsMapUnitScale &averageAngleMapUnitScale() const { return mAverageAngleLengthMapUnitScale; } + + private: + void renderPolylineInterval( const QgsLineString *line, QgsSymbolRenderContext &context, double skipMultiples, const QPointF &labelOffsetPainterUnits, double averageAngleLengthPainterUnits, bool showMarker ); + void renderPolylineVertex( const QgsLineString *line, QgsSymbolRenderContext &context, double skipMultiples, const QPointF &labelOffsetPainterUnits, double averageAngleLengthPainterUnits, bool showMarker ); + static QPointF pointToPainter( QgsSymbolRenderContext &context, double x, double y, double z ); + + Qgis::LinearReferencingPlacement mPlacement = Qgis::LinearReferencingPlacement::IntervalCartesian2D; + Qgis::LinearReferencingLabelSource mLabelSource = Qgis::LinearReferencingLabelSource::CartesianDistance2D; + + double mInterval = 1000; + double mSkipMultiplesOf = 0; + bool mRotateLabels = true; + + QPointF mLabelOffset{ 1, 0 }; + Qgis::RenderUnit mLabelOffsetUnit = Qgis::RenderUnit::Millimeters; + QgsMapUnitScale mLabelOffsetMapUnitScale; + + QgsTextFormat mTextFormat; + std::unique_ptr mNumericFormat; + + bool mShowMarker = false; + std::unique_ptr mMarkerSymbol; + + double mAverageAngleLength = 4; + Qgis::RenderUnit mAverageAngleLengthUnit = Qgis::RenderUnit::Millimeters; + QgsMapUnitScale mAverageAngleLengthMapUnitScale; + + void renderGeometryPart( QgsSymbolRenderContext &context, const QgsAbstractGeometry *geometry, double labelOffsetPainterUnitsX, double labelOffsetPainterUnitsY, double skipMultiples, double averageAngleDistancePainterUnits, bool showMarker ); + void renderLineString( QgsSymbolRenderContext &context, const QgsLineString *line, double labelOffsetPainterUnitsX, double labelOffsetPainterUnitsY, double skipMultiples, double averageAngleDistancePainterUnits, bool showMarker ); +}; + +#endif // QGSLINEARREFERENCINGSYMBOLLAYER_H diff --git a/src/core/symbology/qgssymbollayer.cpp b/src/core/symbology/qgssymbollayer.cpp index 139602681be6b..b0a9cc928e325 100644 --- a/src/core/symbology/qgssymbollayer.cpp +++ b/src/core/symbology/qgssymbollayer.cpp @@ -116,6 +116,8 @@ void QgsSymbolLayer::initPropertyDefinitions() { static_cast< int >( QgsSymbolLayer::Property::RandomOffsetX ), QgsPropertyDefinition( "randomOffsetX", QObject::tr( "Horizontal random offset" ), QgsPropertyDefinition::Double, origin )}, { static_cast< int >( QgsSymbolLayer::Property::RandomOffsetY ), QgsPropertyDefinition( "randomOffsetY", QObject::tr( "Vertical random offset" ), QgsPropertyDefinition::Double, origin )}, { static_cast< int >( QgsSymbolLayer::Property::LineClipping ), QgsPropertyDefinition( "lineClipping", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line clipping mode" ), QObject::tr( "string " ) + QLatin1String( "[no|during_render|before_render]" ), origin )}, + { static_cast< int >( QgsSymbolLayer::Property::SkipMultiples ), QgsPropertyDefinition( "skipMultiples", QObject::tr( "Skip multiples of" ), QgsPropertyDefinition::DoublePositive, origin )}, + { static_cast< int >( QgsSymbolLayer::Property::ShowMarker ), QgsPropertyDefinition( "showMarker", QObject::tr( "Show marker" ), QgsPropertyDefinition::Boolean, origin )}, }; } diff --git a/src/core/symbology/qgssymbollayer.h b/src/core/symbology/qgssymbollayer.h index a83cd74e74a50..8218355df820c 100644 --- a/src/core/symbology/qgssymbollayer.h +++ b/src/core/symbology/qgssymbollayer.h @@ -100,6 +100,8 @@ class CORE_EXPORT QgsSymbolLayer sipType = sipType_QgsRasterLineSymbolLayer; else if ( sipCpp->layerType() == "Lineburst" ) sipType = sipType_QgsLineburstSymbolLayer; + else if ( sipCpp->layerType() == "LinearReferencing" ) + sipType = sipType_QgsLinearReferencingSymbolLayer; else sipType = sipType_QgsLineSymbolLayer; break; @@ -212,6 +214,8 @@ class CORE_EXPORT QgsSymbolLayer RandomOffsetX SIP_MONKEYPATCH_COMPAT_NAME( PropertyRandomOffsetX ), //!< Random offset X (since QGIS 3.24) RandomOffsetY SIP_MONKEYPATCH_COMPAT_NAME( PropertyRandomOffsetY ), //!< Random offset Y (since QGIS 3.24) LineClipping SIP_MONKEYPATCH_COMPAT_NAME( PropertyLineClipping ), //!< Line clipping mode (since QGIS 3.24) + SkipMultiples, //!< Skip multiples of (since QGIS 3.40) + ShowMarker, //!< Show markers (since QGIS 3.40) }; // *INDENT-ON* diff --git a/src/core/symbology/qgssymbollayerregistry.cpp b/src/core/symbology/qgssymbollayerregistry.cpp index 8a3f9363ca74e..356cde81db9ad 100644 --- a/src/core/symbology/qgssymbollayerregistry.cpp +++ b/src/core/symbology/qgssymbollayerregistry.cpp @@ -24,6 +24,7 @@ #include "qgsmasksymbollayer.h" #include "qgsgeometrygeneratorsymbollayer.h" #include "qgsinterpolatedlinerenderer.h" +#include "qgslinearreferencingsymbollayer.h" QgsSymbolLayerRegistry::QgsSymbolLayerRegistry() { @@ -44,6 +45,8 @@ QgsSymbolLayerRegistry::QgsSymbolLayerRegistry() QgsLineburstSymbolLayer::create ) ); addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "FilledLine" ), QObject::tr( "Filled Line" ), Qgis::SymbolType::Line, QgsFilledLineSymbolLayer::create ) ); + addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "LinearReferencing" ), QObject::tr( "Linear Referencing" ), Qgis::SymbolType::Line, + QgsLinearReferencingSymbolLayer::create ) ); addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "SimpleMarker" ), QObject::tr( "Simple Marker" ), Qgis::SymbolType::Marker, QgsSimpleMarkerSymbolLayer::create, QgsSimpleMarkerSymbolLayer::createFromSld ) ); diff --git a/src/gui/symbology/qgslayerpropertieswidget.cpp b/src/gui/symbology/qgslayerpropertieswidget.cpp index 16602e724ec7a..72b2654b4d94f 100644 --- a/src/gui/symbology/qgslayerpropertieswidget.cpp +++ b/src/gui/symbology/qgslayerpropertieswidget.cpp @@ -83,6 +83,7 @@ static void _initWidgetFunctions() _initWidgetFunction( QStringLiteral( "RasterLine" ), QgsRasterLineSymbolLayerWidget::create ); _initWidgetFunction( QStringLiteral( "Lineburst" ), QgsLineburstSymbolLayerWidget::create ); _initWidgetFunction( QStringLiteral( "FilledLine" ), QgsFilledLineSymbolLayerWidget::create ); + _initWidgetFunction( QStringLiteral( "LinearReferencing" ), QgsLinearReferencingSymbolLayerWidget::create ); _initWidgetFunction( QStringLiteral( "SimpleMarker" ), QgsSimpleMarkerSymbolLayerWidget::create ); _initWidgetFunction( QStringLiteral( "FilledMarker" ), QgsFilledMarkerSymbolLayerWidget::create ); diff --git a/src/gui/symbology/qgssymbollayerwidget.cpp b/src/gui/symbology/qgssymbollayerwidget.cpp index e5b4890426216..21c8b061b6a23 100644 --- a/src/gui/symbology/qgssymbollayerwidget.cpp +++ b/src/gui/symbology/qgssymbollayerwidget.cpp @@ -42,6 +42,8 @@ #include "qgsmarkersymbol.h" #include "qgsfillsymbol.h" #include "qgsiconutils.h" +#include "qgslinearreferencingsymbollayer.h" +#include "qgsnumericformatselectorwidget.h" #include #include @@ -5458,3 +5460,242 @@ QgsSymbolLayer *QgsFilledLineSymbolLayerWidget::symbolLayer() { return mLayer; } + +// +// QgsLinearReferencingSymbolLayerWidget +// + +QgsLinearReferencingSymbolLayerWidget::QgsLinearReferencingSymbolLayerWidget( QgsVectorLayer *vl, QWidget *parent ) + : QgsSymbolLayerWidget( parent, vl ) +{ + mLayer = nullptr; + + setupUi( this ); + + mComboPlacement->addItem( tr( "Interval (Cartesian 2D Distances)" ), QVariant::fromValue( Qgis::LinearReferencingPlacement::IntervalCartesian2D ) ); + mComboPlacement->addItem( tr( "Interval (Z Values)" ), QVariant::fromValue( Qgis::LinearReferencingPlacement::IntervalZ ) ); + mComboPlacement->addItem( tr( "Interval (M Values)" ), QVariant::fromValue( Qgis::LinearReferencingPlacement::IntervalM ) ); + mComboPlacement->addItem( tr( "On Every Vertex" ), QVariant::fromValue( Qgis::LinearReferencingPlacement::Vertex ) ); + + mComboQuantity->addItem( tr( "Cartesian 2D Distance" ), QVariant::fromValue( Qgis::LinearReferencingLabelSource::CartesianDistance2D ) ); + mComboQuantity->addItem( tr( "Z Values" ), QVariant::fromValue( Qgis::LinearReferencingLabelSource::Z ) ); + mComboQuantity->addItem( tr( "M Values" ), QVariant::fromValue( Qgis::LinearReferencingLabelSource::M ) ); + + mSpinSkipMultiples->setClearValue( 0, tr( "Not set" ) ); + mSpinLabelOffsetX->setClearValue( 0 ); + mSpinLabelOffsetY->setClearValue( 0 ); + mSpinAverageAngleLength->setClearValue( 4.0 ); + mLabelOffsetUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << Qgis::RenderUnit::Millimeters << Qgis::RenderUnit::MetersInMapUnits << Qgis::RenderUnit::MapUnits << Qgis::RenderUnit::Pixels + << Qgis::RenderUnit::Points << Qgis::RenderUnit::Inches ); + mAverageAngleUnit->setUnits( QgsUnitTypes::RenderUnitList() << Qgis::RenderUnit::Millimeters << Qgis::RenderUnit::MetersInMapUnits << Qgis::RenderUnit::MapUnits << Qgis::RenderUnit::Pixels + << Qgis::RenderUnit::Points << Qgis::RenderUnit::Inches ); + + connect( mComboQuantity, qOverload< int >( &QComboBox::currentIndexChanged ), this, [ = ] + { + if ( mLayer && !mBlockChangesSignal ) + { + mLayer->setLabelSource( mComboQuantity->currentData().value< Qgis::LinearReferencingLabelSource >() ); + emit changed(); + } + } ); + connect( mTextFormatButton, &QgsFontButton::changed, this, [ = ] + { + if ( mLayer && !mBlockChangesSignal ) + { + mLayer->setTextFormat( mTextFormatButton->textFormat() ); + emit changed(); + } + } ); + connect( spinInterval, qOverload< double >( &QgsDoubleSpinBox::valueChanged ), this, [ = ] + { + if ( mLayer && !mBlockChangesSignal ) + { + mLayer->setInterval( spinInterval->value() ); + emit changed(); + } + } ); + connect( mSpinSkipMultiples, qOverload< double >( &QgsDoubleSpinBox::valueChanged ), this, [ = ] + { + if ( mLayer && !mBlockChangesSignal ) + { + mLayer->setSkipMultiplesOf( mSpinSkipMultiples->value() ); + emit changed(); + } + } ); + connect( mCheckRotate, &QCheckBox::toggled, this, [ = ]( bool checked ) + { + if ( mLayer && !mBlockChangesSignal ) + { + mLayer->setRotateLabels( checked ); + emit changed(); + } + mSpinAverageAngleLength->setEnabled( checked ); + mAverageAngleUnit->setEnabled( mSpinAverageAngleLength->isEnabled() ); + } ); + connect( mCheckShowMarker, &QCheckBox::toggled, this, [ = ]( bool checked ) + { + if ( mLayer && !mBlockChangesSignal ) + { + mLayer->setShowMarker( checked ); + emit symbolChanged(); + } + } ); + + connect( mSpinLabelOffsetX, qOverload< double >( &QgsDoubleSpinBox::valueChanged ), this, [ = ] + { + if ( mLayer && !mBlockChangesSignal ) + { + mLayer->setLabelOffset( QPointF( mSpinLabelOffsetX->value(), mSpinLabelOffsetY->value() ) ); + emit changed(); + } + } ); + connect( mSpinLabelOffsetY, qOverload< double >( &QgsDoubleSpinBox::valueChanged ), this, [ = ] + { + if ( mLayer && !mBlockChangesSignal ) + { + mLayer->setLabelOffset( QPointF( mSpinLabelOffsetX->value(), mSpinLabelOffsetY->value() ) ); + emit changed(); + } + } ); + connect( mLabelOffsetUnitWidget, &QgsUnitSelectionWidget::changed, this, [ = ] + { + if ( mLayer && !mBlockChangesSignal ) + { + mLayer->setLabelOffsetUnit( mLabelOffsetUnitWidget->unit() ); + mLayer->setLabelOffsetMapUnitScale( mLabelOffsetUnitWidget->getMapUnitScale() ); + emit changed(); + } + } ); + + connect( mComboPlacement, qOverload< int>( &QComboBox::currentIndexChanged ), this, [ = ] + { + if ( mLayer && !mBlockChangesSignal ) + { + const Qgis::LinearReferencingPlacement placement = mComboPlacement->currentData().value< Qgis::LinearReferencingPlacement >(); + mLayer->setPlacement( placement ); + switch ( placement ) + { + case Qgis::LinearReferencingPlacement::IntervalCartesian2D: + case Qgis::LinearReferencingPlacement::IntervalZ: + case Qgis::LinearReferencingPlacement::IntervalM: + mIntervalWidget->show(); + break; + case Qgis::LinearReferencingPlacement::Vertex: + mIntervalWidget->hide(); + break; + } + emit changed(); + } + } ); + + connect( mSpinAverageAngleLength, qOverload< double >( &QgsDoubleSpinBox::valueChanged ), this, [ = ] + { + if ( mLayer && !mBlockChangesSignal ) + { + mLayer->setAverageAngleLength( mSpinAverageAngleLength->value() ); + emit changed(); + } + } ); + connect( mAverageAngleUnit, &QgsUnitSelectionWidget::changed, this, [ = ] + { + if ( mLayer && !mBlockChangesSignal ) + { + mLayer->setAverageAngleUnit( mAverageAngleUnit->unit() ); + mLayer->setAverageAngleMapUnitScale( mAverageAngleUnit->getMapUnitScale() ); + emit changed(); + } + } ); + + connect( mNumberFormatPushButton, &QPushButton::clicked, this, &QgsLinearReferencingSymbolLayerWidget::changeNumberFormat ); + + mTextFormatButton->registerExpressionContextGenerator( this ); +} + +QgsLinearReferencingSymbolLayerWidget::~QgsLinearReferencingSymbolLayerWidget() = default; + + +void QgsLinearReferencingSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer ) +{ + if ( !layer || layer->layerType() != QLatin1String( "LinearReferencing" ) ) + return; + + // layer type is correct, we can do the cast + mLayer = qgis::down_cast( layer ); + + mBlockChangesSignal = true; + + mComboPlacement->setCurrentIndex( mComboPlacement->findData( QVariant::fromValue( mLayer->placement() ) ) ); + switch ( mLayer->placement() ) + { + case Qgis::LinearReferencingPlacement::IntervalCartesian2D: + case Qgis::LinearReferencingPlacement::IntervalZ: + case Qgis::LinearReferencingPlacement::IntervalM: + mIntervalWidget->show(); + break; + case Qgis::LinearReferencingPlacement::Vertex: + mIntervalWidget->hide(); + break; + } + + mComboQuantity->setCurrentIndex( mComboQuantity->findData( QVariant::fromValue( mLayer->labelSource() ) ) ); + + mTextFormatButton->setTextFormat( mLayer->textFormat() ); + spinInterval->setValue( mLayer->interval() ); + mSpinSkipMultiples->setValue( mLayer->skipMultiplesOf() ); + mCheckRotate->setChecked( mLayer->rotateLabels() ); + mCheckShowMarker->setChecked( mLayer->showMarker() ); + mSpinLabelOffsetX->setValue( mLayer->labelOffset().x() ); + mSpinLabelOffsetY->setValue( mLayer->labelOffset().y() ); + mLabelOffsetUnitWidget->setUnit( mLayer->labelOffsetUnit() ); + mLabelOffsetUnitWidget->setMapUnitScale( mLayer->labelOffsetMapUnitScale() ); + + mAverageAngleUnit->setUnit( mLayer->averageAngleUnit() ); + mAverageAngleUnit->setMapUnitScale( mLayer->averageAngleMapUnitScale() ); + mSpinAverageAngleLength->setValue( mLayer->averageAngleLength() ); + + mSpinAverageAngleLength->setEnabled( mCheckRotate->isChecked() ); + mAverageAngleUnit->setEnabled( mSpinAverageAngleLength->isEnabled() ); + + registerDataDefinedButton( mIntervalDDBtn, QgsSymbolLayer::Property::Interval ); + registerDataDefinedButton( mAverageAngleDDBtn, QgsSymbolLayer::Property::AverageAngleLength ); + registerDataDefinedButton( mSkipMultiplesDDBtn, QgsSymbolLayer::Property::SkipMultiples ); + registerDataDefinedButton( mShowMarkerDDBtn, QgsSymbolLayer::Property::ShowMarker ); + + mBlockChangesSignal = false; +} + +QgsSymbolLayer *QgsLinearReferencingSymbolLayerWidget::symbolLayer() +{ + return mLayer; +} + +void QgsLinearReferencingSymbolLayerWidget::setContext( const QgsSymbolWidgetContext &context ) +{ + QgsSymbolLayerWidget::setContext( context ); + mTextFormatButton->setMapCanvas( context.mapCanvas() ); + mTextFormatButton->setMessageBar( context.messageBar() ); +} + +void QgsLinearReferencingSymbolLayerWidget::changeNumberFormat() +{ + QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ); + if ( panel && panel->dockMode() ) + { + QgsNumericFormatSelectorWidget *widget = new QgsNumericFormatSelectorWidget( this ); + widget->setPanelTitle( tr( "Number Format" ) ); + widget->setFormat( mLayer->numericFormat() ); + connect( widget, &QgsNumericFormatSelectorWidget::changed, this, [ = ] + { + if ( !mBlockChangesSignal ) + { + mLayer->setNumericFormat( widget->format() ); + emit changed(); + } + } ); + panel->openPanel( widget ); + } + else + { + // TODO!! dialog mode + } +} diff --git a/src/gui/symbology/qgssymbollayerwidget.h b/src/gui/symbology/qgssymbollayerwidget.h index 66f543d9fe6b4..21ae4acd92f4c 100644 --- a/src/gui/symbology/qgssymbollayerwidget.h +++ b/src/gui/symbology/qgssymbollayerwidget.h @@ -144,7 +144,6 @@ class GUI_EXPORT QgsSimpleLineSymbolLayerWidget : public QgsSymbolLayerWidget, p */ static QgsSymbolLayerWidget *create( QgsVectorLayer *vl ) SIP_FACTORY { return new QgsSimpleLineSymbolLayerWidget( vl ); } - // from base class void setSymbolLayer( QgsSymbolLayer *layer ) override; QgsSymbolLayer *symbolLayer() override; void setContext( const QgsSymbolWidgetContext &context ) override; @@ -1291,6 +1290,51 @@ class GUI_EXPORT QgsCentroidFillSymbolLayerWidget : public QgsSymbolLayerWidget, }; +/////////// + +#include "ui_qgslinearreferencingsymbollayerwidgetbase.h" + +class QgsLinearReferencingSymbolLayer; + +/** + * \ingroup gui + * \class QgsLinearReferencingSymbolLayerWidget + * \brief Widget for controlling the properties of a QgsLinearReferencingSymbolLayer. + * \since QGIS 3.40 + */ +class GUI_EXPORT QgsLinearReferencingSymbolLayerWidget : public QgsSymbolLayerWidget, private Ui::QgsLinearReferencingSymbolLayerWidgetBase +{ + Q_OBJECT + + public: + + /** + * Constructor for QgsLinearReferencingSymbolLayerWidget. + */ + QgsLinearReferencingSymbolLayerWidget( QgsVectorLayer *vl, QWidget *parent SIP_TRANSFERTHIS = nullptr ); + + ~QgsLinearReferencingSymbolLayerWidget() override; + + /** + * Creates a new QgsLinearReferencingSymbolLayerWidget. + * \param vl associated vector layer + */ + static QgsSymbolLayerWidget *create( QgsVectorLayer *vl ) SIP_FACTORY { return new QgsLinearReferencingSymbolLayerWidget( vl ); } + + void setSymbolLayer( QgsSymbolLayer *layer ) override; + QgsSymbolLayer *symbolLayer() override; + void setContext( const QgsSymbolWidgetContext &context ) override; + + private slots: + void changeNumberFormat(); + + private: + + QgsLinearReferencingSymbolLayer *mLayer = nullptr; + bool mBlockChangesSignal = false; +}; + + #include "ui_qgsgeometrygeneratorwidgetbase.h" #include "qgis_gui.h" diff --git a/src/gui/symbology/qgssymbolselectordialog.cpp b/src/gui/symbology/qgssymbolselectordialog.cpp index dec8f4395a302..5f85c8126fa8d 100644 --- a/src/gui/symbology/qgssymbolselectordialog.cpp +++ b/src/gui/symbology/qgssymbolselectordialog.cpp @@ -814,9 +814,8 @@ void QgsSymbolSelectorWidget::duplicateLayer() void QgsSymbolSelectorWidget::changeLayer( QgsSymbolLayer *newLayer ) { SymbolLayerItem *item = currentLayerItem(); - QgsSymbolLayer *layer = item->layer(); - if ( layer->subSymbol() ) + if ( item->rowCount() > 0 ) { item->removeRow( 0 ); } diff --git a/src/ui/symbollayer/qgslinearreferencingsymbollayerwidgetbase.ui b/src/ui/symbollayer/qgslinearreferencingsymbollayerwidgetbase.ui new file mode 100644 index 0000000000000..e76b3cb7b9f17 --- /dev/null +++ b/src/ui/symbollayer/qgslinearreferencingsymbollayerwidgetbase.ui @@ -0,0 +1,439 @@ + + + QgsLinearReferencingSymbolLayerWidgetBase + + + + 0 + 0 + 388 + 419 + + + + Qt::FocusPolicy::WheelFocus + + + Linear Referencing Symbol Layer + + + + 0 + + + 0 + + + 0 + + + 9 + + + + + Number format + + + true + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + x + + + + + + + + 1 + 0 + + + + 6 + + + -99999999.989999994635582 + + + 99999999.989999994635582 + + + 0.200000000000000 + + + + + + + y + + + + + + + + 1 + 0 + + + + 6 + + + -99999999.989999994635582 + + + 99999999.989999994635582 + + + 0.200000000000000 + + + + + + + + 0 + 0 + + + + Qt::FocusPolicy::StrongFocus + + + + + + + + + Label offset + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + Text format + + + + + + + Average angle over + + + + + + + Qt::FocusPolicy::NoFocus + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Interval + + + + + + + 0 + + + + + + 1 + 0 + + + + 6 + + + 10000000.000000000000000 + + + 0.200000000000000 + + + 1.000000000000000 + + + false + + + + + + + + + + + + + + + + + + + 0 + + + + + + 1 + 0 + + + + 6 + + + 10000000.000000000000000 + + + 0.200000000000000 + + + 1.000000000000000 + + + true + + + + + + + + + + + + + + + + Customize + + + + + + + 0 + + + + + + 1 + 0 + + + + 6 + + + 10000000.000000000000000 + + + 0.200000000000000 + + + 1.000000000000000 + + + + + + + + 20 + 0 + + + + Qt::FocusPolicy::TabFocus + + + + + + + + + + + + + + + + Measure placement + + + + + + + Skip multiples of + + + + + + + Rotate labels to follow line direction + + + + + + + Quantity + + + + + + + + + + + + + Text format + + + + + + + 0 + + + + + Show marker symbols + + + + + + + + + + + + + + + + + QgsPropertyOverrideButton + QToolButton +
qgspropertyoverridebutton.h
+
+ + QgsDoubleSpinBox + QDoubleSpinBox +
qgsdoublespinbox.h
+
+ + QgsUnitSelectionWidget + QWidget +
qgsunitselectionwidget.h
+ 1 +
+ + QgsFontButton + QToolButton +
qgsfontbutton.h
+
+
+ + mComboPlacement + spinInterval + mIntervalDDBtn + mComboQuantity + mTextFormatButton + mNumberFormatPushButton + mSpinSkipMultiples + mSkipMultiplesDDBtn + mSpinLabelOffsetX + mLabelOffsetUnitWidget + mSpinLabelOffsetY + mCheckShowMarker + mShowMarkerDDBtn + mCheckRotate + mSpinAverageAngleLength + mAverageAngleUnit + mAverageAngleDDBtn + + + +