Skip to content

Commit

Permalink
Merge pull request #5869 from pblottiere/bugfix_reshape2_218
Browse files Browse the repository at this point in the history
[backport][bugfix] Do not add binding line in both side in reshape map tool
  • Loading branch information
pblottiere committed Dec 18, 2017
2 parents f4b007e + 0dd8db6 commit 3277853
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 53 deletions.
154 changes: 103 additions & 51 deletions src/app/qgsmaptoolreshape.cpp
Expand Up @@ -75,70 +75,122 @@ void QgsMapToolReshape::cadCanvasReleaseEvent( QgsMapMouseEvent * e )
stopCapturing();
return;
}
QgsPoint firstPoint = points().at( 0 );
QgsRectangle bbox( firstPoint.x(), firstPoint.y(), firstPoint.x(), firstPoint.y() );
for ( int i = 1; i < size(); ++i )
{
bbox.combineExtentWith( points().at( i ).x(), points().at( i ).y() );
}

//query all the features that intersect bounding box of capture line
QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( bbox ).setSubsetOfAttributes( QgsAttributeList() ) );
QgsFeature f;
int reshapeReturn;
bool reshapeDone = false;
reshape( vlayer );

stopCapturing();
}
}

void QgsMapToolReshape::reshape( QgsVectorLayer *vlayer )
{
std::cout << "QgsMapToolReshape::reshape 0" << std::endl;
if ( !vlayer )
return;

QgsPoint firstPoint = points().at( 0 );
QgsRectangle bbox( firstPoint.x(), firstPoint.y(), firstPoint.x(), firstPoint.y() );
for ( int i = 1; i < size(); ++i )
{
bbox.combineExtentWith( points().at( i ).x(), points().at( i ).y() );
}

vlayer->beginEditCommand( tr( "Reshape" ) );
while ( fit.nextFeature( f ) )
//query all the features that intersect bounding box of capture line
std::cout << "QgsMapToolReshape::reshape 1" << std::endl;
QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( bbox ).setSubsetOfAttributes( QgsAttributeList() ) );
QgsFeature f;
int reshapeReturn;
bool reshapeDone = false;
bool isBinding = isBindingLine( vlayer, bbox );

vlayer->beginEditCommand( tr( "Reshape" ) );
std::cout << "QgsMapToolReshape::reshape 2" << std::endl;
while ( fit.nextFeature( f ) )
{
std::cout << "QgsMapToolReshape::reshape 3" << std::endl;
//query geometry
//call geometry->reshape(mCaptureList)
//register changed geometry in vector layer
QgsGeometry* geom = f.geometry();
if ( geom )
{
//query geometry
//call geometry->reshape(mCaptureList)
//register changed geometry in vector layer
QgsGeometry* geom = f.geometry();
if ( geom )
std::cout << "QgsMapToolReshape::reshape 4" << std::endl;
// in case of a binding line, we just want to update the line from
// the starting point and not both side
if ( isBinding && !geom->asPolyline().contains( points().first() ) )
continue;

std::cout << "QgsMapToolReshape::reshape 5" << std::endl;
reshapeReturn = geom->reshapeGeometry( pointsV2() );
if ( reshapeReturn == 0 )
{
reshapeReturn = geom->reshapeGeometry( pointsV2() );
if ( reshapeReturn == 0 )
//avoid intersections on polygon layers
if ( vlayer->geometryType() == QGis::Polygon )
{
//avoid intersections on polygon layers
if ( vlayer->geometryType() == QGis::Polygon )
//ignore all current layer features as they should be reshaped too
QMap<QgsVectorLayer*, QSet<QgsFeatureId> > ignoreFeatures;
ignoreFeatures.insert( vlayer, vlayer->allFeatureIds() );

if ( geom->avoidIntersections( ignoreFeatures ) != 0 )
{
//ignore all current layer features as they should be reshaped too
QMap<QgsVectorLayer*, QSet<QgsFeatureId> > ignoreFeatures;
ignoreFeatures.insert( vlayer, vlayer->allFeatureIds() );

if ( geom->avoidIntersections( ignoreFeatures ) != 0 )
{
emit messageEmitted( tr( "An error was reported during intersection removal" ), QgsMessageBar::CRITICAL );
vlayer->destroyEditCommand();
stopCapturing();
return;
}

if ( geom->isGeosEmpty() ) //intersection removal might have removed the whole geometry
{
emit messageEmitted( tr( "The feature cannot be reshaped because the resulting geometry is empty" ), QgsMessageBar::CRITICAL );
vlayer->destroyEditCommand();
stopCapturing();
return;
}
emit messageEmitted( tr( "An error was reported during intersection removal" ), QgsMessageBar::CRITICAL );
vlayer->destroyEditCommand();
stopCapturing();
return;
}

vlayer->changeGeometry( f.id(), geom );
reshapeDone = true;
if ( geom->isGeosEmpty() ) //intersection removal might have removed the whole geometry
{
emit messageEmitted( tr( "The feature cannot be reshaped because the resulting geometry is empty" ), QgsMessageBar::CRITICAL );
vlayer->destroyEditCommand();
stopCapturing();
return;
}
}

vlayer->changeGeometry( f.id(), geom );
reshapeDone = true;
}
}
}

