Skip to content

Commit

Permalink
Split only on split points
Browse files Browse the repository at this point in the history
  • Loading branch information
uclaros committed Feb 11, 2022
1 parent dd1082d commit 3421770
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 40 deletions.
11 changes: 0 additions & 11 deletions src/core/geometry/qgsgeometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -942,17 +942,6 @@ Qgis::GeometryOperationResult QgsGeometry::splitGeometry( const QgsPointSequence
QVector<QgsGeometry > newGeoms;
QgsLineString splitLineString( splitLine );

/**
* QGIS uses GEOS algorithm to split geometries.
* Using 3D points in GEOS will returns an interpolation value which is the
* mean between geometries.
* On the contrary, in our logic, the interpolation is a linear interpolation
* on the split point. By dropping Z/M value, GEOS will returns the expected
* result. See https://github.com/qgis/QGIS/issues/33489
*/
splitLineString.dropZValue();
splitLineString.dropMValue();

QgsGeos geos( d->geometry.get() );
mLastError.clear();
QgsGeometryEngine::EngineOperationResult result = geos.splitGeometry( splitLineString, newGeoms, topological, topologyTestPoints, &mLastError, skipIntersectionTest );
Expand Down
114 changes: 85 additions & 29 deletions src/core/geometry/qgsgeos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -923,11 +923,19 @@ geos::unique_ptr QgsGeos::linePointDifference( GEOSGeometry *GEOSsplitPoint ) co
}


// we might have a point or a multipoint, depending on number of
// intersections between the geometry and the split geometry
std::unique_ptr< QgsAbstractGeometry > splitGeom( fromGeos( GEOSsplitPoint ) );
QgsPoint *splitPoint = qgsgeometry_cast<QgsPoint *>( splitGeom.get() );
if ( !splitPoint )
std::unique_ptr< QgsMultiPoint > splitPoints( qgsgeometry_cast<QgsMultiPoint *>( splitGeom->clone() ) );
if ( !splitPoints )
{
return nullptr;
QgsPoint *splitPoint = qgsgeometry_cast<QgsPoint *>( splitGeom->clone() );
if ( !splitPoint )
{
return nullptr;
}
splitPoints.reset( new QgsMultiPoint() );
splitPoints->addGeometry( splitPoint );
}

QgsMultiCurve lines;
Expand All @@ -936,26 +944,79 @@ geos::unique_ptr QgsGeos::linePointDifference( GEOSGeometry *GEOSsplitPoint ) co
for ( int i = 0; i < multiCurve->numGeometries(); ++i )
{
const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( multiCurve->geometryN( i ) );
if ( line )
if ( !line )
{
const QgsCurve *curve = qgsgeometry_cast<const QgsCurve *>( multiCurve->geometryN( i ) );
line = curve->curveToLine();
}
if ( !line )
{
return nullptr;
}
// we gather the intersection points and their distance from previous node grouped by segment
QMap< int, QVector< QPair< double, QgsPoint > > >pointMap;
for ( int p = 0; p < splitPoints->numGeometries(); ++p )
{
const QgsPoint *intersectionPoint = splitPoints->pointN( p );
QgsPoint segmentPoint2D;
QgsVertexId nextVertex;
// With closestSegment we only get a 2D point so we need to interpolate if we
// don't want to lose Z data
line->closestSegment( *intersectionPoint, segmentPoint2D, nextVertex );
const QgsLineString segment = QgsLineString( line->pointN( nextVertex.vertex - 1 ), line->pointN( nextVertex.vertex ) );
const double distance = segmentPoint2D.distance( line->pointN( nextVertex.vertex - 1 ) );
std::unique_ptr< QgsPoint > correctSegmentPoint( segment.interpolatePoint( distance ) );
const QPair< double, QgsPoint > pair = qMakePair( distance, *correctSegmentPoint.get() );
if ( pointMap.contains( nextVertex.vertex - 1 ) )
pointMap[ nextVertex.vertex - 1 ].append( pair );
else
pointMap[ nextVertex.vertex - 1 ] = QVector< QPair< double, QgsPoint > >() << pair;
}

// When we have more than one intersection point on a segment we need those points
// to be sorted by their distance from the previous geometry vertex
for ( auto &p : pointMap )
{
//For each segment
QgsLineString newLine;
newLine.addVertex( line->pointN( 0 ) );
int nVertices = line->numPoints();
for ( int j = 1; j < ( nVertices - 1 ); ++j )
std::sort( p.begin(), p.end(), []( const QPair< double, QgsPoint > &a, const QPair< double, QgsPoint > &b ) { return a.first < b.first; } );
}

//For each segment
QgsLineString newLine;
int nVertices = line->numPoints();
QgsPoint splitPoint;
for ( int j = 0; j < nVertices; ++j )
{
QgsPoint currentPoint = line->pointN( j );
newLine.addVertex( currentPoint );
if ( pointMap.contains( j ) )
{
QgsPoint currentPoint = line->pointN( j );
newLine.addVertex( currentPoint );
if ( currentPoint == *splitPoint )
// For each intersecting point
for ( int k = 0; k < pointMap[ j ].size(); ++k )
{
lines.addGeometry( newLine.clone() );
newLine = QgsLineString();
newLine.addVertex( currentPoint );
splitPoint = pointMap[ j ][k].second;
if ( splitPoint == currentPoint )
{
lines.addGeometry( newLine.clone() );
newLine = QgsLineString();
newLine.addVertex( currentPoint );
}
else if ( splitPoint == line->pointN( j + 1 ) )
{
newLine.addVertex( line->pointN( j + 1 ) );
lines.addGeometry( newLine.clone() );
newLine = QgsLineString();
}
else
{
newLine.addVertex( splitPoint );
lines.addGeometry( newLine.clone() );
newLine = QgsLineString();
newLine.addVertex( splitPoint );
}
}
}
newLine.addVertex( line->pointN( nVertices - 1 ) );
lines.addGeometry( newLine.clone() );
}
lines.addGeometry( newLine.clone() );
}

return asGeos( &lines, mPrecision );
Expand All @@ -969,26 +1030,21 @@ QgsGeometryEngine::EngineOperationResult QgsGeos::splitLinearGeometry( GEOSGeome
if ( !mGeos )
return InvalidBaseGeometry;

//first test if linestring intersects geometry. If not, return straight away
if ( !skipIntersectionCheck && !GEOSIntersects_r( geosinit()->ctxt, splitLine, mGeos.get() ) )
geos::unique_ptr intersectGeom( GEOSIntersection_r( geosinit()->ctxt, splitLine, mGeos.get() ) );
if ( !intersectGeom )
return NothingHappened;

//check that split line has no linear intersection
int linearIntersect = GEOSRelatePattern_r( geosinit()->ctxt, mGeos.get(), splitLine, "1********" );
if ( linearIntersect > 0 )
return InvalidInput;

int splitGeomType = GEOSGeomTypeId_r( geosinit()->ctxt, splitLine );

geos::unique_ptr splitGeom;
if ( splitGeomType == GEOS_POINT )
{
splitGeom = linePointDifference( splitLine );
}
else
{
splitGeom.reset( GEOSDifference_r( geosinit()->ctxt, mGeos.get(), splitLine ) );
}
splitGeom = linePointDifference( intersectGeom.get() );

if ( !splitGeom )
return InvalidBaseGeometry;

QVector<GEOSGeometry *> lineGeoms;

int splitType = GEOSGeomTypeId_r( geosinit()->ctxt, splitGeom.get() );
Expand Down

0 comments on commit 3421770

Please sign in to comment.