Skip to content

Commit 5c14c21

Browse files
committed
Implement smoothing algorithm for geometries
1 parent 49ea51e commit 5c14c21

File tree

4 files changed

+241
-3
lines changed

4 files changed

+241
-3
lines changed

python/core/qgsgeometry.sip

+23-2
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,17 @@ class QgsGeometry
341341

342342
/** Returns a simplified version of this geometry using a specified tolerance value */
343343
QgsGeometry* simplify( double tolerance ) /Factory/;
344+
345+
/**Smooths a geometry by rounding off corners using the Chaikin algorithm. This operation
346+
* roughly doubles the number of vertices in a geometry.
347+
* @param iterations number of smoothing iterations to run. More iterations results
348+
* in a smoother geometry
349+
* @param offset fraction of line to create new vertices along, between 0 and 1.0
350+
* eg the default value of 0.25 will create new vertices 25% and 75% along each line segment
351+
* of the geometry for each iteration. Smaller values result in "tighter" smoothing.
352+
* @note added in 2.9
353+
*/
354+
QgsGeometry* smooth( const unsigned int iterations = 1, const double offset = 0.25 ) /Factory/;
344355

345356
/** Returns the center of mass of a geometry
346357
* @note for line based geometries, the center point of the line is returned,
@@ -353,7 +364,7 @@ class QgsGeometry
353364
/** Returns the smallest convex polygon that contains all the points in the geometry. */
354365
QgsGeometry* convexHull() /Factory/;
355366

356-
/* Return interpolated point on line at distance */
367+
/** Return interpolated point on line at distance */
357368
QgsGeometry* interpolate( double distance ) /Factory/;
358369

359370
/** Returns a geometry representing the points shared by this geometry and other. */
@@ -495,7 +506,17 @@ class QgsGeometry
495506
* @note added in QGIS 2.9
496507
*/
497508
static bool compare( const QgsPolygon& p1, const QgsPolygon& p2, double epsilon = 4 * DBL_EPSILON );
498-
509+
510+
/** Compares two multipolygons for equality within a specified tolerance.
511+
* @param p1 first multipolygon
512+
* @param p2 second multipolygon
513+
* @param epsilon maximum difference for coordinates between the multipolygons
514+
* @returns true if multipolygons have the same number of polygons, the polygons have the same number
515+
* of rings, and each ring has the same number of points and all points are equal within the specified
516+
* tolerance
517+
* @note added in QGIS 2.9
518+
*/
519+
static bool compare( const QgsMultiPolygon& p1, const QgsMultiPolygon& p2, double epsilon = 4 * DBL_EPSILON );
499520

500521
}; // class QgsGeometry
501522

src/core/qgsgeometry.cpp

+124
Original file line numberDiff line numberDiff line change
@@ -5797,6 +5797,116 @@ QgsGeometry* QgsGeometry::simplify( double tolerance )
57975797
CATCH_GEOS( 0 )
57985798
}
57995799

