Skip to content

Commit a769448

Browse files
committed
Also port processing densify to distance to c++
- Add QgsGeometry method to densify by distance - Fix bug in processing algorithm which resulted in duplicate vertices and incorrectly spaced extra vertices
1 parent 77e7693 commit a769448

File tree

8 files changed

+171
-64
lines changed

8 files changed

+171
-64
lines changed

python/core/geometry/qgsgeometry.sip

+1
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,7 @@ class QgsGeometry
543543
QgsGeometry extendLine( double startDistance, double endDistance ) const;
544544
QgsGeometry simplify( double tolerance ) const;
545545
QgsGeometry densifyByCount( int extraNodesPerSegment ) const;
546+
QgsGeometry densifyByDistance( double distance ) const;
546547
QgsGeometry centroid() const;
547548

548549
/**

python/plugins/processing/algs/help/qgis.yaml

+6-2
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,13 @@ qgis:densifygeometries:
139139
The number of new vertices to add to each feature geometry is specified as an input parameter.
140140

141141
qgis:densifygeometriesgivenaninterval:
142-
This algorithm takes a polygon or line layer and generates a new one in which the geometries have a larger number of vertices than the original one.
142+
This algorithm takes a polygon or line layer and generates a new one in which the geometries have a larger number of vertices than the original one. The geometries are densified by adding regularly placed extra nodes inside each segment so that the maximum distance between any two nodes does not exceed the specified distance.
143+
144+
E.g. specifying a distance 3 would cause the segment [0 0] -> [10 0] to be converted to [0 0] -> [2.5 0] -> [5 0] -> [7.5 0] -> [10 0], since 3 extra nodes are required on the segment and spacing these at 2.5 increments allows them to be evenly spaced over the segment.
145+
146+
If the geometries have z or m values present then these will be linearly interpolated at the added nodes.
143147

144-
The number of new vertices depends on the length of the geometry, and is specified as a distance between them. The distance is expressed in the same units used by the layer CRS.
148+
The distance is expressed in the same units used by the layer CRS.
145149

146150
qgis:difference: >
147151
This algorithm extracts features from the Input layer that fall outside, or partially overlap, features in the Difference layer. Input layer features that partially overlap the difference layer feature(s) are split along the boundary of the difference layer feature(s) and only the portions outside the difference layer features are retained.

python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py

+1-52
Original file line numberDiff line numberDiff line change
@@ -71,61 +71,10 @@ def processAlgorithm(self, feedback):
7171
for current, f in enumerate(features):
7272
feature = f
7373
if feature.hasGeometry():
74-
new_geometry = self.densifyGeometry(feature.geometry(), interval,
75-
isPolygon)
74+
new_geometry = feature.geometry().densifyByCount(float(interval))
7675
feature.setGeometry(new_geometry)
7776
writer.addFeature(feature)
7877

7978
feedback.setProgress(int(current * total))
8079

8180
del writer
82-
83-
def densifyGeometry(self, geometry, interval, isPolygon):
84-
output = []
85-
if isPolygon:
86-
if geometry.isMultipart():
87-
polygons = geometry.asMultiPolygon()
88-
for poly in polygons:
89-
p = []
90-
for ring in poly:
91-
p.append(self.densify(ring, interval))
92-
output.append(p)
93-
return QgsGeometry.fromMultiPolygon(output)
94-
else:
95-
rings = geometry.asPolygon()
96-
for ring in rings:
97-
output.append(self.densify(ring, interval))
98-
return QgsGeometry.fromPolygon(output)
99-
else:
100-
if geometry.isMultipart():
101-
lines = geometry.asMultiPolyline()
102-
for points in lines:
103-
output.append(self.densify(points, interval))
104-
return QgsGeometry.fromMultiPolyline(output)
105-
else:
106-
points = geometry.asPolyline()
107-
output = self.densify(points, interval)
108-
return QgsGeometry.fromPolyline(output)
109-
110-
def densify(self, polyline, interval):
111-
output = []
112-
for i in range(len(polyline) - 1):
113-
p1 = polyline[i]
114-
p2 = polyline[i + 1]
115-
output.append(p1)
116-
117-
# Calculate necessary number of points between p1 and p2
118-
pointsNumber = sqrt(p1.sqrDist(p2)) / interval
119-
if pointsNumber > 1:
120-
multiplier = 1.0 / float(pointsNumber)
121-
else:
122-
multiplier = 1
123-
for j in range(int(pointsNumber)):
124-
delta = multiplier * (j + 1)
125-
x = p1.x() + delta * (p2.x() - p1.x())
126-
y = p1.y() + delta * (p2.y() - p1.y())
127-
output.append(QgsPoint(x, y))
128-
if j + 1 == pointsNumber:
129-
break
130-
output.append(polyline[len(polyline) - 1])
131-
return output

src/core/geometry/qgsgeometry.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -1583,6 +1583,13 @@ QgsGeometry QgsGeometry::densifyByCount( int extraNodesPerSegment ) const
15831583
return engine.densifyByCount( extraNodesPerSegment );
15841584
}
15851585

1586+
QgsGeometry QgsGeometry::densifyByDistance( double distance ) const
1587+
{
1588+
QgsInternalGeometryEngine engine( *this );
1589+
1590+
return engine.densifyByDistance( distance );
1591+
}
1592+
15861593
QgsGeometry QgsGeometry::centroid() const
15871594
{
15881595
if ( !d->geometry )

src/core/geometry/qgsgeometry.h

+17
Original file line numberDiff line numberDiff line change
@@ -618,9 +618,26 @@ class CORE_EXPORT QgsGeometry
618618
* at the added nodes.
619619
* Curved geometry types are automatically segmentized by this routine.
620620
* @node added in QGIS 3.0
621+
* @see densifyByDistance()
621622
*/
622623
QgsGeometry densifyByCount( int extraNodesPerSegment ) const;
623624

