Skip to content

Commit 72d9e9a

Browse files
committed
Fix crash using Visvalingam simplification, add test
1 parent 15dd295 commit 72d9e9a

5 files changed

+107
-90
lines changed

src/core/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ SET(QGIS_CORE_SRCS
4444
symbology-ng/qgssymbol.cpp
4545
symbology-ng/qgsvectorfieldsymbollayer.cpp
4646

47+
simplify/effectivearea.cpp
48+
4749
diagram/qgsdiagram.cpp
4850
diagram/qgshistogramdiagram.cpp
4951
diagram/qgspiediagram.cpp

src/core/qgsmaptopixelgeometrysimplifier.cpp

+56-61
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,7 @@ bool QgsMapToPixelSimplifier::equalSnapToGrid( double x1, double y1, double x2,
7070
// https://github.com/postgis/postgis/blob/svn-trunk/liblwgeom/effectivearea.h
7171
// https://github.com/postgis/postgis/blob/svn-trunk/liblwgeom/effectivearea.c
7272

73-
#define LWDEBUG //
74-
#define LWDEBUGF //
75-
#define FP_MAX qMax
76-
#define FLAGS_GET_Z( flags ) ( ( flags ) & 0x01 )
77-
#define LW_MSG_MAXLEN 256
78-
#define lwalloc qgsMalloc
79-
#define lwfree qgsFree
80-
#define lwerror qWarning
81-
8273
#include "simplify/effectivearea.h"
83-
#include "simplify/effectivearea.c"
8474

8575
//////////////////////////////////////////////////////////////////////////////////////////////
8676

@@ -175,75 +165,80 @@ QgsGeometry QgsMapToPixelSimplifier::simplifyGeometry(
175165
}
176166

177167
// Process each vertex...
178-
if ( simplifyAlgorithm == SnapToGrid )
168+
switch ( simplifyAlgorithm )
179169
{
180-
double gridOriginX = envelope.xMinimum();
181-
double gridOriginY = envelope.yMinimum();
182-
183-
// Use a factor for the maximum displacement distance for simplification, similar as GeoServer does
184-
float gridInverseSizeXY = map2pixelTol != 0 ? ( float )( 1.0f / ( 0.8 * map2pixelTol ) ) : 0.0f;
185-
186-
for ( int i = 0; i < numPoints; ++i )
170+
case SnapToGrid:
187171
{
188-
x = srcCurve.xAt( i );
189-
y = srcCurve.yAt( i );
172+
double gridOriginX = envelope.xMinimum();
173+
double gridOriginY = envelope.yMinimum();
190174

191-
if ( i == 0 ||
192-
!isGeneralizable ||
193-
!equalSnapToGrid( x, y, lastX, lastY, gridOriginX, gridOriginY, gridInverseSizeXY ) ||
194-
( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) )
175+
// Use a factor for the maximum displacement distance for simplification, similar as GeoServer does
176+
float gridInverseSizeXY = map2pixelTol != 0 ? ( float )( 1.0f / ( 0.8 * map2pixelTol ) ) : 0.0f;
177+
178+
for ( int i = 0; i < numPoints; ++i )
195179
{
196-
output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPointV2( x, y ) );
197-
lastX = x;
198-
lastY = y;
180+
x = srcCurve.xAt( i );
181+
y = srcCurve.yAt( i );
182+
183+
if ( i == 0 ||
184+
!isGeneralizable ||
185+
!equalSnapToGrid( x, y, lastX, lastY, gridOriginX, gridOriginY, gridInverseSizeXY ) ||
186+
( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) )
187+
{
188+
output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPointV2( x, y ) );
189+
lastX = x;
190+
lastY = y;
191+
}
192+
193+
r.combineExtentWith( x, y );
199194
}
200-
201-
r.combineExtentWith( x, y );
195+
break;
202196
}
203-
}
204-
else if ( simplifyAlgorithm == Visvalingam )
205-
{
206-
map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'Area' calculations.
207197

208-
EFFECTIVE_AREAS* ea;
209-
ea = initiate_effectivearea( srcCurve );
198+
case Visvalingam:
199+
{
200+
map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'Area' calculations.
210201

211-
int set_area = 0;
212-
ptarray_calc_areas( ea, isaLinearRing ? 4 : 2, set_area, map2pixelTol );
202+
EFFECTIVE_AREAS ea( srcCurve );
213203

214-
for ( int i = 0; i < numPoints; ++i )
215-
{
216-
if ( ea->res_arealist[ i ] > map2pixelTol )
204+
int set_area = 0;
205+
ptarray_calc_areas( &ea, isaLinearRing ? 4 : 2, set_area, map2pixelTol );
206+
207+
for ( int i = 0; i < numPoints; ++i )
217208
{
218-
output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), ea->inpts.at( i ) );
209+
if ( ea.res_arealist[ i ] > map2pixelTol )
210+
{
211+
output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), ea.inpts.at( i ) );
212+
}
219213
}
214+
break;
220215
}
221-
destroy_effectivearea( ea );
222-
}
223-
else
224-
{
225-
map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'LengthSquare' calculations.
226216

227-
for ( int i = 0; i < numPoints; ++i )
217+
case Distance:
228218
{
229-
x = srcCurve.xAt( i );
230-
y = srcCurve.yAt( i );
231-
232-
isLongSegment = false;
219+
map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'LengthSquare' calculations.
233220

234-
if ( i == 0 ||
235-
!isGeneralizable ||
236-
( isLongSegment = ( calculateLengthSquared2D( x, y, lastX, lastY ) > map2pixelTol ) ) ||
237-
( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) )
221+
for ( int i = 0; i < numPoints; ++i )
238222
{
239-
output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPointV2( x, y ) );
240-
lastX = x;
241-
lastY = y;
223+
x = srcCurve.xAt( i );
224+
y = srcCurve.yAt( i );
242225

243-
hasLongSegments |= isLongSegment;
244-
}
226+
isLongSegment = false;
227+
228+
if ( i == 0 ||
229+
!isGeneralizable ||
230+
( isLongSegment = ( calculateLengthSquared2D( x, y, lastX, lastY ) > map2pixelTol ) ) ||
231+
( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) )
232+
{
233+
output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPointV2( x, y ) );
234+
lastX = x;
235+
lastY = y;
245236

246-
r.combineExtentWith( x, y );
237+
hasLongSegments |= isLongSegment;
238+
}
239+
240+
r.combineExtentWith( x, y );
241+
}
247242
}
248243
}
249244

