Skip to content

Commit

Permalink
Merge pull request #38439 from lbartoletti/fix_wkt
Browse files Browse the repository at this point in the history
Fix WKT parser
  • Loading branch information
nyalldawson committed Aug 26, 2020
2 parents 707bad7 + 2c33e6e commit 1b8761f
Show file tree
Hide file tree
Showing 16 changed files with 391 additions and 59 deletions.
12 changes: 10 additions & 2 deletions src/core/geometry/qgscircularstring.cpp
Expand Up @@ -314,10 +314,18 @@ bool QgsCircularString::fromWkt( const QString &wkt )
return false;
mWkbType = parts.first;

if ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 )
parts.second = parts.second.remove( '(' ).remove( ')' );
QString secondWithoutParentheses = parts.second;
secondWithoutParentheses = secondWithoutParentheses.simplified().remove( ' ' );
if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
secondWithoutParentheses.isEmpty() )
return true;

setPoints( QgsGeometryUtils::pointsFromWKT( parts.second, is3D(), isMeasure() ) );
QgsPointSequence points = QgsGeometryUtils::pointsFromWKT( parts.second, is3D(), isMeasure() );
if ( points.isEmpty() )
return false;

setPoints( points );
return true;
}

Expand Down
5 changes: 4 additions & 1 deletion src/core/geometry/qgscompoundcurve.cpp
Expand Up @@ -179,7 +179,10 @@ bool QgsCompoundCurve::fromWkt( const QString &wkt )
return false;
mWkbType = parts.first;

if ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 )
QString secondWithoutParentheses = parts.second;
secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
secondWithoutParentheses.isEmpty() )
return true;

QString defaultChildWkbType = QStringLiteral( "LineString%1%2" ).arg( is3D() ? QStringLiteral( "Z" ) : QString(), isMeasure() ? QStringLiteral( "M" ) : QString() );
Expand Down
5 changes: 4 additions & 1 deletion src/core/geometry/qgscurvepolygon.cpp
Expand Up @@ -215,7 +215,10 @@ bool QgsCurvePolygon::fromWkt( const QString &wkt )

mWkbType = parts.first;

if ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 )
QString secondWithoutParentheses = parts.second;
secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
secondWithoutParentheses.isEmpty() )
return true;

QString defaultChildWkbType = QStringLiteral( "LineString%1%2" ).arg( is3D() ? QStringLiteral( "Z" ) : QString(), isMeasure() ? QStringLiteral( "M" ) : QString() );
Expand Down
5 changes: 4 additions & 1 deletion src/core/geometry/qgsgeometrycollection.cpp
Expand Up @@ -698,7 +698,10 @@ bool QgsGeometryCollection::fromCollectionWkt( const QString &wkt, const QVector
}
mWkbType = parts.first;

if ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 )
QString secondWithoutParentheses = parts.second;
secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
secondWithoutParentheses.isEmpty() )
return true;

