mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
[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:
parent
785d9952a0
commit
8c5f3f88a9
@ -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*/
|
||||
|
@ -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;
|
||||
|
||||
|
@ -58,6 +58,7 @@ class QgsGeometryCollectionV2: public QgsAbstractGeometryV2
|
||||
|
||||
virtual double length() const;
|
||||
virtual double area() const;
|
||||
virtual double perimeter() const;
|
||||
|
||||
bool hasCurvedSegments() const;
|
||||
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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([
|
||||
|
17
tests/testdata/area_data.csv
vendored
17
tests/testdata/area_data.csv
vendored
@ -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
|
|
13
tests/testdata/length_data.csv
vendored
13
tests/testdata/length_data.csv
vendored
@ -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
|
|
Loading…
x
Reference in New Issue
Block a user