Skip to content

Commit

Permalink
Add method to split QgsCurve geometries into two parts at a specific
Browse files Browse the repository at this point in the history
vertex index
  • Loading branch information
nyalldawson committed Apr 27, 2021
1 parent da76c99 commit 36e52f8
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 2 deletions.
2 changes: 1 addition & 1 deletion python/core/auto_generated/geometry/qgscurve.sip.in
Expand Up @@ -10,7 +10,7 @@



class QgsCurve: QgsAbstractGeometry
class QgsCurve: QgsAbstractGeometry /Abstract/
{
%Docstring(signature="appended")
Abstract base class for curved geometry type
Expand Down
1 change: 1 addition & 0 deletions python/core/auto_generated/geometry/qgslinestring.sip.in
Expand Up @@ -464,6 +464,7 @@ If ``useZValues`` is ``True`` then z values will also be considered when testing
virtual double length() const /HoldGIL/;



double length3D() const /HoldGIL/;
%Docstring
Returns the length in 3D world of the line string.
Expand Down
59 changes: 59 additions & 0 deletions src/core/geometry/qgscircularstring.cpp
Expand Up @@ -786,6 +786,65 @@ void QgsCircularString::transformVertices( const std::function<QgsPoint( const Q
clearCache();
}

std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsCircularString::splitCurveAtVertex( int index ) const
{
const bool useZ = is3D();
const bool useM = isMeasure();

const int size = mX.size();
if ( size == 0 )
return std::make_tuple( std::make_unique< QgsCircularString >(), std::make_unique< QgsCircularString >() );

index = std::clamp( index, 0, size - 1 );

const int part1Size = index + 1;
QVector< double > x1( part1Size );
QVector< double > y1( part1Size );
QVector< double > z1( useZ ? part1Size : 0 );
QVector< double > m1( useM ? part1Size : 0 );

const double *sourceX = mX.constData();
const double *sourceY = mY.constData();
const double *sourceZ = useZ ? mZ.constData() : nullptr;
const double *sourceM = useM ? mM.constData() : nullptr;

double *destX = x1.data();
double *destY = y1.data();
double *destZ = useZ ? z1.data() : nullptr;
double *destM = useM ? m1.data() : nullptr;

std::copy( sourceX, sourceX + part1Size, destX );
std::copy( sourceY, sourceY + part1Size, destY );
if ( useZ )
std::copy( sourceZ, sourceZ + part1Size, destZ );
if ( useM )
std::copy( sourceM, sourceM + part1Size, destM );

const int part2Size = size - index;
if ( part2Size < 2 )
return std::make_tuple( std::make_unique< QgsCircularString >( x1, y1, z1, m1 ), std::make_unique< QgsCircularString >() );

QVector< double > x2( part2Size );
QVector< double > y2( part2Size );
QVector< double > z2( useZ ? part2Size : 0 );
QVector< double > m2( useM ? part2Size : 0 );
destX = x2.data();
destY = y2.data();
destZ = useZ ? z2.data() : nullptr;
destM = useM ? m2.data() : nullptr;
std::copy( sourceX + index, sourceX + size, destX );
std::copy( sourceY + index, sourceY + size, destY );
if ( useZ )
std::copy( sourceZ + index, sourceZ + size, destZ );
if ( useM )
std::copy( sourceM + index, sourceM + size, destM );

if ( part1Size < 2 )
return std::make_tuple( std::make_unique< QgsCircularString >(), std::make_unique< QgsCircularString >( x2, y2, z2, m2 ) );
else
return std::make_tuple( std::make_unique< QgsCircularString >( x1, y1, z1, m1 ), std::make_unique< QgsCircularString >( x2, y2, z2, m2 ) );
}

void QgsCircularString::points( QgsPointSequence &pts ) const
{
pts.clear();
Expand Down
1 change: 1 addition & 0 deletions src/core/geometry/qgscircularstring.h
Expand Up @@ -167,6 +167,7 @@ class CORE_EXPORT QgsCircularString: public QgsCurve
#ifndef SIP_RUN
void filterVertices( const std::function< bool( const QgsPoint & ) > &filter ) override;
void transformVertices( const std::function< QgsPoint( const QgsPoint & ) > &transform ) override;
std::tuple< std::unique_ptr< QgsCurve >, std::unique_ptr< QgsCurve > > splitCurveAtVertex( int index ) const final;

/**
* Cast the \a geom to a QgsCircularString.
Expand Down
40 changes: 40 additions & 0 deletions src/core/geometry/qgscompoundcurve.cpp
Expand Up @@ -913,6 +913,46 @@ void QgsCompoundCurve::transformVertices( const std::function<QgsPoint( const Qg
clearCache();
}

std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsCompoundCurve::splitCurveAtVertex( int index ) const
{
if ( mCurves.empty() )
return std::make_tuple( std::make_unique< QgsCompoundCurve >(), std::make_unique< QgsCompoundCurve >() );

int curveStart = 0;

std::unique_ptr< QgsCompoundCurve > curve1 = std::make_unique< QgsCompoundCurve >();
std::unique_ptr< QgsCompoundCurve > curve2;

for ( const QgsCurve *curve : mCurves )
{
const int curveSize = curve->numPoints();
if ( !curve2 && index < curveStart + curveSize )
{
// split the curve
auto [ p1, p2 ] = curve->splitCurveAtVertex( index - curveStart );
if ( !p1->isEmpty() )
curve1->addCurve( p1.release() );

curve2 = std::make_unique< QgsCompoundCurve >();
if ( !p2->isEmpty() )
curve2->addCurve( p2.release() );
}
else
{
if ( curve2 )
curve2->addCurve( curve->clone() );
else
curve1->addCurve( curve->clone() );
}

// subtract 1 here, because the next curve will start with the same
// vertex as this curve ended at
curveStart += curve->numPoints() - 1;
}

return std::make_tuple( std::move( curve1 ), curve2 ? std::move( curve2 ) : std::make_unique< QgsCompoundCurve >() );
}

void QgsCompoundCurve::sumUpArea( double &sum ) const
{
for ( const QgsCurve *curve : mCurves )
Expand Down
1 change: 1 addition & 0 deletions src/core/geometry/qgscompoundcurve.h
Expand Up @@ -144,6 +144,7 @@ class CORE_EXPORT QgsCompoundCurve: public QgsCurve
#ifndef SIP_RUN
void filterVertices( const std::function< bool( const QgsPoint & ) > &filter ) override;
void transformVertices( const std::function< QgsPoint( const QgsPoint & ) > &transform ) override;
std::tuple< std::unique_ptr< QgsCurve >, std::unique_ptr< QgsCurve > > splitCurveAtVertex( int index ) const final;

/**
* Cast the \a geom to a QgsCompoundCurve.
Expand Down
15 changes: 14 additions & 1 deletion src/core/geometry/qgscurve.h
Expand Up @@ -32,7 +32,7 @@ class QgsLineString;
* \brief Abstract base class for curved geometry type
* \since QGIS 2.10
*/
class CORE_EXPORT QgsCurve: public QgsAbstractGeometry
class CORE_EXPORT QgsCurve: public QgsAbstractGeometry SIP_ABSTRACT
{
public:

Expand Down Expand Up @@ -283,6 +283,19 @@ class CORE_EXPORT QgsCurve: public QgsAbstractGeometry
}
return nullptr;
}

/**
* Splits the curve at the specified vertex \a index, returning two curves which represent the portion of the
* curve up to an including the vertex at \a index, and the portion of the curve from the vertex at \a index (inclusive)
* to the end of the curve.
*
* \note The vertex \a index must correspond to a segment vertex, not a curve vertex.
*
* \note Not available in Python bindings.
* \since QGIS 3.20
*/
virtual std::tuple< std::unique_ptr< QgsCurve >, std::unique_ptr< QgsCurve > > splitCurveAtVertex( int index ) const = 0;

#endif


Expand Down
59 changes: 59 additions & 0 deletions src/core/geometry/qgslinestring.cpp
Expand Up @@ -782,6 +782,65 @@ double QgsLineString::length() const
return total;
}

std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsLineString::splitCurveAtVertex( int index ) const
{
const bool useZ = is3D();
const bool useM = isMeasure();

const int size = mX.size();
if ( size == 0 )
return std::make_tuple( std::make_unique< QgsLineString >(), std::make_unique< QgsLineString >() );

index = std::clamp( index, 0, size - 1 );

const int part1Size = index + 1;
QVector< double > x1( part1Size );
QVector< double > y1( part1Size );
QVector< double > z1( useZ ? part1Size : 0 );
QVector< double > m1( useM ? part1Size : 0 );

const double *sourceX = mX.constData();
const double *sourceY = mY.constData();
const double *sourceZ = useZ ? mZ.constData() : nullptr;
const double *sourceM = useM ? mM.constData() : nullptr;

double *destX = x1.data();
double *destY = y1.data();
double *destZ = useZ ? z1.data() : nullptr;
double *destM = useM ? m1.data() : nullptr;

std::copy( sourceX, sourceX + part1Size, destX );
std::copy( sourceY, sourceY + part1Size, destY );
if ( useZ )
std::copy( sourceZ, sourceZ + part1Size, destZ );
if ( useM )
std::copy( sourceM, sourceM + part1Size, destM );

const int part2Size = size - index;
if ( part2Size < 2 )
return std::make_tuple( std::make_unique< QgsLineString >( x1, y1, z1, m1 ), std::make_unique< QgsLineString >() );

QVector< double > x2( part2Size );
QVector< double > y2( part2Size );
QVector< double > z2( useZ ? part2Size : 0 );
QVector< double > m2( useM ? part2Size : 0 );
destX = x2.data();
destY = y2.data();
destZ = useZ ? z2.data() : nullptr;
destM = useM ? m2.data() : nullptr;
std::copy( sourceX + index, sourceX + size, destX );
std::copy( sourceY + index, sourceY + size, destY );
if ( useZ )
std::copy( sourceZ + index, sourceZ + size, destZ );
if ( useM )
std::copy( sourceM + index, sourceM + size, destM );

if ( part1Size < 2 )
return std::make_tuple( std::make_unique< QgsLineString >(), std::make_unique< QgsLineString >( x2, y2, z2, m2 ) );
else
return std::make_tuple( std::make_unique< QgsLineString >( x1, y1, z1, m1 ), std::make_unique< QgsLineString >( x2, y2, z2, m2 ) );
}

double QgsLineString::length3D() const
{
if ( is3D() )
Expand Down
4 changes: 4 additions & 0 deletions src/core/geometry/qgslinestring.h
Expand Up @@ -622,6 +622,10 @@ class CORE_EXPORT QgsLineString: public QgsCurve
//curve interface
double length() const override SIP_HOLDGIL;

#ifndef SIP_RUN
std::tuple< std::unique_ptr< QgsCurve >, std::unique_ptr< QgsCurve > > splitCurveAtVertex( int index ) const final;
#endif

/**
* Returns the length in 3D world of the line string.
* If it is not a 3D line string, return its 2D length.
Expand Down

0 comments on commit 36e52f8

Please sign in to comment.