From fbdc9cd5c3e23dd0824e17d70ba57fad299bbc52 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 3 Jun 2024 13:53:18 +1000 Subject: [PATCH] Optimise QgsLineString::snappedToGrid The old approach was quite inefficient and resulted in many reallocations and array resizing --- src/core/geometry/qgslinestring.cpp | 73 +++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/src/core/geometry/qgslinestring.cpp b/src/core/geometry/qgslinestring.cpp index 64e8a3c69d5..b4cfa2497e6 100644 --- a/src/core/geometry/qgslinestring.cpp +++ b/src/core/geometry/qgslinestring.cpp @@ -328,15 +328,72 @@ bool QgsLineString::isValid( QString &error, Qgis::GeometryValidityFlags flags ) QgsLineString *QgsLineString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const { - // prepare result - std::unique_ptr result { createEmptyWithSameType() }; - - bool res = snapToGridPrivate( hSpacing, vSpacing, dSpacing, mSpacing, mX, mY, mZ, mM, - result->mX, result->mY, result->mZ, result->mM ); - if ( res ) - return result.release(); - else + const int length = numPoints(); + if ( length < 2 ) return nullptr; + + const bool hasZ = is3D(); + const bool hasM = isMeasure(); + + QVector< double > outX; + outX.reserve( length ); + QVector< double > outY; + outY.reserve( length ); + QVector< double > outZ; + if ( hasZ ) + outZ.reserve( length ); + QVector< double > outM; + if ( hasM ) + outM.reserve( length ); + + const double *xIn = mX.constData(); + const double *yIn = mY.constData(); + const double *zIn = hasZ ? mZ.constData() : nullptr; + const double *mIn = hasM ? mM.constData() : nullptr; + + double previousX = 0; + double previousY = 0; + double previousZ = 0; + double previousM = 0; + for ( int i = 0; i < length; ++i ) + { + const double currentX = *xIn++; + const double currentY = *yIn++; + const double currentZ = zIn ? *zIn++ : 0; + const double currentM = mIn ? *mIn++ : 0; + + const double roundedX = hSpacing > 0 ? ( std::round( currentX / hSpacing ) * hSpacing ) : currentX; + const double roundedY = vSpacing > 0 ? ( std::round( currentY / vSpacing ) * vSpacing ) : currentY; + const double roundedZ = hasZ && dSpacing > 0 ? ( std::round( currentZ / dSpacing ) * dSpacing ) : currentZ; + const double roundedM = hasM && mSpacing > 0 ? ( std::round( currentM / mSpacing ) * mSpacing ) : currentM; + + if ( i > 0 ) + { + const bool isPointEqual = qgsDoubleNear( roundedX, previousX ) + && qgsDoubleNear( roundedY, previousY ) + && ( !hasZ || dSpacing <= 0 || qgsDoubleNear( roundedZ, previousZ ) ) + && ( !hasM || mSpacing <= 0 || qgsDoubleNear( roundedM, previousM ) ); + if ( isPointEqual ) + continue; + } + + outX.append( roundedX ); + outY.append( roundedY ); + if ( hasZ ) + outZ.append( roundedZ ); + if ( hasM ) + outM.append( roundedM ); + + previousX = roundedX; + previousY = roundedY; + previousZ = roundedZ; + previousM = roundedM; + } + + if ( outX.length() < 2 || ( isClosed() && outX.length() < 4 ) ) + return nullptr; + + return new QgsLineString( outX, outY, outZ, outM ); } bool QgsLineString::removeDuplicateNodes( double epsilon, bool useZValues )