1
/***************************************************************************
2
qgsmaptoolsimplify.cpp - simplify vector layer features
5
copyright : (C) 2009 by Richard Kostecky
6
email : csf dot kostej at mail dot com
7
***************************************************************************
9
* This program is free software; you can redistribute it and/or modify *
10
* it under the terms of the GNU General Public License as published by *
11
* the Free Software Foundation; either version 2 of the License, or *
12
* (at your option) any later version. *
14
***************************************************************************/
16
#include "qgsmaptoolsimplify.h"
18
#include "qgsgeometry.h"
19
#include "qgsmapcanvas.h"
20
#include "qgsrubberband.h"
21
#include "qgsvectorlayer.h"
22
#include "qgstolerance.h"
24
#include <QMouseEvent>
25
#include <QMessageBox>
30
QgsSimplifyDialog::QgsSimplifyDialog( QWidget* parent )
34
connect( horizontalSlider, SIGNAL( valueChanged( int ) ),
35
this, SLOT( valueChanged( int ) ) );
36
connect( okButton, SIGNAL( clicked() ),
37
this, SLOT( simplify() ) );
41
void QgsSimplifyDialog::valueChanged( int value )
43
emit toleranceChanged( value );
46
void QgsSimplifyDialog::simplify()
48
emit storeSimplified();
51
void QgsSimplifyDialog::setRange( int minValue, int maxValue )
53
// let's have 20 page steps
54
horizontalSlider->setPageStep(( maxValue - minValue ) / 20 );
56
horizontalSlider->setMinimum(( minValue - 1 < 0 ? 0 : minValue - 1 ) );// -1 for count with minimum tolerance end caused by double imprecision
57
horizontalSlider->setMaximum( maxValue );
62
QgsMapToolSimplify::QgsMapToolSimplify( QgsMapCanvas* canvas )
63
: QgsMapToolEdit( canvas ), mRubberBand( 0 )
65
mSimplifyDialog = new QgsSimplifyDialog( canvas->topLevelWidget() );
66
connect( mSimplifyDialog, SIGNAL( toleranceChanged( int ) ),
67
this, SLOT( toleranceChanged( int ) ) );
68
connect( mSimplifyDialog, SIGNAL( storeSimplified() ),
69
this, SLOT( storeSimplified() ) );
70
connect( mSimplifyDialog, SIGNAL( finished( int ) ),
71
this, SLOT( removeRubberBand() ) );
74
QgsMapToolSimplify::~QgsMapToolSimplify()
77
delete mSimplifyDialog;
81
void QgsMapToolSimplify::toleranceChanged( int tolerance )
83
mTolerance = double( tolerance ) / toleranceDivider;
85
// create a copy of selected feature and do the simplification
86
QgsFeature f = mSelectedFeature;
87
//QgsSimplifyFeature::simplifyLine(f, mTolerance);
90
if ( mSelectedFeature.geometry()->type() == QGis::Line )
92
QgsSimplifyFeature::simplifyLine( f, mTolerance );
96
QgsSimplifyFeature::simplifyPolygon( f, mTolerance );
99
mRubberBand->setToGeometry( f.geometry(), false );
103
void QgsMapToolSimplify::storeSimplified()
105
QgsVectorLayer * vlayer = currentVectorLayer();
106
if ( mSelectedFeature.geometry()->type() == QGis::Line )
108
QgsSimplifyFeature::simplifyLine( mSelectedFeature, mTolerance );
112
QgsSimplifyFeature::simplifyPolygon( mSelectedFeature, mTolerance );
115
vlayer->beginEditCommand( tr( "Geometry simplified" ) );
116
vlayer->changeGeometry( mSelectedFeature.id(), mSelectedFeature.geometry() );
117
vlayer->endEditCommand();
122
int QgsMapToolSimplify::calculateDivider( double minimum, double maximum )
124
double tmp = minimum;
127
{ //exception if min = 0 than divider must be counted from maximum
130
//count divider in such way so it can be used as whole number
137
{ //special case that minimum is 0 to have more than 1 step
140
//taking care of problem when multiplication would overflow maxint
141
while ( int( i * maximum ) < 0 )
148
bool QgsMapToolSimplify::calculateSliderBoudaries()
150
double minTolerance = -1, maxTolerance = -1;
152
double tol = 0.000001;
154
bool isLine = mSelectedFeature.geometry()->type() == QGis::Line;
155
QVector<QgsPoint> pts = getPointList( mSelectedFeature );
156
int size = pts.size();
157
if ( size == 0 || ( isLine && size <= 2 ) || ( !isLine && size <= 4 ) )
162
// calculate minimum tolerance where no vertex is excluded
163
bool maximized = false;
168
if ( count == 30 && !maximized )
169
{ //special case when tolerance is tool low to be correct so it's similat to 0
170
// else in some special cases this algorithm would create infinite loop
175
if ( QgsSimplifyFeature::simplifyPoints( pts, tol ).size() < size )
176
{ //some vertexes were already excluded
177
if ( maximized ) //if we were already in second direction end
180
minTolerance = tol / 2;
182
else //only lowering tolerance till it's low enough to have all vertexes
188
{ // simplified feature has all vertexes therefore no need we need higher tolerance also ending flag set
189
// when some tolerance will exclude some of vertexes
195
int requiredCnt = ( isLine ? 2 : 4 ); //4 for polygon is correct because first and last points are the same
196
bool bottomFound = false;
197
double highTol = DBL_MAX, lowTol = DBL_MIN;// two boundaries to be used when no directly correct solution is found
198
// calculate minimum tolerance where minimum (requiredCnt) of vertexes are left in geometry
202
int foundVertexes = QgsSimplifyFeature::simplifyPoints( pts, tol ).size();
203
if ( foundVertexes < requiredCnt + 1 )
204
{ //required or lower number of verticies found
205
if ( foundVertexes == requiredCnt )
211
{ //solving problem that polygon would have less than minimum alowed vertexes
214
tol = ( highTol + lowTol ) / 2;
215
if ( doubleNear( highTol, lowTol ) )
216
{ //solving problem that two points are in same distance from line, so they will be both excluded at same time
217
//so some time more than required count of vertices can stay
219
maxTolerance = lowTol;
228
tol = ( highTol + lowTol ) / 2;
229
if ( doubleNear( highTol, lowTol ) )
230
{ //solving problem that two points are in same distance from line, so they will be both excluded at same time
231
//so some time more than required count of vertices can stay
233
maxTolerance = lowTol;
237
{ //still too much verticies left so we need to increase tolerance
243
toleranceDivider = calculateDivider( minTolerance, maxTolerance );
245
mSimplifyDialog->setRange( int( minTolerance * toleranceDivider ),
246
int( maxTolerance * toleranceDivider ) );
251
void QgsMapToolSimplify::canvasPressEvent( QMouseEvent * e )
253
QgsVectorLayer * vlayer = currentVectorLayer();
254
QgsPoint layerCoords = mCanvas->getCoordinateTransform()->toMapPoint( e->pos().x(), e->pos().y() );
256
double r = QgsTolerance::vertexSearchRadius( vlayer, mCanvas->mapRenderer() );
257
QgsRectangle selectRect = QgsRectangle( layerCoords.x() - r, layerCoords.y() - r,
258
layerCoords.x() + r, layerCoords.y() + r );
259
vlayer->select( QgsAttributeList(), selectRect, true );
261
QgsGeometry* geometry = QgsGeometry::fromPoint( layerCoords );
262
double minDistance = 10000000;
263
double currentDistance;
266
mSelectedFeature.setValid( FALSE );
268
while ( vlayer->nextFeature( f ) )
270
currentDistance = geometry->distance( *( f.geometry() ) );
271
if ( currentDistance < minDistance )
273
minDistance = currentDistance;
274
mSelectedFeature = f;
277
if ( mSelectedFeature.geometry()->isMultipart() )
279
QMessageBox::critical( 0, tr( "Unsupported operation" ), tr( "Multipart features are not supported for simplification." ) );
282
// delete previous rubberband (if any)
285
if ( mSelectedFeature.isValid() )
287
mRubberBand = new QgsRubberBand( mCanvas );
288
mRubberBand->setToGeometry( mSelectedFeature.geometry(), false );
289
mRubberBand->setColor( Qt::red );
290
mRubberBand->setWidth( 2 );
292
//calculate boudaries for slidebar
293
if ( calculateSliderBoudaries() )
295
// show dialog as a non-modal window
296
mSimplifyDialog->show();
300
QMessageBox::warning( 0, tr( "Unsupported operation" ), tr( "This feature cannot be simplified. Check if feature has enough vertices to be simplified." ) );
305
void QgsMapToolSimplify::removeRubberBand()
311
void QgsMapToolSimplify::deactivate()
313
if ( mSimplifyDialog->isVisible() )
314
mSimplifyDialog->close();
316
QgsMapTool::deactivate();
320
QVector<QgsPoint> QgsMapToolSimplify::getPointList( QgsFeature& f )
322
QgsGeometry* line = f.geometry();
323
if (( line->type() != QGis::Line && line->type() != QGis::Polygon ) || line->isMultipart() )
325
return QVector<QgsPoint>();
327
if (( line->type() == QGis::Line ) )
329
return line->asPolyline();
333
if ( line->asPolygon().size() > 1 )
335
return QVector<QgsPoint>();
337
return line->asPolygon()[0];
343
bool QgsSimplifyFeature::simplifyLine( QgsFeature& lineFeature, double tolerance )
345
QgsGeometry* line = lineFeature.geometry();
346
if ( line->type() != QGis::Line )
351
QVector<QgsPoint> resultPoints = simplifyPoints( line->asPolyline(), tolerance );
352
lineFeature.setGeometry( QgsGeometry::fromPolyline( resultPoints ) );
356
bool QgsSimplifyFeature::simplifyPolygon( QgsFeature& polygonFeature, double tolerance )
358
QgsGeometry* polygon = polygonFeature.geometry();
359
if ( polygon->type() != QGis::Polygon )
364
QVector<QgsPoint> resultPoints = simplifyPoints( polygon->asPolygon()[0], tolerance );
365
//resultPoints.push_back(resultPoints[0]);
366
QVector<QgsPolyline> poly;
367
poly.append( resultPoints );
368
polygonFeature.setGeometry( QgsGeometry::fromPolygon( poly ) );
373
QVector<QgsPoint> QgsSimplifyFeature::simplifyPoints( const QVector<QgsPoint>& pts, double tolerance )
375
//just safety precaution
378
// Douglas-Peucker simplification algorithm
381
int floater = pts.size() - 1;
383
QList<StackEntry> stack;
384
StackEntry temporary;
385
StackEntry entry = {anchor, floater};
386
stack.append( entry );
398
while ( !stack.empty() )
400
temporary = stack.takeLast();
401
anchor = temporary.anchor;
402
floater = temporary.floater;
403
// initialize line segment
404
if ( pts[floater] != pts[anchor] )
406
anchorX = pts[floater].x() - pts[anchor].x();
407
anchorY = pts[floater].y() - pts[anchor].y();
408
seg_len = sqrt( anchorX * anchorX + anchorY * anchorY );
409
// get the unit vector
415
anchorX = anchorY = seg_len = 0.0;
419
farthest = anchor + 1;
420
for ( int i = anchor + 1; i < floater; i++ )
424
vecX = pts[i].x() - pts[anchor].x();
425
vecY = pts[i].y() - pts[anchor].y();
426
seg_len = sqrt( vecX * vecX + vecY * vecY );
428
double proj = vecX * anchorX + vecY * anchorY;
431
dist_to_seg = seg_len;
435
// compare to floater
436
vecX = pts[i].x() - pts[floater].x();
437
vecY = pts[i].y() - pts[floater].y();
438
seg_len = sqrt( vecX * vecX + vecY * vecY );
440
proj = vecX * ( -anchorX ) + vecY * ( -anchorY );
443
dist_to_seg = seg_len;
446
{ // calculate perpendicular distance to line (pythagorean theorem):
447
dist_to_seg = sqrt( fabs( seg_len * seg_len - proj * proj ) );
449
if ( max_dist < dist_to_seg )
451
max_dist = dist_to_seg;
456
if ( max_dist <= tolerance )
457
{ // # use line segment
458
keep.insert( anchor );
459
keep.insert( floater );
463
StackEntry s = {anchor, farthest};
466
StackEntry r = {farthest, floater};
471
QList<int> keep2 = keep.toList();
474
QVector<QgsPoint> result;
476
while ( !keep2.empty() )
478
position = keep2.takeFirst();
479
result.append( pts[position] );