From 5e4b542d1e94619021faec52898f9e4e51343457 Mon Sep 17 00:00:00 2001 From: JuhoErvasti Date: Wed, 4 Dec 2024 09:28:54 +0200 Subject: [PATCH 01/15] Add extent buffer property to QgsSymbol --- python/PyQt6/core/auto_additions/qgssymbol.py | 7 ++++++ .../auto_generated/symbology/qgssymbol.sip.in | 22 +++++++++++++++++++ python/core/auto_additions/qgssymbol.py | 7 ++++++ .../auto_generated/symbology/qgssymbol.sip.in | 22 +++++++++++++++++++ src/core/symbology/qgssymbol.cpp | 15 +++++++++++++ src/core/symbology/qgssymbol.h | 20 +++++++++++++++++ src/core/symbology/qgssymbollayerutils.cpp | 2 ++ tests/src/python/test_qgssymbol.py | 7 ++++++ 8 files changed, 102 insertions(+) diff --git a/python/PyQt6/core/auto_additions/qgssymbol.py b/python/PyQt6/core/auto_additions/qgssymbol.py index 2f30f8a66584..80a96bdf33ca 100644 --- a/python/PyQt6/core/auto_additions/qgssymbol.py +++ b/python/PyQt6/core/auto_additions/qgssymbol.py @@ -4,6 +4,9 @@ QgsSymbol.Property.PropertyOpacity = QgsSymbol.Property.Opacity QgsSymbol.PropertyOpacity.is_monkey_patched = True QgsSymbol.PropertyOpacity.__doc__ = "Opacity" +QgsSymbol.ExtentBuffer = QgsSymbol.Property.ExtentBuffer +QgsSymbol.ExtentBuffer.is_monkey_patched = True +QgsSymbol.ExtentBuffer.__doc__ = "Extent buffer \n.. versionadded:: 3.42" QgsSymbol.Property.__doc__ = """Data definable properties. .. versionadded:: 3.18 @@ -12,6 +15,10 @@ Available as ``QgsSymbol.PropertyOpacity`` in older QGIS releases. +* ``ExtentBuffer``: Extent buffer + + .. versionadded:: 3.42 + """ # -- diff --git a/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in b/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in index c04f05b5114a..8f3a64d9858b 100644 --- a/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in +++ b/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in @@ -244,6 +244,7 @@ Returns the default symbol type required for the specified geometry ``type``. enum class Property /BaseType=IntEnum/ { Opacity, + ExtentBuffer, }; static const QgsPropertiesDefinition &propertyDefinitions(); @@ -863,6 +864,26 @@ Internally, this notifies all symbol layers which were used via a call to :py:func:`QgsSymbolLayer.stopFeatureRender()`. .. versionadded:: 3.20 +%End + + double extentBuffer() const; +%Docstring +Returns the symbol's extent buffer. + +.. versionadded:: 3.42 + +:return: The symbol's extent buffer +%End + + void setExtentBuffer( double extentBuffer ); +%Docstring +Sets the symbol's extent buffer. + +.. versionadded:: 3.42 + +:param extentBuffer: buffer distance in map units + +.. seealso:: :py:func:`extentBuffer` %End protected: @@ -934,6 +955,7 @@ Render editing vertex marker at specified point + private: QgsSymbol( const QgsSymbol & ); }; diff --git a/python/core/auto_additions/qgssymbol.py b/python/core/auto_additions/qgssymbol.py index 2f30f8a66584..80a96bdf33ca 100644 --- a/python/core/auto_additions/qgssymbol.py +++ b/python/core/auto_additions/qgssymbol.py @@ -4,6 +4,9 @@ QgsSymbol.Property.PropertyOpacity = QgsSymbol.Property.Opacity QgsSymbol.PropertyOpacity.is_monkey_patched = True QgsSymbol.PropertyOpacity.__doc__ = "Opacity" +QgsSymbol.ExtentBuffer = QgsSymbol.Property.ExtentBuffer +QgsSymbol.ExtentBuffer.is_monkey_patched = True +QgsSymbol.ExtentBuffer.__doc__ = "Extent buffer \n.. versionadded:: 3.42" QgsSymbol.Property.__doc__ = """Data definable properties. .. versionadded:: 3.18 @@ -12,6 +15,10 @@ Available as ``QgsSymbol.PropertyOpacity`` in older QGIS releases. +* ``ExtentBuffer``: Extent buffer + + .. versionadded:: 3.42 + """ # -- diff --git a/python/core/auto_generated/symbology/qgssymbol.sip.in b/python/core/auto_generated/symbology/qgssymbol.sip.in index ad492e708570..488e3d512e86 100644 --- a/python/core/auto_generated/symbology/qgssymbol.sip.in +++ b/python/core/auto_generated/symbology/qgssymbol.sip.in @@ -244,6 +244,7 @@ Returns the default symbol type required for the specified geometry ``type``. enum class Property { Opacity, + ExtentBuffer, }; static const QgsPropertiesDefinition &propertyDefinitions(); @@ -863,6 +864,26 @@ Internally, this notifies all symbol layers which were used via a call to :py:func:`QgsSymbolLayer.stopFeatureRender()`. .. versionadded:: 3.20 +%End + + double extentBuffer() const; +%Docstring +Returns the symbol's extent buffer. + +.. versionadded:: 3.42 + +:return: The symbol's extent buffer +%End + + void setExtentBuffer( double extentBuffer ); +%Docstring +Sets the symbol's extent buffer. + +.. versionadded:: 3.42 + +:param extentBuffer: buffer distance in map units + +.. seealso:: :py:func:`extentBuffer` %End protected: @@ -934,6 +955,7 @@ Render editing vertex marker at specified point + private: QgsSymbol( const QgsSymbol & ); }; diff --git a/src/core/symbology/qgssymbol.cpp b/src/core/symbology/qgssymbol.cpp index 583625f8e34a..01b0635cd929 100644 --- a/src/core/symbology/qgssymbol.cpp +++ b/src/core/symbology/qgssymbol.cpp @@ -20,12 +20,14 @@ #include #include +#include #include #include #include #include "qgssymbol.h" #include "qgspolyhedralsurface.h" +#include "qgsrectangle.h" #include "qgssymbollayer.h" #include "qgsgeometrygeneratorsymbollayer.h" @@ -2223,6 +2225,17 @@ QgsSymbolRenderContext *QgsSymbol::symbolRenderContext() return mSymbolRenderContext.get(); } +double QgsSymbol::extentBuffer() const +{ + return mExtentBuffer; +} + +void QgsSymbol::setExtentBuffer( double extentBuffer ) +{ + mExtentBuffer = extentBuffer; +} + + void QgsSymbol::renderVertexMarker( QPointF pt, QgsRenderContext &context, Qgis::VertexMarkerType currentVertexMarkerType, double currentVertexMarkerSize ) { int markerSize = context.convertToPainterUnits( currentVertexMarkerSize, Qgis::RenderUnit::Millimeters ); @@ -2239,6 +2252,7 @@ void QgsSymbol::initPropertyDefinitions() sPropertyDefinitions = QgsPropertiesDefinition { { static_cast< int >( QgsSymbol::Property::Opacity ), QgsPropertyDefinition( "alpha", QObject::tr( "Opacity" ), QgsPropertyDefinition::Opacity, origin )}, + { static_cast< int >( QgsSymbol::Property::ExtentBuffer ), QgsPropertyDefinition( "extent_buffer", QObject::tr( "Extent buffer" ), QgsPropertyDefinition::Double, origin )}, }; } @@ -2298,6 +2312,7 @@ void QgsSymbol::copyCommonProperties( const QgsSymbol *other ) mDataDefinedProperties = other->mDataDefinedProperties; mSymbolFlags = other->mSymbolFlags; mAnimationSettings = other->mAnimationSettings; + mExtentBuffer = other->mExtentBuffer; if ( other->mBufferSettings ) mBufferSettings = std::make_unique< QgsSymbolBufferSettings >( *other->mBufferSettings ); else diff --git a/src/core/symbology/qgssymbol.h b/src/core/symbology/qgssymbol.h index 8007a43876c9..3c8c1cb88e19 100644 --- a/src/core/symbology/qgssymbol.h +++ b/src/core/symbology/qgssymbol.h @@ -269,6 +269,7 @@ class CORE_EXPORT QgsSymbol enum class Property SIP_MONKEYPATCH_SCOPEENUM_UNNEST( QgsSymbol, Property ) : int { Opacity SIP_MONKEYPATCH_COMPAT_NAME( PropertyOpacity ), //!< Opacity + ExtentBuffer, //!< Extent buffer \since QGIS 3.42 }; // *INDENT-ON* @@ -867,6 +868,23 @@ class CORE_EXPORT QgsSymbol */ void stopFeatureRender( const QgsFeature &feature, QgsRenderContext &context, int layer = -1 ); + /** + * Returns the symbol's extent buffer. + * + * \since QGIS 3.42 + * \returns The symbol's extent buffer + */ + double extentBuffer() const; + + /** + * Sets the symbol's extent buffer. + * + * \since QGIS 3.42 + * \param extentBuffer buffer distance in map units + * \see extentBuffer() + */ + void setExtentBuffer( double extentBuffer ); + protected: /** @@ -955,6 +973,8 @@ class CORE_EXPORT QgsSymbol Qgis::SymbolType mType; QgsSymbolLayerList mLayers; + double mExtentBuffer = 0; + //! Symbol opacity (in the range 0 - 1) qreal mOpacity = 1.0; diff --git a/src/core/symbology/qgssymbollayerutils.cpp b/src/core/symbology/qgssymbollayerutils.cpp index 99784f691626..df7bcc65e933 100644 --- a/src/core/symbology/qgssymbollayerutils.cpp +++ b/src/core/symbology/qgssymbollayerutils.cpp @@ -1334,6 +1334,7 @@ QgsSymbol *QgsSymbolLayerUtils::loadSymbol( const QDomElement &element, const Qg symbol->setMapUnitScale( mapUnitScale ); } symbol->setOpacity( element.attribute( QStringLiteral( "alpha" ), QStringLiteral( "1.0" ) ).toDouble() ); + symbol->setExtentBuffer( element.attribute( QStringLiteral( "extent_buffer" ), QStringLiteral( "0.0" ) ).toDouble() ); symbol->setClipFeaturesToExtent( element.attribute( QStringLiteral( "clip_to_extent" ), QStringLiteral( "1" ) ).toInt() ); symbol->setForceRHR( element.attribute( QStringLiteral( "force_rhr" ), QStringLiteral( "0" ) ).toInt() ); Qgis::SymbolFlags flags; @@ -1452,6 +1453,7 @@ QDomElement QgsSymbolLayerUtils::saveSymbol( const QString &name, const QgsSymbo symEl.setAttribute( QStringLiteral( "name" ), name ); symEl.setAttribute( QStringLiteral( "alpha" ), QString::number( symbol->opacity() ) ); symEl.setAttribute( QStringLiteral( "clip_to_extent" ), symbol->clipFeaturesToExtent() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); + symEl.setAttribute( QStringLiteral( "extent_buffer" ), QString::number( symbol->extentBuffer() ) ); symEl.setAttribute( QStringLiteral( "force_rhr" ), symbol->forceRHR() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); if ( symbol->flags() & Qgis::SymbolFlag::RendererShouldUseSymbolLevels ) symEl.setAttribute( QStringLiteral( "renderer_should_use_levels" ), QStringLiteral( "1" ) ); diff --git a/tests/src/python/test_qgssymbol.py b/tests/src/python/test_qgssymbol.py index 39265c797e48..bc9c814aa3d2 100644 --- a/tests/src/python/test_qgssymbol.py +++ b/tests/src/python/test_qgssymbol.py @@ -921,6 +921,13 @@ def test_animation_settings(self): self.assertTrue(s3.animationSettings().isAnimated()) self.assertEqual(s3.animationSettings().frameRate(), 30) + def test_extent_buffer(self): + s = QgsFillSymbol() + self.assertEqual(s.extentBuffer(), 0) + + s.setExtentBuffer(10) + self.assertEqual(s.extentBuffer(), 10) + def renderCollection(self, geom, symbol): f = QgsFeature() f.setGeometry(geom) From d19bd8d87788db26170a8f76b07349d0dde503c2 Mon Sep 17 00:00:00 2001 From: JuhoErvasti Date: Wed, 4 Dec 2024 09:30:43 +0200 Subject: [PATCH 02/15] Add maximumExtentBuffer() function to QgsFeatureRenderer --- .../symbology/qgsrenderer.sip.in | 12 +++++++ .../symbology/qgsrenderer.sip.in | 12 +++++++ src/core/symbology/qgsrenderer.cpp | 33 +++++++++++++++++++ src/core/symbology/qgsrenderer.h | 9 +++++ 4 files changed, 66 insertions(+) diff --git a/python/PyQt6/core/auto_generated/symbology/qgsrenderer.sip.in b/python/PyQt6/core/auto_generated/symbology/qgsrenderer.sip.in index 3ef1a60e7918..d1820bf91d54 100644 --- a/python/PyQt6/core/auto_generated/symbology/qgsrenderer.sip.in +++ b/python/PyQt6/core/auto_generated/symbology/qgsrenderer.sip.in @@ -628,6 +628,18 @@ Currently clones :param destRenderer: destination renderer for copied effect .. versionadded:: 3.22 +%End + + double maximumExtentBuffer( QgsRenderContext &context ) const; +%Docstring +Returns the maximum extent buffer found in this renderer's +symbols' symbol layers. + +.. note:: + + Returns 0 if the renderer doesn't have any symbols. + +.. versionadded:: 3.42 %End protected: diff --git a/python/core/auto_generated/symbology/qgsrenderer.sip.in b/python/core/auto_generated/symbology/qgsrenderer.sip.in index c17fb32bd6d4..a704df505a77 100644 --- a/python/core/auto_generated/symbology/qgsrenderer.sip.in +++ b/python/core/auto_generated/symbology/qgsrenderer.sip.in @@ -628,6 +628,18 @@ Currently clones :param destRenderer: destination renderer for copied effect .. versionadded:: 3.22 +%End + + double maximumExtentBuffer( QgsRenderContext &context ) const; +%Docstring +Returns the maximum extent buffer found in this renderer's +symbols' symbol layers. + +.. note:: + + Returns 0 if the renderer doesn't have any symbols. + +.. versionadded:: 3.42 %End protected: diff --git a/src/core/symbology/qgsrenderer.cpp b/src/core/symbology/qgsrenderer.cpp index c6fb1aed2724..ac3b980e8859 100644 --- a/src/core/symbology/qgsrenderer.cpp +++ b/src/core/symbology/qgsrenderer.cpp @@ -38,6 +38,7 @@ #include #include #include +#include QgsPropertiesDefinition QgsFeatureRenderer::sPropertyDefinitions; @@ -417,6 +418,38 @@ QgsLegendSymbolList QgsFeatureRenderer::legendSymbolItems() const return QgsLegendSymbolList(); } +double QgsFeatureRenderer::maximumExtentBuffer( QgsRenderContext &context ) const +{ + const QgsSymbolList symbolList = symbols( context ); + + if ( symbolList.empty() ) + return 0; + + const QgsExpressionContext &expContext = context.expressionContext(); + + auto getValueFromSymbol = [ &expContext ]( const QgsSymbol * sym ) -> double + { + const QgsProperty property = sym->dataDefinedProperties().property( QgsSymbol::Property::ExtentBuffer ); + + if ( property.isActive() && ! property.expressionString().isEmpty() ) + { + return sym->dataDefinedProperties().valueAsDouble( QgsSymbol::Property::ExtentBuffer, expContext ); + } + + return sym->extentBuffer(); + }; + + if ( symbolList.size() == 1 ) + return getValueFromSymbol( symbolList[0] ); + + auto it = std::max_element( symbolList.constBegin(), symbolList.constEnd(), [ &getValueFromSymbol ]( const QgsSymbol * a, const QgsSymbol * b ) -> bool + { + return getValueFromSymbol( a ) < getValueFromSymbol( b ); + } ); + + return getValueFromSymbol( *it ); +} + QList QgsFeatureRenderer::createLegendNodes( QgsLayerTreeLayer *nodeLayer ) const { QList nodes; diff --git a/src/core/symbology/qgsrenderer.h b/src/core/symbology/qgsrenderer.h index 7adc935214bd..431a8635ca9c 100644 --- a/src/core/symbology/qgsrenderer.h +++ b/src/core/symbology/qgsrenderer.h @@ -653,6 +653,15 @@ class CORE_EXPORT QgsFeatureRenderer */ void copyRendererData( QgsFeatureRenderer *destRenderer ) const; + /** + * Returns the maximum extent buffer found in this renderer's + * symbols' symbol layers. + * + * \note Returns 0 if the renderer doesn't have any symbols. + * \since QGIS 3.42 + */ + double maximumExtentBuffer( QgsRenderContext &context ) const; + protected: QgsFeatureRenderer( const QString &type ); From 8496dcdddf19ba21d193369435e30c167ab50acd Mon Sep 17 00:00:00 2001 From: JuhoErvasti Date: Wed, 4 Dec 2024 09:31:33 +0200 Subject: [PATCH 03/15] Add tests for maximumExtentBuffer() --- .../test_qgscategorizedsymbolrenderer.py | 36 +++++++++++++++++++ .../python/test_qgssinglesymbolrenderer.py | 18 ++++++++++ 2 files changed, 54 insertions(+) diff --git a/tests/src/python/test_qgscategorizedsymbolrenderer.py b/tests/src/python/test_qgscategorizedsymbolrenderer.py index fb230c3c92cf..21633dca57c5 100644 --- a/tests/src/python/test_qgscategorizedsymbolrenderer.py +++ b/tests/src/python/test_qgscategorizedsymbolrenderer.py @@ -1405,6 +1405,42 @@ def test_to_sld(self): """, ) + def testMaximumExtentBuffer(self): + # setup renderer + + fields = QgsFields() + fields.append(QgsField("x")) + + renderer = QgsCategorizedSymbolRenderer() + renderer.setClassAttribute("x") + + symbol_a = createMarkerSymbol() + symbol_a.setColor(QColor(255, 0, 0)) + symbol_a.setExtentBuffer(10) + renderer.addCategory(QgsRendererCategory("a", symbol_a, "a")) + + symbol_b = createMarkerSymbol() + symbol_b.setColor(QColor(0, 255, 0)) + symbol_b.setExtentBuffer(-10) + renderer.addCategory(QgsRendererCategory("b", symbol_b, "b")) + + symbol_c = createMarkerSymbol() + symbol_c.setColor(QColor(0, 0, 255)) + symbol_c.setExtentBuffer(20) + renderer.addCategory(QgsRendererCategory("c", symbol_c, "c", False)) + + symbol_d = createMarkerSymbol() + symbol_d.setColor(QColor(255, 0, 255)) + symbol_d.setExtentBuffer(15) + renderer.addCategory(QgsRendererCategory(["d", "e"], symbol_d, "de")) + + # add default category + + default_symbol = createMarkerSymbol() + default_symbol.setColor(QColor(255, 255, 255)) + renderer.addCategory(QgsRendererCategory("", default_symbol, "default")) + + self.assertEqual(renderer.maximumExtentBuffer(QgsRenderContext()), 20) if __name__ == "__main__": unittest.main() diff --git a/tests/src/python/test_qgssinglesymbolrenderer.py b/tests/src/python/test_qgssinglesymbolrenderer.py index 9eab10ecdc51..768fdbc3427a 100644 --- a/tests/src/python/test_qgssinglesymbolrenderer.py +++ b/tests/src/python/test_qgssinglesymbolrenderer.py @@ -114,6 +114,24 @@ def test_legend_key_to_expression(self): exp, ok = renderer.legendKeyToExpression("xxxx", None) self.assertFalse(ok) + def test_maximum_extent_buffer(self): + sym1 = QgsFillSymbol.createSimple( + {"color": "#fdbf6f", "outline_color": "black"} + ) + + renderer1 = QgsSingleSymbolRenderer(sym1) + + self.assertEqual(renderer1.maximumExtentBuffer(QgsRenderContext()), 0) + + sym2 = QgsFillSymbol.createSimple( + {"color": "#fdbf6f", "outline_color": "black"} + ) + sym2.setExtentBuffer(100) + + renderer2 = QgsSingleSymbolRenderer(sym2) + + self.assertEqual(renderer2.maximumExtentBuffer(QgsRenderContext()), 100) + if __name__ == "__main__": unittest.main() From ab49e82ab647ba5beb6c1ac50f617170d3119d4f Mon Sep 17 00:00:00 2001 From: JuhoErvasti Date: Wed, 4 Dec 2024 09:32:49 +0200 Subject: [PATCH 04/15] Modify feature request extent based on max extent buffer --- src/core/vector/qgsvectorlayerrenderer.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/core/vector/qgsvectorlayerrenderer.cpp b/src/core/vector/qgsvectorlayerrenderer.cpp index 4cf56ac980ef..69048c0f0c26 100644 --- a/src/core/vector/qgsvectorlayerrenderer.cpp +++ b/src/core/vector/qgsvectorlayerrenderer.cpp @@ -17,6 +17,7 @@ #include "qgsmessagelog.h" #include "qgspallabeling.h" +#include "qgsrectangle.h" #include "qgsrenderer.h" #include "qgsrendercontext.h" #include "qgssinglesymbolrenderer.h" @@ -354,8 +355,26 @@ bool QgsVectorLayerRenderer::renderInternal( QgsFeatureRenderer *renderer, int r if ( mDiagramProvider ) mDiagramProvider->setClipFeatureGeometry( mLabelClipFeatureGeom ); } + + renderer->modifyRequestExtent( requestExtent, context ); + double maximumExtentBuffer = renderer->maximumExtentBuffer( context ); + + if ( maximumExtentBuffer != 0 ) + { + bool bufferDisappearsExtent = maximumExtentBuffer < 0 && ( requestExtent.width() + ( maximumExtentBuffer * 2 ) < 0 || requestExtent.height() + ( maximumExtentBuffer * 2 ) < 0 ); + + // nothing to draw + if ( bufferDisappearsExtent ) + { + renderer->stopRender( context ); + return true; + } + + requestExtent = requestExtent.buffered( maximumExtentBuffer ); + } + QgsFeatureRequest featureRequest = QgsFeatureRequest() .setFilterRect( requestExtent ) .setSubsetOfAttributes( mAttrNames, mFields ) From 984a9475f0ce9a8a76e8f459f883f9b5dfb78e9d Mon Sep 17 00:00:00 2001 From: JuhoErvasti Date: Wed, 4 Dec 2024 09:33:51 +0200 Subject: [PATCH 05/15] Add tests for rendering with extent buffer --- .../src/python/test_qgsvectorlayerrenderer.py | 100 ++++++++++++++++++ .../expected_buffer_extent.png | Bin 0 -> 4825 bytes .../expected_buffer_extent_zero.png | Bin 0 -> 3618 bytes .../expected_negative_buffer_extent.png | Bin 0 -> 9219 bytes 4 files changed, 100 insertions(+) create mode 100644 tests/testdata/control_images/vectorlayerrenderer/expected_buffer_extent/expected_buffer_extent.png create mode 100644 tests/testdata/control_images/vectorlayerrenderer/expected_buffer_extent_zero/expected_buffer_extent_zero.png create mode 100644 tests/testdata/control_images/vectorlayerrenderer/expected_negative_buffer_extent/expected_negative_buffer_extent.png diff --git a/tests/src/python/test_qgsvectorlayerrenderer.py b/tests/src/python/test_qgsvectorlayerrenderer.py index 67644fc8a3b9..eace39b992ad 100644 --- a/tests/src/python/test_qgsvectorlayerrenderer.py +++ b/tests/src/python/test_qgsvectorlayerrenderer.py @@ -16,21 +16,27 @@ from qgis.PyQt.QtGui import QColor from qgis.core import ( + edit, Qgis, QgsCategorizedSymbolRenderer, QgsCentroidFillSymbolLayer, QgsCoordinateReferenceSystem, + QgsFeature, QgsFeatureRendererGenerator, QgsFillSymbol, QgsGeometry, + QgsGeometryGeneratorSymbolLayer, QgsLineSymbol, QgsMapClippingRegion, QgsMapSettings, QgsMarkerSymbol, + QgsPointXY, QgsRectangle, QgsRendererCategory, QgsRuleBasedRenderer, QgsSingleSymbolRenderer, + QgsSymbol, + QgsRenderContext, QgsVectorLayer, ) import unittest @@ -927,6 +933,100 @@ def testRenderWithSelectedFeatureSymbol(self): ) ) + def testRenderWithExtentBuffer(self): + def createFeature(x: float, y: float) -> QgsFeature: + feat = QgsFeature() + feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y))) + + return feat + + def createSymbol() -> QgsMarkerSymbol: + sym = QgsMarkerSymbol.createSimple({'color': '#33aa33', 'outline_style': 'no', 'size': '5'}) + return sym + + def createGeometryGenerator() -> QgsGeometryGeneratorSymbolLayer: + geomgen = QgsGeometryGeneratorSymbolLayer.create( + {'geometryModifier': 'make_point($x + if($x <= 0, 5, -5), $y)'} + ) + geomgen.setSymbolType(QgsSymbol.SymbolType.Marker) + geomgen.setSubSymbol(QgsMarkerSymbol.createSimple({'color': '#ff00ff', 'outline_style': 'no', 'size': '6'})) + + return geomgen + + point_layer = QgsVectorLayer("Point?crs=EPSG:3857", "point layer", "memory") + + with edit(point_layer): + point_layer.addFeature(createFeature(-15.999, 8)) + point_layer.addFeature(createFeature(-16.001, 6)) + point_layer.addFeature(createFeature(-13, 4)) + point_layer.addFeature(createFeature(-10, 2)) + point_layer.addFeature(createFeature(17, 0)) + point_layer.addFeature(createFeature(4, -2)) + point_layer.addFeature(createFeature(15.999, -4)) + point_layer.addFeature(createFeature(17, -6)) + point_layer.addFeature(createFeature(15.999, -8)) + + sym1 = createSymbol() + sym1.appendSymbolLayer(createGeometryGenerator()) + + renderer1 = QgsSingleSymbolRenderer(sym1) + point_layer.setRenderer(renderer1) + + mapsettings = QgsMapSettings() + mapsettings.setOutputSize(QSize(400, 400)) + mapsettings.setOutputDpi(96) + mapsettings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3857')) + mapsettings.setExtent(QgsRectangle(-10, -10, 10, 10)) + mapsettings.setLayers([point_layer]) + + self.assertTrue( + self.render_map_settings_check( + 'buffer_extent_zero', + 'buffer_extent_zero', + mapsettings, + ) + ) + + sym2 = createSymbol() + sym2.appendSymbolLayer(createGeometryGenerator()) + sym2.setExtentBuffer(1) + + renderer2 = QgsSingleSymbolRenderer(sym2) + point_layer.setRenderer(renderer2) + + self.assertTrue( + self.render_map_settings_check( + 'buffer_extent', + 'buffer_extent', + mapsettings, + ) + ) + + def testRenderWithExtentBufferNegative(self): + poly_layer = QgsVectorLayer(os.path.join(TEST_DATA_DIR, 'polys.shp')) + self.assertTrue(poly_layer.isValid()) + + sym1 = QgsFillSymbol.createSimple({'color': '#ff00ff', 'outline_color': '#000000', 'outline_width': '1'}) + sym1.setExtentBuffer(-13.5) + + renderer = QgsSingleSymbolRenderer(sym1) + poly_layer.setRenderer(renderer) + + mapsettings = QgsMapSettings() + mapsettings.setOutputSize(QSize(400, 400)) + mapsettings.setOutputDpi(96) + mapsettings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3857')) + mapsettings.setExtent(QgsRectangle(-13875783.2, 2266009.4, -8690110.7, 6673344.5)) + mapsettings.setLayers([poly_layer]) + + self.assertTrue( + self.render_map_settings_check( + 'negative_buffer_extent', + 'negative_buffer_extent', + mapsettings + ) + ) + if __name__ == "__main__": unittest.main() diff --git a/tests/testdata/control_images/vectorlayerrenderer/expected_buffer_extent/expected_buffer_extent.png b/tests/testdata/control_images/vectorlayerrenderer/expected_buffer_extent/expected_buffer_extent.png new file mode 100644 index 0000000000000000000000000000000000000000..9380ffb80814e74628dd2c91d176bf825b8b93aa GIT binary patch literal 4825 zcmcIoc{r49+rKAkF-Rd>)F=tb9?6z6RJ^t@LhFnrhQ`<@Qz9gWqO2__OJfw0tkH%s zgc)Re>|4k(WPdO99B=RQJn&t zLNzl8MO#>$D6Z@D1;H&`{CO{6>kwJ~a^6=pl!KexL&f5<`G_NY@^Uuk3qsz8y&Bmo zHG>$5IDFXjId2VNr-1DOHx50tn%#wsQtu{U(6m&~HTy*oPKX#*Y^DHQ2$zIGa(W~P zIxUHWAXz>sh$z6x4^?hq-3*;&yUq&bZPMEW*<1r=&vgIFOn>hf+kHlHuai^vr^?4U zx!HG@2QnE#PDPUBXq5uZog;Og-A0_yOQVyd83DuU=2V(CK4Z$i9~n!dy^ckYPpVj! zkGf(=2Vq3{WU11s(r<6+BNLIOWz|9N8;l#K7=?iuTf3_u=%(N@1}U`DSEPiFij=D9 zxbxoql2`9FAsTomAC4zQMBBp~3WV|xp5S*+0v+pl1kz5G9 zVh6S|&OnuvcS^{T5Z`(>Xayw~T%gTQTf|A|CvrjG3c&_=tu~#?4lzTNcz?V&WUwb* zZ|RCwW9^OhoP@y|_9>%HL{9L1EC^CvLt?@pCL=?phk6jI#D=?(yxCcfRM@F2jklL+ zDvYxb!``VOV+#T^9$iVYRvVOYl)ZBq4G}ZnX(D6iW1B~1R8X!;w>c5P#BYRbk{e`+jEyLQal-b!Yn z#*D1c=&py7aCX9LHQwvjYpG);B94-xY|8tbd_On0Kh2%9$BG#>wcA=>NskK;tyL#X zaP#klYKB)zW>j0SB`AUgR&99BeL3W%>y*`O(cM}9mXyO;&0RwHCkwGx61orGJA1ot zn{`@|^2|KXy0DHcNL1@g+;Lwn7KrPHV7hL(10gWIm40)H@olA9r?q!yb8(ewijJpM zRO!L^^C4WkTJVOIwH}Jr;P@cC=a&em^1vh3QCET;4m*|kBHwGIfqUr0>$zc_B}B8E zzebF#wzulM&$5r9&?GK{RkAp8>IK(8kr=`#`-`0OflgqP!w{G>m z_Jm9np}8^QF}=7~#iv==G8s=Veq+T?98g?xTSVg~E8*NXr^(Z{AV6^PKGe}!8t+T* zA{kPsAm@gWNV-plLL}9;LrGcM1&~SX#wUrUGf-IUC+y3K&jASB%Xvm5H5~PwSdzx* z2fG&S&_jXBz`EG9w-d$XdH^!0aUxxDh)&QDM-Lg08A7yOJI>X!5%&Q)`e z93L`pE8?RRLwtME^fbE&pFSpA90AsD1gT^T0qq+qLeS%jDoBjKLRR$=1|yPZ;XZ9H zN)%;QaQZ{=Dobg-XF6Tcy&ZeG%6Z7;$&|34AyYr^YCZA>Z z{SOM0Y#yx)tVf?LI(RxCv|eXZ%!9R&1@n3Ywc%fqUo({f72I^|SPuWJ5Ek&!vxtxJvmgUreMp8edy-{Dv-H9vG*-4L*A-+Wz)x*P4B2$nqH(n3KJW zPdnR;T~v&em3O?#!hF=GJ>&Z&9X(b2KTr|xK6MBW%06+7D2w==@P8(NLep*dU&ZI9@Ey|#5 za4;#0{lP%4oG+S~&fksEFzb+i>w18A5qNLn!=`6cOCLMMVV7p|(P&VbocG&eR$-iuF2MsI zn=^FQoi&V?pAw`e1hyXzzLwfU7ENG&aY(txggM!|1WOqOU3`sSq&UYdQPyV?%x?yq z%vc+Lz0&Hn+wsUQqdIGm{Y8G4EK+9*m1@xJ5cvdinY5O(_`c1L>{iyu=uUL$d3kqy z#0*5ign6bVp?mtRmhzH7_YN*e&e{|2_665O_`Tv!JxCe|3xeHHx$1=?cWkIg&>2h z|5z2xuSxET`wxBFxhtTxtX;o^jQit)myZOsLp=%SU$m;;5GCFxn zvpK-1_$0Qa&Y+4n z>v0O;CN8aKeQNWHkecZbAIo-I%{dJF>>m52S&7w{ zQ6A4rNG|X8T>{`ZX+BgfCGoNpv+}+j372l^UaeD%O|PZJ4s~~%JsfaQ?Hd8!RDCUI zZBkAP_6;!4y4`VA&7V1KR;T#cy4|)ZCpwy&!s%>4BKf9>n&8w^^xoN8r-;_PGR~%} zGhZ*|k%fU$S><3wP+OZKBzH}R4y8z3o^ZF2gv(1Ikf1X1bXI!o=n;9qMB};}BS@!N zH;1d|?V#z*u91dz7XGV3a*W1BuBQ=Mya34;GTmCcMHBK1i_jN&nr?wruRN$KE)-;R zrk$QGqEZ*p=pT$%c$;90Su{4n!=Li4#_AA566CkC_tb-hg;tw~i<;ut*!{2xd3 zCTD-$m0oN(;k9rKfinT01kde8B1?bLTTZy>3y@>LuV-Xo@kzy$QE~Nx-RS~)`h5hA zolcjkzR;;N7ZHk6M*l7VhgL=15J+Um%qhpK462-tto*4x<6mCRO6R&9bX+1g@C=;* z@}1o)uRt46n8?-P!%DP2$WppCq0iE=szhoW%!)bx<=Eh^9d4Mq|+%!PG=gG1#)^vZNUxvUQpiEmw zJ3SIhM!A8;jdg|%Tk&r!%3Xh$qhf8qhxPl+0~MSFwTJQxz-6d>Y7RS*btL<}t9u(b zw}I5Jbp|fF@m zgLMxbY8u;7^aYG=-+qoe$1nHXF#tCDgGuEp&k}5}o<=3k)N|j0Hr$`!X7|5ZrUeWEq!dPylYNqBy%6qHv9ltXZ`)vlZ{jL8UD#+Q|D?Xklz z`-W&hHd4P&^aVoFWCb{lP3DFgUQb&CGXj0cQ@5~Owur-DY~O`^W6=5k zPRxIo%x`pJ5OHb)uZcb?d7w1yAJo?PKC&Ul5pZ%82be6w{1c9E1PS?9lg?hmNm_-h zx1e1CO}bnN9P+wd59|IjaUQ4e(zo=h%lO&Pk(MFZ14L^ zNPZq-BZ=qVeaH>PeCDeC8u_&Ej&D%LhX5wThXc6(bQryJIJ%p*&(SlI8Ue!bUtv?H z?Q_=?T@e(xw;&2~%Z1JgbV@XJ=DowwArfRy_pSed#(`g_@gHbJIUeE|yrHUo8C-Ad zeqk5Z+L;HU5cY8id1$zS!0G%0&li6?j6Y;^jN(8g91Kv+*J=-&2#DRld}d(ty$7nw zbq&TREXotkZ3S-dJxY0_3vMcIQ~kXuJIC)aH;D9*kdTA` literal 0 HcmV?d00001 diff --git a/tests/testdata/control_images/vectorlayerrenderer/expected_buffer_extent_zero/expected_buffer_extent_zero.png b/tests/testdata/control_images/vectorlayerrenderer/expected_buffer_extent_zero/expected_buffer_extent_zero.png new file mode 100644 index 0000000000000000000000000000000000000000..ff30b65d25a48330cdc1a89fb396051d71e8e90a GIT binary patch literal 3618 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 Date: Thu, 5 Dec 2024 14:28:46 +0200 Subject: [PATCH 06/15] Add test for loading symbol with extent buffer --- tests/src/python/test_qgssymbollayerutils.py | 57 ++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/src/python/test_qgssymbollayerutils.py b/tests/src/python/test_qgssymbollayerutils.py index b4a1127e291a..bb55da07ab53 100644 --- a/tests/src/python/test_qgssymbollayerutils.py +++ b/tests/src/python/test_qgssymbollayerutils.py @@ -31,6 +31,7 @@ QgsSimpleFillSymbolLayer, QgsSimpleLineSymbolLayer, QgsSingleSymbolRenderer, + QgsSymbol, QgsSymbolLayer, QgsSymbolLayerUtils, QgsUnitTypes, @@ -1253,6 +1254,62 @@ def test_font_marker_load(self): font_marker = QgsSymbolLayerUtils.loadSymbol(elem, QgsReadWriteContext()) self.assertEqual(font_marker.symbolLayers()[0].character(), "()") + def test_extent_buffer_load(self): + doc = QDomDocument() + elem = QDomElement() + + extent_buffer_xml_string = """ + + + + + + + + +""" + + doc.setContent(extent_buffer_xml_string) + elem = doc.documentElement() + symbol = QgsSymbolLayerUtils.loadSymbol(elem, QgsReadWriteContext()) + self.assertEqual(symbol.extentBuffer(), 1000) + + property = symbol.dataDefinedProperties().property( + QgsSymbol.Property.ExtentBuffer + ) + + self.assertTrue(property.isActive()) + self.assertEqual( + property.expressionString(), "if(@map_scale <= 25000, 5000, 10000)" + ) + def test_collect_symbol_layer_clip_geometries(self): """ Test logic relating to symbol layer clip geometries. From f25a33b846f47d53ef8e82e66ef10c0884e7f70c Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Thu, 5 Dec 2024 14:31:08 +0200 Subject: [PATCH 07/15] Fix style --- .../auto_generated/symbology/qgssymbol.sip.in | 8 ++-- .../auto_generated/symbology/qgssymbol.sip.in | 8 ++-- src/core/symbology/qgssymbol.h | 4 +- .../test_qgscategorizedsymbolrenderer.py | 1 + .../src/python/test_qgsvectorlayerrenderer.py | 38 +++++++++++-------- 5 files changed, 34 insertions(+), 25 deletions(-) diff --git a/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in b/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in index 8f3a64d9858b..3a721d1779e5 100644 --- a/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in +++ b/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in @@ -870,20 +870,20 @@ Internally, this notifies all symbol layers which were used via a call to %Docstring Returns the symbol's extent buffer. -.. versionadded:: 3.42 - :return: The symbol's extent buffer + +.. versionadded:: 3.42 %End void setExtentBuffer( double extentBuffer ); %Docstring Sets the symbol's extent buffer. -.. versionadded:: 3.42 - :param extentBuffer: buffer distance in map units .. seealso:: :py:func:`extentBuffer` + +.. versionadded:: 3.42 %End protected: diff --git a/python/core/auto_generated/symbology/qgssymbol.sip.in b/python/core/auto_generated/symbology/qgssymbol.sip.in index 488e3d512e86..d67493cb2043 100644 --- a/python/core/auto_generated/symbology/qgssymbol.sip.in +++ b/python/core/auto_generated/symbology/qgssymbol.sip.in @@ -870,20 +870,20 @@ Internally, this notifies all symbol layers which were used via a call to %Docstring Returns the symbol's extent buffer. -.. versionadded:: 3.42 - :return: The symbol's extent buffer + +.. versionadded:: 3.42 %End void setExtentBuffer( double extentBuffer ); %Docstring Sets the symbol's extent buffer. -.. versionadded:: 3.42 - :param extentBuffer: buffer distance in map units .. seealso:: :py:func:`extentBuffer` + +.. versionadded:: 3.42 %End protected: diff --git a/src/core/symbology/qgssymbol.h b/src/core/symbology/qgssymbol.h index 3c8c1cb88e19..e112b6b49a53 100644 --- a/src/core/symbology/qgssymbol.h +++ b/src/core/symbology/qgssymbol.h @@ -871,17 +871,17 @@ class CORE_EXPORT QgsSymbol /** * Returns the symbol's extent buffer. * - * \since QGIS 3.42 * \returns The symbol's extent buffer + * \since QGIS 3.42 */ double extentBuffer() const; /** * Sets the symbol's extent buffer. * - * \since QGIS 3.42 * \param extentBuffer buffer distance in map units * \see extentBuffer() + * \since QGIS 3.42 */ void setExtentBuffer( double extentBuffer ); diff --git a/tests/src/python/test_qgscategorizedsymbolrenderer.py b/tests/src/python/test_qgscategorizedsymbolrenderer.py index 21633dca57c5..3cb84fd7b5a1 100644 --- a/tests/src/python/test_qgscategorizedsymbolrenderer.py +++ b/tests/src/python/test_qgscategorizedsymbolrenderer.py @@ -1442,5 +1442,6 @@ def testMaximumExtentBuffer(self): self.assertEqual(renderer.maximumExtentBuffer(QgsRenderContext()), 20) + if __name__ == "__main__": unittest.main() diff --git a/tests/src/python/test_qgsvectorlayerrenderer.py b/tests/src/python/test_qgsvectorlayerrenderer.py index eace39b992ad..515032ee554f 100644 --- a/tests/src/python/test_qgsvectorlayerrenderer.py +++ b/tests/src/python/test_qgsvectorlayerrenderer.py @@ -941,15 +941,21 @@ def createFeature(x: float, y: float) -> QgsFeature: return feat def createSymbol() -> QgsMarkerSymbol: - sym = QgsMarkerSymbol.createSimple({'color': '#33aa33', 'outline_style': 'no', 'size': '5'}) + sym = QgsMarkerSymbol.createSimple( + {"color": "#33aa33", "outline_style": "no", "size": "5"} + ) return sym def createGeometryGenerator() -> QgsGeometryGeneratorSymbolLayer: geomgen = QgsGeometryGeneratorSymbolLayer.create( - {'geometryModifier': 'make_point($x + if($x <= 0, 5, -5), $y)'} + {"geometryModifier": "make_point($x + if($x <= 0, 5, -5), $y)"} ) geomgen.setSymbolType(QgsSymbol.SymbolType.Marker) - geomgen.setSubSymbol(QgsMarkerSymbol.createSimple({'color': '#ff00ff', 'outline_style': 'no', 'size': '6'})) + geomgen.setSubSymbol( + QgsMarkerSymbol.createSimple( + {"color": "#ff00ff", "outline_style": "no", "size": "6"} + ) + ) return geomgen @@ -975,14 +981,14 @@ def createGeometryGenerator() -> QgsGeometryGeneratorSymbolLayer: mapsettings = QgsMapSettings() mapsettings.setOutputSize(QSize(400, 400)) mapsettings.setOutputDpi(96) - mapsettings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3857')) + mapsettings.setDestinationCrs(QgsCoordinateReferenceSystem("EPSG:3857")) mapsettings.setExtent(QgsRectangle(-10, -10, 10, 10)) mapsettings.setLayers([point_layer]) self.assertTrue( self.render_map_settings_check( - 'buffer_extent_zero', - 'buffer_extent_zero', + "buffer_extent_zero", + "buffer_extent_zero", mapsettings, ) ) @@ -996,17 +1002,19 @@ def createGeometryGenerator() -> QgsGeometryGeneratorSymbolLayer: self.assertTrue( self.render_map_settings_check( - 'buffer_extent', - 'buffer_extent', + "buffer_extent", + "buffer_extent", mapsettings, ) ) def testRenderWithExtentBufferNegative(self): - poly_layer = QgsVectorLayer(os.path.join(TEST_DATA_DIR, 'polys.shp')) + poly_layer = QgsVectorLayer(os.path.join(TEST_DATA_DIR, "polys.shp")) self.assertTrue(poly_layer.isValid()) - sym1 = QgsFillSymbol.createSimple({'color': '#ff00ff', 'outline_color': '#000000', 'outline_width': '1'}) + sym1 = QgsFillSymbol.createSimple( + {"color": "#ff00ff", "outline_color": "#000000", "outline_width": "1"} + ) sym1.setExtentBuffer(-13.5) renderer = QgsSingleSymbolRenderer(sym1) @@ -1015,15 +1023,15 @@ def testRenderWithExtentBufferNegative(self): mapsettings = QgsMapSettings() mapsettings.setOutputSize(QSize(400, 400)) mapsettings.setOutputDpi(96) - mapsettings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3857')) - mapsettings.setExtent(QgsRectangle(-13875783.2, 2266009.4, -8690110.7, 6673344.5)) + mapsettings.setDestinationCrs(QgsCoordinateReferenceSystem("EPSG:3857")) + mapsettings.setExtent( + QgsRectangle(-13875783.2, 2266009.4, -8690110.7, 6673344.5) + ) mapsettings.setLayers([poly_layer]) self.assertTrue( self.render_map_settings_check( - 'negative_buffer_extent', - 'negative_buffer_extent', - mapsettings + "negative_buffer_extent", "negative_buffer_extent", mapsettings ) ) From 75a6562fd1a5467d4b89b66f953e8808a29b48cf Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Thu, 5 Dec 2024 14:31:16 +0200 Subject: [PATCH 08/15] Add a way to change extent buffer in the GUI --- src/gui/CMakeLists.txt | 2 + src/gui/symbology/qgsextentbufferdialog.cpp | 128 +++++++++++++++++++ src/gui/symbology/qgsextentbufferdialog.h | 135 ++++++++++++++++++++ src/gui/symbology/qgssymbolslistwidget.cpp | 46 +++++++ src/gui/symbology/qgssymbolslistwidget.h | 2 + src/ui/qgsextentbufferdialogbase.ui | 92 +++++++++++++ 6 files changed, 405 insertions(+) create mode 100644 src/gui/symbology/qgsextentbufferdialog.cpp create mode 100644 src/gui/symbology/qgsextentbufferdialog.h create mode 100644 src/ui/qgsextentbufferdialogbase.ui diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 9e9aa88fa51e..a150742dd2a3 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -80,6 +80,7 @@ set(QGIS_GUI_SRCS symbology/qgssymbolbuffersettingswidget.cpp symbology/qgssymbollayerwidget.cpp symbology/qgssymbollevelsdialog.cpp + symbology/qgsextentbufferdialog.cpp symbology/qgssymbolslistwidget.cpp symbology/qgssymbolselectordialog.cpp symbology/qgssymbolwidgetcontext.cpp @@ -1551,6 +1552,7 @@ set(QGIS_GUI_HDRS symbology/qgssymbolbuffersettingswidget.h symbology/qgssymbollayerwidget.h symbology/qgssymbollevelsdialog.h + symbology/qgsextentbufferdialog.h symbology/qgssymbolselectordialog.h symbology/qgssymbolslistwidget.h symbology/qgssymbolwidgetcontext.h diff --git a/src/gui/symbology/qgsextentbufferdialog.cpp b/src/gui/symbology/qgsextentbufferdialog.cpp new file mode 100644 index 000000000000..375ce66799bc --- /dev/null +++ b/src/gui/symbology/qgsextentbufferdialog.cpp @@ -0,0 +1,128 @@ +/*************************************************************************** + qgsextentbufferdialog.cpp + --------------------- + begin : December 2024 + copyright : (C) 2024 by Juho Ervasti + email : juho dot ervasti at gispo dot fi + *************************************************************************** + * * + * 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 "qgsextentbufferdialog.h" +#include "moc_qgsextentbufferdialog.cpp" +#include "qdialogbuttonbox.h" +#include "qgsexpressioncontext.h" +#include "qgshelp.h" +#include "qgspanelwidget.h" +#include "qgssymbol.h" +#include "qgssymbolwidgetcontext.h" +#include "qgsvectorlayer.h" + +QgsExtentBufferWidget::QgsExtentBufferWidget( QgsSymbol *symbol, QgsVectorLayer *layer, QWidget *parent ) + : QgsPanelWidget( parent ), mSymbol( symbol ), mLayer( layer ) +{ + setupUi( this ); + + mExtentBufferSpinBox->setValue( mSymbol-> extentBuffer() ); + + connect( mExtentBufferSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, [ = ]() + { + emit widgetChanged(); + } ); + + registerDataDefinedButton( mExtentBufferDDButton, QgsSymbol::Property::ExtentBuffer ); +} + +QgsSymbolWidgetContext QgsExtentBufferWidget::context() const +{ + return mContext; +} + +void QgsExtentBufferWidget::setContext( const QgsSymbolWidgetContext &context ) +{ + mContext = context; +} + +void QgsExtentBufferWidget::registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsSymbol::Property key ) +{ + // pass in nullptr to avoid id, feature and geometry variables being added + // since the buffer is not evaluated per-feature + button->init( static_cast< int >( key ), mSymbol->dataDefinedProperties(), QgsSymbol::propertyDefinitions(), nullptr ); + connect( button, &QgsPropertyOverrideButton::changed, this, [ = ]() + { + emit widgetChanged(); + } ); + + button->registerExpressionContextGenerator( this ); +} + +QgsExpressionContext QgsExtentBufferWidget::createExpressionContext() const +{ + QList scopes = mContext.globalProjectAtlasMapLayerScopes( mLayer ); + QgsExpressionContext expContext( scopes ); + + return expContext; +} + +double QgsExtentBufferWidget::extentBuffer() const +{ + return mExtentBufferSpinBox->value(); +} + +QgsProperty QgsExtentBufferWidget::dataDefinedProperty() const +{ + return mExtentBufferDDButton->toProperty(); +} + + +/// QgsExtentBufferDialog + +QgsExtentBufferDialog::QgsExtentBufferDialog( QgsSymbol *symbol, QgsVectorLayer *layer, QWidget *parent ) + : QDialog( parent ) +{ + QVBoxLayout *vLayout = new QVBoxLayout(); + mWidget = new QgsExtentBufferWidget( symbol, layer ); + vLayout->addWidget( mWidget ); + + QDialogButtonBox *bbox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok, Qt::Horizontal ); + connect( bbox, &QDialogButtonBox::accepted, this, &QgsExtentBufferDialog::accept ); + connect( bbox, &QDialogButtonBox::rejected, this, &QgsExtentBufferDialog::reject ); + connect( bbox, &QDialogButtonBox::helpRequested, this, &QgsExtentBufferDialog::showHelp ); + + vLayout->addWidget( bbox ); + setLayout( vLayout ); + + setWindowTitle( tr( "Extent buffer" ) ); +} + +double QgsExtentBufferDialog::extentBuffer() const +{ + if ( !mWidget ) + return 0; + + return mWidget->extentBuffer(); +} + +QgsProperty QgsExtentBufferDialog::dataDefinedProperty() const +{ + if ( !mWidget ) + return QgsProperty(); + + return mWidget->dataDefinedProperty(); +} + +QgsExtentBufferWidget *QgsExtentBufferDialog::widget() const +{ + return mWidget; +} + +void QgsExtentBufferDialog::showHelp() +{ + QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#extent-buffer" ) ); +} + diff --git a/src/gui/symbology/qgsextentbufferdialog.h b/src/gui/symbology/qgsextentbufferdialog.h new file mode 100644 index 000000000000..698b107e5aa7 --- /dev/null +++ b/src/gui/symbology/qgsextentbufferdialog.h @@ -0,0 +1,135 @@ +/*************************************************************************** + qgsextentbufferdialog.h + --------------------- + begin : December 2024 + copyright : (C) 2024 by Juho Ervasti + email : juho dot ervasti at gispo dot fi + *************************************************************************** + * * + * 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 QGSEXTENTBUFFERDIALOG_H +#define QGSEXTENTBUFFERDIALOG_H + +#define SIP_NO_FILE + +#include +#include "qgis_sip.h" + +#include "qgssymbol.h" +#include "qgssymbolwidgetcontext.h" +#include "ui_qgsextentbufferdialogbase.h" +#include "qgis_gui.h" + +class QgsVectorLayer; + +/** + * \class QgsExtentBufferWidget + * \ingroup gui + * \brief A widget which allows the user to modify the rendering order of extent buffers. + * \see QgsExtentBufferDialog + * \since QGIS 3.42 + */ +class GUI_EXPORT QgsExtentBufferWidget : public QgsPanelWidget, public QgsExpressionContextGenerator, private Ui::QgsExtentBufferDialogBase +{ + Q_OBJECT + public: + + /** + * Constructor for QgsExtentBufferWidget + */ + QgsExtentBufferWidget( QgsSymbol *symbol, QgsVectorLayer *layer, QWidget *parent SIP_TRANSFERTHIS = nullptr ); + + /** + * Returns the extent buffer value currently set in the widget. + * + * \returns extent buffer value + */ + double extentBuffer() const; + + /** + * Returns the data defined property currently set in the widget. + * + * \returns property + */ + QgsProperty dataDefinedProperty() const; + + /** + * Sets the context in which widget is shown, e.g., the associated map canvas and expression contexts. + * \param context symbol widget context + * \see context() + */ + void setContext( const QgsSymbolWidgetContext &context ); + + /** + * Returns the context in which the widget is shown, e.g., the associated map canvas and expression contexts. + * \see setContext() + */ + QgsSymbolWidgetContext context() const; + + private: + QgsSymbol *mSymbol = nullptr; + QgsVectorLayer *mLayer = nullptr; + QgsSymbolWidgetContext mContext; + + QgsExpressionContext createExpressionContext() const override; + + /** + * Registers a data defined override button. Handles setting up connections + * for the button and initializing the button to show the correct descriptions + * and help text for the associated property. + */ + void registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsSymbol::Property key ); +}; + +/** + * \class QgsExtentBufferDialog + * \ingroup gui + * \brief A dialog which allows the user to modify the extent buffer of a symbol. + * \since QGIS 3.42 +*/ +class GUI_EXPORT QgsExtentBufferDialog : public QDialog +{ + Q_OBJECT + public: + + //! Constructor for QgsExtentBufferDialog. + QgsExtentBufferDialog( QgsSymbol *symbol, QgsVectorLayer *layer, QWidget *parent SIP_TRANSFERTHIS = nullptr ); + + /** + * Returns the extent buffer value currently set in the widget. + */ + double extentBuffer() const; + + /** + * Returns the extent buffer value currently set in the widget. + * + * \note returns 0 if widget does not exist + * + * \returns extent buffer value + */ + QgsProperty dataDefinedProperty() const; + + /** + * Returns the data defined property currently set in the widget. + * + * \note returns empty property if widget does not exist + * + * \returns property + */ + QgsExtentBufferWidget *widget() const; + + private: + QgsExtentBufferWidget *mWidget; + + private slots: + + void showHelp(); + +}; + +#endif // QGSEXTENTBUFFERDIALOG_H diff --git a/src/gui/symbology/qgssymbolslistwidget.cpp b/src/gui/symbology/qgssymbolslistwidget.cpp index fd047f8d00cb..76d08c506866 100644 --- a/src/gui/symbology/qgssymbolslistwidget.cpp +++ b/src/gui/symbology/qgssymbolslistwidget.cpp @@ -15,6 +15,7 @@ #include "qgssymbolslistwidget.h" #include "moc_qgssymbolslistwidget.cpp" +#include "qgsextentbufferdialog.h" #include "qgsstylesavedialog.h" #include "qgsstyleitemslistwidget.h" #include "qgsvectorlayer.h" @@ -59,6 +60,9 @@ QgsSymbolsListWidget::QgsSymbolsListWidget( QgsSymbol *symbol, QgsStyle *style, mAnimationSettingsAction = new QAction( tr( "Animation Settings…" ), this ); connect( mAnimationSettingsAction, &QAction::triggered, this, &QgsSymbolsListWidget::showAnimationSettings ); + mExtentBufferAction = new QAction( tr( "Extent buffer…" ), this ); + connect( mExtentBufferAction, &QAction::triggered, this, &QgsSymbolsListWidget::showExtentBufferSettings ); + // select correct page in stacked widget QgsPropertyOverrideButton *opacityDDBtn = nullptr; switch ( symbol->type() ) @@ -148,6 +152,7 @@ QgsSymbolsListWidget::~QgsSymbolsListWidget() mStyleItemsListWidget->advancedMenu()->removeAction( mClipFeaturesAction ); mStyleItemsListWidget->advancedMenu()->removeAction( mStandardizeRingsAction ); mStyleItemsListWidget->advancedMenu()->removeAction( mAnimationSettingsAction ); + mStyleItemsListWidget->advancedMenu()->removeAction( mExtentBufferAction ); mStyleItemsListWidget->advancedMenu()->removeAction( mBufferSettingsAction ); } @@ -299,6 +304,45 @@ void QgsSymbolsListWidget::showAnimationSettings() } } +void QgsSymbolsListWidget::showExtentBufferSettings() +{ + QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ); + if ( panel && panel->dockMode() ) + { + QgsExtentBufferWidget *widget = new QgsExtentBufferWidget( mSymbol, mLayer, panel ); + widget->setPanelTitle( tr( "Extent Buffer" ) ); + widget->setContext( mContext ); + + connect( widget, &QgsPanelWidget::widgetChanged, this, [ = ]() + { + mSymbol->setExtentBuffer( widget->extentBuffer() ); + mSymbol->setDataDefinedProperty( QgsSymbol::Property::ExtentBuffer, widget->dataDefinedProperty() ); + + emit changed(); + } ); + + panel->openPanel( widget ); + + } + else + { + QgsExtentBufferDialog dlg( mSymbol, mLayer, panel ); + + if ( dlg.widget() ) + { + dlg.widget()->setContext( mContext ); + } + + if ( dlg.exec() == QDialog::Accepted ) + { + mSymbol->setExtentBuffer( dlg.extentBuffer() ); + mSymbol->setDataDefinedProperty( QgsSymbol::Property::ExtentBuffer, dlg.dataDefinedProperty() ); + + emit changed(); + } + } +} + void QgsSymbolsListWidget::showBufferSettings() { QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ); @@ -613,6 +657,7 @@ void QgsSymbolsListWidget::updateSymbolInfo() mClipFeaturesAction, mStandardizeRingsAction, mAnimationSettingsAction, + mExtentBufferAction, mBufferSettingsAction } ) { @@ -638,6 +683,7 @@ void QgsSymbolsListWidget::updateSymbolInfo() mStyleItemsListWidget->advancedMenu()->addAction( mBufferSettingsAction ); } mStyleItemsListWidget->advancedMenu()->addAction( mAnimationSettingsAction ); + mStyleItemsListWidget->advancedMenu()->addAction( mExtentBufferAction ); mStyleItemsListWidget->showAdvancedButton( mAdvancedMenu || !mStyleItemsListWidget->advancedMenu()->isEmpty() ); diff --git a/src/gui/symbology/qgssymbolslistwidget.h b/src/gui/symbology/qgssymbolslistwidget.h index 719caac94263..60bc447d933c 100644 --- a/src/gui/symbology/qgssymbolslistwidget.h +++ b/src/gui/symbology/qgssymbolslistwidget.h @@ -98,6 +98,7 @@ class GUI_EXPORT QgsSymbolsListWidget : public QWidget, private Ui::SymbolsListW void createSymbolAuxiliaryField(); void forceRHRToggled( bool checked ); void showAnimationSettings(); + void showExtentBufferSettings(); void showBufferSettings(); void saveSymbol(); void updateSymbolDataDefinedProperty(); @@ -114,6 +115,7 @@ class GUI_EXPORT QgsSymbolsListWidget : public QWidget, private Ui::SymbolsListW QAction *mStandardizeRingsAction = nullptr; QAction *mBufferSettingsAction = nullptr; QAction *mAnimationSettingsAction = nullptr; + QAction *mExtentBufferAction = nullptr; QgsVectorLayer *mLayer = nullptr; QgsColorButton *mSymbolColorButton = nullptr; diff --git a/src/ui/qgsextentbufferdialogbase.ui b/src/ui/qgsextentbufferdialogbase.ui new file mode 100644 index 000000000000..6aa07ee35e16 --- /dev/null +++ b/src/ui/qgsextentbufferdialogbase.ui @@ -0,0 +1,92 @@ + + + QgsExtentBufferDialogBase + + + + 0 + 0 + 406 + 337 + + + + Symbol Levels + + + + + + Define an extent buffer distance in map units. The symbol will be rendered for features which are within the buffered map extent. + + + true + + + + + + + + + Distance + + + + + + + -9999999999.000000000000000 + + + 9999999999.000000000000000 + + + false + + + false + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + QgsDoubleSpinBox + QDoubleSpinBox +
qgsdoublespinbox.h
+
+ + QgsPropertyOverrideButton + QToolButton +
qgspropertyoverridebutton.h
+
+ + QgsPanelWidget + QWidget +
qgspanelwidget.h
+ 1 +
+
+ + +
From 3c2952e414d95fa5b9d376e9f1bf63bd30d15414 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Thu, 5 Dec 2024 14:44:52 +0200 Subject: [PATCH 09/15] Apply suggestions from review --- python/PyQt6/core/auto_generated/symbology/qgsrenderer.sip.in | 3 +-- python/core/auto_generated/symbology/qgsrenderer.sip.in | 3 +-- src/core/symbology/qgsrenderer.cpp | 4 ++-- src/core/symbology/qgsrenderer.h | 3 +-- src/core/symbology/qgssymbol.cpp | 1 - src/core/symbology/qgssymbollayerutils.cpp | 3 ++- src/core/vector/qgsvectorlayerrenderer.cpp | 4 ++-- src/gui/symbology/qgssymbolslistwidget.cpp | 2 +- 8 files changed, 10 insertions(+), 13 deletions(-) diff --git a/python/PyQt6/core/auto_generated/symbology/qgsrenderer.sip.in b/python/PyQt6/core/auto_generated/symbology/qgsrenderer.sip.in index d1820bf91d54..ae8f269ceb11 100644 --- a/python/PyQt6/core/auto_generated/symbology/qgsrenderer.sip.in +++ b/python/PyQt6/core/auto_generated/symbology/qgsrenderer.sip.in @@ -632,8 +632,7 @@ Currently clones double maximumExtentBuffer( QgsRenderContext &context ) const; %Docstring -Returns the maximum extent buffer found in this renderer's -symbols' symbol layers. +Returns the maximum extent buffer found in this renderer's symbols. .. note:: diff --git a/python/core/auto_generated/symbology/qgsrenderer.sip.in b/python/core/auto_generated/symbology/qgsrenderer.sip.in index a704df505a77..6bc3b4dca987 100644 --- a/python/core/auto_generated/symbology/qgsrenderer.sip.in +++ b/python/core/auto_generated/symbology/qgsrenderer.sip.in @@ -632,8 +632,7 @@ Currently clones double maximumExtentBuffer( QgsRenderContext &context ) const; %Docstring -Returns the maximum extent buffer found in this renderer's -symbols' symbol layers. +Returns the maximum extent buffer found in this renderer's symbols. .. note:: diff --git a/src/core/symbology/qgsrenderer.cpp b/src/core/symbology/qgsrenderer.cpp index ac3b980e8859..9596ac5c3454 100644 --- a/src/core/symbology/qgsrenderer.cpp +++ b/src/core/symbology/qgsrenderer.cpp @@ -431,9 +431,9 @@ double QgsFeatureRenderer::maximumExtentBuffer( QgsRenderContext &context ) cons { const QgsProperty property = sym->dataDefinedProperties().property( QgsSymbol::Property::ExtentBuffer ); - if ( property.isActive() && ! property.expressionString().isEmpty() ) + if ( property.isActive() ) { - return sym->dataDefinedProperties().valueAsDouble( QgsSymbol::Property::ExtentBuffer, expContext ); + return sym->dataDefinedProperties().valueAsDouble( QgsSymbol::Property::ExtentBuffer, expContext, sym->extentBuffer() ); } return sym->extentBuffer(); diff --git a/src/core/symbology/qgsrenderer.h b/src/core/symbology/qgsrenderer.h index 431a8635ca9c..eaaa60cb8f8c 100644 --- a/src/core/symbology/qgsrenderer.h +++ b/src/core/symbology/qgsrenderer.h @@ -654,8 +654,7 @@ class CORE_EXPORT QgsFeatureRenderer void copyRendererData( QgsFeatureRenderer *destRenderer ) const; /** - * Returns the maximum extent buffer found in this renderer's - * symbols' symbol layers. + * Returns the maximum extent buffer found in this renderer's symbols. * * \note Returns 0 if the renderer doesn't have any symbols. * \since QGIS 3.42 diff --git a/src/core/symbology/qgssymbol.cpp b/src/core/symbology/qgssymbol.cpp index 01b0635cd929..7f19c519bed0 100644 --- a/src/core/symbology/qgssymbol.cpp +++ b/src/core/symbology/qgssymbol.cpp @@ -20,7 +20,6 @@ #include #include -#include #include #include #include diff --git a/src/core/symbology/qgssymbollayerutils.cpp b/src/core/symbology/qgssymbollayerutils.cpp index df7bcc65e933..e208010b3577 100644 --- a/src/core/symbology/qgssymbollayerutils.cpp +++ b/src/core/symbology/qgssymbollayerutils.cpp @@ -1453,7 +1453,8 @@ QDomElement QgsSymbolLayerUtils::saveSymbol( const QString &name, const QgsSymbo symEl.setAttribute( QStringLiteral( "name" ), name ); symEl.setAttribute( QStringLiteral( "alpha" ), QString::number( symbol->opacity() ) ); symEl.setAttribute( QStringLiteral( "clip_to_extent" ), symbol->clipFeaturesToExtent() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); - symEl.setAttribute( QStringLiteral( "extent_buffer" ), QString::number( symbol->extentBuffer() ) ); + if ( !qgsDoubleNear( symbol->extentBuffer(), 0 ) ) + symEl.setAttribute( QStringLiteral( "extent_buffer" ), QString::number( symbol->extentBuffer() ) ); symEl.setAttribute( QStringLiteral( "force_rhr" ), symbol->forceRHR() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); if ( symbol->flags() & Qgis::SymbolFlag::RendererShouldUseSymbolLevels ) symEl.setAttribute( QStringLiteral( "renderer_should_use_levels" ), QStringLiteral( "1" ) ); diff --git a/src/core/vector/qgsvectorlayerrenderer.cpp b/src/core/vector/qgsvectorlayerrenderer.cpp index 69048c0f0c26..d689ad953dc2 100644 --- a/src/core/vector/qgsvectorlayerrenderer.cpp +++ b/src/core/vector/qgsvectorlayerrenderer.cpp @@ -363,10 +363,10 @@ bool QgsVectorLayerRenderer::renderInternal( QgsFeatureRenderer *renderer, int r if ( maximumExtentBuffer != 0 ) { - bool bufferDisappearsExtent = maximumExtentBuffer < 0 && ( requestExtent.width() + ( maximumExtentBuffer * 2 ) < 0 || requestExtent.height() + ( maximumExtentBuffer * 2 ) < 0 ); + bool bufferCausesEmptyExtent = maximumExtentBuffer < 0 && ( requestExtent.width() + ( maximumExtentBuffer * 2 ) < 0 || requestExtent.height() + ( maximumExtentBuffer * 2 ) < 0 ); // nothing to draw - if ( bufferDisappearsExtent ) + if ( bufferCausesEmptyExtent ) { renderer->stopRender( context ); return true; diff --git a/src/gui/symbology/qgssymbolslistwidget.cpp b/src/gui/symbology/qgssymbolslistwidget.cpp index 76d08c506866..c6913af68328 100644 --- a/src/gui/symbology/qgssymbolslistwidget.cpp +++ b/src/gui/symbology/qgssymbolslistwidget.cpp @@ -60,7 +60,7 @@ QgsSymbolsListWidget::QgsSymbolsListWidget( QgsSymbol *symbol, QgsStyle *style, mAnimationSettingsAction = new QAction( tr( "Animation Settings…" ), this ); connect( mAnimationSettingsAction, &QAction::triggered, this, &QgsSymbolsListWidget::showAnimationSettings ); - mExtentBufferAction = new QAction( tr( "Extent buffer…" ), this ); + mExtentBufferAction = new QAction( tr( "Extent Buffer…" ), this ); connect( mExtentBufferAction, &QAction::triggered, this, &QgsSymbolsListWidget::showExtentBufferSettings ); // select correct page in stacked widget From c103bb2fe84f950ab20b06e88313c2ad0f210952 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Thu, 5 Dec 2024 15:48:38 +0200 Subject: [PATCH 10/15] Disallow negative buffer --- .../auto_generated/symbology/qgssymbol.sip.in | 4 +++ .../auto_generated/symbology/qgssymbol.sip.in | 4 +++ src/core/symbology/qgsrenderer.cpp | 5 ++-- src/core/symbology/qgssymbol.cpp | 5 +++- src/core/symbology/qgssymbol.h | 1 + src/core/vector/qgsvectorlayerrenderer.cpp | 17 ----------- src/ui/qgsextentbufferdialogbase.ui | 4 +-- tests/src/python/test_qgssymbol.py | 3 ++ .../src/python/test_qgsvectorlayerrenderer.py | 27 ------------------ .../expected_negative_buffer_extent.png | Bin 9219 -> 0 bytes 10 files changed, 21 insertions(+), 49 deletions(-) delete mode 100644 tests/testdata/control_images/vectorlayerrenderer/expected_negative_buffer_extent/expected_negative_buffer_extent.png diff --git a/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in b/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in index 3a721d1779e5..484d03a0df09 100644 --- a/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in +++ b/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in @@ -883,6 +883,10 @@ Sets the symbol's extent buffer. .. seealso:: :py:func:`extentBuffer` +.. note:: + + Negative values are not supported and will be changed to 0. + .. versionadded:: 3.42 %End diff --git a/python/core/auto_generated/symbology/qgssymbol.sip.in b/python/core/auto_generated/symbology/qgssymbol.sip.in index d67493cb2043..2152b57fa750 100644 --- a/python/core/auto_generated/symbology/qgssymbol.sip.in +++ b/python/core/auto_generated/symbology/qgssymbol.sip.in @@ -883,6 +883,10 @@ Sets the symbol's extent buffer. .. seealso:: :py:func:`extentBuffer` +.. note:: + + Negative values are not supported and will be changed to 0. + .. versionadded:: 3.42 %End diff --git a/src/core/symbology/qgsrenderer.cpp b/src/core/symbology/qgsrenderer.cpp index 9596ac5c3454..58d683c7b402 100644 --- a/src/core/symbology/qgsrenderer.cpp +++ b/src/core/symbology/qgsrenderer.cpp @@ -525,8 +525,9 @@ QgsSymbolList QgsFeatureRenderer::symbolsForFeature( const QgsFeature &feature, void QgsFeatureRenderer::modifyRequestExtent( QgsRectangle &extent, QgsRenderContext &context ) { - Q_UNUSED( extent ) - Q_UNUSED( context ) + double extentBuffer = maximumExtentBuffer( context ); + + extent.grow( extentBuffer ); } QgsSymbolList QgsFeatureRenderer::originalSymbolsForFeature( const QgsFeature &feature, QgsRenderContext &context ) const diff --git a/src/core/symbology/qgssymbol.cpp b/src/core/symbology/qgssymbol.cpp index 7f19c519bed0..2ba180a1a726 100644 --- a/src/core/symbology/qgssymbol.cpp +++ b/src/core/symbology/qgssymbol.cpp @@ -2231,7 +2231,10 @@ double QgsSymbol::extentBuffer() const void QgsSymbol::setExtentBuffer( double extentBuffer ) { - mExtentBuffer = extentBuffer; + if ( extentBuffer < 0 ) + mExtentBuffer = 0; + else + mExtentBuffer = extentBuffer; } diff --git a/src/core/symbology/qgssymbol.h b/src/core/symbology/qgssymbol.h index e112b6b49a53..5302f35d1226 100644 --- a/src/core/symbology/qgssymbol.h +++ b/src/core/symbology/qgssymbol.h @@ -881,6 +881,7 @@ class CORE_EXPORT QgsSymbol * * \param extentBuffer buffer distance in map units * \see extentBuffer() + * \note Negative values are not supported and will be changed to 0. * \since QGIS 3.42 */ void setExtentBuffer( double extentBuffer ); diff --git a/src/core/vector/qgsvectorlayerrenderer.cpp b/src/core/vector/qgsvectorlayerrenderer.cpp index d689ad953dc2..22ede9519769 100644 --- a/src/core/vector/qgsvectorlayerrenderer.cpp +++ b/src/core/vector/qgsvectorlayerrenderer.cpp @@ -356,25 +356,8 @@ bool QgsVectorLayerRenderer::renderInternal( QgsFeatureRenderer *renderer, int r mDiagramProvider->setClipFeatureGeometry( mLabelClipFeatureGeom ); } - renderer->modifyRequestExtent( requestExtent, context ); - double maximumExtentBuffer = renderer->maximumExtentBuffer( context ); - - if ( maximumExtentBuffer != 0 ) - { - bool bufferCausesEmptyExtent = maximumExtentBuffer < 0 && ( requestExtent.width() + ( maximumExtentBuffer * 2 ) < 0 || requestExtent.height() + ( maximumExtentBuffer * 2 ) < 0 ); - - // nothing to draw - if ( bufferCausesEmptyExtent ) - { - renderer->stopRender( context ); - return true; - } - - requestExtent = requestExtent.buffered( maximumExtentBuffer ); - } - QgsFeatureRequest featureRequest = QgsFeatureRequest() .setFilterRect( requestExtent ) .setSubsetOfAttributes( mAttrNames, mFields ) diff --git a/src/ui/qgsextentbufferdialogbase.ui b/src/ui/qgsextentbufferdialogbase.ui index 6aa07ee35e16..bdd16d9d4184 100644 --- a/src/ui/qgsextentbufferdialogbase.ui +++ b/src/ui/qgsextentbufferdialogbase.ui @@ -36,10 +36,10 @@ - -9999999999.000000000000000 + 0 - 9999999999.000000000000000 + 999999999.000000000000000 false diff --git a/tests/src/python/test_qgssymbol.py b/tests/src/python/test_qgssymbol.py index bc9c814aa3d2..26b626a572af 100644 --- a/tests/src/python/test_qgssymbol.py +++ b/tests/src/python/test_qgssymbol.py @@ -928,6 +928,9 @@ def test_extent_buffer(self): s.setExtentBuffer(10) self.assertEqual(s.extentBuffer(), 10) + s.setExtentBuffer(-10) + self.assertEqual(s.extentBuffer(), 0) + def renderCollection(self, geom, symbol): f = QgsFeature() f.setGeometry(geom) diff --git a/tests/src/python/test_qgsvectorlayerrenderer.py b/tests/src/python/test_qgsvectorlayerrenderer.py index 515032ee554f..d1586c0f4603 100644 --- a/tests/src/python/test_qgsvectorlayerrenderer.py +++ b/tests/src/python/test_qgsvectorlayerrenderer.py @@ -1008,33 +1008,6 @@ def createGeometryGenerator() -> QgsGeometryGeneratorSymbolLayer: ) ) - def testRenderWithExtentBufferNegative(self): - poly_layer = QgsVectorLayer(os.path.join(TEST_DATA_DIR, "polys.shp")) - self.assertTrue(poly_layer.isValid()) - - sym1 = QgsFillSymbol.createSimple( - {"color": "#ff00ff", "outline_color": "#000000", "outline_width": "1"} - ) - sym1.setExtentBuffer(-13.5) - - renderer = QgsSingleSymbolRenderer(sym1) - poly_layer.setRenderer(renderer) - - mapsettings = QgsMapSettings() - mapsettings.setOutputSize(QSize(400, 400)) - mapsettings.setOutputDpi(96) - mapsettings.setDestinationCrs(QgsCoordinateReferenceSystem("EPSG:3857")) - mapsettings.setExtent( - QgsRectangle(-13875783.2, 2266009.4, -8690110.7, 6673344.5) - ) - mapsettings.setLayers([poly_layer]) - - self.assertTrue( - self.render_map_settings_check( - "negative_buffer_extent", "negative_buffer_extent", mapsettings - ) - ) - if __name__ == "__main__": unittest.main() diff --git a/tests/testdata/control_images/vectorlayerrenderer/expected_negative_buffer_extent/expected_negative_buffer_extent.png b/tests/testdata/control_images/vectorlayerrenderer/expected_negative_buffer_extent/expected_negative_buffer_extent.png deleted file mode 100644 index 8332ebb02fe6747706e5a4f38308dbcd2bfce895..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9219 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 Date: Thu, 5 Dec 2024 19:34:22 +0200 Subject: [PATCH 11/15] Add unit selection for extent buffer --- .../auto_generated/symbology/qgssymbol.sip.in | 16 +++++++++- .../auto_generated/symbology/qgssymbol.sip.in | 16 +++++++++- src/core/symbology/qgsrenderer.cpp | 17 ++++++++-- src/core/symbology/qgssymbol.cpp | 1 + src/core/symbology/qgssymbol.h | 17 +++++++++- src/core/symbology/qgssymbollayerutils.cpp | 4 +++ src/gui/symbology/qgsextentbufferdialog.cpp | 32 +++++++++++++++++-- src/gui/symbology/qgsextentbufferdialog.h | 11 +++++++ src/gui/symbology/qgssymbolslistwidget.cpp | 2 ++ src/ui/qgsextentbufferdialogbase.ui | 13 ++++++-- 10 files changed, 119 insertions(+), 10 deletions(-) diff --git a/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in b/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in index 484d03a0df09..0495714b5624 100644 --- a/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in +++ b/python/PyQt6/core/auto_generated/symbology/qgssymbol.sip.in @@ -879,7 +879,7 @@ Returns the symbol's extent buffer. %Docstring Sets the symbol's extent buffer. -:param extentBuffer: buffer distance in map units +:param extentBuffer: buffer distance. .. seealso:: :py:func:`extentBuffer` @@ -888,6 +888,20 @@ Sets the symbol's extent buffer. Negative values are not supported and will be changed to 0. .. versionadded:: 3.42 +%End + + Qgis::RenderUnit extentBufferSizeUnit() const; +%Docstring +Returns the units for the buffer size. + +.. seealso:: :py:func:`setExtentBufferSizeUnit` +%End + + void setExtentBufferSizeUnit( Qgis::RenderUnit unit ); +%Docstring +Sets the ``unit`` used for the extent buffer. + +.. seealso:: :py:func:`extentBufferSizeUnit` %End protected: diff --git a/python/core/auto_generated/symbology/qgssymbol.sip.in b/python/core/auto_generated/symbology/qgssymbol.sip.in index 2152b57fa750..ca7a0ab5c81d 100644 --- a/python/core/auto_generated/symbology/qgssymbol.sip.in +++ b/python/core/auto_generated/symbology/qgssymbol.sip.in @@ -879,7 +879,7 @@ Returns the symbol's extent buffer. %Docstring Sets the symbol's extent buffer. -:param extentBuffer: buffer distance in map units +:param extentBuffer: buffer distance. .. seealso:: :py:func:`extentBuffer` @@ -888,6 +888,20 @@ Sets the symbol's extent buffer. Negative values are not supported and will be changed to 0. .. versionadded:: 3.42 +%End + + Qgis::RenderUnit extentBufferSizeUnit() const; +%Docstring +Returns the units for the buffer size. + +.. seealso:: :py:func:`setExtentBufferSizeUnit` +%End + + void setExtentBufferSizeUnit( Qgis::RenderUnit unit ); +%Docstring +Sets the ``unit`` used for the extent buffer. + +.. seealso:: :py:func:`extentBufferSizeUnit` %End protected: diff --git a/src/core/symbology/qgsrenderer.cpp b/src/core/symbology/qgsrenderer.cpp index 58d683c7b402..361fcf15c82a 100644 --- a/src/core/symbology/qgsrenderer.cpp +++ b/src/core/symbology/qgsrenderer.cpp @@ -427,16 +427,27 @@ double QgsFeatureRenderer::maximumExtentBuffer( QgsRenderContext &context ) cons const QgsExpressionContext &expContext = context.expressionContext(); - auto getValueFromSymbol = [ &expContext ]( const QgsSymbol * sym ) -> double + auto getValueFromSymbol = [ &expContext, &context ]( const QgsSymbol * sym ) -> double { const QgsProperty property = sym->dataDefinedProperties().property( QgsSymbol::Property::ExtentBuffer ); + double value = 0.0; + if ( property.isActive() ) { - return sym->dataDefinedProperties().valueAsDouble( QgsSymbol::Property::ExtentBuffer, expContext, sym->extentBuffer() ); + value = sym->dataDefinedProperties().valueAsDouble( QgsSymbol::Property::ExtentBuffer, expContext, sym->extentBuffer() ); + } + else + { + value = sym->extentBuffer(); + } + + if ( sym->extentBufferSizeUnit() != Qgis::RenderUnit::MapUnits ) + { + value = context.convertToMapUnits( value, sym->extentBufferSizeUnit(), sym->mapUnitScale() ); } - return sym->extentBuffer(); + return value; }; if ( symbolList.size() == 1 ) diff --git a/src/core/symbology/qgssymbol.cpp b/src/core/symbology/qgssymbol.cpp index 2ba180a1a726..d6e3d15228c5 100644 --- a/src/core/symbology/qgssymbol.cpp +++ b/src/core/symbology/qgssymbol.cpp @@ -2315,6 +2315,7 @@ void QgsSymbol::copyCommonProperties( const QgsSymbol *other ) mSymbolFlags = other->mSymbolFlags; mAnimationSettings = other->mAnimationSettings; mExtentBuffer = other->mExtentBuffer; + mExtentBufferSizeUnit = other->mExtentBufferSizeUnit; if ( other->mBufferSettings ) mBufferSettings = std::make_unique< QgsSymbolBufferSettings >( *other->mBufferSettings ); else diff --git a/src/core/symbology/qgssymbol.h b/src/core/symbology/qgssymbol.h index 5302f35d1226..ccadef984fbf 100644 --- a/src/core/symbology/qgssymbol.h +++ b/src/core/symbology/qgssymbol.h @@ -879,13 +879,27 @@ class CORE_EXPORT QgsSymbol /** * Sets the symbol's extent buffer. * - * \param extentBuffer buffer distance in map units + * \param extentBuffer buffer distance. * \see extentBuffer() * \note Negative values are not supported and will be changed to 0. * \since QGIS 3.42 */ void setExtentBuffer( double extentBuffer ); + /** + * Returns the units for the buffer size. + * + * \see setExtentBufferSizeUnit() + */ + Qgis::RenderUnit extentBufferSizeUnit() const { return mExtentBufferSizeUnit; } + + /** + * Sets the \a unit used for the extent buffer. + * + * \see extentBufferSizeUnit() + */ + void setExtentBufferSizeUnit( Qgis::RenderUnit unit ) { mExtentBufferSizeUnit = unit; } + protected: /** @@ -975,6 +989,7 @@ class CORE_EXPORT QgsSymbol QgsSymbolLayerList mLayers; double mExtentBuffer = 0; + Qgis::RenderUnit mExtentBufferSizeUnit = Qgis::RenderUnit::MapUnits; //! Symbol opacity (in the range 0 - 1) qreal mOpacity = 1.0; diff --git a/src/core/symbology/qgssymbollayerutils.cpp b/src/core/symbology/qgssymbollayerutils.cpp index e208010b3577..c2b5ed72e94c 100644 --- a/src/core/symbology/qgssymbollayerutils.cpp +++ b/src/core/symbology/qgssymbollayerutils.cpp @@ -1335,6 +1335,7 @@ QgsSymbol *QgsSymbolLayerUtils::loadSymbol( const QDomElement &element, const Qg } symbol->setOpacity( element.attribute( QStringLiteral( "alpha" ), QStringLiteral( "1.0" ) ).toDouble() ); symbol->setExtentBuffer( element.attribute( QStringLiteral( "extent_buffer" ), QStringLiteral( "0.0" ) ).toDouble() ); + symbol->setExtentBufferSizeUnit( QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "extent_buffer_unit" ), QStringLiteral( "MapUnit" ) ) ) ); symbol->setClipFeaturesToExtent( element.attribute( QStringLiteral( "clip_to_extent" ), QStringLiteral( "1" ) ).toInt() ); symbol->setForceRHR( element.attribute( QStringLiteral( "force_rhr" ), QStringLiteral( "0" ) ).toInt() ); Qgis::SymbolFlags flags; @@ -1454,7 +1455,10 @@ QDomElement QgsSymbolLayerUtils::saveSymbol( const QString &name, const QgsSymbo symEl.setAttribute( QStringLiteral( "alpha" ), QString::number( symbol->opacity() ) ); symEl.setAttribute( QStringLiteral( "clip_to_extent" ), symbol->clipFeaturesToExtent() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); if ( !qgsDoubleNear( symbol->extentBuffer(), 0 ) ) + { symEl.setAttribute( QStringLiteral( "extent_buffer" ), QString::number( symbol->extentBuffer() ) ); + symEl.setAttribute( QStringLiteral( "extent_buffer_unit" ), QgsUnitTypes::encodeUnit( symbol->extentBufferSizeUnit() ) ); + } symEl.setAttribute( QStringLiteral( "force_rhr" ), symbol->forceRHR() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); if ( symbol->flags() & Qgis::SymbolFlag::RendererShouldUseSymbolLevels ) symEl.setAttribute( QStringLiteral( "renderer_should_use_levels" ), QStringLiteral( "1" ) ); diff --git a/src/gui/symbology/qgsextentbufferdialog.cpp b/src/gui/symbology/qgsextentbufferdialog.cpp index 375ce66799bc..35f6f9c72ea6 100644 --- a/src/gui/symbology/qgsextentbufferdialog.cpp +++ b/src/gui/symbology/qgsextentbufferdialog.cpp @@ -21,6 +21,7 @@ #include "qgspanelwidget.h" #include "qgssymbol.h" #include "qgssymbolwidgetcontext.h" +#include "qgsunittypes.h" #include "qgsvectorlayer.h" QgsExtentBufferWidget::QgsExtentBufferWidget( QgsSymbol *symbol, QgsVectorLayer *layer, QWidget *parent ) @@ -28,13 +29,27 @@ QgsExtentBufferWidget::QgsExtentBufferWidget( QgsSymbol *symbol, QgsVectorLayer { setupUi( this ); - mExtentBufferSpinBox->setValue( mSymbol-> extentBuffer() ); + mExtentBufferSpinBox->setValue( mSymbol->extentBuffer() ); + + mExtentBufferUnitSelectionWidget->setShowMapScaleButton( false ); + mExtentBufferUnitSelectionWidget->setUnits( { Qgis::RenderUnit::Millimeters, + Qgis::RenderUnit::MetersInMapUnits, + Qgis::RenderUnit::MapUnits, + Qgis::RenderUnit::Pixels, + Qgis::RenderUnit::Points, + Qgis::RenderUnit::Inches } ); + mExtentBufferUnitSelectionWidget->setUnit( mSymbol->extentBufferSizeUnit() ); connect( mExtentBufferSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, [ = ]() { emit widgetChanged(); } ); + connect( mExtentBufferUnitSelectionWidget, &QgsUnitSelectionWidget::changed, this, [ = ]() + { + emit widgetChanged(); + } ); + registerDataDefinedButton( mExtentBufferDDButton, QgsSymbol::Property::ExtentBuffer ); } @@ -97,7 +112,7 @@ QgsExtentBufferDialog::QgsExtentBufferDialog( QgsSymbol *symbol, QgsVectorLayer vLayout->addWidget( bbox ); setLayout( vLayout ); - setWindowTitle( tr( "Extent buffer" ) ); + setWindowTitle( tr( "Extent Buffer" ) ); } double QgsExtentBufferDialog::extentBuffer() const @@ -108,6 +123,19 @@ double QgsExtentBufferDialog::extentBuffer() const return mWidget->extentBuffer(); } +Qgis::RenderUnit QgsExtentBufferWidget::sizeUnit() const +{ + return mExtentBufferUnitSelectionWidget->unit(); +} + +Qgis::RenderUnit QgsExtentBufferDialog::sizeUnit() const +{ + if ( !mWidget ) + return Qgis::RenderUnit::MapUnits; + + return mWidget->sizeUnit(); +} + QgsProperty QgsExtentBufferDialog::dataDefinedProperty() const { if ( !mWidget ) diff --git a/src/gui/symbology/qgsextentbufferdialog.h b/src/gui/symbology/qgsextentbufferdialog.h index 698b107e5aa7..86c691bef11c 100644 --- a/src/gui/symbology/qgsextentbufferdialog.h +++ b/src/gui/symbology/qgsextentbufferdialog.h @@ -71,6 +71,12 @@ class GUI_EXPORT QgsExtentBufferWidget : public QgsPanelWidget, public QgsExpres */ QgsSymbolWidgetContext context() const; + /** + * Returns the extent buffer unit currently set in the widget. + * \see setContext() + */ + Qgis::RenderUnit sizeUnit() const; + private: QgsSymbol *mSymbol = nullptr; QgsVectorLayer *mLayer = nullptr; @@ -105,6 +111,11 @@ class GUI_EXPORT QgsExtentBufferDialog : public QDialog */ double extentBuffer() const; + /** + * Returns the extent buffer unit currently set in the widget. + */ + Qgis::RenderUnit sizeUnit() const; + /** * Returns the extent buffer value currently set in the widget. * diff --git a/src/gui/symbology/qgssymbolslistwidget.cpp b/src/gui/symbology/qgssymbolslistwidget.cpp index c6913af68328..b9883ec17ddb 100644 --- a/src/gui/symbology/qgssymbolslistwidget.cpp +++ b/src/gui/symbology/qgssymbolslistwidget.cpp @@ -317,6 +317,7 @@ void QgsSymbolsListWidget::showExtentBufferSettings() { mSymbol->setExtentBuffer( widget->extentBuffer() ); mSymbol->setDataDefinedProperty( QgsSymbol::Property::ExtentBuffer, widget->dataDefinedProperty() ); + mSymbol->setExtentBufferSizeUnit( widget->sizeUnit() ); emit changed(); } ); @@ -337,6 +338,7 @@ void QgsSymbolsListWidget::showExtentBufferSettings() { mSymbol->setExtentBuffer( dlg.extentBuffer() ); mSymbol->setDataDefinedProperty( QgsSymbol::Property::ExtentBuffer, dlg.dataDefinedProperty() ); + mSymbol->setExtentBufferSizeUnit( dlg.sizeUnit() ); emit changed(); } diff --git a/src/ui/qgsextentbufferdialogbase.ui b/src/ui/qgsextentbufferdialogbase.ui index bdd16d9d4184..b3c8740f6ac6 100644 --- a/src/ui/qgsextentbufferdialogbase.ui +++ b/src/ui/qgsextentbufferdialogbase.ui @@ -17,7 +17,7 @@ - Define an extent buffer distance in map units. The symbol will be rendered for features which are within the buffered map extent. + Define an extent buffer distance. The symbol will be rendered for features which are within the buffered map extent. true @@ -36,7 +36,7 @@ - 0 + 0.000000000000000 999999999.000000000000000 @@ -49,6 +49,9 @@ + + + @@ -86,6 +89,12 @@
qgspanelwidget.h
1 + + QgsUnitSelectionWidget + QWidget +
qgsunitselectionwidget.h
+ 1 +
From 1a2fdd00869ca9ce47916c866aa8f1f8d453bad6 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Thu, 5 Dec 2024 19:34:42 +0200 Subject: [PATCH 12/15] Check extent buffer size unit in tests --- tests/src/python/test_qgssymbol.py | 3 +++ tests/src/python/test_qgssymbollayerutils.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/src/python/test_qgssymbol.py b/tests/src/python/test_qgssymbol.py index 26b626a572af..218ec980f730 100644 --- a/tests/src/python/test_qgssymbol.py +++ b/tests/src/python/test_qgssymbol.py @@ -931,6 +931,9 @@ def test_extent_buffer(self): s.setExtentBuffer(-10) self.assertEqual(s.extentBuffer(), 0) + s.setExtentBufferSizeUnit(Qgis.RenderUnit.Pixels) + self.assertEqual(s.extentBufferSizeUnit(), Qgis.RenderUnit.Pixels) + def renderCollection(self, geom, symbol): f = QgsFeature() f.setGeometry(geom) diff --git a/tests/src/python/test_qgssymbollayerutils.py b/tests/src/python/test_qgssymbollayerutils.py index bb55da07ab53..6d6ceb4d929c 100644 --- a/tests/src/python/test_qgssymbollayerutils.py +++ b/tests/src/python/test_qgssymbollayerutils.py @@ -1258,7 +1258,7 @@ def test_extent_buffer_load(self): doc = QDomDocument() elem = QDomElement() - extent_buffer_xml_string = """ + extent_buffer_xml_string = """