if ( reshapeDone )
{
vlayer->endEditCommand();
}
else
if ( reshapeDone )
{
vlayer->endEditCommand();
}
else
{
vlayer->destroyEditCommand();
}
}

bool QgsMapToolReshape::isBindingLine( QgsVectorLayer *vlayer, const QgsRectangle &bbox ) const
{
if ( vlayer->geometryType() != QGis::Line )
return false;

bool begin = false;
bool end = false;
const QgsPoint beginPoint = points().first();
const QgsPoint endPoint = points().last();

QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( bbox ).setSubsetOfAttributes( QgsAttributeList() ) );
QgsFeature f;

// check that extremities of the new line are contained by features
while ( fit.nextFeature( f ) )
{
const QgsGeometry *geom = f.geometry();
if ( geom )
{
vlayer->destroyEditCommand();
}
const QgsPolyline line = geom->asPolyline();

stopCapturing();
if ( line.contains( beginPoint ) )
begin = true;
else if ( line.contains( endPoint ) )
end = true;
}
}

return end && begin;
}
7 changes: 7 additions & 0 deletions src/app/qgsmaptoolreshape.h
Expand Up @@ -28,6 +28,13 @@ class APP_EXPORT QgsMapToolReshape: public QgsMapToolCapture
QgsMapToolReshape( QgsMapCanvas* canvas );
virtual ~QgsMapToolReshape();
void cadCanvasReleaseEvent( QgsMapMouseEvent * e ) override;

private:
void reshape( QgsVectorLayer *vlayer );

bool isBindingLine( QgsVectorLayer *vlayer, const QgsRectangle &bbox ) const;

friend class TestQgsMapToolReshape;
};

#endif
2 changes: 1 addition & 1 deletion src/gui/qgsmaptoolcapture.cpp
Expand Up @@ -713,7 +713,7 @@ int QgsMapToolCapture::size()
return mCaptureCurve.numPoints();
}

