Skip to content

Commit

Permalink
Fix crash when simplification has overly high tolerance
Browse files Browse the repository at this point in the history
Got rid of the custom simplification code and replaced it with simplification
provided by GEOS. The code is much simpler now.

As a side effect, tolerance can be now entered also in pixels.
  • Loading branch information
wonder-sk committed Feb 4, 2015
1 parent 6d4f444 commit 6a58bc0
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 220 deletions.
222 changes: 30 additions & 192 deletions src/app/qgsmaptoolsimplify.cpp
Expand Up @@ -46,6 +46,11 @@ void QgsSimplifyDialog::updateStatusText()
labelStatus->setText( mTool->statusText() );
}

void QgsSimplifyDialog::enableOkButton( bool enabled )
{
okButton->setEnabled( enabled );
}


////////////////////////////////////////////////////////////////////////////

Expand All @@ -56,10 +61,11 @@ QgsMapToolSimplify::QgsMapToolSimplify( QgsMapCanvas* canvas )
, mDragging( false )
, mOriginalVertexCount( 0 )
, mReducedVertexCount( 0 )
, mReducedHasErrors( false )
{
QSettings settings;
mTolerance = settings.value( "/digitizing/simplify_tolerance", 1 ).toDouble();
mToleranceUnits = ( ToleranceUnits ) settings.value( "/digitizing/simplify_tolerance_units", 0 ).toInt();
mToleranceUnits = ( QgsTolerance::UnitType ) settings.value( "/digitizing/simplify_tolerance_units", 0 ).toInt();

mSimplifyDialog = new QgsSimplifyDialog( this, canvas->topLevelWidget() );
}
Expand All @@ -84,7 +90,7 @@ void QgsMapToolSimplify::setTolerance( double tolerance )