QString defChildWkbType = QStringLiteral( "%1%2%3 " ).arg( defaultChildWkbType, is3D() ? QStringLiteral( "Z" ) : QString(), isMeasure() ? QStringLiteral( "M" ) : QString() );
Expand Down
15 changes: 15 additions & 0 deletions src/core/geometry/qgsgeometryutils.cpp
Expand Up @@ -1100,15 +1100,22 @@ QgsPointSequence QgsGeometryUtils::pointsFromWKT( const QString &wktCoordinateLi
{
int dim = 2 + is3D + isMeasure;
QgsPointSequence points;

const QStringList coordList = wktCoordinateList.split( ',', QString::SkipEmptyParts );

//first scan through for extra unexpected dimensions
bool foundZ = false;
bool foundM = false;
QRegularExpression rx( QStringLiteral( "\\s" ) );
QRegularExpression rxIsNumber( QStringLiteral( "^[+-]?(\\d\\.?\\d*[Ee][+\\-]?\\d+|(\\d+\\.\\d*|\\d*\\.\\d+)|\\d+)$" ) );
for ( const QString &pointCoordinates : coordList )
{
QStringList coordinates = pointCoordinates.split( rx, QString::SkipEmptyParts );

// exit with an empty set if one list contains invalid value.
if ( coordinates.filter( rxIsNumber ).size() != coordinates.size() )
return points;

if ( coordinates.size() == 3 && !foundZ && !foundM && !is3D && !isMeasure )
{
// 3 dimensional coordinates, but not specifically marked as such. We allow this
Expand Down Expand Up @@ -1313,6 +1320,14 @@ QPair<QgsWkbTypes::Type, QString> QgsGeometryUtils::wktReadBlock( const QString
}
else
{
const int openedParenthesisCount = wktParsed.count( '(' );
const int closedParenthesisCount = wktParsed.count( ')' );
// closes missing parentheses
for ( int i = 0 ; i < openedParenthesisCount - closedParenthesisCount; ++i )
wktParsed.push_back( ')' );
// removes extra parentheses
wktParsed.truncate( wktParsed.size() - ( closedParenthesisCount - openedParenthesisCount ) );

QRegularExpression cooRegEx( QStringLiteral( "^[^\\(]*\\((.*)\\)[^\\)]*$" ) );
cooRegEx.setPatternOptions( QRegularExpression::DotMatchesEverythingOption );
QRegularExpressionMatch match = cooRegEx.match( wktParsed );
Expand Down
14 changes: 12 additions & 2 deletions src/core/geometry/qgslinestring.cpp
Expand Up @@ -434,10 +434,20 @@ bool QgsLineString::fromWkt( const QString &wkt )
return false;
mWkbType = parts.first;

if ( parts.second == "EMPTY" )
QString secondWithoutParentheses = parts.second;
secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
parts.second = parts.second.remove( '(' ).remove( ')' );
if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
secondWithoutParentheses.isEmpty() )
return true;

setPoints( QgsGeometryUtils::pointsFromWKT( parts.second, is3D(), isMeasure() ) );
QgsPointSequence points = QgsGeometryUtils::pointsFromWKT( parts.second, is3D(), isMeasure() );
// There is a non number in the coordinates sequence
// LineString ( A b, 1 2)
if ( points.isEmpty() )
return false;

setPoints( points );
return true;
}

Expand Down
22 changes: 21 additions & 1 deletion src/core/geometry/qgspoint.cpp
Expand Up @@ -169,11 +169,31 @@ bool QgsPoint::fromWkt( const QString &wkt )
return false;
mWkbType = parts.first;

if ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 )
QString secondWithoutParentheses = parts.second;
secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
parts.second = parts.second.remove( '(' ).remove( ')' );
if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
secondWithoutParentheses.isEmpty() )
return true;

QRegularExpression rx( QStringLiteral( "\\s" ) );
QStringList coordinates = parts.second.split( rx, QString::SkipEmptyParts );

// So far the parser hasn't looked at the coordinates. We'll avoid having anything but numbers and return NULL instead of 0 as a coordinate.
// Without this check, "POINT (a, b)" or "POINT (( 4, 3 ))" returned "POINT (0 ,0)"
// And some strange conversion...
// .. python:
// p = QgsPoint()
// p.fromWkt("POINT (-3.12, -4.2")
// False
// p.fromWkt( "POINT (-5.1234, -1.4321)" )
// True
// p.asWkt()
// 'Point (0 -1.43209999999999993)'
QRegularExpression rxIsNumber( QStringLiteral( "^[+-]?(\\d\\.?\\d*[Ee][+\\-]?\\d+|(\\d+\\.\\d*|\\d*\\.\\d+)|\\d+)$" ) );
if ( coordinates.filter( rxIsNumber ).size() != coordinates.size() )
return false;

if ( coordinates.size() < 2 )
{
clear();
Expand Down
11 changes: 9 additions & 2 deletions src/core/geometry/qgstriangle.cpp
Expand Up @@ -159,7 +159,6 @@ bool QgsTriangle::fromWkb( QgsConstWkbPtr &wkbPtr )

bool QgsTriangle::fromWkt( const QString &wkt )
{

clear();

QPair<QgsWkbTypes::Type, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
Expand All @@ -169,7 +168,10 @@ bool QgsTriangle::fromWkt( const QString &wkt )

mWkbType = parts.first;

if ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 )
QString secondWithoutParentheses = parts.second;
secondWithoutParentheses = secondWithoutParentheses.simplified().remove( ' ' );
if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
secondWithoutParentheses.isEmpty() )
return true;

QString defaultChildWkbType = QStringLiteral( "LineString%1%2" ).arg( is3D() ? QStringLiteral( "Z" ) : QString(), isMeasure() ? QStringLiteral( "M" ) : QString() );
Expand Down Expand Up @@ -200,6 +202,11 @@ bool QgsTriangle::fromWkt( const QString &wkt )
return false;
}
mExteriorRing.reset( mInteriorRings.takeFirst() );
if ( ( mExteriorRing->numPoints() < 3 ) || ( mExteriorRing->numPoints() > 4 ) || ( mExteriorRing->numPoints() == 4 && mExteriorRing->startPoint() != mExteriorRing->endPoint() ) )
{
clear();
return false;
}