625+
/**
626+
* Densifies the geometry by adding regularly placed extra nodes inside each segment
627+
* so that the maximum distance between any two nodes does not exceed the
628+
* specified \a distance.
629+
* E.g. specifying a distance 3 would cause the segment [0 0] -> [10 0]
630+
* to be converted to [0 0] -> [2.5 0] -> [5 0] -> [7.5 0] -> [10 0], since
631+
* 3 extra nodes are required on the segment and spacing these at 2.5 increments
632+
* allows them to be evenly spaced over the segment.
633+
* If the geometry has z or m values present then these will be linearly interpolated
634+
* at the added nodes.
635+
* Curved geometry types are automatically segmentized by this routine.
636+
* @node added in QGIS 3.0
637+
* @see densifyByCount()
638+
*/
639+
QgsGeometry densifyByDistance( double distance ) const;
640+
624641
/**
625642
* Returns the center of mass of a geometry.
626643
* @note for line based geometries, the center point of the line is returned,

src/core/geometry/qgsinternalgeometryengine.cpp

+56-10
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,8 @@ QgsGeometry QgsInternalGeometryEngine::orthogonalize( double tolerance, int maxI
477477
}
478478
}
479479

480-
QgsLineString *doDensifyByCount( QgsLineString *ring, int extraNodesPerSegment )
480+
// if extraNodesPerSegment < 0, then use distance based mode
481+
QgsLineString *doDensify( QgsLineString *ring, int extraNodesPerSegment = -1, double distance = 1 )
481482
{
482483
QgsPointSequence out;
483484
double multiplier = 1.0 / double( extraNodesPerSegment + 1 );
@@ -503,6 +504,7 @@ QgsLineString *doDensifyByCount( QgsLineString *ring, int extraNodesPerSegment )
503504
double yOut = 0;
504505
double zOut = 0;
505506
double mOut = 0;
507+
int extraNodesThisSegment = extraNodesPerSegment;
506508
for ( int i = 0; i < nPoints - 1; ++i )
507509
{
508510
x1 = ring->xAt( i );
@@ -521,7 +523,16 @@ QgsLineString *doDensifyByCount( QgsLineString *ring, int extraNodesPerSegment )
521523
}
522524

523525
out << QgsPointV2( outType, x1, y1, z1, m1 );
524-
for ( int j = 0; j < extraNodesPerSegment; ++j )
526+
527+
if ( extraNodesPerSegment < 0 )
528+
{
529+
// distance mode
530+
extraNodesThisSegment = floor( sqrt( ( x2 - x1 ) * ( x2 - x1 ) + ( y2 - y1 ) * ( y2 - y1 ) ) / distance );
531+
if ( extraNodesThisSegment >= 1 )
532+
multiplier = 1.0 / ( extraNodesThisSegment + 1 );
533+
}
534+
535+
for ( int j = 0; j < extraNodesThisSegment; ++j )
525536
{
526537
double delta = multiplier * ( j + 1 );
527538
xOut = x1 + delta * ( x2 - x1 );
@@ -542,7 +553,7 @@ QgsLineString *doDensifyByCount( QgsLineString *ring, int extraNodesPerSegment )
542553
return result;
543554
}
544555

545-
QgsAbstractGeometry *densifyGeometryByCount( const QgsAbstractGeometry *geom, int extraNodesPerSegment )
556+
QgsAbstractGeometry *densifyGeometry( const QgsAbstractGeometry *geom, int extraNodesPerSegment = 1, double distance = 1 )
546557
{
547558
std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
548559
if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
@@ -553,20 +564,20 @@ QgsAbstractGeometry *densifyGeometryByCount( const QgsAbstractGeometry *geom, in
553564

554565
if ( QgsWkbTypes::geometryType( geom->wkbType() ) == QgsWkbTypes::LineGeometry )
555566
{
556-
return doDensifyByCount( static_cast< QgsLineString * >( geom->clone() ), extraNodesPerSegment );
567+
return doDensify( static_cast< QgsLineString * >( geom->clone() ), extraNodesPerSegment, distance );
557568
}
558569
else
559570
{
560571
// polygon
561572
const QgsPolygonV2 *polygon = static_cast< const QgsPolygonV2 * >( geom );
562573
QgsPolygonV2 *result = new QgsPolygonV2();
563574

564-
result->setExteriorRing( doDensifyByCount( static_cast< QgsLineString * >( polygon->exteriorRing()->clone() ),
565-
extraNodesPerSegment ) );
575+
result->setExteriorRing( doDensify( static_cast< QgsLineString * >( polygon->exteriorRing()->clone() ),
576+
extraNodesPerSegment, distance ) );
566577
for ( int i = 0; i < polygon->numInteriorRings(); ++i )
567578
{
568-
result->addInteriorRing( doDensifyByCount( static_cast< QgsLineString * >( polygon->interiorRing( i )->clone() ),
569-
extraNodesPerSegment ) );
579+
result->addInteriorRing( doDensify( static_cast< QgsLineString * >( polygon->interiorRing( i )->clone() ),
580+
extraNodesPerSegment, distance ) );
570581
}
571582

572583
return result;
@@ -592,7 +603,42 @@ QgsGeometry QgsInternalGeometryEngine::densifyByCount( int extraNodesPerSegment
592603
geometryList.reserve( numGeom );
593604
for ( int i = 0; i < numGeom; ++i )
594605
{
595-
geometryList << densifyGeometryByCount( gc->geometryN( i ), extraNodesPerSegment );
606+
geometryList << densifyGeometry( gc->geometryN( i ), extraNodesPerSegment );
607+
}
608+
609+
QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
610+
Q_FOREACH ( QgsAbstractGeometry *g, geometryList )
611+
{
612+
first.addPart( g );
613+
}
614+
return first;
615+
}
616+
else
617+
{
618+
return QgsGeometry( densifyGeometry( mGeometry, extraNodesPerSegment ) );
619+
}
620+
}
621+
622+
QgsGeometry QgsInternalGeometryEngine::densifyByDistance( double distance ) const
623+
{
624+
if ( !mGeometry )
625+
{
626+
return QgsGeometry();
627+
}
628+
629+
if ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) == QgsWkbTypes::PointGeometry )
630+
{
631+
return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
632+
}
633+
634+
if ( const QgsGeometryCollection *gc = dynamic_cast< const QgsGeometryCollection *>( mGeometry ) )
635+
{
636+
int numGeom = gc->numGeometries();
637+
QList< QgsAbstractGeometry * > geometryList;
638+
geometryList.reserve( numGeom );
639+
for ( int i = 0; i < numGeom; ++i )
640+
{
641+
geometryList << densifyGeometry( gc->geometryN( i ), -1, distance );
596642
}
597643

598644
QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
@@ -604,6 +650,6 @@ QgsGeometry QgsInternalGeometryEngine::densifyByCount( int extraNodesPerSegment
604650
}
605651
else
606652
{
607-
return QgsGeometry( densifyGeometryByCount( mGeometry, extraNodesPerSegment ) );
653+
return QgsGeometry( densifyGeometry( mGeometry, -1, distance ) );
608654
}
609655
}

src/core/geometry/qgsinternalgeometryengine.h

+15
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,21 @@ class QgsInternalGeometryEngine
8181
*/
8282
QgsGeometry densifyByCount( int extraNodesPerSegment ) const;
8383

