QGIS API Documentation 3.99.0-Master (21b3aa880ba)
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
35
37// Helper simplification methods
38
39float QgsMapToPixelSimplifier::calculateLengthSquared2D( double x1, double y1, double x2, double y2 )
40{
41 const float vx = static_cast< float >( x2 - x1 );
42 const float vy = static_cast< float >( y2 - y1 );
43
44 return ( vx * vx ) + ( vy * vy );
45}
46
47bool QgsMapToPixelSimplifier::equalSnapToGrid( double x1, double y1, double x2, double y2, double gridOriginX, double gridOriginY, float gridInverseSizeXY )
48{
49 const int grid_x1 = std::round( ( x1 - gridOriginX ) * gridInverseSizeXY );
50 const int grid_x2 = std::round( ( x2 - gridOriginX ) * gridInverseSizeXY );
51 if ( grid_x1 != grid_x2 ) 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(
71 Qgis::WkbType wkbType,
72 const QgsAbstractGeometry &geometry,
73 const QgsRectangle &envelope,
74 bool isRing )
75{
76 const Qgis::WkbType geometryType = QgsWkbTypes::singleType( QgsWkbTypes::flatType( wkbType ) );
77
78 // If the geometry is already minimal skip the generalization
79 const int minimumSize = geometryType == Qgis::WkbType::LineString ? 2 : 5;
80
81 if ( geometry.nCoordinates() <= minimumSize )
82 {
83 return std::unique_ptr< QgsAbstractGeometry >( geometry.clone() );
84 }
85
86 const double x1 = envelope.xMinimum();
87 const double y1 = envelope.yMinimum();
88 const double x2 = envelope.xMaximum();
89 const double y2 = envelope.yMaximum();
90
91 // Write the generalized geometry
92 if ( geometryType == Qgis::WkbType::LineString && !isRing )
93 {
94 return std::make_unique< QgsLineString >( QVector<double>() << x1 << x2, QVector<double>() << y1 << y2 );
95 }
96 else
97 {
98 auto ext = std::make_unique< QgsLineString >(
99 QVector< double >() << x1
100 << x2
101 << x2
102 << x1
103 << x1,
104 QVector< double >() << y1
105 << y1
106 << y2
107 << y2
108 << y1 );
109 if ( geometryType == Qgis::WkbType::LineString )
110 return std::move( ext );
111 else
112 {
113 auto polygon = std::make_unique< QgsPolygon >();
114 polygon->setExteriorRing( ext.release() );
115 return std::move( polygon );
116 }
117 }
118}
119
120std::unique_ptr< QgsAbstractGeometry > QgsMapToPixelSimplifier::simplifyGeometry( int simplifyFlags,
121 Qgis::VectorSimplificationAlgorithm simplifyAlgorithm,
122 const QgsAbstractGeometry &geometry, double map2pixelTol,
123 bool isaLinearRing )
124{
125 bool isGeneralizable = true;
126 const Qgis::WkbType wkbType = geometry.wkbType();
127
128 // Can replace the geometry by its BBOX ?
129 const QgsRectangle envelope = geometry.boundingBox();
131 isGeneralizableByMapBoundingBox( envelope, map2pixelTol ) )
132 {
133 return generalizeWkbGeometryByBoundingBox( wkbType, geometry, envelope, isaLinearRing );
134 }
135
137 isGeneralizable = false;
138
139 const Qgis::WkbType flatType = QgsWkbTypes::flatType( wkbType );
140
141 // Write the geometry
142 if ( flatType == Qgis::WkbType::LineString || flatType == Qgis::WkbType::CircularString )
143 {
144 const QgsCurve &srcCurve = dynamic_cast<const QgsCurve &>( geometry );
145 const int numPoints = srcCurve.numPoints();
146
147 std::unique_ptr<QgsCurve> output;
148
149 QVector< double > lineStringX;
150 QVector< double > lineStringY;
151 QVector< double > lineStringZ;
152 QVector< double > lineStringM;
153 if ( flatType == Qgis::WkbType::LineString )
154 {
155 // if we are making a linestring, we do it in an optimised way by directly constructing
156 // the final x/y vectors, which avoids calling the slower insertVertex method
157 lineStringX.reserve( numPoints );
158 lineStringY.reserve( numPoints );
159
160 if ( geometry.is3D() )
161 lineStringZ.reserve( numPoints );
162
163 if ( geometry.isMeasure() )
164 lineStringM.reserve( numPoints );
165 }
166 else
167 {
168 output.reset( qgsgeometry_cast< QgsCurve * >( srcCurve.createEmptyWithSameType() ) );
169 }
170
171 double x = 0.0, y = 0.0, z = 0.0, m = 0.0, lastX = 0.0, lastY = 0.0;
172
173 if ( numPoints <= ( isaLinearRing ? 4 : 2 ) )
174 isGeneralizable = false;
175
176 bool isLongSegment;
177 bool hasLongSegments = false; //-> To avoid replace the simplified geometry by its BBOX when there are 'long' segments.
178 const bool is3D = geometry.is3D();
179 const bool isMeasure = geometry.isMeasure();
180
181 // Check whether the LinearRing is really closed.
182 if ( isaLinearRing )
183 {
184 isaLinearRing = qgsDoubleNear( srcCurve.xAt( 0 ), srcCurve.xAt( numPoints - 1 ) ) &&
185 qgsDoubleNear( srcCurve.yAt( 0 ), srcCurve.yAt( numPoints - 1 ) );
186 }
187
188 // Process each vertex...
189 switch ( simplifyAlgorithm )
190 {
192 {
193 const double gridOriginX = envelope.xMinimum();
194 const double gridOriginY = envelope.yMinimum();
195
196 // Use a factor for the maximum displacement distance for simplification, similar as GeoServer does
197 const float gridInverseSizeXY = map2pixelTol != 0 ? ( float )( 1.0f / ( 0.8 * map2pixelTol ) ) : 0.0f;
198
199 const double *xData = nullptr;
200 const double *yData = nullptr;
201 const double *zData = nullptr;
202 const double *mData = nullptr;
203 if ( flatType == Qgis::WkbType::LineString )
204 {
205 xData = qgsgeometry_cast< const QgsLineString * >( &srcCurve )->xData();
206 yData = qgsgeometry_cast< const QgsLineString * >( &srcCurve )->yData();
207
208 if ( is3D )
209 zData = qgsgeometry_cast< const QgsLineString * >( &srcCurve )->zData();
210
211 if ( isMeasure )
212 mData = qgsgeometry_cast< const QgsLineString * >( &srcCurve )->mData();
213 }
214
215 for ( int i = 0; i < numPoints; ++i )
216 {
217 if ( xData && yData )
218 {
219 x = *xData++;
220 y = *yData++;
221 }
222 else
223 {
224 x = srcCurve.xAt( i );
225 y = srcCurve.yAt( i );
226 }
227
228 if ( is3D )
229 z = zData ? *zData++ : srcCurve.zAt( i );
230
231 if ( isMeasure )
232 m = mData ? *mData++ : srcCurve.mAt( i );
233
234 if ( i == 0 ||
235 !isGeneralizable ||
236 !equalSnapToGrid( x, y, lastX, lastY, gridOriginX, gridOriginY, gridInverseSizeXY ) ||
237 ( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) )
238 {
239 if ( output )
240 output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPoint( x, y, z, m ) );
241 else
242 {
243 lineStringX.append( x );
244 lineStringY.append( y );
245
246 if ( is3D )
247 lineStringZ.append( z );
248
249 if ( isMeasure )
250 lineStringM.append( m );
251 }
252 lastX = x;
253 lastY = y;
254 }
255 }
256 break;
257 }
258
260 {
261 output.reset( qgsgeometry_cast< QgsCurve * >( srcCurve.snappedToGrid( map2pixelTol, map2pixelTol ) ) );
262 break;
263 }
264
266 {
267 map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'Area' calculations.
268
269 EFFECTIVE_AREAS ea( srcCurve );
270
271 const int set_area = 0;
272 ptarray_calc_areas( &ea, isaLinearRing ? 4 : 2, set_area, map2pixelTol );
273
274 for ( int i = 0; i < numPoints; ++i )
275 {
276 if ( ea.res_arealist[ i ] > map2pixelTol )
277 {
278 if ( output )
279 output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), ea.inpts.at( i ) );
280 else
281 {
282 lineStringX.append( ea.inpts.at( i ).x() );
283 lineStringY.append( ea.inpts.at( i ).y() );
284
285 if ( is3D )
286 lineStringZ.append( ea.inpts.at( i ).z() );
287
288 if ( isMeasure )
289 lineStringM.append( ea.inpts.at( i ).m() );
290 }
291 }
292 }
293 break;
294 }
295
297 {
298 map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'LengthSquare' calculations.
299
300 const double *xData = nullptr;
301 const double *yData = nullptr;
302 if ( flatType == Qgis::WkbType::LineString )
303 {
304 xData = qgsgeometry_cast< const QgsLineString * >( &srcCurve )->xData();
305 yData = qgsgeometry_cast< const QgsLineString * >( &srcCurve )->yData();
306 }
307
308 for ( int i = 0; i < numPoints; ++i )
309 {
310 if ( xData && yData )
311 {
312 x = *xData++;
313 y = *yData++;
314 }
315 else
316 {
317 x = srcCurve.xAt( i );
318 y = srcCurve.yAt( i );
319 }
320
321 isLongSegment = false;
322
323 if ( i == 0 ||
324 !isGeneralizable ||
325 ( isLongSegment = ( calculateLengthSquared2D( x, y, lastX, lastY ) > map2pixelTol ) ) ||
326 ( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) )
327 {
328 if ( output )
329 output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPoint( x, y ) );
330 else
331 {
332 lineStringX.append( x );
333 lineStringY.append( y );
334 }
335 lastX = x;
336 lastY = y;
337
338 hasLongSegments |= isLongSegment;
339 }
340 }
341 }
342 }
343
344 if ( !output )
345 {
346 output = std::make_unique< QgsLineString >( lineStringX, lineStringY, lineStringZ, lineStringM );
347 }
348 if ( output->numPoints() < ( isaLinearRing ? 4 : 2 ) )
349 {
350 // we simplified the geometry too much!
351 if ( !hasLongSegments )
352 {
353 // approximate the geometry's shape by its bounding box
354 // (rect for linear ring / one segment for line string)
355 return generalizeWkbGeometryByBoundingBox( wkbType, geometry, envelope, isaLinearRing );
356 }
357 else
358 {
359 // Bad luck! The simplified geometry is invalid and approximation by bounding box
360 // would create artifacts due to long segments.
361 // We will return the original geometry
362 return std::unique_ptr< QgsAbstractGeometry >( geometry.clone() );
363 }
364 }
365
366 if ( isaLinearRing )
367 {
368 // make sure we keep the linear ring closed
369 if ( !qgsDoubleNear( lastX, output->xAt( 0 ) ) || !qgsDoubleNear( lastY, output->yAt( 0 ) ) )
370 {
371 output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPoint( output->xAt( 0 ), output->yAt( 0 ) ) );
372 }
373 }
374
375 return std::move( output );
376 }
377 else if ( flatType == Qgis::WkbType::Polygon )
378 {
379 const QgsPolygon &srcPolygon = dynamic_cast<const QgsPolygon &>( geometry );
380 auto polygon = std::make_unique<QgsPolygon>();
381 std::unique_ptr<QgsAbstractGeometry> extRing = simplifyGeometry( simplifyFlags, simplifyAlgorithm, *srcPolygon.exteriorRing(), map2pixelTol, true );
382 polygon->setExteriorRing( qgis::down_cast<QgsCurve *>( extRing.release() ) );
383 for ( int i = 0; i < srcPolygon.numInteriorRings(); ++i )
384 {
385 const QgsCurve *sub = srcPolygon.interiorRing( i );
386 std::unique_ptr< QgsAbstractGeometry > ring = simplifyGeometry( simplifyFlags, simplifyAlgorithm, *sub, map2pixelTol, true );
387 polygon->addInteriorRing( qgis::down_cast<QgsCurve *>( ring.release() ) );
388 }
389 return std::move( polygon );
390 }
391 else if ( QgsWkbTypes::isMultiType( flatType ) )
392 {
393 const QgsGeometryCollection &srcCollection = dynamic_cast<const QgsGeometryCollection &>( geometry );
394 std::unique_ptr<QgsGeometryCollection> collection( srcCollection.createEmptyWithSameType() );
395 const int numGeoms = srcCollection.numGeometries();
396 collection->reserve( numGeoms );
397 for ( int i = 0; i < numGeoms; ++i )
398 {
399 const QgsAbstractGeometry *sub = srcCollection.geometryN( i );
400 std::unique_ptr< QgsAbstractGeometry > part = simplifyGeometry( simplifyFlags, simplifyAlgorithm, *sub, map2pixelTol, false );
401 collection->addGeometry( part.release() );
402 }
403 return std::move( collection );
404 }
405 return std::unique_ptr< QgsAbstractGeometry >( geometry.clone() );
406}
407
409
411{
412 // Can replace the geometry by its BBOX ?
413 return envelope.width() < map2pixelTol && envelope.height() < map2pixelTol;
414}
415
417{
418 if ( geometry.isNull() )
419 {
420 return QgsGeometry();
421 }
423 {
424 return geometry;
425 }
426
427 // Check whether the geometry can be simplified using the map2pixel context
428 const Qgis::WkbType singleType = QgsWkbTypes::singleType( geometry.wkbType() );
429 const Qgis::WkbType flatType = QgsWkbTypes::flatType( singleType );
430 if ( flatType == Qgis::WkbType::Point )
431 {
432 return geometry;
433 }
434
435 const bool isaLinearRing = flatType == Qgis::WkbType::Polygon;
436 const int numPoints = geometry.constGet()->nCoordinates();
437
438 if ( numPoints <= ( isaLinearRing ? 6 : 3 ) )
439 {
440 // No simplify simple geometries
441 return geometry;
442 }
443
444 const QgsRectangle envelope = geometry.boundingBox();
445 if ( std::max( envelope.width(), envelope.height() ) / numPoints > mTolerance * 2.0 )
446 {
447 //points are in average too far apart to lead to any significant simplification
448 return geometry;
449 }
450
451 return QgsGeometry( simplifyGeometry( mSimplifyFlags, mSimplifyAlgorithm, *geometry.constGet(), mTolerance, false ) );
452}
453
455{
456 //
457 // IMPORTANT!!!!!!!
458 // We want to avoid any geometry cloning we possibly can here, which is why the
459 // "fail" paths always return nullptr
460 //
461
462 if ( !geometry )
463 {
464 return nullptr;
465 }
467 {
468 return nullptr;
469 }
470
471 // Check whether the geometry can be simplified using the map2pixel context
472 const Qgis::WkbType singleType = QgsWkbTypes::singleType( geometry->wkbType() );
473 const Qgis::WkbType flatType = QgsWkbTypes::flatType( singleType );
474 if ( flatType == Qgis::WkbType::Point )
475 {
476 return nullptr;
477 }
478
479 const bool isaLinearRing = flatType == Qgis::WkbType::Polygon;
480 const int numPoints = geometry->nCoordinates();
481
482 if ( numPoints <= ( isaLinearRing ? 6 : 3 ) )
483 {
484 // No simplify simple geometries
485 return nullptr;
486 }
487
488 const QgsRectangle envelope = geometry->boundingBox();
489 if ( std::max( envelope.width(), envelope.height() ) / numPoints > mTolerance * 2.0 )
490 {
491 //points are in average too far apart to lead to any significant simplification
492 return nullptr;
493 }
494
495 return simplifyGeometry( mSimplifyFlags, mSimplifyAlgorithm, *geometry, mTolerance, false ).release();
496}
VectorSimplificationAlgorithm
Simplification algorithms for vector features.
Definition qgis.h:3013
@ Distance
The simplification uses the distance between points to remove duplicate points.
Definition qgis.h:3014
@ SnappedToGridGlobal
Snap to a global grid based on the tolerance. Good for consistent results for incoming vertices,...
Definition qgis.h:3017
@ SnapToGrid
The simplification uses a grid (similar to ST_SnapToGrid) to remove duplicate points.
Definition qgis.h:3015
@ Visvalingam
The simplification gives each point in a line an importance weighting, so that least important points...
Definition qgis.h:3016
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:277
@ Point
Point.
Definition qgis.h:279
@ LineString
LineString.
Definition qgis.h:280
@ Polygon
Polygon.
Definition qgis.h:281
@ CircularString
CircularString.
Definition qgis.h:287
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:6607
T qgsgeometry_cast(QgsAbstractGeometry *geom)