Skip to content
Permalink
Browse files

Merge pull request #9379 from lbartoletti/intersection_linestringZ

Fixes intersection on (multi)linestring/polygon/curve Z, M and ZM
  • Loading branch information
m-kuhn committed Apr 8, 2019
2 parents 28784ca + 155e0eb commit 67dabd705db046a86c36b205add4b19736a9c1ea
Showing with 120 additions and 182 deletions.
  1. +32 −180 src/core/qgspointlocator.cpp
  2. +1 −2 src/gui/qgsmaptoolcapture.cpp
  3. +87 −0 tests/src/core/testqgssnappingutils.cpp
@@ -23,7 +23,7 @@
#include "qgslogger.h"
#include "qgsrenderer.h"
#include "qgsexpressioncontextutils.h"

#include "qgslinestring.h"
#include <spatialindex/SpatialIndex.h>

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

QgsPointLocator::MatchList lst;
QByteArray wkb( geom->asWkb() );
if ( wkb.isEmpty() )
return lst;

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

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

QgsWkbTypes::Type wkbType = geom->wkbType();
_CohenSutherland cs( rect );

bool hasZValue = false;
switch ( wkbType )
int pointIndex = 0;
for ( auto part = straightGeom.const_parts_begin(); part != straightGeom.const_parts_end(); ++part )
{
case QgsWkbTypes::Point25D:
case QgsWkbTypes::Point:
case QgsWkbTypes::MultiPoint25D:
case QgsWkbTypes::MultiPoint:
{
// Points have no lines
return lst;
}

case QgsWkbTypes::LineString25D:
hasZValue = true;
//intentional fall-through
FALLTHROUGH
case QgsWkbTypes::LineString:
{
int nPoints;
wkbPtr >> nPoints;

double prevx = 0.0, prevy = 0.0;
for ( int index = 0; index < nPoints; ++index )
{
double thisx = 0.0, thisy = 0.0;
wkbPtr >> thisx >> thisy;
if ( hasZValue )
wkbPtr += sizeof( double );

if ( index > 0 )
{
if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
{
QgsPointXY edgePoints[2];
edgePoints[0].set( prevx, prevy );
edgePoints[1].set( thisx, thisy );
lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), index - 1, edgePoints );
}
}

prevx = thisx;
prevy = thisy;
}
break;
}

case QgsWkbTypes::MultiLineString25D:
hasZValue = true;
//intentional fall-through
FALLTHROUGH
case QgsWkbTypes::MultiLineString:
{
int nLines;
wkbPtr >> nLines;
for ( int linenr = 0, pointIndex = 0; linenr < nLines; ++linenr )
{
wkbPtr.readHeader();
int nPoints;
wkbPtr >> nPoints;

double prevx = 0.0, prevy = 0.0;
for ( int pointnr = 0; pointnr < nPoints; ++pointnr )
{
double thisx = 0.0, thisy = 0.0;
wkbPtr >> thisx >> thisy;
if ( hasZValue )
wkbPtr += sizeof( double );

if ( pointnr > 0 )
{
if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
{
QgsPointXY edgePoints[2];
edgePoints[0].set( prevx, prevy );
edgePoints[1].set( thisx, thisy );
lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
}
}

prevx = thisx;
prevy = thisy;
++pointIndex;
}
}
break;
}
// Checking for invalid linestrings
// A linestring should/(must?) have at least two points
if ( qgsgeometry_cast<QgsLineString *>( *part )->numPoints() < 2 )
continue;

