Skip to content

Commit

Permalink
[geometry] Fix calculation of length/perimeter for collections
Browse files Browse the repository at this point in the history
Split length from perimeter calculation in geometry API, as
returning perimeter for length for polygons is misleading and
results in incorrect length/perimeter calculations for mixed
geometry collections.

Enable length & perimeter unit tests against reference geometries.
Now the length & perimeter values match those calculated by
PostGIS.
  • Loading branch information
nyalldawson committed Oct 17, 2015
1 parent 785d995 commit 8c5f3f8
Show file tree
Hide file tree
Showing 12 changed files with 55 additions and 76 deletions.
16 changes: 15 additions & 1 deletion python/core/geometry/qgsabstractgeometryv2.sip
Expand Up @@ -118,8 +118,22 @@ class QgsAbstractGeometryV2
virtual bool moveVertex( const QgsVertexId& position, const QgsPointV2& newPos ) = 0;
virtual bool deleteVertex( const QgsVertexId& position ) = 0;

/** Length for linear geometries,perimeter for area geometries*/
/** Returns the length of the geometry.
* @see area()
* @see perimeter()
*/
virtual double length() const;

/** Returns the perimeter of the geometry.
* @see area()
* @see length()
*/
virtual double perimeter() const;

/** Returns the area of the geometry.
* @see length()
* @see perimeter()
*/
virtual double area() const;

/** Returns the centroid of the geometry*/
Expand Down
2 changes: 1 addition & 1 deletion python/core/geometry/qgscurvepolygonv2.sip
Expand Up @@ -29,7 +29,7 @@ class QgsCurvePolygonV2: public QgsSurfaceV2

//surface interface
virtual double area() const;
virtual double length() const;
virtual double perimeter() const;
QgsPointV2 pointOnSurface() const;
QgsPolygonV2* surfaceToPolygon() const;

Expand Down
1 change: 1 addition & 0 deletions python/core/geometry/qgsgeometrycollectionv2.sip
Expand Up @@ -58,6 +58,7 @@ class QgsGeometryCollectionV2: public QgsAbstractGeometryV2

virtual double length() const;
virtual double area() const;
virtual double perimeter() const;

bool hasCurvedSegments() const;

Expand Down
14 changes: 11 additions & 3 deletions src/core/geometry/qgsabstractgeometryv2.h
Expand Up @@ -259,13 +259,21 @@ class CORE_EXPORT QgsAbstractGeometryV2
*/
virtual bool deleteVertex( const QgsVertexId& position ) = 0;

/** Returns the length (or perimeter for area geometries) of the geometry.
* @see area
/** Returns the length of the geometry.
* @see area()
* @see perimeter()
*/
virtual double length() const { return 0.0; }

/** Returns the perimeter of the geometry.
* @see area()
* @see length()
*/
virtual double perimeter() const { return 0.0; }

/** Returns the area of the geometry.
* @see length
* @see length()
* @see perimeter()
*/
virtual double area() const { return 0.0; }

Expand Down
8 changes: 4 additions & 4 deletions src/core/geometry/qgscurvepolygonv2.cpp
Expand Up @@ -336,16 +336,16 @@ double QgsCurvePolygonV2::area() const
return area;
}

double QgsCurvePolygonV2::length() const
double QgsCurvePolygonV2::perimeter() const
{
//sum perimeter of rings
double length = mExteriorRing->length();
double perimeter = mExteriorRing->length();
QList<QgsCurveV2*>::const_iterator ringIt = mInteriorRings.constBegin();
for ( ; ringIt != mInteriorRings.constEnd(); ++ringIt )
{
length += ( *ringIt )->length();
perimeter += ( *ringIt )->length();
}
return length;
return perimeter;
}

QgsPointV2 QgsCurvePolygonV2::pointOnSurface() const
Expand Down
2 changes: 1 addition & 1 deletion src/core/geometry/qgscurvepolygonv2.h
Expand Up @@ -55,7 +55,7 @@ class CORE_EXPORT QgsCurvePolygonV2: public QgsSurfaceV2

//surface interface
virtual double area() const override;
virtual double length() const override;
virtual double perimeter() const override;
QgsPointV2 pointOnSurface() const override;
QgsPolygonV2* surfaceToPolygon() const override;

Expand Down
11 changes: 11 additions & 0 deletions src/core/geometry/qgsgeometrycollectionv2.cpp
Expand Up @@ -454,6 +454,17 @@ double QgsGeometryCollectionV2::area() const
return area;
}

