diff --git a/python/PyQt6/core/auto_generated/vector/qgsvectorlayerelevationproperties.sip.in b/python/PyQt6/core/auto_generated/vector/qgsvectorlayerelevationproperties.sip.in index e07d53a2b29..1c061aca8f6 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 e07d53a2b29..1c061aca8f6 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 5ec89d48a30..dfeb624367a 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( "
  • %1
  • " ).arg( properties.join( QLatin1String( "
  • " ) ) ); @@ -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 7a021e1582f..7349785449b 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 8a709dcaa03..8f5659a4340 100644 --- a/tests/src/python/test_qgslayoutelevationprofile.py +++ b/tests/src/python/test_qgslayoutelevationprofile.py @@ -916,6 +916,10 @@ class TestQgsLayoutItemElevationProfile(QgisTestCase, LayoutItemTestCase): 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 @@ class TestQgsLayoutItemElevationProfile(QgisTestCase, LayoutItemTestCase): ) ) - 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 b57614823db..7b3b0bfbdce 100644 --- a/tests/src/python/test_qgsvectorlayerelevationproperties.py +++ b/tests/src/python/test_qgsvectorlayerelevationproperties.py @@ -46,6 +46,8 @@ class TestQgsVectorLayerElevationProperties(QgisTestCase): 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 @@ class TestQgsVectorLayerElevationProperties(QgisTestCase): ) 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 @@ class TestQgsVectorLayerElevationProperties(QgisTestCase): ) 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 @@ class TestQgsVectorLayerElevationProperties(QgisTestCase): 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 89bf08803f7..e1fac53c446 100644 --- a/tests/src/python/test_qgsvectorlayerprofilegenerator.py +++ b/tests/src/python/test_qgsvectorlayerprofilegenerator.py @@ -691,7 +691,9 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): 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() @@ -739,7 +741,9 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): 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() @@ -787,7 +791,9 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): 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() @@ -1391,11 +1397,14 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): 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( @@ -1413,7 +1422,7 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): req.setTerrainProvider(terrain_provider) req.setCrs(QgsCoordinateReferenceSystem("EPSG:3857")) - req.setTolerance(2.0) + req.setTolerance(tolerance) generator = vl.createProfileGenerator(req) self.assertTrue(generator.generateProfile()) @@ -2784,6 +2793,7 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): 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( @@ -2797,6 +2807,7 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): 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( @@ -2806,7 +2817,7 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): req.setTransformContext(self.create_transform_context()) req.setCrs(QgsCoordinateReferenceSystem()) - req.setTolerance(20) + req.setTolerance(tolerance) plot_renderer = QgsProfilePlotRenderer([vl], req) plot_renderer.startGeneration() @@ -2881,11 +2892,13 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): 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( @@ -2895,7 +2908,7 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): req.setTransformContext(self.create_transform_context()) req.setCrs(QgsCoordinateReferenceSystem()) - req.setTolerance(10) + req.setTolerance(tolerance) plot_renderer = QgsProfilePlotRenderer([vl], req) plot_renderer.startGeneration() @@ -2998,6 +3011,8 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): expectedFeatures: list[list[int]], ): request.setTolerance(tolerance) + if tolerance > 0 and layer.geometryType() != Qgis.GeometryType.Point: + layer.elevationProperties().setCustomTolerance(tolerance) profGen = layer.createProfileGenerator(request) self.assertIsNotNone(profGen) @@ -3284,6 +3299,9 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): 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)) @@ -3297,7 +3315,7 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): req = QgsProfileRequest(curve) req.setCrs(QgsCoordinateReferenceSystem("EPSG:3857")) - req.setTolerance(10) + req.setTolerance(tolerance) generator = vl.createProfileGenerator(req) self.assertTrue(generator.generateProfile()) @@ -3340,6 +3358,9 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): vl.elevationProperties().setClamping(Qgis.AltitudeClamping.Absolute) vl.elevationProperties().setExtrusionEnabled(False) + tolerance = 10 + vl.elevationProperties().setCustomTolerance(tolerance) + vl_feature = QgsFeature() vl_feature.setGeometry( QgsGeometry.fromWkt( @@ -3356,7 +3377,7 @@ class TestQgsVectorLayerProfileGenerator(QgisTestCase): req = QgsProfileRequest(curve) req.setCrs(QgsCoordinateReferenceSystem("EPSG:3857")) - req.setTolerance(10) + req.setTolerance(tolerance) generator = vl.createProfileGenerator(req) self.assertTrue(generator.generateProfile())