//scan through rings and check if dimensionality of rings is different to CurvePolygon.
//if so, update the type dimensionality of the CurvePolygon to match
Expand Down
2 changes: 1 addition & 1 deletion tests/src/app/testqgsmaptooladdfeatureline.cpp
Expand Up @@ -212,7 +212,7 @@ void TestQgsMapToolAddFeatureLine::initTestCase()

mLayerTopoZ->startEditing();
QgsFeature topoFeat;
topoFeat.setGeometry( QgsGeometry::fromWkt( "MultiLineStringZ ((7.25 6 0, 7.25 7 0, 7.5 7 0, 7.5 6 0, 7.25 6 0),(6 6 0, 6 7 10, 7 7 0, 7 6 0, 6 6 0),(6.25 6.25 0, 6.75 6.25 0, 6.75 6.75 0, 6.25 6.75 0, 6.25 6.25 0)));" ) );
topoFeat.setGeometry( QgsGeometry::fromWkt( "MultiLineStringZ ((7.25 6 0, 7.25 7 0, 7.5 7 0, 7.5 6 0, 7.25 6 0),(6 6 0, 6 7 10, 7 7 0, 7 6 0, 6 6 0),(6.25 6.25 0, 6.75 6.25 0, 6.75 6.75 0, 6.25 6.75 0, 6.25 6.25 0))" ) );