double QgsGeometryCollectionV2::perimeter() const
{
double perimeter = 0.0;
QVector< QgsAbstractGeometryV2* >::const_iterator geomIt = mGeometries.constBegin();
for ( ; geomIt != mGeometries.constEnd(); ++geomIt )
{
perimeter += ( *geomIt )->perimeter();
}
return perimeter;
}

bool QgsGeometryCollectionV2::fromCollectionWkt( const QString &wkt, const QList<QgsAbstractGeometryV2*>& subtypes, const QString& defaultChildWkbType )
{
clear();
Expand Down
1 change: 1 addition & 0 deletions src/core/geometry/qgsgeometrycollectionv2.h
Expand Up @@ -103,6 +103,7 @@ class CORE_EXPORT QgsGeometryCollectionV2: public QgsAbstractGeometryV2

virtual double length() const override;
virtual double area() const override;
virtual double perimeter() const override;

bool hasCurvedSegments() const override;

Expand Down
2 changes: 1 addition & 1 deletion src/core/qgsdistancearea.cpp
Expand Up @@ -384,7 +384,7 @@ double QgsDistanceArea::measurePerimeter( const QgsGeometry* geometry ) const

if ( !mEllipsoidalMode || mEllipsoid == GEO_NONE )
{
return geomV2->length();
return geomV2->perimeter();
}

//create list with (single) surfaces
Expand Down
44 changes: 9 additions & 35 deletions tests/src/python/test_qgsgeometry.py
Expand Up @@ -182,41 +182,15 @@ def testReferenceGeometry(self):
result = geom.geometry().area()
assert doubleNear(result, exp), "Area {}: mismatch Expected:\n{}\nGot:\n{}\n".format(i + 1, exp, result)

#NOTE - disabled due to misleading length/perimeter calculations for geometry collections
#exp = float(row['length'])
#result = geom.geometry().length()
#assert doubleNear(result, exp), "Length {}: mismatch Expected:\n{}\nGot:\n{}\n".format(i + 1, exp, result)
#exp = float(row['perimeter'])
#result = geom.geometry().length()
#assert doubleNear(result, exp), "Length {}: mismatch Expected:\n{}\nGot:\n{}\n".format(i + 1, exp, result)

def testArea(self):
""" Test area calculations """
with open(os.path.join(TEST_DATA_DIR, 'area_data.csv'), 'r') as d:
for i, t in enumerate(d):
if not t:
continue

test_data = t.strip().split('|')
geom = QgsGeometry.fromWkt(test_data[0])
assert geom, "Area {} failed: could not create geom:\n{}\n".format(i + 1, test_data[0])
result = geom.area()
exp = float(test_data[1])
assert abs(float(result) - float(exp)) < 0.0000001, "Area failed: mismatch Expected:\n{}\nGot:\n{}\nGeom:\n{}\n".format(i + 1, exp, result, test_data[0])

def testLength(self):
""" Test length/perimeter calculations """
with open(os.path.join(TEST_DATA_DIR, 'length_data.csv'), 'r') as d:
for i, t in enumerate(d):
if not t:
continue

test_data = t.strip().split('|')
geom = QgsGeometry.fromWkt(test_data[0])
assert geom, "Length {} failed: could not create geom:\n{}\n".format(i + 1, test_data[0])
result = geom.length()
exp = float(test_data[1])
assert abs(float(result) - float(exp)) < 0.0000001, "Length {} failed: mismatch Expected:\n{}\nGot:\n{}\nGeom:\n{}\n".format(i + 1, exp, result, test_data[0])
#test length calculation
exp = float(row['length'])
result = geom.geometry().length()
assert doubleNear(result, exp, 0.00001), "Length {}: mismatch Expected:\n{}\nGot:\n{}\n".format(i + 1, exp, result)

#test perimeter calculation
exp = float(row['perimeter'])
result = geom.geometry().perimeter()
assert doubleNear(result, exp, 0.00001), "Perimeter {}: mismatch Expected:\n{}\nGot:\n{}\n".format(i + 1, exp, result)

def testIntersection(self):
myLine = QgsGeometry.fromPolyline([
Expand Down
17 changes: 0 additions & 17 deletions tests/testdata/area_data.csv

This file was deleted.

13 changes: 0 additions & 13 deletions tests/testdata/length_data.csv

This file was deleted.

0 comments on commit 8c5f3f8

Please sign in to comment.