[geometry] Fix calculation of length/perimeter for collections

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.
This commit is contained in:
Nyall Dawson 2015-10-17 15:40:36 +11:00
parent 785d9952a0
commit 8c5f3f88a9
12 changed files with 55 additions and 76 deletions

View File

@ -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*/

View File

@ -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;

View File

@ -58,6 +58,7 @@ class QgsGeometryCollectionV2: public QgsAbstractGeometryV2
virtual double length() const;
virtual double area() const;
virtual double perimeter() const;
bool hasCurvedSegments() const;

View File

@ -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; }

View File

@ -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

View File

@ -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;

View File

@ -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();

View File

@ -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;

View File

@ -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

View File

@ -182,41 +182,15 @@ class TestQgsGeometry(TestCase):
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([

View File

@ -1,17 +0,0 @@
MultiPolygon( ((0 0, 10 0, 10 10, 0 10, 0 0)),( (0 0, 10 0, 10 10, 0 10, 0 0),(5 5, 7 5, 7 7 , 5 7, 5 5) ) ,( (0 0, 10 0, 10 10, 0 10, 0 0),(5 5, 7 5, 7 7, 5 7, 5 5),(1 1,2 1, 2 2, 1 2, 1 1) ) )|291
Point(1 2)|0
MultiPoint ((1 2),(2 3))|0
LineString(1 2,2 3,3 4)|0
Polygon (())|0
LineString ()|0
MultiPolygon ((()))|0
MultiLineString ()|0
MultiPoint ()|0
GeometryCollection ()|0
Polygon ((60 180, 140 240, 140 240, 140 240, 200 180, 120 120, 60 180))|8400
Polygon ((60 180, 140 120, 100 180, 140 240, 60 180))|2400
Polygon ((60 180, 140 120, 100 180, 140 240, 140 240, 60 180))|2400
Polygon ((1 1,2 2,3 1,2 0,1 1))|2.0
Polygon ((0 0,0 7,4 2,2 0,0 0))|16.0
Polygon ((0 0,0 7,4 2,2 0,0 0), (1 1,2 1,2 2,1 2,1 1))|15.0
Polygon ((1872000 528000,1872000 192000,1536119 192000,1536000 528000,1200000 528000,1200000 863880,1536000 863880,1872000 863880,1872000 528000))|338587368000
1 MultiPolygon( ((0 0, 10 0, 10 10, 0 10, 0 0)),( (0 0, 10 0, 10 10, 0 10, 0 0),(5 5, 7 5, 7 7 , 5 7, 5 5) ) ,( (0 0, 10 0, 10 10, 0 10, 0 0),(5 5, 7 5, 7 7, 5 7, 5 5),(1 1,2 1, 2 2, 1 2, 1 1) ) ) 291
2 Point(1 2) 0
3 MultiPoint ((1 2),(2 3)) 0
4 LineString(1 2,2 3,3 4) 0
5 Polygon (()) 0
6 LineString () 0
7 MultiPolygon ((())) 0
8 MultiLineString () 0
9 MultiPoint () 0
10 GeometryCollection () 0
11 Polygon ((60 180, 140 240, 140 240, 140 240, 200 180, 120 120, 60 180)) 8400
12 Polygon ((60 180, 140 120, 100 180, 140 240, 60 180)) 2400
13 Polygon ((60 180, 140 120, 100 180, 140 240, 140 240, 60 180)) 2400
14 Polygon ((1 1,2 2,3 1,2 0,1 1)) 2.0
15 Polygon ((0 0,0 7,4 2,2 0,0 0)) 16.0
16 Polygon ((0 0,0 7,4 2,2 0,0 0), (1 1,2 1,2 2,1 2,1 1)) 15.0
17 Polygon ((1872000 528000,1872000 192000,1536119 192000,1536000 528000,1200000 528000,1200000 863880,1536000 863880,1872000 863880,1872000 528000)) 338587368000

View File

@ -1,13 +0,0 @@
MultiPolygon( ((0 0, 10 0, 10 10, 0 10, 0 0)),( (0 0, 10 0, 10 10, 0 10, 0 0),(5 5, 7 5, 7 7 , 5 7, 5 5) ) ,( (0 0, 10 0, 10 10, 0 10, 0 0),(5 5, 7 5, 7 7, 5 7, 5 5),(1 1,2 1, 2 2, 1 2, 1 1) ) )|140
MultiLineString((0 0, 1 1),(0 0, 1 1, 2 2) )|4.24264068711929
Point (1 2)|0
MultiPoint ((1 2),(2 3))|0
Polygon (())|0
LineString ()|0
MultiPolygon ((()))|0
MultiLineString ()|0
MultiPoint ()|0
GeometryCollection ()|0
LineString (0 0, 10 10, 20 0)|28.2842712
LineString (0 0,3 4)|5
LineString (0 0,3 4,4 3)|6.4142135624
1 MultiPolygon( ((0 0, 10 0, 10 10, 0 10, 0 0)),( (0 0, 10 0, 10 10, 0 10, 0 0),(5 5, 7 5, 7 7 , 5 7, 5 5) ) ,( (0 0, 10 0, 10 10, 0 10, 0 0),(5 5, 7 5, 7 7, 5 7, 5 5),(1 1,2 1, 2 2, 1 2, 1 1) ) ) 140
2 MultiLineString((0 0, 1 1),(0 0, 1 1, 2 2) ) 4.24264068711929
3 Point (1 2) 0
4 MultiPoint ((1 2),(2 3)) 0
5 Polygon (()) 0
6 LineString () 0
7 MultiPolygon ((())) 0
8 MultiLineString () 0
9 MultiPoint () 0
10 GeometryCollection () 0
11 LineString (0 0, 10 10, 20 0) 28.2842712
12 LineString (0 0,3 4) 5
13 LineString (0 0,3 4,4 3) 6.4142135624