src/core/simplify/effectivearea.c src/core/simplify/effectivearea.cpp

-19
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,6 @@
2424

2525
#include "effectivearea.h"
2626

27-
EFFECTIVE_AREAS* initiate_effectivearea( const QgsCurve& inpts )
28-
{
29-
//LWDEBUG( 2, "Entered initiate_effectivearea" );
30-
EFFECTIVE_AREAS *ea;
31-
ea = ( EFFECTIVE_AREAS* )lwalloc( sizeof( EFFECTIVE_AREAS ) );
32-
inpts.points( ea->inpts );
33-
ea->is3d = inpts.is3D();
34-
ea->initial_arealist = ( areanode* )lwalloc( ea->inpts.size() * sizeof( areanode ) );
35-
ea->res_arealist = ( double* )lwalloc( ea->inpts.size() * sizeof( double ) );
36-
return ea;
37-
}
38-
39-
void destroy_effectivearea( EFFECTIVE_AREAS *ea )
40-
{
41-
lwfree( ea->initial_arealist );
42-
lwfree( ea->res_arealist );
43-
lwfree( ea );
44-
}
45-
4627
static MINHEAP initiate_minheap( int npoints )
4728
{
4829
MINHEAP tree;

src/core/simplify/effectivearea.h

+37-10
Original file line numberDiff line numberDiff line change
@@ -22,48 +22,75 @@
2222
*
2323
**********************************************************************/
2424

25+
#include "qgsabstractgeometry.h"
26+
#include "qgscurve.h"
27+
#include "qgspointv2.h"
28+
2529
#ifndef _EFFECTIVEAREA_H
2630
#define _EFFECTIVEAREA_H 1
2731

32+
33+
#define LWDEBUG //
34+
#define LWDEBUGF //
35+
#define FP_MAX qMax
36+
#define FLAGS_GET_Z( flags ) ( ( flags ) & 0x01 )
37+
#define LW_MSG_MAXLEN 256
38+
#define lwalloc qgsMalloc
39+
#define lwfree qgsFree
40+
#define lwerror qWarning
41+
42+
2843
/**
2944
* This structure is placed in an array with one member per point.
3045
* It has links into the minheap rtee and kepps track of eliminated points.
3146
*/
32-
typedef struct
47+
struct areanode
3348
{
3449
double area;
3550
int treeindex;
3651
int prev;
3752
int next;
38-
} areanode;
53+
};
3954

