QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
Loading...
Searching...
No Matches
qgsmaptopixelgeometrysimplifier.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmaptopixelgeometrysimplifier.cpp
3 ---------------------
4 begin : December 2013
5 copyright : (C) 2013 by Alvaro Huarte
6 email : http://wiki.osgeo.org/wiki/Alvaro_Huarte
7
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
18
19#include <limits>
20#include <memory>
21
22#include "qgsgeometry.h"
24#include "qgslinestring.h"
25#include "qgspolygon.h"
26#include "qgsrectangle.h"
27#include "qgsvertexid.h"
28
34
36// Helper simplification methods
37
38float QgsMapToPixelSimplifier::calculateLengthSquared2D( double x1, double y1, double x2, double y2 )
39{
40 const float vx = static_cast< float >( x2 - x1 );
41 const float vy = static_cast< float >( y2 - y1 );
42
43 return ( vx * vx ) + ( vy * vy );
44}
45
46bool QgsMapToPixelSimplifier::equalSnapToGrid( double x1, double y1, double x2, double y2, double gridOriginX, double gridOriginY, float gridInverseSizeXY )
47{
48 const int grid_x1 = std::round( ( x1 - gridOriginX ) * gridInverseSizeXY );
49 const int grid_x2 = std::round( ( x2 - gridOriginX ) * gridInverseSizeXY );
50 if ( grid_x1 != grid_x2 )
51 return false;
52
53 const int grid_y1 = std::round( ( y1 - gridOriginY ) * gridInverseSizeXY );
54 const int grid_y2 = std::round( ( y2 - gridOriginY ) * gridInverseSizeXY );
55 return grid_y1 == grid_y2;
56}
57
59// Helper simplification methods for Visvalingam method
60
61// It uses a refactored code of the liblwgeom implementation:
62// https://github.com/postgis/postgis/blob/svn-trunk/liblwgeom/effectivearea.h
63// https://github.com/postgis/postgis/blob/svn-trunk/liblwgeom/effectivearea.c
64
65#include "simplify/effectivearea.h"
66
68
70static std::unique_ptr< QgsAbstractGeometry > generalizeWkbGeometryByBoundingBox( Qgis::WkbType wkbType, const QgsAbstractGeometry &geometry, const QgsRectangle &envelope, bool isRing )
71{
72 const Qgis::WkbType geometryType = QgsWkbTypes::singleType( QgsWkbTypes::flatType( wkbType ) );
73
74 // If the geometry is already minimal skip the generalization
75 const int minimumSize = geometryType == Qgis::WkbType::LineString ? 2 : 5;
76
77 if ( geometry.nCoordinates() <= minimumSize )
78 {
79 return std::unique_ptr< QgsAbstractGeometry >( geometry.clone() );
80 }
81
82 const double x1 = envelope.xMinimum();
83 const double y1 = envelope.yMinimum();
84 const double x2 = envelope.xMaximum();
85 const double y2 = envelope.yMaximum();
86
87 // Write the generalized geometry
88 if ( geometryType == Qgis::WkbType::LineString && !isRing )
89 {
90 return std::make_unique< QgsLineString >( QVector<double>() << x1 << x2, QVector<double>() << y1 << y2 );
91 }
92 else
93 {
94 auto ext = std::make_unique< QgsLineString >( QVector< double >() << x1 << x2 << x2 << x1 << x1, QVector< double >() << y1 << y1 << y2 << y2 << y1 );
95 if ( geometryType == Qgis::WkbType::LineString )
96 return std::move( ext );
97 else
98 {
99 auto polygon = std::make_unique< QgsPolygon >();
100 polygon->setExteriorRing( ext.release() );
101 return std::move( polygon );
102 }
103 }
104}
105
106std::unique_ptr< QgsAbstractGeometry > QgsMapToPixelSimplifier::simplifyGeometry(
107 int simplifyFlags, Qgis::VectorSimplificationAlgorithm simplifyAlgorithm, const QgsAbstractGeometry &geometry, double map2pixelTol, bool isaLinearRing
108)
109{
110 bool isGeneralizable = true;
111 const Qgis::WkbType wkbType = geometry.wkbType();
112
113 // Can replace the geometry by its BBOX ?
114 const QgsRectangle envelope = geometry.boundingBox();
116 {
117 return generalizeWkbGeometryByBoundingBox( wkbType, geometry, envelope, isaLinearRing );
118 }
119
121 isGeneralizable = false;
122
123 const Qgis::WkbType flatType = QgsWkbTypes::flatType( wkbType );
124
125 // Write the geometry
126 if ( flatType == Qgis::WkbType::LineString || flatType == Qgis::WkbType::CircularString )
127 {
128 const QgsCurve &srcCurve = dynamic_cast<const QgsCurve &>( geometry );
129 const int numPoints = srcCurve.numPoints();
130
131 std::unique_ptr<QgsCurve> output;
132
133 QVector< double > lineStringX;
134 QVector< double > lineStringY;
135 QVector< double > lineStringZ;
136 QVector< double > lineStringM;
137 if ( flatType == Qgis::WkbType::LineString )
138 {
139 // if we are making a linestring, we do it in an optimised way by directly constructing
140 // the final x/y vectors, which avoids calling the slower insertVertex method
141 lineStringX.reserve( numPoints );
142 lineStringY.reserve( numPoints );
143
144 if ( geometry.is3D() )
145 lineStringZ.reserve( numPoints );
146
147 if ( geometry.isMeasure() )
148 lineStringM.reserve( numPoints );
149 }
150 else
151 {
152 output.reset( qgsgeometry_cast< QgsCurve * >( srcCurve.createEmptyWithSameType() ) );
153 }
154
155 double x = 0.0, y = 0.0, z = 0.0, m = 0.0, lastX = 0.0, lastY = 0.0;
156
157 if ( numPoints <= ( isaLinearRing ? 4 : 2 ) )
158 isGeneralizable = false;
159
160 bool isLongSegment;
161 bool hasLongSegments = false; //-> To avoid replace the simplified geometry by its BBOX when there are 'long' segments.
162 const bool is3D = geometry.is3D();
163 const bool isMeasure = geometry.isMeasure();
164
165 // Check whether the LinearRing is really closed.
166 if ( isaLinearRing )
167 {
168 isaLinearRing = qgsDoubleNear( srcCurve.xAt( 0 ), srcCurve.xAt( numPoints - 1 ) ) && qgsDoubleNear( srcCurve.yAt( 0 ), srcCurve.yAt( numPoints - 1 ) );
169 }
170
171 // Process each vertex...
172 switch ( simplifyAlgorithm )
173 {
175 {
176 const double gridOriginX = envelope.xMinimum();
177 const double gridOriginY = envelope.yMinimum();
178
179 // Use a factor for the maximum displacement distance for simplification, similar as GeoServer does
180 const float gridInverseSizeXY = map2pixelTol != 0 ? ( float ) ( 1.0f / ( 0.8 * map2pixelTol ) ) : 0.0f;
181
182 const double *xData = nullptr;
183 const double *yData = nullptr;
184 const double *zData = nullptr;
185 const double *mData = nullptr;
186 if ( flatType == Qgis::WkbType::LineString )
187 {
188 xData = qgsgeometry_cast< const QgsLineString * >( &srcCurve )->xData();
189 yData = qgsgeometry_cast< const QgsLineString * >( &srcCurve )->yData();
190
191 if ( is3D )
192 zData = qgsgeometry_cast< const QgsLineString * >( &srcCurve )->zData();
193
194 if ( isMeasure )
195 mData = qgsgeometry_cast< const QgsLineString * >( &srcCurve )->mData();
196 }
197
198 for ( int i = 0; i < numPoints; ++i )
199 {
200 if ( xData && yData )
201 {
202 x = *xData++;
203 y = *yData++;
204 }
205 else
206 {
207 x = srcCurve.xAt( i );
208 y = srcCurve.yAt( i );
209 }
210
211 if ( is3D )
212 z = zData ? *zData++ : srcCurve.zAt( i );
213
214 if ( isMeasure )
215 m = mData ? *mData++ : srcCurve.mAt( i );
216
217 if ( i == 0 || !isGeneralizable || !equalSnapToGrid( x, y, lastX, lastY, gridOriginX, gridOriginY, gridInverseSizeXY ) || ( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) )
218 {
219 if ( output )
220 output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPoint( x, y, z, m ) );
221 else
222 {
223 lineStringX.append( x );
224 lineStringY.append( y );
225
226 if ( is3D )
227 lineStringZ.append( z );
228
229 if ( isMeasure )
230 lineStringM.append( m );
231 }
232 lastX = x;
233 lastY = y;
234 }
235 }
236 break;
237 }
238
240 {
241 output.reset( qgsgeometry_cast< QgsCurve * >( srcCurve.snappedToGrid( map2pixelTol, map2pixelTol ) ) );
242 break;
243 }
244
246 {
247 map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'Area' calculations.
248
249 EFFECTIVE_AREAS ea( srcCurve );
250
251 const int set_area = 0;
252 ptarray_calc_areas( &ea, isaLinearRing ? 4 : 2, set_area, map2pixelTol );
253
254 for ( int i = 0; i < numPoints; ++i )
255 {
256 if ( ea.res_arealist[i] > map2pixelTol )
257 {
258 if ( output )
259 output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), ea.inpts.at( i ) );
260 else
261 {
262 lineStringX.append( ea.inpts.at( i ).x() );
263 lineStringY.append( ea.inpts.at( i ).y() );
264
265 if ( is3D )
266 lineStringZ.append( ea.inpts.at( i ).z() );
267
268 if ( isMeasure )
269 lineStringM.append( ea.inpts.at( i ).m() );
270 }
271 }
272 }
273 break;
274 }
275
277 {
278 map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'LengthSquare' calculations.
279
280 const double *xData = nullptr;
281 const double *yData = nullptr;
282 if ( flatType == Qgis::WkbType::LineString )
283 {
284 xData = qgsgeometry_cast< const QgsLineString * >( &srcCurve )->xData();
285 yData = qgsgeometry_cast< const QgsLineString * >( &srcCurve )->yData();
286 }
287
288 for ( int i = 0; i < numPoints; ++i )
289 {
290 if ( xData && yData )
291 {
292 x = *xData++;
293 y = *yData++;
294 }
295 else
296 {
297 x = srcCurve.xAt( i );
298 y = srcCurve.yAt( i );
299 }
300
301 isLongSegment = false;
302
303 if ( i == 0 || !isGeneralizable || ( isLongSegment = ( calculateLengthSquared2D( x, y, lastX, lastY ) > map2pixelTol ) ) || ( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) )
304 {
305 if ( output )
306 output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPoint( x, y ) );
307 else
308 {
309 lineStringX.append( x );
310 lineStringY.append( y );
311 }
312 lastX = x;
313 lastY = y;
314
315 hasLongSegments |= isLongSegment;
316 }
317 }
318 }
319 }
320
321 if ( !output )
322 {
323 output = std::make_unique< QgsLineString >( lineStringX, lineStringY, lineStringZ, lineStringM );
324 }
325 if ( output->numPoints() < ( isaLinearRing ? 4 : 2 ) )
326 {
327 // we simplified the geometry too much!
328 if ( !hasLongSegments )
329 {
330 // approximate the geometry's shape by its bounding box
331 // (rect for linear ring / one segment for line string)
332 return generalizeWkbGeometryByBoundingBox( wkbType, geometry, envelope, isaLinearRing );
333 }
334 else
335 {
336 // Bad luck! The simplified geometry is invalid and approximation by bounding box
337 // would create artifacts due to long segments.
338 // We will return the original geometry
339 return std::unique_ptr< QgsAbstractGeometry >( geometry.clone() );
340 }
341 }
342
343 if ( isaLinearRing )
344 {
345 // make sure we keep the linear ring closed
346 if ( !qgsDoubleNear( lastX, output->xAt( 0 ) ) || !qgsDoubleNear( lastY, output->yAt( 0 ) ) )
347 {
348 output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPoint( output->xAt( 0 ), output->yAt( 0 ) ) );
349 }
350 }
351
352 return std::move( output );
353 }
354 else if ( flatType == Qgis::WkbType::Polygon )
355 {
356 const QgsPolygon &srcPolygon = dynamic_cast<const QgsPolygon &>( geometry );
357 auto polygon = std::make_unique<QgsPolygon>();
358 std::unique_ptr<QgsAbstractGeometry> extRing = simplifyGeometry( simplifyFlags, simplifyAlgorithm, *srcPolygon.exteriorRing(), map2pixelTol, true );
359 polygon->setExteriorRing( qgis::down_cast<QgsCurve *>( extRing.release() ) );
360 for ( int i = 0; i < srcPolygon.numInteriorRings(); ++i )
361 {
362 const QgsCurve *sub = srcPolygon.interiorRing( i );
363 std::unique_ptr< QgsAbstractGeometry > ring = simplifyGeometry( simplifyFlags, simplifyAlgorithm, *sub, map2pixelTol, true );
364 polygon->addInteriorRing( qgis::down_cast<QgsCurve *>( ring.release() ) );
365 }
366 return std::move( polygon );
367 }
368 else if ( QgsWkbTypes::isMultiType( flatType ) )
369 {
370 const QgsGeometryCollection &srcCollection = dynamic_cast<const QgsGeometryCollection &>( geometry );
371 std::unique_ptr<QgsGeometryCollection> collection( srcCollection.createEmptyWithSameType() );
372 const int numGeoms = srcCollection.numGeometries();
373 collection->reserve( numGeoms );
374 for ( int i = 0; i < numGeoms; ++i )
375 {
376 const QgsAbstractGeometry *sub = srcCollection.geometryN( i );
377 std::unique_ptr< QgsAbstractGeometry > part = simplifyGeometry( simplifyFlags, simplifyAlgorithm, *sub, map2pixelTol, false );
378 collection->addGeometry( part.release() );
379 }
380 return std::move( collection );
381 }
382 return std::unique_ptr< QgsAbstractGeometry >( geometry.clone() );
383}
384
386
388{
389 // Can replace the geometry by its BBOX ?
390 return envelope.width() < map2pixelTol && envelope.height() < map2pixelTol;
391}
392
394{
395 if ( geometry.isNull() )
396 {
397 return QgsGeometry();
398 }
400 {
401 return geometry;
402 }
403
404 // Check whether the geometry can be simplified using the map2pixel context
405 const Qgis::WkbType singleType = QgsWkbTypes::singleType( geometry.wkbType() );
406 const Qgis::WkbType flatType = QgsWkbTypes::flatType( singleType );
407 if ( flatType == Qgis::WkbType::Point )
408 {
409 return geometry;
410 }
411
412 const bool isaLinearRing = flatType == Qgis::WkbType::Polygon;
413 const int numPoints = geometry.constGet()->nCoordinates();
414
415 if ( numPoints <= ( isaLinearRing ? 6 : 3 ) )
416 {
417 // No simplify simple geometries
418 return geometry;
419 }
420
421 const QgsRectangle envelope = geometry.boundingBox();
422 if ( std::max( envelope.width(), envelope.height() ) / numPoints > mTolerance * 2.0 )
423 {
424 //points are in average too far apart to lead to any significant simplification
425 return geometry;
426 }
427
428 return QgsGeometry( simplifyGeometry( mSimplifyFlags, mSimplifyAlgorithm, *geometry.constGet(), mTolerance, false ) );
429}
430
432{
433 //
434 // IMPORTANT!!!!!!!
435 // We want to avoid any geometry cloning we possibly can here, which is why the
436 // "fail" paths always return nullptr
437 //
438
439 if ( !geometry )
440 {
441 return nullptr;
442 }
444 {
445 return nullptr;
446 }
447
448 // Check whether the geometry can be simplified using the map2pixel context
449 const Qgis::WkbType singleType = QgsWkbTypes::singleType( geometry->wkbType() );
450 const Qgis::WkbType flatType = QgsWkbTypes::flatType( singleType );
451 if ( flatType == Qgis::WkbType::Point )
452 {
453 return nullptr;
454 }
455
456 const bool isaLinearRing = flatType == Qgis::WkbType::Polygon;
457 const int numPoints = geometry->nCoordinates();
458
459 if ( numPoints <= ( isaLinearRing ? 6 : 3 ) )
460 {
461 // No simplify simple geometries
462 return nullptr;
463 }
464
465 const QgsRectangle envelope = geometry->boundingBox();
466 if ( std::max( envelope.width(), envelope.height() ) / numPoints > mTolerance * 2.0 )
467 {
468 //points are in average too far apart to lead to any significant simplification
469 return nullptr;
470 }
471
472 return simplifyGeometry( mSimplifyFlags, mSimplifyAlgorithm, *geometry, mTolerance, false ).release();
473}
VectorSimplificationAlgorithm
Simplification algorithms for vector features.
Definition qgis.h:3114
@ Distance
The simplification uses the distance between points to remove duplicate points.
Definition qgis.h:3115
@ SnappedToGridGlobal
Snap to a global grid based on the tolerance. Good for consistent results for incoming vertices,...
Definition qgis.h:3118
@ SnapToGrid
The simplification uses a grid (similar to ST_SnapToGrid) to remove duplicate points.
Definition qgis.h:3116
@ Visvalingam
The simplification gives each point in a line an importance weighting, so that least important points...
Definition qgis.h:3117
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
@ Point
Point.
Definition qgis.h:296
@ LineString
LineString.
Definition qgis.h:297
@ Polygon
Polygon.
Definition qgis.h:298
@ CircularString
CircularString.
Definition qgis.h:304
Abstract base class for all geometries.
virtual QgsAbstractGeometry * snappedToGrid(double hSpacing, double vSpacing, double dSpacing=0, double mSpacing=0, bool removeRedundantPoints=false) const =0
Makes a new geometry with all the points or vertices snapped to the closest point of the grid.
bool isMeasure() const
Returns true if the geometry contains m values.
virtual QgsRectangle boundingBox() const
Returns the minimal bounding box for the geometry.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
virtual QgsAbstractGeometry * createEmptyWithSameType() const =0
Creates a new geometry with the same class and same WKB type as the original and transfers ownership.
virtual int nCoordinates() const
Returns the number of nodes contained in the geometry.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
virtual QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
virtual int numPoints() const =0
Returns the number of points in the curve.
virtual double xAt(int index) const =0
Returns the x-coordinate of the specified node in the line string.
virtual double zAt(int index) const =0
Returns the z-coordinate of the specified node in the line string.
virtual double mAt(int index) const =0
Returns the m-coordinate of the specified node in the line string.
virtual double yAt(int index) const =0
Returns the y-coordinate of the specified node in the line string.
QgsGeometryCollection * createEmptyWithSameType() const override
Creates a new geometry with the same class and same WKB type as the original and transfers ownership.
int numGeometries() const
Returns the number of geometries within the collection.
const QgsAbstractGeometry * geometryN(int n) const
Returns a const reference to a geometry from within the collection.
A geometry is the spatial representation of a feature.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Qgis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.).
double mTolerance
Distance tolerance for the simplification.
static float calculateLengthSquared2D(double x1, double y1, double x2, double y2)
Returns the squared 2D-distance of the vector defined by the two points specified.
static bool isGeneralizableByMapBoundingBox(const QgsRectangle &envelope, double map2pixelTol)
Returns whether the envelope can be replaced by its BBOX when is applied the specified map2pixel cont...
int simplifyFlags() const
Gets the simplification hints of the vector layer managed.
Qgis::VectorSimplificationAlgorithm mSimplifyAlgorithm
Current algorithm.
@ NoFlags
No simplification can be applied.
@ SimplifyEnvelope
The geometries can be fully simplified by its BoundingBox.
@ SimplifyGeometry
The geometries can be simplified using the current map2pixel context state.
int mSimplifyFlags
Current simplification flags.
static bool equalSnapToGrid(double x1, double y1, double x2, double y2, double gridOriginX, double gridOriginY, float gridInverseSizeXY)
Returns whether the points belong to the same grid.
QgsGeometry simplify(const QgsGeometry &geometry) const override
Returns a simplified version the specified geometry.
Qgis::VectorSimplificationAlgorithm simplifyAlgorithm() const
Gets the local simplification algorithm of the vector layer managed.
QgsMapToPixelSimplifier(int simplifyFlags, double tolerance, Qgis::VectorSimplificationAlgorithm simplifyAlgorithm=Qgis::VectorSimplificationAlgorithm::Distance)
Constructor.
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
static Qgis::WkbType singleType(Qgis::WkbType type)
Returns the single type for a WKB type.
Definition qgswkbtypes.h:53
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
static Q_INVOKABLE bool isMultiType(Qgis::WkbType type)
Returns true if the WKB type is a multi type.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6975
T qgsgeometry_cast(QgsAbstractGeometry *geom)