From 694f375f2855d02a21d180490d38a7509e4572aa Mon Sep 17 00:00:00 2001 From: Jean Felder <jean.felder@oslandia.com> Date: Fri, 25 Oct 2024 13:25:12 +0200 Subject: [PATCH 1/4] qgsvectorlayerelevationproperties: Add support for custom tolerance This allows to define a custom tolerance. If this tolerance is enabled, then this tolerance is used instead of the one defined in the elevation profile widget. This custom tolerance is enabled and set to 0 by default for Lines and Polygons. Indeed, most of the time, only want to use the tolerance for points. --- .../qgsvectorlayerelevationproperties.sip.in | 46 +++++++++++++++++++ .../qgsvectorlayerelevationproperties.sip.in | 46 +++++++++++++++++++ .../qgsvectorlayerelevationproperties.cpp | 37 +++++++++++++++ .../qgsvectorlayerelevationproperties.h | 42 +++++++++++++++++ .../python/test_qgslayoutelevationprofile.py | 6 ++- .../test_qgsvectorlayerelevationproperties.py | 38 +++++++++++++++ .../test_qgsvectorlayerprofilegenerator.py | 37 +++++++++++---- 7 files changed, 243 insertions(+), 9 deletions(-) diff --git a/python/PyQt6/core/auto_generated/vector/qgsvectorlayerelevationproperties.sip.in b/python/PyQt6/core/auto_generated/vector/qgsvectorlayerelevationproperties.sip.in index e07d53a2b29d..1c061aca8f61 100644 --- a/python/PyQt6/core/auto_generated/vector/qgsvectorlayerelevationproperties.sip.in +++ b/python/PyQt6/core/auto_generated/vector/qgsvectorlayerelevationproperties.sip.in @@ -147,6 +147,52 @@ Sets the feature extrusion height. the :py:func:`~QgsVectorLayerElevationProperties.zScale` factor is NOT applied to extrusion heights. .. seealso:: :py:func:`extrusionHeight` +%End + + bool customToleranceEnabled() const; +%Docstring +Returns ``True`` if custom tolerance is enabled. + +.. seealso:: :py:func:`setCustomToleranceEnabled` + +.. seealso:: :py:func:`customTolerance` +%End + + void setCustomToleranceEnabled( bool enabled ); +%Docstring +Sets whether custom tolerance is ``enabled``. + +.. seealso:: :py:func:`customToleranceEnabled` + +.. seealso:: :py:func:`setCustomTolerance` +%End + + double customTolerance() const; +%Docstring +Returns the feature custom tolerance. + +.. warning:: + + custom tolerance is only applied if :py:func:`~QgsVectorLayerElevationProperties.customToleranceEnabled` is ``True``. + +If enabled, the profile generator will use this tolerance instead of the one +defined in the elevation profile widget. + +.. seealso:: :py:func:`setCustomTolerance` +%End + + void setCustomTolerance( double tolerance ); +%Docstring +Sets the feature custom tolerance. + +.. warning:: + + custom tolerance is only applied if :py:func:`~QgsVectorLayerElevationProperties.customToleranceEnabled` is ``True``. + +If enabled, the profile generator will use this tolerance instead of the one +defined in the elevation profile widget. + +.. seealso:: :py:func:`customTolerance` %End bool respectLayerSymbology() const; diff --git a/python/core/auto_generated/vector/qgsvectorlayerelevationproperties.sip.in b/python/core/auto_generated/vector/qgsvectorlayerelevationproperties.sip.in index e07d53a2b29d..1c061aca8f61 100644 --- a/python/core/auto_generated/vector/qgsvectorlayerelevationproperties.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayerelevationproperties.sip.in @@ -147,6 +147,52 @@ Sets the feature extrusion height. the :py:func:`~QgsVectorLayerElevationProperties.zScale` factor is NOT applied to extrusion heights. .. seealso:: :py:func:`extrusionHeight` +%End + + bool customToleranceEnabled() const; +%Docstring +Returns ``True`` if custom tolerance is enabled. + +.. seealso:: :py:func:`setCustomToleranceEnabled` + +.. seealso:: :py:func:`customTolerance` +%End + + void setCustomToleranceEnabled( bool enabled ); +%Docstring +Sets whether custom tolerance is ``enabled``. + +.. seealso:: :py:func:`customToleranceEnabled` + +.. seealso:: :py:func:`setCustomTolerance` +%End + + double customTolerance() const; +%Docstring +Returns the feature custom tolerance. + +.. warning:: + + custom tolerance is only applied if :py:func:`~QgsVectorLayerElevationProperties.customToleranceEnabled` is ``True``. + +If enabled, the profile generator will use this tolerance instead of the one +defined in the elevation profile widget. + +.. seealso:: :py:func:`setCustomTolerance` +%End + + void setCustomTolerance( double tolerance ); +%Docstring +Sets the feature custom tolerance. + +.. warning:: + + custom tolerance is only applied if :py:func:`~QgsVectorLayerElevationProperties.customToleranceEnabled` is ``True``. + +If enabled, the profile generator will use this tolerance instead of the one +defined in the elevation profile widget. + +.. seealso:: :py:func:`customTolerance` %End bool respectLayerSymbology() const; diff --git a/src/core/vector/qgsvectorlayerelevationproperties.cpp b/src/core/vector/qgsvectorlayerelevationproperties.cpp index 5ec89d48a305..dfeb624367a4 100644 --- a/src/core/vector/qgsvectorlayerelevationproperties.cpp +++ b/src/core/vector/qgsvectorlayerelevationproperties.cpp @@ -54,6 +54,8 @@ QDomElement QgsVectorLayerElevationProperties::writeXml( QDomElement &parentElem element.setAttribute( QStringLiteral( "extrusionEnabled" ), mEnableExtrusion ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); element.setAttribute( QStringLiteral( "extrusion" ), qgsDoubleToString( mExtrusionHeight ) ); + element.setAttribute( QStringLiteral( "customToleranceEnabled" ), mEnableCustomTolerance ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); + element.setAttribute( QStringLiteral( "customTolerance" ), qgsDoubleToString( mCustomTolerance ) ); element.setAttribute( QStringLiteral( "clamping" ), qgsEnumValueToKey( mClamping ) ); element.setAttribute( QStringLiteral( "binding" ), qgsEnumValueToKey( mBinding ) ); element.setAttribute( QStringLiteral( "type" ), qgsEnumValueToKey( mType ) ); @@ -93,6 +95,8 @@ bool QgsVectorLayerElevationProperties::readXml( const QDomElement &element, con mType = qgsEnumKeyToValue( elevationElement.attribute( QStringLiteral( "type" ) ), Qgis::VectorProfileType::IndividualFeatures ); mEnableExtrusion = elevationElement.attribute( QStringLiteral( "extrusionEnabled" ), QStringLiteral( "0" ) ).toInt(); mExtrusionHeight = elevationElement.attribute( QStringLiteral( "extrusion" ), QStringLiteral( "0" ) ).toDouble(); + mEnableCustomTolerance = elevationElement.attribute( QStringLiteral( "customToleranceEnabled" ), QStringLiteral( "0" ) ).toInt(); + mCustomTolerance = elevationElement.attribute( QStringLiteral( "customTolerance" ), QStringLiteral( "0" ) ).toDouble(); mSymbology = qgsEnumKeyToValue( elevationElement.attribute( QStringLiteral( "symbology" ) ), Qgis::ProfileSurfaceSymbology::Line ); if ( elevationElement.hasAttribute( QStringLiteral( "elevationLimit" ) ) ) mElevationLimit = elevationElement.attribute( QStringLiteral( "elevationLimit" ) ).toDouble(); @@ -135,6 +139,12 @@ void QgsVectorLayerElevationProperties::setDefaultsFromLayer( QgsMapLayer *layer mEnableExtrusion = false; mExtrusionHeight = 0; + // By default override default tolerance for Polygon and Line + // to avoid unexpected behaviors. + // For example, see: https://github.com/qgis/QGIS/issues/58016 + mEnableCustomTolerance = vlayer->geometryType() != Qgis::GeometryType::Point; + mCustomTolerance = 0; + mDataDefinedProperties.clear(); mBinding = Qgis::AltitudeBinding::Centroid; @@ -157,6 +167,8 @@ QgsVectorLayerElevationProperties *QgsVectorLayerElevationProperties::clone() co res->setType( mType ); res->setExtrusionEnabled( mEnableExtrusion ); res->setExtrusionHeight( mExtrusionHeight ); + res->setCustomToleranceEnabled( mEnableCustomTolerance ); + res->setCustomTolerance( mCustomTolerance ); res->setProfileLineSymbol( mProfileLineSymbol->clone() ); res->setProfileFillSymbol( mProfileFillSymbol->clone() ); res->setProfileMarkerSymbol( mProfileMarkerSymbol->clone() ); @@ -228,6 +240,11 @@ QString QgsVectorLayerElevationProperties::htmlSummary() const } } + if ( mEnableCustomTolerance ) + { + properties << tr( "CustomTolerance: %1" ).arg( mCustomTolerance ); + } + properties << tr( "Scale: %1" ).arg( mZScale ); return QStringLiteral( "<li>%1</li>" ).arg( properties.join( QLatin1String( "</li><li>" ) ) ); @@ -305,6 +322,26 @@ void QgsVectorLayerElevationProperties::setExtrusionHeight( double height ) emit profileGenerationPropertyChanged(); } +void QgsVectorLayerElevationProperties::setCustomTolerance( double tolerance ) +{ + if ( mCustomTolerance == tolerance ) + return; + + mCustomTolerance = tolerance; + emit changed(); + emit profileGenerationPropertyChanged(); +} + +void QgsVectorLayerElevationProperties::setCustomToleranceEnabled( bool enabled ) +{ + if ( mEnableCustomTolerance == enabled ) + return; + + mEnableCustomTolerance = enabled; + emit changed(); + emit profileGenerationPropertyChanged(); +} + void QgsVectorLayerElevationProperties::setRespectLayerSymbology( bool enabled ) { if ( mRespectLayerSymbology == enabled ) diff --git a/src/core/vector/qgsvectorlayerelevationproperties.h b/src/core/vector/qgsvectorlayerelevationproperties.h index 7a021e1582ff..7349785449bb 100644 --- a/src/core/vector/qgsvectorlayerelevationproperties.h +++ b/src/core/vector/qgsvectorlayerelevationproperties.h @@ -142,6 +142,46 @@ class CORE_EXPORT QgsVectorLayerElevationProperties : public QgsMapLayerElevatio */ void setExtrusionHeight( double height ); + /** + * Returns TRUE if custom tolerance is enabled. + * + * \see setCustomToleranceEnabled() + * \see customTolerance() + */ + bool customToleranceEnabled() const { return mEnableCustomTolerance; } + + /** + * Sets whether custom tolerance is \a enabled. + * + * \see customToleranceEnabled() + * \see setCustomTolerance() + */ + void setCustomToleranceEnabled( bool enabled ); + + /** + * Returns the feature custom tolerance. + * + * \warning custom tolerance is only applied if customToleranceEnabled() is TRUE. + * + * If enabled, the profile generator will use this tolerance instead of the one + * defined in the elevation profile widget. + * + * \see setCustomTolerance() + */ + double customTolerance() const { return mCustomTolerance; } + + /** + * Sets the feature custom tolerance. + * + * \warning custom tolerance is only applied if customToleranceEnabled() is TRUE. + * + * If enabled, the profile generator will use this tolerance instead of the one + * defined in the elevation profile widget. + * + * \see customTolerance() + */ + void setCustomTolerance( double tolerance ); + /** * Returns TRUE if layer symbology should be respected when rendering elevation profile plots. * @@ -313,6 +353,8 @@ class CORE_EXPORT QgsVectorLayerElevationProperties : public QgsMapLayerElevatio bool mEnableExtrusion = false; double mExtrusionHeight = 0; + bool mEnableCustomTolerance = false; + double mCustomTolerance = 0; std::unique_ptr< QgsLineSymbol > mProfileLineSymbol; std::unique_ptr< QgsFillSymbol > mProfileFillSymbol; diff --git a/tests/src/python/test_qgslayoutelevationprofile.py b/tests/src/python/test_qgslayoutelevationprofile.py index 8a709dcaa03a..8f5659a4340a 100644 --- a/tests/src/python/test_qgslayoutelevationprofile.py +++ b/tests/src/python/test_qgslayoutelevationprofile.py @@ -916,6 +916,10 @@ def test_draw_map_units_tolerance(self): f.setGeometry(QgsGeometry.fromWkt(line)) self.assertTrue(vl.dataProvider().addFeature(f)) + tolerance = 1 + + vl.elevationProperties().setCustomToleranceEnabled(True) + vl.elevationProperties().setCustomTolerance(tolerance) vl.elevationProperties().setClamping(Qgis.AltitudeClamping.Absolute) line_symbol = QgsLineSymbol.createSimple({"color": "#ff00ff", "width": "0.8"}) line_symbol.setWidthUnit(Qgis.RenderUnit.MapUnits) @@ -977,7 +981,7 @@ def test_draw_map_units_tolerance(self): ) ) - profile_item.setTolerance(1) + profile_item.setTolerance(tolerance) profile_item.setLayers([vl]) self.assertTrue( diff --git a/tests/src/python/test_qgsvectorlayerelevationproperties.py b/tests/src/python/test_qgsvectorlayerelevationproperties.py index b57614823db4..7b3b0bfbdced 100644 --- a/tests/src/python/test_qgsvectorlayerelevationproperties.py +++ b/tests/src/python/test_qgsvectorlayerelevationproperties.py @@ -46,6 +46,8 @@ def testBasic(self): self.assertEqual(props.type(), Qgis.VectorProfileType.IndividualFeatures) self.assertEqual(props.profileSymbology(), Qgis.ProfileSurfaceSymbology.Line) self.assertFalse(props.showMarkerSymbolInSurfacePlots()) + self.assertFalse(props.customToleranceEnabled()) + self.assertEqual(props.customTolerance(), 0) props.setZOffset(0.5) props.setZScale(2) @@ -135,6 +137,8 @@ def testBasic(self): ) self.assertTrue(props2.showMarkerSymbolInSurfacePlots()) self.assertEqual(props2.elevationLimit(), 909) + self.assertFalse(props2.customToleranceEnabled()) + self.assertEqual(props2.customTolerance(), 0) self.assertEqual(props2.profileLineSymbol().color().name(), "#ff4433") self.assertEqual(props2.profileFillSymbol().color().name(), "#ff4455") @@ -161,6 +165,8 @@ def testBasic(self): ) self.assertTrue(props_clone.showMarkerSymbolInSurfacePlots()) self.assertEqual(props2.elevationLimit(), 909) + self.assertFalse(props_clone.customToleranceEnabled()) + self.assertEqual(props_clone.customTolerance(), 0) self.assertEqual(props_clone.profileLineSymbol().color().name(), "#ff4433") self.assertEqual(props_clone.profileFillSymbol().color().name(), "#ff4455") @@ -210,6 +216,38 @@ def test_show_by_default(self): props.setClamping(Qgis.AltitudeClamping.Absolute) self.assertTrue(props.showByDefaultInElevationProfilePlots()) + def test_defaults_from_layer(self): + props = QgsVectorLayerElevationProperties(None) + self.assertFalse(props.customToleranceEnabled()) + self.assertEqual(props.customTolerance(), 0) + + point_layer = QgsVectorLayer("Point", "my layer point", "memory") + self.assertTrue(point_layer.isValid()) + props = QgsVectorLayerElevationProperties(None) + self.assertFalse(props.customToleranceEnabled()) + self.assertEqual(props.customTolerance(), 0) + props.setDefaultsFromLayer(point_layer) + self.assertFalse(props.customToleranceEnabled()) + self.assertEqual(props.customTolerance(), 0) + + line_layer = QgsVectorLayer("LineStringZ", "my layer point", "memory") + self.assertTrue(line_layer.isValid()) + props = QgsVectorLayerElevationProperties(None) + self.assertFalse(props.customToleranceEnabled()) + self.assertEqual(props.customTolerance(), 0) + props.setDefaultsFromLayer(line_layer) + self.assertTrue(props.customToleranceEnabled()) + self.assertEqual(props.customTolerance(), 0) + + polygon_layer = QgsVectorLayer("Polygon", "Polys", "memory") + self.assertTrue(polygon_layer.isValid()) + props = QgsVectorLayerElevationProperties(None) + self.assertFalse(props.customToleranceEnabled()) + self.assertEqual(props.customTolerance(), 0) + props.setDefaultsFromLayer(polygon_layer) + self.assertTrue(props.customToleranceEnabled()) + self.assertEqual(props.customTolerance(), 0) + if __name__ == "__main__": unittest.main() diff --git a/tests/src/python/test_qgsvectorlayerprofilegenerator.py b/tests/src/python/test_qgsvectorlayerprofilegenerator.py index a9905d151f56..3adc95573d23 100644 --- a/tests/src/python/test_qgsvectorlayerprofilegenerator.py +++ b/tests/src/python/test_qgsvectorlayerprofilegenerator.py @@ -690,7 +690,9 @@ def testLineGenerationTerrainTolerance(self): req.setCrs(QgsCoordinateReferenceSystem("EPSG:3857")) # very small tolerance - req.setTolerance(0.1) + tolerance = 0.1 + vl.elevationProperties().setCustomTolerance(tolerance) + req.setTolerance(tolerance) generator = vl.createProfileGenerator(req) self.assertTrue(generator.generateProfile()) results = generator.takeResults() @@ -738,7 +740,9 @@ def testLineGenerationTerrainTolerance(self): self.assertAlmostEqual(results.zRange().upper(), 62.7499, 2) # 1 meter tolerance - req.setTolerance(1) + tolerance = 1 + vl.elevationProperties().setCustomTolerance(tolerance) + req.setTolerance(tolerance) generator = vl.createProfileGenerator(req) self.assertTrue(generator.generateProfile()) results = generator.takeResults() @@ -786,7 +790,9 @@ def testLineGenerationTerrainTolerance(self): self.assertAlmostEqual(results.zRange().upper(), 62.7499, 2) # 15 meters tolerance - req.setTolerance(15) + tolerance = 15 + vl.elevationProperties().setCustomTolerance(tolerance) + req.setTolerance(tolerance) generator = vl.createProfileGenerator(req) self.assertTrue(generator.generateProfile()) results = generator.takeResults() @@ -1390,11 +1396,14 @@ def testPolygonGenerationRelativeExtrusionTolerance(self): f.setGeometry(QgsGeometry.fromWkt(line)) self.assertTrue(vl.dataProvider().addFeature(f)) + tolerance = 2.0 + vl.elevationProperties().setClamping(Qgis.AltitudeClamping.Relative) vl.elevationProperties().setZScale(2.5) vl.elevationProperties().setZOffset(10) vl.elevationProperties().setExtrusionEnabled(True) vl.elevationProperties().setExtrusionHeight(7) + vl.elevationProperties().setCustomTolerance(tolerance) curve = QgsLineString() curve.fromWkt( @@ -1412,7 +1421,7 @@ def testPolygonGenerationRelativeExtrusionTolerance(self): req.setTerrainProvider(terrain_provider) req.setCrs(QgsCoordinateReferenceSystem("EPSG:3857")) - req.setTolerance(2.0) + req.setTolerance(tolerance) generator = vl.createProfileGenerator(req) self.assertTrue(generator.generateProfile()) @@ -2783,6 +2792,7 @@ def testRenderProfileAsSurfaceFillAboveLimitTolerance(self): f.setGeometry(QgsGeometry.fromWkt(line)) self.assertTrue(vl.dataProvider().addFeature(f)) + tolerance = 20 vl.elevationProperties().setClamping(Qgis.AltitudeClamping.Absolute) vl.elevationProperties().setType(Qgis.VectorProfileType.ContinuousSurface) vl.elevationProperties().setProfileSymbology( @@ -2796,6 +2806,7 @@ def testRenderProfileAsSurfaceFillAboveLimitTolerance(self): vl.elevationProperties().setProfileFillSymbol(fill_symbol) line_symbol = QgsLineSymbol.createSimple({"color": "#ff00ff", "width": "0.8"}) vl.elevationProperties().setProfileLineSymbol(line_symbol) + vl.elevationProperties().setCustomTolerance(tolerance) curve = QgsLineString() curve.fromWkt( @@ -2805,7 +2816,7 @@ def testRenderProfileAsSurfaceFillAboveLimitTolerance(self): req.setTransformContext(self.create_transform_context()) req.setCrs(QgsCoordinateReferenceSystem()) - req.setTolerance(20) + req.setTolerance(tolerance) plot_renderer = QgsProfilePlotRenderer([vl], req) plot_renderer.startGeneration() @@ -2880,11 +2891,13 @@ def testRenderProfileSymbolWithMapUnitsTolerance(self): f.setGeometry(QgsGeometry.fromWkt(line)) self.assertTrue(vl.dataProvider().addFeature(f)) + tolerance = 10 vl.elevationProperties().setClamping(Qgis.AltitudeClamping.Absolute) vl.elevationProperties().setRespectLayerSymbology(False) line_symbol = QgsLineSymbol.createSimple({"color": "#ff00ff", "width": "0.8"}) line_symbol.setWidthUnit(Qgis.RenderUnit.MapUnits) vl.elevationProperties().setProfileLineSymbol(line_symbol) + vl.elevationProperties().setCustomTolerance(tolerance) curve = QgsLineString() curve.fromWkt( @@ -2894,7 +2907,7 @@ def testRenderProfileSymbolWithMapUnitsTolerance(self): req.setTransformContext(self.create_transform_context()) req.setCrs(QgsCoordinateReferenceSystem()) - req.setTolerance(10) + req.setTolerance(tolerance) plot_renderer = QgsProfilePlotRenderer([vl], req) plot_renderer.startGeneration() @@ -2997,6 +3010,8 @@ def doCheckPoint( expectedFeatures, ): request.setTolerance(tolerance) + if tolerance > 0 and layer.geometryType() != Qgis.GeometryType.Point: + layer.elevationProperties().setCustomTolerance(tolerance) profGen = layer.createProfileGenerator(request) self.assertIsNotNone(profGen) @@ -3295,6 +3310,9 @@ def testPolyhedralSurfaceGenerationFeature(self): vl.elevationProperties().setClamping(Qgis.AltitudeClamping.Absolute) vl.elevationProperties().setExtrusionEnabled(False) + tolerance = 10 + vl.elevationProperties().setCustomTolerance(tolerance) + wkt_str = "POLYHEDRALSURFACE Z(((321474.91 129812.38 -20.00,322277.09 130348.29 -20.00,322631.00 129738.23 -20.00,321434.46 129266.36 -20.00,321474.91 129812.38 -20.00)),((321474.91 129812.38 30.00,321434.46 129266.36 30.00,322631.00 129738.23 30.00,322277.09 130348.29 30.00,321474.91 129812.38 30.00)),((321474.91 129812.38 -20.00,321474.91 129812.38 30.00,322277.09 130348.29 30.00,322277.09 130348.29 -20.00,321474.91 129812.38 -20.00)),((322277.09 130348.29 -20.00,322277.09 130348.29 30.00,322631.00 129738.23 30.00,322631.00 129738.23 -20.00,322277.09 130348.29 -20.00)),((322631.00 129738.23 -20.00,322631.00 129738.23 30.00,321434.46 129266.36 30.00,321434.46 129266.36 -20.00,322631.00 129738.23 -20.00)),((321434.46 129266.36 -20.00,321434.46 129266.36 30.00,321474.91 129812.38 30.00,321474.91 129812.38 -20.00,321434.46 129266.36 -20.00)))" vl_feature = QgsFeature() vl_feature.setGeometry(QgsGeometry.fromWkt(wkt_str)) @@ -3308,7 +3326,7 @@ def testPolyhedralSurfaceGenerationFeature(self): req = QgsProfileRequest(curve) req.setCrs(QgsCoordinateReferenceSystem("EPSG:3857")) - req.setTolerance(10) + req.setTolerance(tolerance) generator = vl.createProfileGenerator(req) self.assertTrue(generator.generateProfile()) @@ -3351,6 +3369,9 @@ def testVerticalLineGenerationFeatureTolerance(self): vl.elevationProperties().setClamping(Qgis.AltitudeClamping.Absolute) vl.elevationProperties().setExtrusionEnabled(False) + tolerance = 10 + vl.elevationProperties().setCustomTolerance(tolerance) + vl_feature = QgsFeature() vl_feature.setGeometry( QgsGeometry.fromWkt( @@ -3367,7 +3388,7 @@ def testVerticalLineGenerationFeatureTolerance(self): req = QgsProfileRequest(curve) req.setCrs(QgsCoordinateReferenceSystem("EPSG:3857")) - req.setTolerance(10) + req.setTolerance(tolerance) generator = vl.createProfileGenerator(req) self.assertTrue(generator.generateProfile()) From 8c3a1ab3cf37c7dae1cfa51ef1998595b927a4ce Mon Sep 17 00:00:00 2001 From: Jean Felder <jean.felder@oslandia.com> Date: Fri, 25 Oct 2024 14:34:00 +0200 Subject: [PATCH 2/4] qgsvectorlayerprofilegenerator: const some methods --- src/core/vector/qgsvectorlayerprofilegenerator.cpp | 12 ++++++------ src/core/vector/qgsvectorlayerprofilegenerator.h | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/core/vector/qgsvectorlayerprofilegenerator.cpp b/src/core/vector/qgsvectorlayerprofilegenerator.cpp index 8c7772ed5990..d7971b0205ce 100644 --- a/src/core/vector/qgsvectorlayerprofilegenerator.cpp +++ b/src/core/vector/qgsvectorlayerprofilegenerator.cpp @@ -181,7 +181,7 @@ QgsProfileSnapResult QgsVectorLayerProfileResults::snapPointToIndividualFeatures return res; } -void QgsVectorLayerProfileResults::visitFeaturesAtPoint( const QgsProfilePoint &point, double maximumPointDistanceDelta, double maximumPointElevationDelta, double maximumSurfaceElevationDelta, const std::function< void( QgsFeatureId, double delta, double distance, double elevation ) > &visitor, bool visitWithin ) +void QgsVectorLayerProfileResults::visitFeaturesAtPoint( const QgsProfilePoint &point, double maximumPointDistanceDelta, double maximumPointElevationDelta, double maximumSurfaceElevationDelta, const std::function< void( QgsFeatureId, double delta, double distance, double elevation ) > &visitor, bool visitWithin ) const { // TODO -- add spatial index if performance is an issue @@ -336,7 +336,7 @@ void QgsVectorLayerProfileResults::visitFeaturesAtPoint( const QgsProfilePoint & } } -void QgsVectorLayerProfileResults::visitFeaturesInRange( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const std::function<void ( QgsFeatureId )> &visitor ) +void QgsVectorLayerProfileResults::visitFeaturesInRange( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, const std::function<void ( QgsFeatureId )> &visitor ) const { // TODO -- add spatial index if performance is an issue const QgsRectangle profileRange( distanceRange.lower(), elevationRange.lower(), distanceRange.upper(), elevationRange.upper() ); @@ -1621,7 +1621,7 @@ bool QgsVectorLayerProfileGenerator::generateProfileForPolygons() return true; } -double QgsVectorLayerProfileGenerator::terrainHeight( double x, double y ) +double QgsVectorLayerProfileGenerator::terrainHeight( double x, double y ) const { if ( !mTerrainProvider ) return std::numeric_limits<double>::quiet_NaN(); @@ -1640,7 +1640,7 @@ double QgsVectorLayerProfileGenerator::terrainHeight( double x, double y ) return mTerrainProvider->heightAt( x, y ); } -double QgsVectorLayerProfileGenerator::featureZToHeight( double x, double y, double z, double offset ) +double QgsVectorLayerProfileGenerator::featureZToHeight( double x, double y, double z, double offset ) const { switch ( mClamping ) { @@ -1677,7 +1677,7 @@ double QgsVectorLayerProfileGenerator::featureZToHeight( double x, double y, dou return ( std::isnan( z ) ? 0 : z ) * mScale + offset; } -void QgsVectorLayerProfileGenerator::clampAltitudes( QgsLineString *lineString, const QgsPoint ¢roid, double offset ) +void QgsVectorLayerProfileGenerator::clampAltitudes( QgsLineString *lineString, const QgsPoint ¢roid, double offset ) const { for ( int i = 0; i < lineString->nCoordinates(); ++i ) { @@ -1729,7 +1729,7 @@ void QgsVectorLayerProfileGenerator::clampAltitudes( QgsLineString *lineString, } } -bool QgsVectorLayerProfileGenerator::clampAltitudes( QgsPolygon *polygon, double offset ) +bool QgsVectorLayerProfileGenerator::clampAltitudes( QgsPolygon *polygon, double offset ) const { if ( !polygon->is3D() ) polygon->addZValue( 0 ); diff --git a/src/core/vector/qgsvectorlayerprofilegenerator.h b/src/core/vector/qgsvectorlayerprofilegenerator.h index 8bc647801107..021f210e154e 100644 --- a/src/core/vector/qgsvectorlayerprofilegenerator.h +++ b/src/core/vector/qgsvectorlayerprofilegenerator.h @@ -88,9 +88,9 @@ class CORE_EXPORT QgsVectorLayerProfileResults : public QgsAbstractProfileSurfac QgsProfileSnapResult snapPointToIndividualFeatures( const QgsProfilePoint &point, const QgsProfileSnapContext &context ); void visitFeaturesAtPoint( const QgsProfilePoint &point, double maximumPointDistanceDelta, double maximumPointElevationDelta, double maximumSurfaceElevationDelta, - const std::function< void( QgsFeatureId, double delta, double distance, double elevation ) > &visitor, bool visitWithin ); + const std::function< void( QgsFeatureId, double delta, double distance, double elevation ) > &visitor, bool visitWithin ) const; void visitFeaturesInRange( const QgsDoubleRange &distanceRange, const QgsDoubleRange &elevationRange, - const std::function<void ( QgsFeatureId )> &visitor ); + const std::function<void ( QgsFeatureId )> &visitor ) const; }; @@ -136,11 +136,11 @@ class CORE_EXPORT QgsVectorLayerProfileGenerator : public QgsAbstractProfileSurf void processTriangleIntersectForLine( const QgsPolygon *triangle, const QgsLineString *intersect, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts ); void processTriangleIntersectForPolygon( const QgsPolygon *triangle, const QgsPolygon *intersectionPolygon, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts ); - double terrainHeight( double x, double y ); - double featureZToHeight( double x, double y, double z, double offset ); + double terrainHeight( double x, double y ) const; + double featureZToHeight( double x, double y, double z, double offset ) const; - void clampAltitudes( QgsLineString *lineString, const QgsPoint ¢roid, double offset ); - bool clampAltitudes( QgsPolygon *polygon, double offset ); + void clampAltitudes( QgsLineString *lineString, const QgsPoint ¢roid, double offset ) const; + bool clampAltitudes( QgsPolygon *polygon, double offset ) const; QString mId; std::unique_ptr<QgsFeedback> mFeedback = nullptr; From 670e2883696407887b9624afb678aea0bd7eb61d Mon Sep 17 00:00:00 2001 From: Jean Felder <jean.felder@oslandia.com> Date: Fri, 25 Oct 2024 13:34:28 +0200 Subject: [PATCH 3/4] qgsvectorlayerprofilegenerator: Add support for custom tolerance --- .../vector/qgsvectorlayerprofilegenerator.cpp | 23 ++++++++++++------- .../vector/qgsvectorlayerprofilegenerator.h | 4 ++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/core/vector/qgsvectorlayerprofilegenerator.cpp b/src/core/vector/qgsvectorlayerprofilegenerator.cpp index d7971b0205ce..2c7aac503a5f 100644 --- a/src/core/vector/qgsvectorlayerprofilegenerator.cpp +++ b/src/core/vector/qgsvectorlayerprofilegenerator.cpp @@ -687,6 +687,8 @@ QgsVectorLayerProfileGenerator::QgsVectorLayerProfileGenerator( QgsVectorLayer * , mBinding( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->binding() ) , mExtrusionEnabled( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->extrusionEnabled() ) , mExtrusionHeight( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->extrusionHeight() ) + , mCustomToleranceEnabled( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->customToleranceEnabled() ) + , mCustomTolerance( qgis::down_cast< QgsVectorLayerElevationProperties * >( layer->elevationProperties() )->customTolerance() ) , mExpressionContext( request.expressionContext() ) , mFields( layer->fields() ) , mDataDefinedProperties( layer->elevationProperties()->dataDefinedProperties() ) @@ -818,13 +820,13 @@ bool QgsVectorLayerProfileGenerator::generateProfileInner( const QgsProfileGener mProfileCurveEngine.reset( new QgsGeos( mProfileCurve.get() ) ); mProfileCurveEngine->prepareGeometry(); - if ( mTolerance == 0.0 ) // geos does not handle very well buffer with 0 size + if ( tolerance() == 0.0 ) // geos does not handle very well buffer with 0 size { mProfileBufferedCurve = std::unique_ptr<QgsAbstractGeometry>( mProfileCurve->clone() ); } else { - mProfileBufferedCurve = std::unique_ptr<QgsAbstractGeometry>( mProfileCurveEngine->buffer( mTolerance, 8, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Round, 2 ) ); + mProfileBufferedCurve = std::unique_ptr<QgsAbstractGeometry>( mProfileCurveEngine->buffer( tolerance(), 8, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Round, 2 ) ); } mProfileBufferedCurveEngine.reset( new QgsGeos( mProfileBufferedCurve.get() ) ); @@ -875,7 +877,7 @@ bool QgsVectorLayerProfileGenerator::generateProfileForPoints() // get features from layer QgsFeatureRequest request; request.setCoordinateTransform( QgsCoordinateTransform( mSourceCrs, mTargetCrs, mTransformContext ) ); - request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), mTolerance ); + request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), tolerance() ); request.setSubsetOfAttributes( mDataDefinedProperties.referencedFields( mExpressionContext ), mFields ); request.setFeedback( mFeedback.get() ); @@ -1038,9 +1040,9 @@ bool QgsVectorLayerProfileGenerator::generateProfileForLines() // get features from layer QgsFeatureRequest request; request.setDestinationCrs( mTargetCrs, mTransformContext ); - if ( mTolerance > 0 ) + if ( tolerance() > 0 ) { - request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), mTolerance ); + request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), tolerance() ); } else { @@ -1330,9 +1332,9 @@ bool QgsVectorLayerProfileGenerator::generateProfileForPolygons() // get features from layer QgsFeatureRequest request; request.setDestinationCrs( mTargetCrs, mTransformContext ); - if ( mTolerance > 0 ) + if ( tolerance() > 0 ) { - request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), mTolerance ); + request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), tolerance() ); } else { @@ -1403,7 +1405,7 @@ bool QgsVectorLayerProfileGenerator::generateProfileForPolygons() if ( mFeedback->isCanceled() ) return; - if ( mTolerance > 0.0 ) // if the tolerance is not 0.0 we will have a polygon / polygon intersection, we do not need tessellation + if ( tolerance() > 0.0 ) // if the tolerance is not 0.0 we will have a polygon / polygon intersection, we do not need tessellation { QString error; if ( mProfileBufferedCurveEngine->intersects( clampedPolygon.get(), &error ) ) @@ -1621,6 +1623,11 @@ bool QgsVectorLayerProfileGenerator::generateProfileForPolygons() return true; } +double QgsVectorLayerProfileGenerator::tolerance() const +{ + return mCustomToleranceEnabled ? mCustomTolerance : mTolerance; +} + double QgsVectorLayerProfileGenerator::terrainHeight( double x, double y ) const { if ( !mTerrainProvider ) diff --git a/src/core/vector/qgsvectorlayerprofilegenerator.h b/src/core/vector/qgsvectorlayerprofilegenerator.h index 021f210e154e..e971038937f5 100644 --- a/src/core/vector/qgsvectorlayerprofilegenerator.h +++ b/src/core/vector/qgsvectorlayerprofilegenerator.h @@ -136,6 +136,8 @@ class CORE_EXPORT QgsVectorLayerProfileGenerator : public QgsAbstractProfileSurf void processTriangleIntersectForLine( const QgsPolygon *triangle, const QgsLineString *intersect, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts ); void processTriangleIntersectForPolygon( const QgsPolygon *triangle, const QgsPolygon *intersectionPolygon, QVector< QgsGeometry > &transformedParts, QVector< QgsGeometry > &crossSectionParts ); + double tolerance() const; + double terrainHeight( double x, double y ) const; double featureZToHeight( double x, double y, double z, double offset ) const; @@ -171,6 +173,8 @@ class CORE_EXPORT QgsVectorLayerProfileGenerator : public QgsAbstractProfileSurf Qgis::AltitudeBinding mBinding = Qgis::AltitudeBinding::Centroid; bool mExtrusionEnabled = false; double mExtrusionHeight = 0; + bool mCustomToleranceEnabled = false; + double mCustomTolerance = 0; QgsExpressionContext mExpressionContext; QgsFields mFields; From d9b9b442fce3dedfaf94cda2d7e9c1402acb6ec3 Mon Sep 17 00:00:00 2001 From: Jean Felder <jean.felder@oslandia.com> Date: Fri, 25 Oct 2024 14:23:20 +0200 Subject: [PATCH 4/4] qgsvectorelevationpropertieswidget: Add support for custom tolerance --- .../qgsvectorelevationpropertieswidget.cpp | 6 +++ .../qgsvectorelevationpropertieswidgetbase.ui | 51 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/app/vector/qgsvectorelevationpropertieswidget.cpp b/src/app/vector/qgsvectorelevationpropertieswidget.cpp index e784ed745c95..8688bacba42c 100644 --- a/src/app/vector/qgsvectorelevationpropertieswidget.cpp +++ b/src/app/vector/qgsvectorelevationpropertieswidget.cpp @@ -45,6 +45,7 @@ QgsVectorElevationPropertiesWidget::QgsVectorElevationPropertiesWidget( QgsVecto mOffsetZSpinBox->setClearValue( 0 ); mScaleZSpinBox->setClearValue( 1 ); mExtrusionSpinBox->setClearValue( 0 ); + mToleranceSpinBox->setClearValue( 0 ); mLineStyleButton->setSymbolType( Qgis::SymbolType::Line ); mFillStyleButton->setSymbolType( Qgis::SymbolType::Fill ); @@ -78,6 +79,7 @@ QgsVectorElevationPropertiesWidget::QgsVectorElevationPropertiesWidget( QgsVecto connect( mElevationLimitSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsVectorElevationPropertiesWidget::onChanged ); connect( mExtrusionSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsVectorElevationPropertiesWidget::onChanged ); connect( mExtrusionGroupBox, &QGroupBox::toggled, this, &QgsVectorElevationPropertiesWidget::onChanged ); + connect( mToleranceSpinBox, qOverload<double >( &QDoubleSpinBox::valueChanged ), this, &QgsVectorElevationPropertiesWidget::onChanged ); connect( mComboClamping, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsVectorElevationPropertiesWidget::onChanged ); connect( mComboBinding, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsVectorElevationPropertiesWidget::onChanged ); connect( mComboClamping, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsVectorElevationPropertiesWidget::clampingChanged ); @@ -155,6 +157,8 @@ void QgsVectorElevationPropertiesWidget::syncToLayer( QgsMapLayer *layer ) mElevationLimitSpinBox->setValue( props->elevationLimit() ); mExtrusionGroupBox->setChecked( props->extrusionEnabled() ); mExtrusionSpinBox->setValue( props->extrusionHeight() ); + mToleranceGroupBox->setChecked( props->customToleranceEnabled() ); + mToleranceSpinBox->setValue( props->customTolerance() ); mTypeComboBox->setCurrentIndex( mTypeComboBox->findData( static_cast<int>( props->type() ) ) ); switch ( props->type() ) { @@ -234,6 +238,8 @@ void QgsVectorElevationPropertiesWidget::apply() props->setBinding( static_cast<Qgis::AltitudeBinding>( mComboBinding->currentData().toInt() ) ); props->setExtrusionEnabled( mExtrusionGroupBox->isChecked() ); props->setExtrusionHeight( mExtrusionSpinBox->value() ); + props->setCustomToleranceEnabled( mToleranceGroupBox->isChecked() ); + props->setCustomTolerance( mToleranceSpinBox->value() ); if ( mElevationLimitSpinBox->value() != mElevationLimitSpinBox->clearValue() ) props->setElevationLimit( mElevationLimitSpinBox->value() ); else diff --git a/src/ui/qgsvectorelevationpropertieswidgetbase.ui b/src/ui/qgsvectorelevationpropertieswidgetbase.ui index 0e13169f6905..279a3fa3ad18 100644 --- a/src/ui/qgsvectorelevationpropertieswidgetbase.ui +++ b/src/ui/qgsvectorelevationpropertieswidgetbase.ui @@ -279,6 +279,57 @@ </layout> </widget> </item> + <item> + <widget class="QGroupBox" name="mToleranceGroupBox"> + <property name="toolTip"> + <string>If checked, the layer will use this tolerance instead of the one defined in the Elevation Profile widget.</string> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="title"> + <string>Custom Tolerance</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <property name="syncGroup" stdset="0"> + <string notr="true">vectorgeneral</string> + </property> + <layout class="QGridLayout" name="gridLayout_Tolerance"> + <item row="0" column="0"> + <widget class="QLabel" name="label_Tolerance"> + <property name="text"> + <string>Tolerance</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QgsDoubleSpinBox" name="mToleranceSpinBox"> + <property name="decimals"> + <number>6</number> + </property> + <property name="minimum"> + <double>0.000000000000000</double> + </property> + <property name="maximum"> + <double>99999999999.000000000000000</double> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="Line" name="line_Tolerance"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + </layout> + </widget> + </item> <item> <widget class="QGroupBox" name="groupBox"> <property name="title">