void QgsMapToolSimplify::setToleranceUnits( int units )
{
mToleranceUnits = ( ToleranceUnits ) units;
mToleranceUnits = ( QgsTolerance::UnitType ) units;

QSettings settings;
settings.setValue( "/digitizing/simplify_tolerance_units", units );
Expand All @@ -97,19 +103,25 @@ void QgsMapToolSimplify::updateSimplificationPreview()
{
QgsVectorLayer* vl = currentVectorLayer();

double layerTolerance = QgsTolerance::toleranceInMapUnits( mTolerance, vl, mCanvas->mapSettings(), mToleranceUnits );
mReducedHasErrors = false;
mReducedVertexCount = 0;
int i = 0;
foreach ( const QgsFeature& fSel, mSelectedFeatures )
{
// create a copy of selected feature and do the simplification
QgsFeature f = fSel;
QgsSimplifyFeature::simplify( f, mTolerance, mToleranceUnits, mCanvas->mapSettings().layerTransform( vl ) );
mReducedVertexCount += vertexCount( f.geometry() );
mRubberBands[i]->setToGeometry( f.geometry(), vl );
if ( QgsGeometry* g = fSel.geometry()->simplify( layerTolerance ) )
{
mReducedVertexCount += vertexCount( g );
mRubberBands[i]->setToGeometry( g, vl );
delete g;
}
else
mReducedHasErrors = true;
++i;
}

mSimplifyDialog->updateStatusText();
mSimplifyDialog->enableOkButton( !mReducedHasErrors );
}


Expand Down Expand Up @@ -154,13 +166,16 @@ int QgsMapToolSimplify::vertexCount( QgsGeometry* g ) const
void QgsMapToolSimplify::storeSimplified()
{
QgsVectorLayer * vlayer = currentVectorLayer();
double layerTolerance = QgsTolerance::toleranceInMapUnits( mTolerance, vlayer, mCanvas->mapSettings(), mToleranceUnits );

vlayer->beginEditCommand( tr( "Geometry simplified" ) );
foreach ( const QgsFeature& feat, mSelectedFeatures )
{
QgsFeature f = feat;
QgsSimplifyFeature::simplify( f, mTolerance, mToleranceUnits, mCanvas->mapSettings().layerTransform( vlayer ) );
vlayer->changeGeometry( f.id(), f.geometry() );
if ( QgsGeometry* g = feat.geometry()->simplify( layerTolerance ) )
{
vlayer->changeGeometry( feat.id(), g );
delete g;
}
}
vlayer->endEditCommand();

Expand Down Expand Up @@ -329,186 +344,9 @@ void QgsMapToolSimplify::deactivate()
QString QgsMapToolSimplify::statusText() const
{
int percent = mOriginalVertexCount ? ( 100 * mReducedVertexCount / mOriginalVertexCount ) : 0;
return tr( "%1 feature(s): %2 to %3 vertices (%4%)" )
.arg( mSelectedFeatures.count() ).arg( mOriginalVertexCount ).arg( mReducedVertexCount ).arg( percent );
}


////////////////////////////////////////////////////////////////////////////


bool QgsSimplifyFeature::simplify( QgsFeature& feature, double tolerance, QgsMapToolSimplify::ToleranceUnits units, const QgsCoordinateTransform* ctLayerToMap )
{
if ( tolerance <= 0 )
return false;

QgsGeometry* g = feature.geometry();
if ( g->type() == QGis::Line )
{
if ( g->isMultipart() )
{
QgsMultiPolyline poly;
foreach ( const QgsPolyline& ring, g->asMultiPolyline() )
poly << simplifyPoints( ring, tolerance, units, ctLayerToMap );
feature.setGeometry( QgsGeometry::fromMultiPolyline( poly ) );
}
else
{
QgsPolyline resultPoints = simplifyPoints( g->asPolyline(), tolerance, units, ctLayerToMap );
feature.setGeometry( QgsGeometry::fromPolyline( resultPoints ) );
}
return true;
}
else if ( g->type() == QGis::Polygon )
{
if ( g->isMultipart() )
{
QgsMultiPolygon mpoly;
foreach ( const QgsPolygon& polygon, g->asMultiPolygon() )
{
QgsPolygon poly;
foreach ( const QgsPolyline& ring, polygon )
poly << simplifyPoints( ring, tolerance, units, ctLayerToMap );
mpoly << poly;
}
feature.setGeometry( QgsGeometry::fromMultiPolygon( mpoly ) );
}
else
{
QgsPolygon poly;
foreach ( const QgsPolyline& ring, g->asPolygon() )
poly << simplifyPoints( ring, tolerance, units, ctLayerToMap );
feature.setGeometry( QgsGeometry::fromPolygon( poly ) );
}
return true;
}
else
return false;
QString txt = tr( "%1 feature(s): %2 to %3 vertices (%4%)" )
.arg( mSelectedFeatures.count() ).arg( mOriginalVertexCount ).arg( mReducedVertexCount ).arg( percent );
if ( mReducedHasErrors )
txt += "\n" + tr( "Simplification failed!" );
return txt;
}


QVector<QgsPoint> QgsSimplifyFeature::simplifyPoints( const QVector<QgsPoint>& pts, double tolerance, QgsMapToolSimplify::ToleranceUnits units, const QgsCoordinateTransform* ctLayerToMap )
{
if ( tolerance < 0 )
return pts;

// if using map units, we transform coordinates to map units and, run simplification on transformed points
// and use indices with original coordinates. This will ensure that we do not modify coordinate values
// by transforming them back and forth.
QVector<QgsPoint> ptsForAlg;
bool transform = ( units == QgsMapToolSimplify::MapUnits && ctLayerToMap );
if ( transform )
{
foreach ( const QgsPoint& pt, pts )
ptsForAlg << ctLayerToMap->transform( pt );
}

QList<int> indices = QgsSimplifyFeature::simplifyPointsIndices( transform ? ptsForAlg : pts, tolerance );

QVector<QgsPoint> result;
foreach ( int index, indices )
result.append( pts[index] );
return result;
}


QList<int> QgsSimplifyFeature::simplifyPointsIndices( const QVector<QgsPoint>& pts, double tolerance )
{
// Douglas-Peucker simplification algorithm

int anchor = 0;
int floater = pts.size() - 1;

QList<StackEntry> stack;
StackEntry temporary;
StackEntry entry = {anchor, floater};
stack.append( entry );

QSet<int> keep;
double anchorX;
double anchorY;
double seg_len;
double max_dist;
int farthest;
double dist_to_seg;
double vecX;
double vecY;

while ( !stack.empty() )
{
temporary = stack.takeLast();
anchor = temporary.anchor;
floater = temporary.floater;
// initialize line segment
if ( pts[floater] != pts[anchor] )
{
anchorX = pts[floater].x() - pts[anchor].x();
anchorY = pts[floater].y() - pts[anchor].y();
seg_len = sqrt( anchorX * anchorX + anchorY * anchorY );
// get the unit vector
anchorX /= seg_len;
anchorY /= seg_len;
}
else
{
anchorX = anchorY = seg_len = 0.0;
}
// inner loop:
max_dist = 0.0;
farthest = anchor + 1;
for ( int i = anchor + 1; i < floater; i++ )
{
dist_to_seg = 0.0;
// compare to anchor
vecX = pts[i].x() - pts[anchor].x();
vecY = pts[i].y() - pts[anchor].y();
seg_len = sqrt( vecX * vecX + vecY * vecY );
// dot product:
double proj = vecX * anchorX + vecY * anchorY;
if ( proj < 0.0 )
{
dist_to_seg = seg_len;
}
else
{
// compare to floater
vecX = pts[i].x() - pts[floater].x();
vecY = pts[i].y() - pts[floater].y();
seg_len = sqrt( vecX * vecX + vecY * vecY );
// dot product:
proj = vecX * ( -anchorX ) + vecY * ( -anchorY );
if ( proj < 0.0 )
{
dist_to_seg = seg_len;
}
else
{ // calculate perpendicular distance to line (pythagorean theorem):
dist_to_seg = sqrt( qAbs( seg_len * seg_len - proj * proj ) );
}
if ( max_dist < dist_to_seg )
{
max_dist = dist_to_seg;
farthest = i;
}
}
}
if ( max_dist <= tolerance )
{ // # use line segment
keep.insert( anchor );
keep.insert( floater );
}
else
{
StackEntry s = {anchor, farthest};
stack.append( s );

StackEntry r = {farthest, floater};
stack.append( r );
}
}

QList<int> keep2 = keep.toList();
qSort( keep2 );
return keep2;
}

33 changes: 5 additions & 28 deletions src/app/qgsmaptoolsimplify.h
Expand Up @@ -21,6 +21,7 @@

#include <QVector>
#include "qgsfeature.h"
#include "qgstolerance.h"

class QgsRubberBand;
class QgsMapToolSimplify;
Expand All @@ -35,6 +36,7 @@ class APP_EXPORT QgsSimplifyDialog : public QDialog, private Ui::SimplifyLineDia
QgsSimplifyDialog( QgsMapToolSimplify* tool, QWidget* parent = NULL );

void updateStatusText();
void enableOkButton( bool enabled );

private:
QgsMapToolSimplify* mTool;
Expand All @@ -59,9 +61,7 @@ class APP_EXPORT QgsMapToolSimplify: public QgsMapToolEdit

double tolerance() const { return mTolerance; }

enum ToleranceUnits { LayerUnits = 0, MapUnits = 1 };

ToleranceUnits toleranceUnits() const { return mToleranceUnits; }
QgsTolerance::UnitType toleranceUnits() const { return mToleranceUnits; }

QString statusText() const;

Expand Down Expand Up @@ -97,7 +97,7 @@ class APP_EXPORT QgsMapToolSimplify: public QgsMapToolEdit
/** real value of tolerance */
double mTolerance;

ToleranceUnits mToleranceUnits;
QgsTolerance::UnitType mToleranceUnits;

//! stores actual selection rect
QRect mSelectionRect;
Expand All @@ -108,30 +108,7 @@ class APP_EXPORT QgsMapToolSimplify: public QgsMapToolEdit

int mOriginalVertexCount;
int mReducedVertexCount;
};

/**
Implementation of Douglas-Peucker simplification algorithm.
*/
class APP_EXPORT QgsSimplifyFeature
{
/** structure for one entry in stack for simplification algorithm */
struct StackEntry
{
int anchor;
int floater;
};

public:
/** simplify line/polygon feature with specified tolerance. Returns true on success */
static bool simplify( QgsFeature& feature, double tolerance, QgsMapToolSimplify::ToleranceUnits units, const QgsCoordinateTransform* ctLayerToMap );

protected:
/** simplify a line given by a vector of points and tolerance. Returns simplified vector of points */
static QVector<QgsPoint> simplifyPoints( const QVector<QgsPoint>& pts, double tolerance, QgsMapToolSimplify::ToleranceUnits units, const QgsCoordinateTransform* ctLayerToMap );
/** get indices of points that should be preserved after simplification */
static QList<int> simplifyPointsIndices( const QVector<QgsPoint>& pts, double tolerance );

bool mReducedHasErrors;
};

#endif
5 changes: 5 additions & 0 deletions src/ui/qgssimplifytolerancedialog.ui
Expand Up @@ -28,6 +28,11 @@
<string>Layer units</string>
</property>
</item>
<item>
<property name="text">
<string>Pixels</string>
</property>
</item>
<item>
<property name="text">
<string>Map units</string>
Expand Down

0 comments on commit 6a58bc0

Please sign in to comment.