5800+
QgsGeometry* QgsGeometry::smooth( const unsigned int iterations, const double offset )
5801+
{
5802+
switch ( wkbType() )
5803+
{
5804+
case QGis::WKBPoint:
5805+
case QGis::WKBPoint25D:
5806+
case QGis::WKBMultiPoint:
5807+
case QGis::WKBMultiPoint25D:
5808+
//can't smooth a point based geometry
5809+
return new QgsGeometry( *this );
5810+
5811+
case QGis::WKBLineString:
5812+
case QGis::WKBLineString25D:
5813+
{
5814+
QgsPolyline line = asPolyline();
5815+
return QgsGeometry::fromPolyline( smoothLine( line, iterations, offset ) );
5816+
}
5817+
5818+
case QGis::WKBMultiLineString:
5819+
case QGis::WKBMultiLineString25D:
5820+
{
5821+
QgsMultiPolyline multiline = asMultiPolyline();
5822+
QgsMultiPolyline resultMultiline;
5823+
QgsMultiPolyline::const_iterator lineIt = multiline.constBegin();
5824+
for ( ; lineIt != multiline.constEnd(); ++lineIt )
5825+
{
5826+
resultMultiline << smoothLine( *lineIt, iterations, offset );
5827+
}
5828+
return QgsGeometry::fromMultiPolyline( resultMultiline );
5829+
}
5830+
5831+
case QGis::WKBPolygon:
5832+
case QGis::WKBPolygon25D:
5833+
{
5834+
QgsPolygon poly = asPolygon();
5835+
return QgsGeometry::fromPolygon( smoothPolygon( poly, iterations, offset ) );
5836+
}
5837+
5838+
case QGis::WKBMultiPolygon:
5839+
case QGis::WKBMultiPolygon25D:
5840+
{
5841+
QgsMultiPolygon multipoly = asMultiPolygon();
5842+
QgsMultiPolygon resultMultipoly;
5843+
QgsMultiPolygon::const_iterator polyIt = multipoly.constBegin();
5844+
for ( ; polyIt != multipoly.constEnd(); ++polyIt )
5845+
{
5846+
resultMultipoly << smoothPolygon( *polyIt, iterations, offset );
5847+
}
5848+
return QgsGeometry::fromMultiPolygon( resultMultipoly );
5849+
}
5850+
break;
5851+
5852+
case QGis::WKBUnknown:
5853+
default:
5854+
return new QgsGeometry( *this );
5855+
}
5856+
}
5857+
5858+
inline QgsPoint interpolatePointOnLine( const QgsPoint& p1, const QgsPoint& p2, const double offset )
5859+
{
5860+
double deltaX = p2.x() - p1.x();
5861+
double deltaY = p2.y() - p1.y();
5862+
return QgsPoint( p1.x() + deltaX * offset, p1.y() + deltaY * offset );
5863+
}
5864+
5865+
QgsPolyline QgsGeometry::smoothLine( const QgsPolyline& polyline, const unsigned int iterations, const double offset ) const
5866+
{
5867+
QgsPolyline result = polyline;
5868+
for ( unsigned int iteration = 0; iteration < iterations; ++iteration )
5869+
{
5870+
QgsPolyline outputLine = QgsPolyline();
5871+
for ( int i = 0; i < result.count() - 1; i++ )
5872+
{
5873+
const QgsPoint& p1 = result.at( i );
5874+
const QgsPoint& p2 = result.at( i + 1 );
5875+
outputLine << ( i == 0 ? result.at( i ) : interpolatePointOnLine( p1, p2, offset ) );
5876+
outputLine << ( i == result.count() - 2 ? result.at( i + 1 ) : interpolatePointOnLine( p1, p2, 1.0 - offset ) );
5877+
}
5878+
result = outputLine;
5879+
}
5880+
return result;
5881+
}
5882+
5883+
QgsPolygon QgsGeometry::smoothPolygon( const QgsPolygon& polygon, const unsigned int iterations, const double offset ) const
5884+
{
5885+
QgsPolygon resultPoly;
5886+
QgsPolygon::const_iterator ringIt = polygon.constBegin();
5887+
for ( ; ringIt != polygon.constEnd(); ++ringIt )
5888+
{
5889+
QgsPolyline resultRing = *ringIt;
5890+
for ( unsigned int iteration = 0; iteration < iterations; ++iteration )
5891+
{
5892+
QgsPolyline outputRing = QgsPolyline();
5893+
for ( int i = 0; i < resultRing.count() - 1; ++i )
5894+
{
5895+
const QgsPoint& p1 = resultRing.at( i );
5896+
const QgsPoint& p2 = resultRing.at( i + 1 );
5897+
outputRing << interpolatePointOnLine( p1, p2, offset );
5898+
outputRing << interpolatePointOnLine( p1, p2, 1.0 - offset );
5899+
}
5900+
//close polygon
5901+
outputRing << outputRing.at( 0 );
5902+
5903+
resultRing = outputRing;
5904+
}
5905+
resultPoly << resultRing;
5906+
}
5907+
return resultPoly;
5908+
}
5909+
58005910
QgsGeometry* QgsGeometry::centroid()
58015911
{
58025912
if ( mDirtyGeos )
@@ -6658,3 +6768,17 @@ bool QgsGeometry::compare( const QgsPolygon &p1, const QgsPolygon &p2, double ep
66586768
}
66596769
return true;
66606770
}
6771+
6772+
6773+
bool QgsGeometry::compare( const QgsMultiPolygon &p1, const QgsMultiPolygon &p2, double epsilon )
6774+
{
6775+
if ( p1.count() != p2.count() )
6776+
return false;
6777+
6778+
for ( int i = 0; i < p1.count(); ++i )
6779+
{
6780+
if ( !QgsGeometry::compare( p1.at( i ), p2.at( i ), epsilon ) )
6781+
return false;
6782+
}
6783+
return true;
6784+
}

src/core/qgsgeometry.h

+28-1
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,17 @@ class CORE_EXPORT QgsGeometry
384384
/** Returns a simplified version of this geometry using a specified tolerance value */
385385
QgsGeometry* simplify( double tolerance );
386386