84+
/**
85+
* Densifies the geometry by adding regularly placed extra nodes inside each segment
86+
* so that the maximum distance between any two nodes does not exceed the
87+
* specified \a distance.
88+
* E.g. specifying a distance 3 would cause the segment [0 0] -> [10 0]
89+
* to be converted to [0 0] -> [2.5 0] -> [5 0] -> [7.5 0] -> [10 0], since
90+
* 3 extra nodes are required on the segment and spacing these at 2.5 increments
91+
* allows them to be evenly spaced over the segment.
92+
* If the geometry has z or m values present then these will be linearly interpolated
93+
* at the added nodes.
94+
* Curved geometry types are automatically segmentized by this routine.
95+
* @node added in QGIS 3.0
96+
*/
97+
QgsGeometry densifyByDistance( double distance ) const;
98+
8499
private:
85100
const QgsAbstractGeometry *mGeometry = nullptr;
86101
};

tests/src/python/test_qgsgeometry.py

+68
Original file line numberDiff line numberDiff line change
@@ -3996,5 +3996,73 @@ def testDensifyByCount(self):
39963996
self.assertTrue(compareWkt(result, exp, 0.00001),
39973997
"densify by count: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
39983998

3999+
def testDensifyByDistance(self):
4000+
empty = QgsGeometry()
4001+
o = empty.densifyByDistance(4)
4002+
self.assertFalse(o)
4003+
4004+
# point
4005+
input = QgsGeometry.fromWkt("PointZ( 1 2 3 )")
4006+
o = input.densifyByDistance(0.1)
4007+
exp = "PointZ( 1 2 3 )"
4008+
result = o.exportToWkt()
4009+
self.assertTrue(compareWkt(result, exp, 0.00001),
4010+
"densify by count: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
4011+
input = QgsGeometry.fromWkt(
4012+
"MULTIPOINT ((155 271), (150 360), (260 360), (271 265), (280 260), (270 370), (154 354), (150 260))")
4013+
o = input.densifyByDistance(0.1)
4014+
exp = "MULTIPOINT ((155 271), (150 360), (260 360), (271 265), (280 260), (270 370), (154 354), (150 260))"
4015+
result = o.exportToWkt()
4016+
self.assertTrue(compareWkt(result, exp, 0.00001),
4017+
"densify by count: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
4018+
4019+
# line
4020+
input = QgsGeometry.fromWkt("LineString( 0 0, 10 0, 10 10 )")
4021+
o = input.densifyByDistance(100)
4022+
exp = "LineString( 0 0, 10 0, 10 10 )"
4023+
result = o.exportToWkt()
4024+
self.assertTrue(compareWkt(result, exp, 0.00001),
4025+
"densify by count: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
4026+
o = input.densifyByDistance(3)
4027+
exp = "LineString (0 0, 2.5 0, 5 0, 7.5 0, 10 0, 10 2.5, 10 5, 10 7.5, 10 10)"
4028+
4029+
result = o.exportToWkt()
4030+
self.assertTrue(compareWkt(result, exp, 0.00001),
4031+
"densify by count: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
4032+
input = QgsGeometry.fromWkt("LineStringZ( 0 0 1, 10 0 2, 10 10 0)")
4033+
o = input.densifyByDistance(6)
4034+
exp = "LineStringZ (0 0 1, 5 0 1.5, 10 0 2, 10 5 1, 10 10 0)"
4035+
result = o.exportToWkt()
4036+
self.assertTrue(compareWkt(result, exp, 0.00001),
4037+
"densify by count: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
4038+
input = QgsGeometry.fromWkt("LineStringM( 0 0 0, 10 0 2, 10 10 0)")
4039+
o = input.densifyByDistance(3)
4040+
exp = "LineStringM (0 0 0, 2.5 0 0.5, 5 0 1, 7.5 0 1.5, 10 0 2, 10 2.5 1.5, 10 5 1, 10 7.5 0.5, 10 10 0)"
4041+
result = o.exportToWkt()
4042+
self.assertTrue(compareWkt(result, exp, 0.00001),
4043+
"densify by count: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
4044+
input = QgsGeometry.fromWkt("LineStringZM( 0 0 1 10, 10 0 2 8, 10 10 0 4)")
4045+
o = input.densifyByDistance(6)
4046+
exp = "LineStringZM (0 0 1 10, 5 0 1.5 9, 10 0 2 8, 10 5 1 6, 10 10 0 4)"
4047+
result = o.exportToWkt()
4048+
self.assertTrue(compareWkt(result, exp, 0.00001),
4049+
"densify by count: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
4050+
4051+
# polygon
4052+
input = QgsGeometry.fromWkt("Polygon(( 0 0, 20 0, 20 20, 0 0 ))")
4053+
o = input.densifyByDistance(110)
4054+
exp = "Polygon(( 0 0, 20 0, 20 20, 0 0 ))"
4055+
result = o.exportToWkt()
4056+
self.assertTrue(compareWkt(result, exp, 0.00001),
4057+
"densify by count: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
4058+
4059+
input = QgsGeometry.fromWkt("PolygonZ(( 0 0 1, 20 0 2, 20 20 0, 0 0 1 ))")
4060+
o = input.densifyByDistance(6)
4061+
exp = "PolygonZ ((0 0 1, 5 0 1.25, 10 0 1.5, 15 0 1.75, 20 0 2, 20 5 1.5, 20 10 1, 20 15 0.5, 20 20 0, 16 16 0.2, 12 12 0.4, 8 8 0.6, 4 4 0.8, 0 0 1))"
4062+
result = o.exportToWkt()
4063+
self.assertTrue(compareWkt(result, exp, 0.00001),
4064+
"densify by count: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
4065+
4066+
39994067
if __name__ == '__main__':
40004068
unittest.main()

0 commit comments

Comments
 (0)