case QgsWkbTypes::Polygon25D:
hasZValue = true;
//intentional fall-through
FALLTHROUGH
case QgsWkbTypes::Polygon:
QgsAbstractGeometry::vertex_iterator it = ( *part )->vertices_begin();
QgsPointXY prevPoint( *it );
it++;
while ( it != ( *part )->vertices_end() )
{
int nRings;
wkbPtr >> nRings;

for ( int ringnr = 0, pointIndex = 0; ringnr < nRings; ++ringnr )//loop over rings
QgsPointXY thisPoint( *it );
if ( cs.isSegmentInRect( prevPoint.x(), prevPoint.y(), thisPoint.x(), thisPoint.y() ) )
{
int nPoints;
wkbPtr >> nPoints;

double prevx = 0.0, prevy = 0.0;
for ( int pointnr = 0; pointnr < nPoints; ++pointnr )//loop over points in a ring
{
double thisx = 0.0, thisy = 0.0;
wkbPtr >> thisx >> thisy;
if ( hasZValue )
wkbPtr += sizeof( double );

if ( pointnr > 0 )
{
if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
{
QgsPointXY edgePoints[2];
edgePoints[0].set( prevx, prevy );
edgePoints[1].set( thisx, thisy );
lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
}
}

prevx = thisx;
prevy = thisy;
++pointIndex;
}
QgsPointXY edgePoints[2];
edgePoints[0] = prevPoint;
edgePoints[1] = thisPoint;
lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
}
break;
}
prevPoint = QgsPointXY( *it );
it++;
pointIndex += 1;

case QgsWkbTypes::MultiPolygon25D:
hasZValue = true;
//intentional fall-through
FALLTHROUGH
case QgsWkbTypes::MultiPolygon:
{
int nPolygons;
wkbPtr >> nPolygons;
for ( int polynr = 0, pointIndex = 0; polynr < nPolygons; ++polynr )
{
wkbPtr.readHeader();
int nRings;
wkbPtr >> nRings;
for ( int ringnr = 0; ringnr < nRings; ++ringnr )
{
int nPoints;
wkbPtr >> nPoints;

double prevx = 0.0, prevy = 0.0;
for ( int pointnr = 0; pointnr < nPoints; ++pointnr )
{
double thisx = 0.0, thisy = 0.0;
wkbPtr >> thisx >> thisy;
if ( hasZValue )
wkbPtr += sizeof( double );

if ( pointnr > 0 )
{
if ( cs.isSegmentInRect( prevx, prevy, thisx, thisy ) )
{
QgsPointXY edgePoints[2];
edgePoints[0].set( prevx, prevy );
edgePoints[1].set( thisx, thisy );
lst << QgsPointLocator::Match( QgsPointLocator::Edge, vl, fid, 0, QgsPointXY(), pointIndex - 1, edgePoints );
}
}

prevx = thisx;
prevy = thisy;
++pointIndex;
}
}
}
break;
}

case QgsWkbTypes::Unknown:
default:
return lst;
} // switch (wkbType)

}
return lst;
}

