Skip to content

Commit e2179b3

Browse files
lbartolettinyalldawson
authored andcommittedApr 8, 2019
backport #9379 to release-3_4
1 parent 2c53c1d commit e2179b3

File tree

3 files changed

+120
-181
lines changed

3 files changed

+120
-181
lines changed
 

‎src/core/qgspointlocator.cpp

Lines changed: 32 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "qgslogger.h"
2424
#include "qgsrenderer.h"
2525

26+
#include "qgslinestring.h"
2627
#include <spatialindex/SpatialIndex.h>
2728

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

333334
QgsPointLocator::MatchList lst;
334-
QByteArray wkb( geom->asWkb() );
335-
if ( wkb.isEmpty() )
336-
return lst;
337335

338-
_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();
339340

340-
QgsConstWkbPtr wkbPtr( wkb );
341-
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;
342347

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

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

432-
case QgsWkbTypes::Polygon25D:
433-
hasZValue = true;
434-
//intentional fall-through
435-
FALLTHROUGH
436-
case QgsWkbTypes::Polygon:
358+
QgsAbstractGeometry::vertex_iterator it = ( *part )->vertices_begin();
359+
QgsPointXY prevPoint( *it );
360+
it++;
361+
while ( it != ( *part )->vertices_end() )
437362
{
438-
int nRings;
439-
wkbPtr >> nRings;
440-
441-
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() ) )
442365
{
443-
int nPoints;
444-
wkbPtr >> nPoints;
445-
446-
double prevx = 0.0, prevy = 0.0;
447-
for ( int pointnr = 0; pointnr < nPoints; ++pointnr )//loop over points in a ring
448-
{
449-
double thisx = 0.0, thisy = 0.0;
450-
wkbPtr >> thisx >> thisy;
451-
if ( hasZValue )
452-
wkbPtr += sizeof( double );
453-
454-
if ( pointnr > 0 )
455-
{
456-
if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
457-
{
458-
QgsPointXY edgePoints[2];
459-
edgePoints[0].set( prevx, prevy );
460-
edgePoints[1].set( thisx, thisy );
461-
lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
462-
}
463-
}
464-
465-
prevx = thisx;
466-
prevy = thisy;
467-
++pointIndex;
468-
}
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 );
469370
}
470-
break;
471-
}
371+
prevPoint = QgsPointXY( *it );
372+
it++;
373+
pointIndex += 1;
472374

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

‎src/gui/qgsmaptoolcapture.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -810,9 +810,8 @@ QgsPoint QgsMapToolCapture::mapPoint( const QgsMapMouseEvent &e ) const
810810
if ( e.isSnapped() )
811811
{
812812
const QgsPointLocator::Match match = e.mapPointMatch();
813-
const QgsWkbTypes::Type snappedType = match.layer()->wkbType();
814813

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

‎tests/src/core/testqgssnappingutils.cpp

Lines changed: 87 additions & 0 deletions
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)
Please sign in to comment.