diff --git a/src/core/qgsclipper.h b/src/core/qgsclipper.h index d3a7d7a820e..4a0f7522449 100644 --- a/src/core/qgsclipper.h +++ b/src/core/qgsclipper.h @@ -566,9 +566,9 @@ inline bool QgsClipper::inside( double x, double y, double z, Boundary boundary, case YMin: // y > MIN_Y is inside return ( y > boundaryValue ); case ZMax: // z < MAX_Z is inside - return ( z < boundaryValue ); + return z < boundaryValue || std::isnan( z ) ; case ZMin: // z > MIN_Z is inside - return ( z > boundaryValue ); + return z > boundaryValue || std::isnan( z ); } return false; } diff --git a/tests/src/core/testqgsclipper.cpp b/tests/src/core/testqgsclipper.cpp index 31b5a0d0c23..9f752528923 100644 --- a/tests/src/core/testqgsclipper.cpp +++ b/tests/src/core/testqgsclipper.cpp @@ -42,6 +42,7 @@ class TestQgsClipper: public QgsTest void basicWithZ(); void basicWithZInf(); void epsg4978LineRendering(); + void clipGeometryWithNaNZValues(); private: @@ -202,6 +203,50 @@ void TestQgsClipper::epsg4978LineRendering() QVERIFY( render2dCheck( "4978_2D_line_rendering", layerLines.get(), QgsRectangle( -2.5e7, -2.5e7, 2.5e7, 2.5e7 ) ) ); } +void TestQgsClipper::clipGeometryWithNaNZValues() +{ + // nan z values should not result in clipping + QgsGeometry geom = QgsGeometry::fromWkt( QStringLiteral( "PolygonZ ((704425.82266869 7060014.33574043 19.51, 704439.59844559 7060023.73007711 19.69, 704441.6748229 7060020.65665367 19.63, 704428.333268 7060011.65915509 19.42, 704428.15434668 7060011.92446088 0, 704441.23037799 7060020.74289127 0, 704439.51320673 7060023.28462315 0, 704426.00295955 7060014.07136342 0, 704425.82266869 7060014.33574043 19.51))" ) ); + QgsLineString *exteriorRing = qgsgeometry_cast< QgsLineString * >( qgsgeometry_cast< QgsPolygon *>( geom.get() )->exteriorRing() ); + exteriorRing->setZAt( 4, std::numeric_limits::quiet_NaN() ); + exteriorRing->setZAt( 5, std::numeric_limits::quiet_NaN() ); + exteriorRing->setZAt( 6, std::numeric_limits::quiet_NaN() ); + exteriorRing->setZAt( 7, std::numeric_limits::quiet_NaN() ); + + QPolygonF polygon; + polygon << QPointF( 10.4, 20.5 ) << QPointF( 20.2, 30.2 ); + + QVector< double > pointsX = exteriorRing->xVector(); + QVector< double > pointsY = exteriorRing->yVector(); + QVector< double > pointsZ = exteriorRing->zVector(); + QCOMPARE( pointsX.size(), 9 ); + + // 2d trim + QgsRectangle clipRect( 704430.30, 7060007.72, 704456.51, 7060029.03 ); + QgsClipper::trimPolygon( pointsX, pointsY, pointsZ, clipRect ); + // one vertex should be clipped + QCOMPARE( pointsX.size(), 8 ); + QCOMPARE( pointsY.size(), 8 ); + QCOMPARE( pointsZ.size(), 8 ); + QGSCOMPARENEAR( pointsX.at( 0 ), 704430.30, 0.01 ); + QGSCOMPARENEAR( pointsY.at( 0 ), 7060017.389, 0.01 ); + QGSCOMPARENEAR( pointsZ.at( 0 ), 19.568, 0.01 ); + + // 3d trim, clip box contains whole geometry + pointsX = exteriorRing->xVector(); + pointsY = exteriorRing->yVector(); + pointsZ = exteriorRing->zVector(); + QgsBox3d clipRect3D( 704430.30, 7060007.72, 0, 704456.51, 7060029.03, 100 ); + QgsClipper::trimPolygon( pointsX, pointsY, pointsZ, clipRect3D ); + // still only one vertex should be clipped, the nan z values must remain + QCOMPARE( pointsX.size(), 8 ); + QCOMPARE( pointsY.size(), 8 ); + QCOMPARE( pointsZ.size(), 8 ); + QGSCOMPARENEAR( pointsX.at( 0 ), 704430.30, 0.01 ); + QGSCOMPARENEAR( pointsY.at( 0 ), 7060017.389, 0.01 ); + QGSCOMPARENEAR( pointsZ.at( 0 ), 19.568, 0.01 ); +} + bool TestQgsClipper::render2dCheck( const QString &testName, QgsVectorLayer *layer, QgsRectangle extent ) { const QString myTmpDir = QDir::tempPath() + '/'; diff --git a/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py b/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py index c82ce0b3ef6..1a46b00814a 100644 --- a/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py +++ b/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py @@ -483,6 +483,40 @@ class TestQgsGeometryGeneratorSymbolLayerV2(unittest.TestCase): self.report += renderchecker.report() self.assertTrue(res) + def test_clipped_results_with_z(self): + """ + See https://github.com/qgis/QGIS/issues/51796 + """ + lines = QgsVectorLayer('LineString?crs=epsg:2154', 'Lines', 'memory') + self.assertTrue(lines.isValid()) + f = QgsFeature() + f.setGeometry(QgsGeometry.fromWkt('LineStringZ (704425.82266868802253157 7060014.33574043028056622 19.51000000000000156, 704439.59844558802433312 7060023.7300771102309227 19.69000000000000128, 704441.67482289997860789 7060020.65665366966277361 19.62999999999999901, 704428.333267995971255 7060011.65915509033948183 19.42000000000000171)')) + lines.dataProvider().addFeature(f) + + subsymbol = QgsFillSymbol.createSimple({'color': '#0000ff', 'line_style': 'no'}) + + parent_generator = QgsGeometryGeneratorSymbolLayer.create( + {'geometryModifier': 'single_sided_buffer($geometry,-0.32, 1, 2)'}) + parent_generator.setSymbolType(QgsSymbol.Fill) + + parent_generator.setSubSymbol(subsymbol) + + geom_symbol = QgsLineSymbol() + geom_symbol.changeSymbolLayer(0, parent_generator) + lines.renderer().setSymbol(geom_symbol) + + mapsettings = QgsMapSettings(self.mapsettings) + mapsettings.setDestinationCrs(lines.crs()) + mapsettings.setExtent(QgsRectangle(704433.77, 7060006.64, 704454.78, 7060027.95)) + mapsettings.setLayers([lines]) + + renderchecker = QgsMultiRenderChecker() + renderchecker.setMapSettings(mapsettings) + renderchecker.setControlName('expected_geometrygenerator_z_clipping') + res = renderchecker.runTest('geometrygenerator_z_clipping') + self.report += renderchecker.report() + self.assertTrue(res) + def imageCheck(self, name, reference_image, image): self.report += f"

Render {name}

\n" temp_dir = QDir.tempPath() + '/' diff --git a/tests/testdata/control_images/expected_geometrygenerator_z_clipping/expected_geometrygenerator_z_clipping.png b/tests/testdata/control_images/expected_geometrygenerator_z_clipping/expected_geometrygenerator_z_clipping.png new file mode 100644 index 00000000000..d0ba77a26c2 Binary files /dev/null and b/tests/testdata/control_images/expected_geometrygenerator_z_clipping/expected_geometrygenerator_z_clipping.png differ