@@ -811,9 +811,8 @@ QgsPoint QgsMapToolCapture::mapPoint( const QgsMapMouseEvent &e ) const
if ( e.isSnapped() )
{
const QgsPointLocator::Match match = e.mapPointMatch();
const QgsWkbTypes::Type snappedType = match.layer()->wkbType();

if ( QgsWkbTypes::hasZ( snappedType ) )
if ( match.layer() && QgsWkbTypes::hasZ( match.layer()->wkbType() ) )
{
const QgsFeature ft = match.layer()->getFeature( match.featureId() );
newPoint.setZ( ft.geometry().vertexAt( match.vertexIndex() ).z() );
@@ -314,6 +314,93 @@ class TestQgsSnappingUtils : public QObject

delete vl;
}

void testSnapOnIntersectionCurveZ()
{
// testing with a layer with curve and Z
std::unique_ptr<QgsVectorLayer> vCurveZ( new QgsVectorLayer( QStringLiteral( "CircularStringZ" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) ) );
QgsFeature f1;
QgsGeometry f1g = QgsGeometry::fromWkt( "CircularStringZ (0 0 0, 5 5 5, 0 10 10)" ) ;
f1.setGeometry( f1g );
QgsFeature f2;
QgsGeometry f2g = QgsGeometry::fromWkt( "CircularStringZ (8 0 20, 5 3 30, 8 10 40)" );
f2.setGeometry( f2g );
QgsFeature f3;
QgsGeometry f3g = QgsGeometry::fromWkt( "CircularStringZ" );
f3.setGeometry( f3g );
QgsFeature f4;
// TODO: Issues with Curves, should/must(?) have at least two points.
QgsGeometry f4g = QgsGeometry::fromWkt( "CircularStringZ (1 2 3)" );
f4.setGeometry( f4g );
QgsFeatureList flist;
flist << f1 << f2 << f3 << f4;
vCurveZ->dataProvider()->addFeatures( flist );

QVERIFY( vCurveZ->dataProvider()->featureCount() == 4 );

QgsMapSettings mapSettings;
mapSettings.setOutputSize( QSize( 100, 100 ) );
mapSettings.setExtent( QgsRectangle( 0, 0, 10, 10 ) );
QVERIFY( mapSettings.hasValidSettings() );

QgsSnappingUtils u;
u.setMapSettings( mapSettings );
QgsSnappingConfig snappingConfig = u.config();
snappingConfig.setEnabled( true );
snappingConfig.setMode( QgsSnappingConfig::AdvancedConfiguration );
QgsSnappingConfig::IndividualLayerSettings layerSettings( true, QgsSnappingConfig::Vertex, 0.2, QgsTolerance::ProjectUnits );
snappingConfig.setIntersectionSnapping( true );
snappingConfig.setIndividualLayerSettings( vCurveZ.get(), layerSettings );
u.setConfig( snappingConfig );

QgsPointLocator::Match m2 = u.snapToMap( QgsPointXY( 4.7, 3.7 ) );
QVERIFY( m2.isValid() );
QCOMPARE( m2.type(), QgsPointLocator::Vertex );
QGSCOMPARENEAR( m2.point().x(), 4.8, 0.001 );
QGSCOMPARENEAR( m2.point().y(), 3.6, 0.001 );
}
void testSnapOnIntersectionMultiGeom()
{
std::unique_ptr<QgsVectorLayer> vMulti( new QgsVectorLayer( QStringLiteral( "MultiLineStringZ" ), QStringLiteral( "m" ), QStringLiteral( "memory" ) ) );
QgsFeature f1;
QgsGeometry f1g = QgsGeometry::fromWkt( "MultiLineStringZ ((0 0 0, 0 5 5), (5 0 10, 5 5 10))" );
f1.setGeometry( f1g );
QgsFeature f2;
QgsGeometry f2g = QgsGeometry::fromWkt( "MultiLineStringZ ((-1 2.5 50, 10 2.5 55))" );
f2.setGeometry( f2g );

QgsFeatureList flist;
flist << f1 << f2 ;
vMulti->dataProvider()->addFeatures( flist );

QVERIFY( vMulti->dataProvider()->featureCount() == 2 );

QgsMapSettings mapSettings;
mapSettings.setOutputSize( QSize( 100, 100 ) );
mapSettings.setExtent( QgsRectangle( 0, 0, 10, 10 ) );
QVERIFY( mapSettings.hasValidSettings() );

QgsSnappingUtils u;
u.setMapSettings( mapSettings );
QgsSnappingConfig snappingConfig = u.config();
snappingConfig.setEnabled( true );
snappingConfig.setMode( QgsSnappingConfig::AdvancedConfiguration );
QgsSnappingConfig::IndividualLayerSettings layerSettings( true, QgsSnappingConfig::Vertex, 0.2, QgsTolerance::ProjectUnits );
snappingConfig.setIntersectionSnapping( true );
snappingConfig.setIndividualLayerSettings( vMulti.get(), layerSettings );
u.setConfig( snappingConfig );

QgsPointLocator::Match m = u.snapToMap( QgsPointXY( 0, 2.6 ) );
QVERIFY( m.isValid() );
QCOMPARE( m.type(), QgsPointLocator::Vertex );
QCOMPARE( m.point(), QgsPointXY( 0.0, 2.5 ) );

QgsPointLocator::Match m2 = u.snapToMap( QgsPointXY( 5, 2.6 ) );
QVERIFY( m2.isValid() );
QCOMPARE( m2.type(), QgsPointLocator::Vertex );
QCOMPARE( m2.point(), QgsPointXY( 5.0, 2.5 ) );

}
};

QGSTEST_MAIN( TestQgsSnappingUtils )

0 comments on commit 67dabd7

Please sign in to comment.
You can’t perform that action at this time.