4055
/**
4156
* This structure holds a minheap tree that is used to keep track of what points
4257
* that has the smallest effective area.
4358
* When elliminating points the neighbor points has its effective area affected
4459
* and the minheap helps to resort efficient.
4560
*/
46-
typedef struct
61+
struct MINHEAP
4762
{
4863
int maxSize;
4964
int usedSize;
5065
areanode **key_array;
51-
} MINHEAP;
66+
};
5267

5368
/**
5469
* Structure to hold pointarray and it's arealist.
5570
*/
56-
typedef struct
71+
struct EFFECTIVE_AREAS
5772
{
73+
EFFECTIVE_AREAS( const QgsCurve& curve )
74+
: is3d( curve.is3D() )
75+
, initial_arealist( nullptr )
76+
, res_arealist( nullptr )
77+
{
78+
curve.points( inpts );
79+
initial_arealist = new areanode[ inpts.size()];
80+
res_arealist = new double[ inpts.size()];
81+
}
82+
83+
~EFFECTIVE_AREAS()
84+
{
85+
delete [] initial_arealist;
86+
delete [] res_arealist;
87+
}
88+
5889
bool is3d;
5990
QgsPointSequence inpts;
6091
areanode *initial_arealist;
6192
double *res_arealist;
62-
} EFFECTIVE_AREAS;
63-
64-
EFFECTIVE_AREAS* initiate_effectivearea( const QgsCurve &inpts );
65-
66-
void destroy_effectivearea( EFFECTIVE_AREAS *ea );
93+
};
6794

6895
void ptarray_calc_areas( EFFECTIVE_AREAS *ea, int avoid_collaps, int set_area, double trshld );
6996

tests/src/core/testqgsmaptopixelgeometrysimplifier.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class TestQgsMapToPixelGeometrySimplifier : public QObject
7474
void testIsGeneralizableByMapBoundingBox();
7575
void testWkbDimensionMismatch();
7676
void testCircularString();
77+
void testVisvalingam();
7778

7879
};
7980

@@ -194,5 +195,16 @@ void TestQgsMapToPixelGeometrySimplifier::testCircularString()
194195
QCOMPARE( simplifier.simplify( g ).exportToWkt(), WKT );
195196
}
196197

198+
void TestQgsMapToPixelGeometrySimplifier::testVisvalingam()
199+
{
200+
QString wkt( "LineString (0 0, 30 0, 31 30, 32 0, 40 0, 41 100, 42 0, 50 0)" );
201+
QgsGeometry g = QgsGeometry::fromWkt( wkt );
202+
203+
const QgsMapToPixelSimplifier simplifier( QgsMapToPixelSimplifier::SimplifyGeometry, 7, QgsMapToPixelSimplifier::Visvalingam );
204+
QString expectedWkt( "LineString (0 0, 40 0, 41 100, 42 0, 50 0)" );
205+
206+
QCOMPARE( simplifier.simplify( g ).exportToWkt(), expectedWkt );
207+
}
208+
197209
QTEST_MAIN( TestQgsMapToPixelGeometrySimplifier )
198210
#include "testqgsmaptopixelgeometrysimplifier.moc"

0 commit comments

Comments
 (0)