Skip to content
Permalink
Browse files

Fix crash when simplification has overly high tolerance

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 6a58bc049c39e3b5c0a26e8bf1ec8964b0991576
Showing with 40 additions and 220 deletions.
  1. +30 −192 src/app/qgsmaptoolsimplify.cpp
  2. +5 −28 src/app/qgsmaptoolsimplify.h
  3. +5 −0 src/ui/qgssimplifytolerancedialog.ui
@@ -46,6 +46,11 @@ void QgsSimplifyDialog::updateStatusText()
labelStatus->setText( mTool->statusText() );
}

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


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

@@ -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() );
}
@@ -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 );
@@ -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 );
}


@@ -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();

@@ -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;
}

@@ -21,6 +21,7 @@

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

class QgsRubberBand;
class QgsMapToolSimplify;
@@ -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;
@@ -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;

@@ -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;
@@ -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
@@ -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>

0 comments on commit 6a58bc0

Please sign in to comment.
You can’t perform that action at this time.