QList<QgsPoint> QgsMapToolCapture::points()
QList<QgsPoint> QgsMapToolCapture::points() const
{
QgsPointSequenceV2 pts;
QList<QgsPoint> points;
Expand Down
4 changes: 3 additions & 1 deletion src/gui/qgsmaptoolcapture.h
Expand Up @@ -192,7 +192,7 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
* List of digitized points
* @return List of points
*/
QList<QgsPoint> points();
QList<QgsPoint> points() const;

/**
* List of digitized points with z support
Expand Down Expand Up @@ -248,6 +248,8 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing

QgsVertexMarker* mSnappingMarker;

friend class TestQgsMapToolReshape;

#ifdef Q_OS_WIN
int mSkipNextContextMenuEvent;
#endif
Expand Down
2 changes: 2 additions & 0 deletions tests/src/app/CMakeLists.txt
Expand Up @@ -16,6 +16,7 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/gui/attributetable
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/gui/symbology-ng
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/gui/raster
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/gui/layertree
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/python
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/app
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/app/pluginmanager
Expand Down Expand Up @@ -108,5 +109,6 @@ ADD_QGIS_TEST(attributetabletest testqgsattributetable.cpp)
ADD_QGIS_TEST(fieldcalculatortest testqgsfieldcalculator.cpp)
ADD_QGIS_TEST(maptoolidentifyaction testqgsmaptoolidentifyaction.cpp)
ADD_QGIS_TEST(maptoolselect testqgsmaptoolselect.cpp)
ADD_QGIS_TEST(maptoolreshape testqgsmaptoolreshape.cpp)
ADD_QGIS_TEST(measuretool testqgsmeasuretool.cpp)
ADD_QGIS_TEST(vectorlayersaveasdialogtest testqgsvectorlayersaveasdialog.cpp)
150 changes: 150 additions & 0 deletions tests/src/app/testqgsmaptoolreshape.cpp
@@ -0,0 +1,150 @@
/***************************************************************************
testqgsmaptoolreshape.cpp
--------------------------------
Date : 2017-12-14
Copyright : (C) 2017 by Paul Blottiere
Email : paul.blottiere@oslandia.com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include <QtTest/QtTest>
#include "qgsapplication.h"
#include "qgsmapcanvas.h"
#include "qgsvectorlayer.h"
#include "qgslinestringv2.h"
#include "qgsmaptoolreshape.h"
#include "qgsvectordataprovider.h"
#include "qgsmaplayerregistry.h"
#include "qgisapp.h"

class TestQgsMapToolReshape : public QObject
{
Q_OBJECT
public:
TestQgsMapToolReshape() = default;

private slots:
void initTestCase(); // will be called before the first testfunction is executed.
void cleanupTestCase(); // will be called after the last testfunction was executed.
void init(); // will be called before each testfunction is executed.
void cleanup(); // will be called after every testfunction.

void reshapeWithBindingLine();

private:
QgisApp *mQgisApp = nullptr;
};

void TestQgsMapToolReshape::initTestCase()
{
QgsApplication::init();
QgsApplication::initQgis();

// Set up the QgsSettings environment
QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );

QgsApplication::showSettings();

// enforce C locale because the tests expect it
// (decimal separators / thousand separators)
QLocale::setDefault( QLocale::c() );

mQgisApp = new QgisApp();
}

void TestQgsMapToolReshape::cleanupTestCase()
{
QgsApplication::exitQgis();
}

void TestQgsMapToolReshape::init()
{
}

void TestQgsMapToolReshape::cleanup()
{
}

void TestQgsMapToolReshape::reshapeWithBindingLine()
{
// prepare vector layer
QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326&field=name:string(20)" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );

QgsGeometry *g0 = QgsGeometry::fromWkt( "LineString (0 0, 1 1, 1 2)" );
QgsFeature f0;
f0.setGeometry( g0 );
f0.setAttribute( 0, "polyline0" );

QgsGeometry *g1 = QgsGeometry::fromWkt( "LineString (2 1, 3 2, 3 3, 2 2)" );
QgsFeature f1;
f1.setGeometry( g1 );
f1.setAttribute( 0, "polyline1" );

vl->dataProvider()->addFeatures( QgsFeatureList() << f0 << f1 );

std::cout << "Feature count: " << vl->featureCount() << std::endl;

// prepare canvas
QList<QgsMapLayer *> layers;
layers.append( vl );

QgsCoordinateReferenceSystem srs( 4326, QgsCoordinateReferenceSystem::EpsgCrsId );
QgsMapLayerRegistry::instance()->addMapLayer( vl );
mQgisApp->mapCanvas()->setDestinationCrs( srs );
mQgisApp->mapCanvas()->setCurrentLayer( vl );

// reshape to add line to polyline0
QgsLineStringV2 cl0;
cl0.setPoints( QgsPointSequenceV2() << QgsPointV2( 1, 2 ) << QgsPointV2( 2, 1 ) );

QgsAbstractGeometryV2 *curveGgeom0 = cl0.toCurveType();
QgsCompoundCurveV2 curve0;
curve0.fromWkt( curveGgeom0->asWkt() );

QgsMapToolReshape tool0( mQgisApp->mapCanvas() );
tool0.mCaptureCurve = curve0;

vl->startEditing();
tool0.reshape( vl );

vl->getFeatures( QgsFeatureRequest().setFilterFid( 1 ) ).nextFeature( f0 );
QCOMPARE( f0.geometry()->exportToWkt(), QStringLiteral( "LineString (0 0, 1 1, 1 2, 2 1)" ) );

vl->getFeatures( QgsFeatureRequest().setFilterFid( 2 ) ).nextFeature( f1 );
QCOMPARE( f1.geometry()->exportToWkt(), QStringLiteral( "LineString (2 1, 3 2, 3 3, 2 2)" ) );

vl->rollBack();

// reshape to add line to polyline1
QgsLineStringV2 cl1;
cl1.setPoints( QgsPointSequenceV2() << QgsPointV2( 2, 1 ) << QgsPointV2( 1, 2 ) );

QgsAbstractGeometryV2 *curveGeom1 = cl1.toCurveType();
QgsCompoundCurveV2 curve1;
curve1.fromWkt( curveGeom1->asWkt() );

QgsMapToolReshape tool1( mQgisApp->mapCanvas() );
tool1.mCaptureCurve = curve1;

vl->startEditing();
tool1.reshape( vl );

vl->getFeatures( QgsFeatureRequest().setFilterFid( 1 ) ).nextFeature( f0 );
QCOMPARE( f0.geometry()->exportToWkt(), QStringLiteral( "LineString (0 0, 1 1, 1 2)" ) );

vl->getFeatures( QgsFeatureRequest().setFilterFid( 2 ) ).nextFeature( f1 );
QCOMPARE( f1.geometry()->exportToWkt(), QStringLiteral( "LineString (1 2, 2 1, 3 2, 3 3, 2 2)" ) );

vl->rollBack();
}

QTEST_MAIN( TestQgsMapToolReshape )
#include "testqgsmaptoolreshape.moc"

0 comments on commit 3277853

Please sign in to comment.