mLayerTopoZ->addFeature( topoFeat );
QCOMPARE( mLayerTopoZ->featureCount(), ( long ) 1 );
Expand Down
16 changes: 8 additions & 8 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -1262,27 +1262,27 @@ class TestQgsExpression: public QObject
QTest::newRow( "is_multipart null" ) << "is_multipart(NULL)" << false << QVariant();
QTest::newRow( "z_max no 3D" ) << "z_max(geom_from_wkt('POINT (0 0)'))" << false << QVariant();
QTest::newRow( "z_max NULL" ) << "z_max(geom_from_wkt(NULL))" << false << QVariant();
QTest::newRow( "z_max point Z NaN" ) << "z_max(geom_from_wkt('PointZ (1 1 nan)'))" << false << QVariant( QVariant::Double );
QTest::newRow( "z_max line Z NaN" ) << "z_max(make_line(geom_from_wkt('PointZ (0 0 nan)'),make_point(-1,-1,-2)))" << false << QVariant( -2.0 );
QTest::newRow( "z_max point" ) << "z_max(geom_from_wkt('POINT (0 0 1)'))" << false << QVariant( 1.0 );
QTest::newRow( "z_max point Z NaN" ) << "z_max(geom_from_wkt('PointZ (1 1 nan)'))" << false << QVariant( );
QTest::newRow( "z_max line Z NaN" ) << "z_max(make_line(geom_from_wkt('PointZ (0 0 nan)'),make_point(-1,-1,-2)))" << false << QVariant( );
QTest::newRow( "z_max line" ) << "z_max(make_line(make_point(0,0,0),make_point(-1,-1,-2)))" << false << QVariant( 0.0 );
QTest::newRow( "z_min no 3D" ) << "z_min(geom_from_wkt('POINT (0 0)'))" << false << QVariant();
QTest::newRow( "z_min NULL" ) << "z_min(geom_from_wkt(NULL))" << false << QVariant();
QTest::newRow( "z_min point Z NaN" ) << "z_min(geom_from_wkt('PointZ (1 1 nan)'))" << false << QVariant( QVariant::Double );
QTest::newRow( "z_min line Z NaN" ) << "z_min(make_line(geom_from_wkt('PointZ (0 0 nan)'),make_point(-1,-1,-2)))" << false << QVariant( -2.0 );
QTest::newRow( "z_min point" ) << "z_min(geom_from_wkt('POINT (0 0 1)'))" << false << QVariant( 1.0 );
QTest::newRow( "z_min point Z NaN" ) << "z_min(geom_from_wkt('PointZ (1 1 nan)'))" << false << QVariant( );
QTest::newRow( "z_min line Z NaN" ) << "z_min(make_line(geom_from_wkt('PointZ (0 0 nan)'),make_point(-1,-1,-2)))" << false << QVariant( );
QTest::newRow( "z_min line" ) << "z_min(make_line(make_point(0,0,0),make_point(-1,-1,-2)))" << false << QVariant( -2.0 );
QTest::newRow( "m_max no measure" ) << "m_max(geom_from_wkt('POINT (0 0)'))" << false << QVariant();
QTest::newRow( "m_max NULL" ) << "m_max(geom_from_wkt(NULL))" << false << QVariant();
QTest::newRow( "m_max point M NaN" ) << "m_max(geom_from_wkt('PointZM (0 0 0 nan)'))" << false << QVariant( QVariant::Double );
QTest::newRow( "m_max line M NaN" ) << "m_max(make_line(geom_from_wkt('PointZM (0 0 0 nan)'),geom_from_wkt('PointZM (1 1 1 2)')))" << false << QVariant( 2.0 );
QTest::newRow( "m_max point" ) << "m_max(make_point_m(0,0,1))" << false << QVariant( 1.0 );
QTest::newRow( "m_max point M NaN" ) << "m_max(geom_from_wkt('PointZM (0 0 0 nan)'))" << false << QVariant( );
QTest::newRow( "m_max line M NaN" ) << "m_max(make_line(geom_from_wkt('PointZM (0 0 0 nan)'),geom_from_wkt('PointZM (1 1 1 2)')))" << false << QVariant( );
QTest::newRow( "m_max line" ) << "m_max(make_line(make_point_m(0,0,1),make_point_m(-1,-1,2),make_point_m(-2,-2,0)))" << false << QVariant( 2.0 );
QTest::newRow( "m_min no measure" ) << "m_min(geom_from_wkt('POINT (0 0)'))" << false << QVariant();
QTest::newRow( "m_min NULL" ) << "m_min(geom_from_wkt(NULL))" << false << QVariant();
QTest::newRow( "m_min point M NaN" ) << "m_min(geom_from_wkt('PointZM (0 0 0 nan)'))" << false << QVariant( QVariant::Double );
QTest::newRow( "m_min line M NaN" ) << "m_min(make_line(geom_from_wkt('PointZM (0 0 0 nan)'),geom_from_wkt('PointZM (1 1 1 2)')))" << false << QVariant( 2.0 );
QTest::newRow( "m_min point" ) << "m_min(make_point_m(0,0,1))" << false << QVariant( 1.0 );
QTest::newRow( "m_min point M NaN" ) << "m_min(geom_from_wkt('PointZM (0 0 0 nan)'))" << false << QVariant( );
QTest::newRow( "m_min line M NaN" ) << "m_min(make_line(geom_from_wkt('PointZM (0 0 0 nan)'),geom_from_wkt('PointZM (1 1 1 2)')))" << false << QVariant( );
QTest::newRow( "m_min line" ) << "m_min(make_line(make_point_m(0,0,1),make_point_m(-1,-1,2),make_point_m(-2,-2,0)))" << false << QVariant( 0.0 );
QTest::newRow( "main angle polygon" ) << "round(main_angle( geom_from_wkt('POLYGON((0 0,2 9,9 2,0 0))')))" << false << QVariant( 77 );
QTest::newRow( "main angle multi polygon" ) << "round(main_angle( geom_from_wkt('MULTIPOLYGON(((0 0,3 10,1 10,1 6,0 0)))')))" << false << QVariant( 17 );
Expand Down

0 comments on commit 1b8761f

Please sign in to comment.