Skip to content

Commit 67dabd7

Browse files
authored
Merge pull request #9379 from lbartoletti/intersection_linestringZ
Fixes intersection on (multi)linestring/polygon/curve Z, M and ZM
2 parents 28784ca + 155e0eb commit 67dabd7

File tree

3 files changed

+120
-182
lines changed

3 files changed

+120
-182
lines changed

src/core/qgspointlocator.cpp

+32-180
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
#include "qgslogger.h"
2424
#include "qgsrenderer.h"
2525
#include "qgsexpressioncontextutils.h"
26-
26+
#include "qgslinestring.h"
2727
#include <spatialindex/SpatialIndex.h>
2828

2929
#include <QLinkedListIterator>
@@ -332,196 +332,48 @@ static QgsPointLocator::MatchList _geometrySegmentsInRect( QgsGeometry *geom, co
332332
// we need iterator for segments...
333333

334334
QgsPointLocator::MatchList lst;
335-
QByteArray wkb( geom->asWkb() );
336-
if ( wkb.isEmpty() )
337-
return lst;
338335

339-
_CohenSutherland cs( rect );
336+
// geom is converted to a MultiCurve
337+
QgsGeometry straightGeom = geom->convertToType( QgsWkbTypes::LineGeometry, true );
338+
// and convert to straight segemnt / converts curve to linestring
339+
straightGeom.convertToStraightSegment();
340340

341-
QgsConstWkbPtr wkbPtr( wkb );
342-
wkbPtr.readHeader();
341+
// so, you must have multilinestring
342+
//
343+
// Special case: Intersections cannot be done on an empty linestring like
344+
// QgsGeometry(QgsLineString()) or QgsGeometry::fromWkt("LINESTRING EMPTY")
345+
if ( straightGeom.isEmpty() || ( ( straightGeom.type() != QgsWkbTypes::LineGeometry ) && ( !straightGeom.isMultipart() ) ) )
346+
return lst;
343347

344-
QgsWkbTypes::Type wkbType = geom->wkbType();
348+
_CohenSutherland cs( rect );
345349

346-
bool hasZValue = false;
347-
switch ( wkbType )
350+
int pointIndex = 0;
351+
for ( auto part = straightGeom.const_parts_begin(); part != straightGeom.const_parts_end(); ++part )
348352
{
349-
case QgsWkbTypes::Point25D:
350-
case QgsWkbTypes::Point:
351-
case QgsWkbTypes::MultiPoint25D:
352-
case QgsWkbTypes::MultiPoint:
353-
{
354-
// Points have no lines
355-
return lst;
356-
}
357-
358-
case QgsWkbTypes::LineString25D:
359-
hasZValue = true;
360-
//intentional fall-through
361-
FALLTHROUGH
362-
case QgsWkbTypes::LineString:
363-
{
364-
int nPoints;
365-
wkbPtr >> nPoints;
366-
367-
double prevx = 0.0, prevy = 0.0;
368-
for ( int index = 0; index < nPoints; ++index )
369-
{
370-
double thisx = 0.0, thisy = 0.0;
371-
wkbPtr >> thisx >> thisy;
372-
if ( hasZValue )
373-
wkbPtr += sizeof( double );
374-
375-
if ( index > 0 )
376-
{
377-
if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
378-
{
379-
QgsPointXY edgePoints[2];
380-
edgePoints[0].set( prevx, prevy );
381-
edgePoints[1].set( thisx, thisy );
382-
lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), index - 1, edgePoints );
383-
}
384-
}
385-
386-
prevx = thisx;
387-
prevy = thisy;
388-
}
389-
break;
390-
}
391-
392-
case QgsWkbTypes::MultiLineString25D:
393-
hasZValue = true;
394-
//intentional fall-through
395-
FALLTHROUGH
396-
case QgsWkbTypes::MultiLineString:
397-
{
398-
int nLines;
399-
wkbPtr >> nLines;
400-
for ( int linenr = 0, pointIndex = 0; linenr < nLines; ++linenr )
401-
{
402-
wkbPtr.readHeader();
403-
int nPoints;
404-
wkbPtr >> nPoints;
405-
406-
double prevx = 0.0, prevy = 0.0;
407-
for ( int pointnr = 0; pointnr < nPoints; ++pointnr )
408-
{
409-
double thisx = 0.0, thisy = 0.0;
410-
wkbPtr >> thisx >> thisy;
411-
if ( hasZValue )
412-
wkbPtr += sizeof( double );
413-
414-
if ( pointnr > 0 )
415-
{
416-
if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
417-
{
418-
QgsPointXY edgePoints[2];
419-
edgePoints[0].set( prevx, prevy );
420-
edgePoints[1].set( thisx, thisy );
421-
lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
422-
}
423-
}
424-
425-
prevx = thisx;
426-
prevy = thisy;
427-
++pointIndex;
428-
}
429-
}
430-
break;
431-
}
353+
// Checking for invalid linestrings
354+
// A linestring should/(must?) have at least two points
355+
if ( qgsgeometry_cast<QgsLineString *>( *part )->numPoints() < 2 )
356+
continue;
432357