387+
/**Smooths a geometry by rounding off corners using the Chaikin algorithm. This operation
388+
* roughly doubles the number of vertices in a geometry.
389+
* @param iterations number of smoothing iterations to run. More iterations results
390+
* in a smoother geometry
391+
* @param offset fraction of line to create new vertices along, between 0 and 1.0
392+
* eg the default value of 0.25 will create new vertices 25% and 75% along each line segment
393+
* of the geometry for each iteration. Smaller values result in "tighter" smoothing.
394+
* @note added in 2.9
395+
*/
396+
QgsGeometry* smooth( const unsigned int iterations = 1, const double offset = 0.25 );
397+
387398
/** Returns the center of mass of a geometry
388399
* @note for line based geometries, the center point of the line is returned,
389400
* and for point based geometries, the point itself is returned */
@@ -395,7 +406,7 @@ class CORE_EXPORT QgsGeometry
395406
/** Returns the smallest convex polygon that contains all the points in the geometry. */
396407
QgsGeometry* convexHull();
397408

398-
/* Return interpolated point on line at distance */
409+
/** Return interpolated point on line at distance */
399410
QgsGeometry* interpolate( double distance );
400411

401412
/** Returns a geometry representing the points shared by this geometry and other. */
@@ -541,6 +552,17 @@ class CORE_EXPORT QgsGeometry
541552
*/
542553
static bool compare( const QgsPolygon& p1, const QgsPolygon& p2, double epsilon = 4 * DBL_EPSILON );
543554

555+
/** Compares two multipolygons for equality within a specified tolerance.
556+
* @param p1 first multipolygon
557+
* @param p2 second multipolygon
558+
* @param epsilon maximum difference for coordinates between the multipolygons
559+
* @returns true if multipolygons have the same number of polygons, the polygons have the same number
560+
* of rings, and each ring has the same number of points and all points are equal within the specified
561+
* tolerance
562+
* @note added in QGIS 2.9
563+
*/
564+
static bool compare( const QgsMultiPolygon& p1, const QgsMultiPolygon& p2, double epsilon = 4 * DBL_EPSILON );
565+
544566
private:
545567
// Private variables
546568

@@ -640,6 +662,11 @@ class CORE_EXPORT QgsGeometry
640662
@return the reshaped polygon or 0 in case of error*/
641663
static GEOSGeometry* reshapePolygon( const GEOSGeometry* polygon, const GEOSGeometry* reshapeLineGeos );
642664

665+
/**Smooths a polygon using the Chaikin algorithm*/
666+
QgsPolygon smoothPolygon( const QgsPolygon &polygon, const unsigned int iterations = 1, const double offset = 0.25 ) const;
667+
/**Smooths a polyline using the Chaikin algorithm*/
668+
QgsPolyline smoothLine( const QgsPolyline &polyline, const unsigned int iterations = 1, const double offset = 0.25 ) const;
669+
643670
/**Nodes together a split line and a (multi-) polygon geometry in a multilinestring
644671
@return the noded multiline geometry or 0 in case of error. The calling function takes ownership of the node geometry*/
645672
static GEOSGeometry* nodeGeometries( const GEOSGeometry *splitLine, const GEOSGeometry *poly );

tests/src/core/testqgsgeometry.cpp

+66
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class TestQgsGeometry : public QObject
7474
void differenceCheck1();
7575
void differenceCheck2();
7676
void bufferCheck();
77+
void smoothCheck();
7778

