Skip to content

Commit

Permalink
Add QgsCircularString::append( const QgsCircularString* )
Browse files Browse the repository at this point in the history
Appends the contents of one circular string to the end of another
  • Loading branch information
nyalldawson committed Apr 27, 2021
1 parent 12066cc commit a9a6e38
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 0 deletions.
15 changes: 15 additions & 0 deletions python/core/auto_generated/geometry/qgscircularstring.sip.in
Expand Up @@ -122,6 +122,21 @@ Returns the point at index i within the circular string.
void setPoints( const QgsPointSequence &points );
%Docstring
Sets the circular string's points
%End

void append( const QgsCircularString *string );
%Docstring
Appends the contents of another circular ``string`` to the end of this circular string.

:param string: circular string to append. Ownership is not transferred.

.. warning::

It is the caller's responsibility to ensure that the first point in the appended
``string`` matches the last point in the existing curve, or the result will be undefined.


.. versionadded:: 3.20
%End

virtual double length() const;
Expand Down
65 changes: 65 additions & 0 deletions src/core/geometry/qgscircularstring.cpp
Expand Up @@ -853,6 +853,71 @@ void QgsCircularString::setPoints( const QgsPointSequence &points )
}
}

void QgsCircularString::append( const QgsCircularString *line )
{
if ( !line )
{
return;
}

if ( numPoints() < 1 )
{
setZMTypeFromSubGeometry( line, QgsWkbTypes::CircularString );
}

// do not store duplicate points
if ( numPoints() > 0 &&
line->numPoints() > 0 &&
qgsDoubleNear( endPoint().x(), line->startPoint().x() ) &&
qgsDoubleNear( endPoint().y(), line->startPoint().y() ) &&
( !is3D() || !line->is3D() || qgsDoubleNear( endPoint().z(), line->startPoint().z() ) ) &&
( !isMeasure() || !line->isMeasure() || qgsDoubleNear( endPoint().m(), line->startPoint().m() ) ) )
{
mX.pop_back();
mY.pop_back();

if ( is3D() && line->is3D() )
{
mZ.pop_back();
}
if ( isMeasure() && line->isMeasure() )
{
mM.pop_back();
}
}

mX += line->mX;
mY += line->mY;

if ( is3D() )
{
if ( line->is3D() )
{
mZ += line->mZ;
}
else
{
// if append line does not have z coordinates, fill with NaN to match number of points in final line
mZ.insert( mZ.count(), mX.size() - mZ.size(), std::numeric_limits<double>::quiet_NaN() );
}
}

if ( isMeasure() )
{
if ( line->isMeasure() )
{
mM += line->mM;
}
else
{
// if append line does not have m values, fill with NaN to match number of points in final line
mM.insert( mM.count(), mX.size() - mM.size(), std::numeric_limits<double>::quiet_NaN() );
}
}

clearCache(); //set bounding box invalid
}

void QgsCircularString::draw( QPainter &p ) const
{
QPainterPath path;
Expand Down
12 changes: 12 additions & 0 deletions src/core/geometry/qgscircularstring.h
Expand Up @@ -118,6 +118,18 @@ class CORE_EXPORT QgsCircularString: public QgsCurve
*/
void setPoints( const QgsPointSequence &points );

/**
* Appends the contents of another circular \a string to the end of this circular string.
*
* \param string circular string to append. Ownership is not transferred.
*
* \warning It is the caller's responsibility to ensure that the first point in the appended
* \a string matches the last point in the existing curve, or the result will be undefined.
*
* \since QGIS 3.20
*/
void append( const QgsCircularString *string );

double length() const override;
QgsPoint startPoint() const override SIP_HOLDGIL;
QgsPoint endPoint() const override SIP_HOLDGIL;
Expand Down
122 changes: 122 additions & 0 deletions tests/src/core/testqgsgeometry.cpp
Expand Up @@ -126,6 +126,7 @@ class TestQgsGeometry : public QObject
void lineString(); //test QgsLineString
void circularString();
void circularStringFromArray();
void circularStringAppend();
void polygon(); //test QgsPolygon
void curvePolygon();
void triangle();
Expand Down Expand Up @@ -3163,6 +3164,127 @@ void TestQgsGeometry::circularStringFromArray()
QCOMPARE( fromArray8.pointN( 2 ).m(), 33.0 );
}