433-
case QgsWkbTypes::Polygon25D:
434-
hasZValue = true;
435-
//intentional fall-through
436-
FALLTHROUGH
437-
case QgsWkbTypes::Polygon:
358+
QgsAbstractGeometry::vertex_iterator it = ( *part )->vertices_begin();
359+
QgsPointXY prevPoint( *it );
360+
it++;
361+
while ( it != ( *part )->vertices_end() )
438362
{
439-
int nRings;
440-
wkbPtr >> nRings;
441-
442-
for ( int ringnr = 0, pointIndex = 0; ringnr < nRings; ++ringnr )//loop over rings
363+
QgsPointXY thisPoint( *it );
364+
if ( cs.isSegmentInRect( prevPoint.x(), prevPoint.y(), thisPoint.x(), thisPoint.y() ) )
443365
{
444-
int nPoints;
445-
wkbPtr >> nPoints;
446-
447-
double prevx = 0.0, prevy = 0.0;
448-
for ( int pointnr = 0; pointnr < nPoints; ++pointnr )//loop over points in a ring
449-
{
450-
double thisx = 0.0, thisy = 0.0;
451-
wkbPtr >> thisx >> thisy;
452-
if ( hasZValue )
453-
wkbPtr += sizeof( double );
454-
455-
if ( pointnr > 0 )
456-
{
457-
if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
458-
{
459-
QgsPointXY edgePoints[2];
460-
edgePoints[0].set( prevx, prevy );
461-
edgePoints[1].set( thisx, thisy );
462-
lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
463-
}
464-
}
465-
466-
prevx = thisx;
467-
prevy = thisy;
468-
++pointIndex;
469-
}
366+
QgsPointXY edgePoints[2];
367+
edgePoints[0] = prevPoint;
368+
edgePoints[1] = thisPoint;
369+
lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
470370
}
471-
break;
472-
}
371+
prevPoint = QgsPointXY( *it );
372+
it++;
373+
pointIndex += 1;
473374

474-
case QgsWkbTypes::MultiPolygon25D:
475-
hasZValue = true;
476-
//intentional fall-through
477-
FALLTHROUGH
478-
case QgsWkbTypes::MultiPolygon:
479-
{
480-
int nPolygons;
481-
wkbPtr >> nPolygons;
482-
for ( int polynr = 0, pointIndex = 0; polynr < nPolygons; ++polynr )
483-
{
484-
wkbPtr.readHeader();
485-
int nRings;
486-
wkbPtr >> nRings;
487-
for ( int ringnr = 0; ringnr < nRings; ++ringnr )
488-
{
489-
int nPoints;
490-
wkbPtr >> nPoints;
491-
492-
double prevx = 0.0, prevy = 0.0;
493-
for ( int pointnr = 0; pointnr < nPoints; ++pointnr )
494-
{
495-
double thisx = 0.0, thisy = 0.0;
496-
wkbPtr >> thisx >> thisy;
497-
if ( hasZValue )
498-
wkbPtr += sizeof( double );
499-
500-
if ( pointnr > 0 )
501-
{
502-
if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
503-
{
504-
QgsPointXY edgePoints[2];
505-
edgePoints[0].set( prevx, prevy );
506-
edgePoints[1].set( thisx, thisy );
507-
lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
508-
}
509-
}
510-
511-
prevx = thisx;
512-
prevy = thisy;
513-
++pointIndex;
514-
}
515-
}
516-
}
517-
break;
518375
}
519-
520-
case QgsWkbTypes::Unknown:
521-
default:
522-
return lst;
523-
} // switch (wkbType)
524-
376+
}
525377
return lst;
526378
}
527379

src/gui/qgsmaptoolcapture.cpp