7879
private:
7980
/** A helper method to do a render check to see if the geometry op is as expected */
@@ -550,6 +551,71 @@ void TestQgsGeometry::bufferCheck()
550551
dumpPolygon( myPolygon );
551552
QVERIFY( renderCheck( "geometry_bufferCheck", "Checking buffer(10,10) of B", 10 ) );
552553
}
554+
555+
void TestQgsGeometry::smoothCheck()
556+
{
557+
//can't smooth a point
558+
QString wkt = "POINT(40 50)";
559+
QScopedPointer<QgsGeometry> geom( QgsGeometry::fromWkt( wkt ) );
560+
QgsGeometry* result = geom->smooth( 1, 0.25 );
561+
QString obtained = result->exportToWkt();
562+
delete result;
563+
QCOMPARE( obtained, wkt );
564+
565+
//linestring
566+
wkt = "LINESTRING(0 0, 10 0, 10 10, 20 10)";
567+
geom.reset( QgsGeometry::fromWkt( wkt ) );
568+
result = geom->smooth( 1, 0.25 );
569+
QgsPolyline line = result->asPolyline();
570+
delete result;
571+
QgsPolyline expectedLine;
572+
expectedLine << QgsPoint( 0, 0 ) << QgsPoint( 7.5, 0 ) << QgsPoint( 10.0, 2.5 )
573+
<< QgsPoint( 10.0, 7.5 ) << QgsPoint( 12.5, 10.0 ) << QgsPoint( 20.0, 10.0 );
574+
QVERIFY( QgsGeometry::compare( line, expectedLine ) );
575+
576+
wkt = "MULTILINESTRING((0 0, 10 0, 10 10, 20 10),(30 30, 40 30, 40 40, 50 40))";
577+
geom.reset( QgsGeometry::fromWkt( wkt ) );
578+
result = geom->smooth( 1, 0.25 );
579+
QgsMultiPolyline multiLine = result->asMultiPolyline();
580+
delete result;
581+
QgsMultiPolyline expectedMultiline;
582+
expectedMultiline << ( QgsPolyline() << QgsPoint( 0, 0 ) << QgsPoint( 7.5, 0 ) << QgsPoint( 10.0, 2.5 )
583+
<< QgsPoint( 10.0, 7.5 ) << QgsPoint( 12.5, 10.0 ) << QgsPoint( 20.0, 10.0 ) )
584+
<< ( QgsPolyline() << QgsPoint( 30.0, 30.0 ) << QgsPoint( 37.5, 30.0 ) << QgsPoint( 40.0, 32.5 )
585+
<< QgsPoint( 40.0, 37.5 ) << QgsPoint( 42.5, 40.0 ) << QgsPoint( 50.0, 40.0 ) );
586+
QVERIFY( QgsGeometry::compare( multiLine, expectedMultiline ) );
587+
588+
//polygon
589+
wkt = "POLYGON((0 0, 10 0, 10 10, 0 10, 0 0 ),(2 2, 4 2, 4 4, 2 4, 2 2))";
590+
geom.reset( QgsGeometry::fromWkt( wkt ) );
591+
result = geom->smooth( 1, 0.25 );
592+
QgsPolygon poly = result->asPolygon();
593+
delete result;
594+
QgsPolygon expectedPolygon;
595+
expectedPolygon << ( QgsPolyline() << QgsPoint( 2.5, 0 ) << QgsPoint( 7.5, 0 ) << QgsPoint( 10.0, 2.5 )
596+
<< QgsPoint( 10.0, 7.5 ) << QgsPoint( 7.5, 10.0 ) << QgsPoint( 2.5, 10.0 ) << QgsPoint( 0, 7.5 )
597+
<< QgsPoint( 0, 2.5 ) << QgsPoint( 2.5, 0 ) )
598+
<< ( QgsPolyline() << QgsPoint( 2.5, 2.0 ) << QgsPoint( 3.5, 2.0 ) << QgsPoint( 4.0, 2.5 )
599+
<< QgsPoint( 4.0, 3.5 ) << QgsPoint( 3.5, 4.0 ) << QgsPoint( 2.5, 4.0 )
600+
<< QgsPoint( 2.0, 3.5 ) << QgsPoint( 2.0, 2.5 ) << QgsPoint( 2.5, 2.0 ) );
601+
QVERIFY( QgsGeometry::compare( poly, expectedPolygon ) );
602+
603+
//multipolygon
604+
wkt = "MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0 )),((2 2, 4 2, 4 4, 2 4, 2 2)))";
605+
geom.reset( QgsGeometry::fromWkt( wkt ) );
606+
result = geom->smooth( 1, 0.1 );
607+
QgsMultiPolygon multipoly = result->asMultiPolygon();
608+
delete result;
609+
QgsMultiPolygon expectedMultiPoly;
610+
expectedMultiPoly << ( QgsPolygon() << ( QgsPolyline() << QgsPoint( 1.0, 0 ) << QgsPoint( 9, 0 ) << QgsPoint( 10.0, 1 )
611+
<< QgsPoint( 10.0, 9 ) << QgsPoint( 9, 10.0 ) << QgsPoint( 1, 10.0 ) << QgsPoint( 0, 9 )
612+
<< QgsPoint( 0, 1 ) << QgsPoint( 1, 0 ) ) )
613+
<< ( QgsPolygon() << ( QgsPolyline() << QgsPoint( 2.2, 2.0 ) << QgsPoint( 3.8, 2.0 ) << QgsPoint( 4.0, 2.2 )
614+
<< QgsPoint( 4.0, 3.8 ) << QgsPoint( 3.8, 4.0 ) << QgsPoint( 2.2, 4.0 ) << QgsPoint( 2.0, 3.8 )
615+
<< QgsPoint( 2, 2.2 ) << QgsPoint( 2.2, 2 ) ) );
616+
QVERIFY( QgsGeometry::compare( multipoly, expectedMultiPoly ) );
617+
}
618+
553619
bool TestQgsGeometry::renderCheck( QString theTestName, QString theComment, int mismatchCount )
554620
{
555621
mReport += "<h2>" + theTestName + "</h2>\n";

0 commit comments

Comments
 (0)