void TestQgsGeometry::circularStringAppend()
{
//append circularstring

//append to empty
QgsCircularString l10;
l10.append( nullptr );
QVERIFY( l10.isEmpty() );
QCOMPARE( l10.numPoints(), 0 );

std::unique_ptr<QgsCircularString> toAppend( new QgsCircularString() );
toAppend->setPoints( QgsPointSequence() << QgsPoint( 1, 2 )
<< QgsPoint( 11, 12 )
<< QgsPoint( 21, 22 ) );
l10.append( toAppend.get() );
QVERIFY( !l10.is3D() );
QVERIFY( !l10.isMeasure() );
QCOMPARE( l10.numPoints(), 3 );
QCOMPARE( l10.vertexCount(), 3 );
QCOMPARE( l10.nCoordinates(), 3 );
QCOMPARE( l10.ringCount(), 1 );
QCOMPARE( l10.partCount(), 1 );
QCOMPARE( l10.wkbType(), QgsWkbTypes::CircularString );
QCOMPARE( l10.pointN( 0 ), toAppend->pointN( 0 ) );
QCOMPARE( l10.pointN( 1 ), toAppend->pointN( 1 ) );
QCOMPARE( l10.pointN( 2 ), toAppend->pointN( 2 ) );

//add more points
toAppend.reset( new QgsCircularString() );
toAppend->setPoints( QgsPointSequence() << QgsPoint( 21, 22 )
<< QgsPoint( 41, 42 )
<< QgsPoint( 51, 52 ) );
l10.append( toAppend.get() );
QCOMPARE( l10.numPoints(), 5 );
QCOMPARE( l10.vertexCount(), 5 );
QCOMPARE( l10.nCoordinates(), 5 );
QCOMPARE( l10.ringCount(), 1 );
QCOMPARE( l10.partCount(), 1 );
QCOMPARE( l10.pointN( 2 ), toAppend->pointN( 0 ) );
QCOMPARE( l10.pointN( 3 ), toAppend->pointN( 1 ) );
QCOMPARE( l10.pointN( 4 ), toAppend->pointN( 2 ) );

//check dimensionality is inherited from append line if initially empty
l10.clear();
toAppend.reset( new QgsCircularString() );
toAppend->setPoints( QgsPointSequence() << QgsPoint( QgsWkbTypes::PointZM, 31, 32, 33, 34 )
<< QgsPoint( QgsWkbTypes::PointZM, 41, 42, 43, 44 )
<< QgsPoint( QgsWkbTypes::PointZM, 51, 52, 53, 54 ) );
l10.append( toAppend.get() );
QVERIFY( l10.is3D() );
QVERIFY( l10.isMeasure() );
QCOMPARE( l10.numPoints(), 3 );
QCOMPARE( l10.ringCount(), 1 );
QCOMPARE( l10.partCount(), 1 );
QCOMPARE( l10.wkbType(), QgsWkbTypes::CircularStringZM );
QCOMPARE( l10.pointN( 0 ), toAppend->pointN( 0 ) );
QCOMPARE( l10.pointN( 1 ), toAppend->pointN( 1 ) );
QCOMPARE( l10.pointN( 2 ), toAppend->pointN( 2 ) );

//append points with z to non z circular string
l10.clear();
l10.setPoints( QgsPointSequence() << QgsPoint( QgsWkbTypes::Point, 31, 32 )
<< QgsPoint( QgsWkbTypes::Point, 41, 42 )
<< QgsPoint( QgsWkbTypes::Point, 51, 52 ) );
QVERIFY( !l10.is3D() );
QCOMPARE( l10.wkbType(), QgsWkbTypes::CircularString );
toAppend.reset( new QgsCircularString() );
toAppend->setPoints( QgsPointSequence() << QgsPoint( QgsWkbTypes::PointZM, 51, 52, 33, 34 )
<< QgsPoint( QgsWkbTypes::PointZM, 141, 142, 43, 44 )
<< QgsPoint( QgsWkbTypes::PointZM, 151, 152, 53, 54 ) );
l10.append( toAppend.get() );
QCOMPARE( l10.wkbType(), QgsWkbTypes::CircularString );
QCOMPARE( l10.pointN( 0 ), QgsPoint( 31, 32 ) );
QCOMPARE( l10.pointN( 1 ), QgsPoint( 41, 42 ) );
QCOMPARE( l10.pointN( 2 ), QgsPoint( 51, 52 ) );
QCOMPARE( l10.pointN( 3 ), QgsPoint( 141, 142 ) );
QCOMPARE( l10.pointN( 4 ), QgsPoint( 151, 152 ) );

//append points without z/m to circularstring with z & m
l10.clear();
l10.setPoints( QgsPointSequence() << QgsPoint( QgsWkbTypes::PointZM, 31, 32, 11, 21 )
<< QgsPoint( QgsWkbTypes::PointZM, 41, 42, 12, 22 )
<< QgsPoint( QgsWkbTypes::PointZM, 51, 52, 13, 23 ) );
QVERIFY( l10.is3D() );
QVERIFY( l10.isMeasure() );
QCOMPARE( l10.wkbType(), QgsWkbTypes::CircularStringZM );
toAppend.reset( new QgsCircularString() );
toAppend->setPoints( QgsPointSequence() << QgsPoint( 51, 52 )
<< QgsPoint( 141, 142 )
<< QgsPoint( 151, 152 ) );
l10.append( toAppend.get() );
QCOMPARE( l10.wkbType(), QgsWkbTypes::CircularStringZM );
QCOMPARE( l10.pointN( 0 ), QgsPoint( QgsWkbTypes::PointZM, 31, 32, 11, 21 ) );
QCOMPARE( l10.pointN( 1 ), QgsPoint( QgsWkbTypes::PointZM, 41, 42, 12, 22 ) );
QCOMPARE( l10.pointN( 2 ), QgsPoint( QgsWkbTypes::PointZM, 51, 52, 13, 23 ) );
QCOMPARE( l10.pointN( 3 ), QgsPoint( QgsWkbTypes::PointZM, 141, 142 ) );
QCOMPARE( l10.pointN( 4 ), QgsPoint( QgsWkbTypes::PointZM, 151, 152 ) );

//append another line the closes the original geometry.
//Make sure there are not duplicate points except start and end point
l10.clear();
toAppend.reset( new QgsCircularString() );
toAppend->setPoints( QgsPointSequence()
<< QgsPoint( 1, 1 )
<< QgsPoint( 5, 5 )
<< QgsPoint( 10, 1 ) );
l10.append( toAppend.get() );
QCOMPARE( l10.numPoints(), 3 );
QCOMPARE( l10.vertexCount(), 3 );
toAppend.reset( new QgsCircularString() );
toAppend->setPoints( QgsPointSequence()
<< QgsPoint( 10, 1 )
<< QgsPoint( 5, 2 )
<< QgsPoint( 1, 1 ) );
l10.append( toAppend.get() );

QVERIFY( l10.isClosed() );
QCOMPARE( l10.numPoints(), 5 );
QCOMPARE( l10.vertexCount(), 5 );
}

void TestQgsGeometry::lineString()
{
//test constructors
Expand Down

0 comments on commit a9a6e38

Please sign in to comment.