+1-2
Original file line numberDiff line numberDiff line change
@@ -811,9 +811,8 @@ QgsPoint QgsMapToolCapture::mapPoint( const QgsMapMouseEvent &e ) const
811811
if ( e.isSnapped() )
812812
{
813813
const QgsPointLocator::Match match = e.mapPointMatch();
814-
const QgsWkbTypes::Type snappedType = match.layer()->wkbType();
815814

816-
if ( QgsWkbTypes::hasZ( snappedType ) )
815+
if ( match.layer() && QgsWkbTypes::hasZ( match.layer()->wkbType() ) )
817816
{
818817
const QgsFeature ft = match.layer()->getFeature( match.featureId() );
819818
newPoint.setZ( ft.geometry().vertexAt( match.vertexIndex() ).z() );

tests/src/core/testqgssnappingutils.cpp

+87
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,93 @@ class TestQgsSnappingUtils : public QObject
314314

315315
delete vl;
316316
}
317+
318+
void testSnapOnIntersectionCurveZ()
319+
{
320+
// testing with a layer with curve and Z
321+
std::unique_ptr<QgsVectorLayer> vCurveZ( new QgsVectorLayer( QStringLiteral( "CircularStringZ" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) ) );
322+
QgsFeature f1;
323+
QgsGeometry f1g = QgsGeometry::fromWkt( "CircularStringZ (0 0 0, 5 5 5, 0 10 10)" ) ;
324+
f1.setGeometry( f1g );
325+
QgsFeature f2;
326+
QgsGeometry f2g = QgsGeometry::fromWkt( "CircularStringZ (8 0 20, 5 3 30, 8 10 40)" );
327+
f2.setGeometry( f2g );
328+
QgsFeature f3;
329+
QgsGeometry f3g = QgsGeometry::fromWkt( "CircularStringZ" );
330+
f3.setGeometry( f3g );
331+
QgsFeature f4;
332+
// TODO: Issues with Curves, should/must(?) have at least two points.
333+
QgsGeometry f4g = QgsGeometry::fromWkt( "CircularStringZ (1 2 3)" );
334+
f4.setGeometry( f4g );
335+
QgsFeatureList flist;
336+
flist << f1 << f2 << f3 << f4;
337+
vCurveZ->dataProvider()->addFeatures( flist );
338+
339+
QVERIFY( vCurveZ->dataProvider()->featureCount() == 4 );
340+
341+
QgsMapSettings mapSettings;
342+
mapSettings.setOutputSize( QSize( 100, 100 ) );
343+
mapSettings.setExtent( QgsRectangle( 0, 0, 10, 10 ) );
344+
QVERIFY( mapSettings.hasValidSettings() );
345+
346+
QgsSnappingUtils u;
347+
u.setMapSettings( mapSettings );
348+
QgsSnappingConfig snappingConfig = u.config();
349+
snappingConfig.setEnabled( true );
350+
snappingConfig.setMode( QgsSnappingConfig::AdvancedConfiguration );
351+
QgsSnappingConfig::IndividualLayerSettings layerSettings( true, QgsSnappingConfig::Vertex, 0.2, QgsTolerance::ProjectUnits );
352+
snappingConfig.setIntersectionSnapping( true );
353+
snappingConfig.setIndividualLayerSettings( vCurveZ.get(), layerSettings );
354+
u.setConfig( snappingConfig );
355+
356+
QgsPointLocator::Match m2 = u.snapToMap( QgsPointXY( 4.7, 3.7 ) );
357+
QVERIFY( m2.isValid() );
358+
QCOMPARE( m2.type(), QgsPointLocator::Vertex );
359+
QGSCOMPARENEAR( m2.point().x(), 4.8, 0.001 );
360+
QGSCOMPARENEAR( m2.point().y(), 3.6, 0.001 );
361+
}
362+
void testSnapOnIntersectionMultiGeom()
363+
{
364+
std::unique_ptr<QgsVectorLayer> vMulti( new QgsVectorLayer( QStringLiteral( "MultiLineStringZ" ), QStringLiteral( "m" ), QStringLiteral( "memory" ) ) );
365+
QgsFeature f1;
366+
QgsGeometry f1g = QgsGeometry::fromWkt( "MultiLineStringZ ((0 0 0, 0 5 5), (5 0 10, 5 5 10))" );
367+
f1.setGeometry( f1g );
368+
QgsFeature f2;
369+
QgsGeometry f2g = QgsGeometry::fromWkt( "MultiLineStringZ ((-1 2.5 50, 10 2.5 55))" );
370+
f2.setGeometry( f2g );
371+
372+
QgsFeatureList flist;
373+
flist << f1 << f2 ;
374+
vMulti->dataProvider()->addFeatures( flist );
375+
376+
QVERIFY( vMulti->dataProvider()->featureCount() == 2 );
377+
378+
QgsMapSettings mapSettings;
379+
mapSettings.setOutputSize( QSize( 100, 100 ) );
380+
mapSettings.setExtent( QgsRectangle( 0, 0, 10, 10 ) );
381+
QVERIFY( mapSettings.hasValidSettings() );
382+
383+
QgsSnappingUtils u;
384+
u.setMapSettings( mapSettings );
385+
QgsSnappingConfig snappingConfig = u.config();
386+
snappingConfig.setEnabled( true );
387+
snappingConfig.setMode( QgsSnappingConfig::AdvancedConfiguration );
388+
QgsSnappingConfig::IndividualLayerSettings layerSettings( true, QgsSnappingConfig::Vertex, 0.2, QgsTolerance::ProjectUnits );
389+
snappingConfig.setIntersectionSnapping( true );
390+
snappingConfig.setIndividualLayerSettings( vMulti.get(), layerSettings );
391+
u.setConfig( snappingConfig );
392+
393+
QgsPointLocator::Match m = u.snapToMap( QgsPointXY( 0, 2.6 ) );
394+
QVERIFY( m.isValid() );
395+
QCOMPARE( m.type(), QgsPointLocator::Vertex );
396+
QCOMPARE( m.point(), QgsPointXY( 0.0, 2.5 ) );
397+
398+
QgsPointLocator::Match m2 = u.snapToMap( QgsPointXY( 5, 2.6 ) );
399+
QVERIFY( m2.isValid() );
400+
QCOMPARE( m2.type(), QgsPointLocator::Vertex );
401+
QCOMPARE( m2.point(), QgsPointXY( 5.0, 2.5 ) );
402+
403+
}
317404
};
318405

319406
QGSTEST_MAIN( TestQgsSnappingUtils )

0 commit comments

Comments
 (0)