Skip to content

Commit 3277853

Browse files
authored
Merge pull request #5869 from pblottiere/bugfix_reshape2_218
[backport][bugfix] Do not add binding line in both side in reshape map tool
2 parents f4b007e + 0dd8db6 commit 3277853

File tree

6 files changed

+266
-53
lines changed

6 files changed

+266
-53
lines changed

src/app/qgsmaptoolreshape.cpp

+103-51
Original file line numberDiff line numberDiff line change
@@ -75,70 +75,122 @@ void QgsMapToolReshape::cadCanvasReleaseEvent( QgsMapMouseEvent * e )
7575
stopCapturing();
7676
return;
7777
}
78-
QgsPoint firstPoint = points().at( 0 );
79-
QgsRectangle bbox( firstPoint.x(), firstPoint.y(), firstPoint.x(), firstPoint.y() );
80-
for ( int i = 1; i < size(); ++i )
81-
{
82-
bbox.combineExtentWith( points().at( i ).x(), points().at( i ).y() );
83-
}
8478

85-
//query all the features that intersect bounding box of capture line
86-
QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( bbox ).setSubsetOfAttributes( QgsAttributeList() ) );
87-
QgsFeature f;
88-
int reshapeReturn;
89-
bool reshapeDone = false;
79+
reshape( vlayer );
80+
81+
stopCapturing();
82+
}
83+
}
84+
85+
void QgsMapToolReshape::reshape( QgsVectorLayer *vlayer )
86+
{
87+
std::cout << "QgsMapToolReshape::reshape 0" << std::endl;
88+
if ( !vlayer )
89+
return;
90+
91+
QgsPoint firstPoint = points().at( 0 );
92+
QgsRectangle bbox( firstPoint.x(), firstPoint.y(), firstPoint.x(), firstPoint.y() );
93+
for ( int i = 1; i < size(); ++i )
94+
{
95+
bbox.combineExtentWith( points().at( i ).x(), points().at( i ).y() );
96+
}
9097

91-
vlayer->beginEditCommand( tr( "Reshape" ) );
92-
while ( fit.nextFeature( f ) )
98+
//query all the features that intersect bounding box of capture line
99+
std::cout << "QgsMapToolReshape::reshape 1" << std::endl;
100+
QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( bbox ).setSubsetOfAttributes( QgsAttributeList() ) );
101+
QgsFeature f;
102+
int reshapeReturn;
103+
bool reshapeDone = false;
104+
bool isBinding = isBindingLine( vlayer, bbox );
105+
106+
vlayer->beginEditCommand( tr( "Reshape" ) );
107+
std::cout << "QgsMapToolReshape::reshape 2" << std::endl;
108+
while ( fit.nextFeature( f ) )
109+
{
110+
std::cout << "QgsMapToolReshape::reshape 3" << std::endl;
111+
//query geometry
112+
//call geometry->reshape(mCaptureList)
113+
//register changed geometry in vector layer
114+
QgsGeometry* geom = f.geometry();
115+
if ( geom )
93116
{
94-
//query geometry
95-
//call geometry->reshape(mCaptureList)
96-
//register changed geometry in vector layer
97-
QgsGeometry* geom = f.geometry();
98-
if ( geom )
117+
std::cout << "QgsMapToolReshape::reshape 4" << std::endl;
118+
// in case of a binding line, we just want to update the line from
119+
// the starting point and not both side
120+
if ( isBinding && !geom->asPolyline().contains( points().first() ) )
121+
continue;
122+
123+
std::cout << "QgsMapToolReshape::reshape 5" << std::endl;
124+
reshapeReturn = geom->reshapeGeometry( pointsV2() );
125+
if ( reshapeReturn == 0 )
99126
{
100-
reshapeReturn = geom->reshapeGeometry( pointsV2() );
101-
if ( reshapeReturn == 0 )
127+
//avoid intersections on polygon layers
128+
if ( vlayer->geometryType() == QGis::Polygon )
102129
{
103-
//avoid intersections on polygon layers
104-
if ( vlayer->geometryType() == QGis::Polygon )
130+
//ignore all current layer features as they should be reshaped too
131+
QMap<QgsVectorLayer*, QSet<QgsFeatureId> > ignoreFeatures;
132+
ignoreFeatures.insert( vlayer, vlayer->allFeatureIds() );
133+
134+
if ( geom->avoidIntersections( ignoreFeatures ) != 0 )
105135
{
106-
//ignore all current layer features as they should be reshaped too
107-
QMap<QgsVectorLayer*, QSet<QgsFeatureId> > ignoreFeatures;
108-
ignoreFeatures.insert( vlayer, vlayer->allFeatureIds() );
109-
110-
if ( geom->avoidIntersections( ignoreFeatures ) != 0 )
111-
{
112-
emit messageEmitted( tr( "An error was reported during intersection removal" ), QgsMessageBar::CRITICAL );
113-
vlayer->destroyEditCommand();
114-
stopCapturing();
115-
return;
116-
}
117-
118-
if ( geom->isGeosEmpty() ) //intersection removal might have removed the whole geometry
119-
{
120-
emit messageEmitted( tr( "The feature cannot be reshaped because the resulting geometry is empty" ), QgsMessageBar::CRITICAL );
121-
vlayer->destroyEditCommand();
122-
stopCapturing();
123-
return;
124-
}
136+
emit messageEmitted( tr( "An error was reported during intersection removal" ), QgsMessageBar::CRITICAL );
137+
vlayer->destroyEditCommand();
138+
stopCapturing();
139+
return;
125140
}
126141

127-
vlayer->changeGeometry( f.id(), geom );
128-
reshapeDone = true;
142+
if ( geom->isGeosEmpty() ) //intersection removal might have removed the whole geometry
143+
{
144+
emit messageEmitted( tr( "The feature cannot be reshaped because the resulting geometry is empty" ), QgsMessageBar::CRITICAL );
145+
vlayer->destroyEditCommand();
146+
stopCapturing();
147+
return;
148+
}
129149
}
150+
151+
vlayer->changeGeometry( f.id(), geom );
152+
reshapeDone = true;
130153
}
131154
}
155+
}
132156

133-
if ( reshapeDone )
134-
{
135-
vlayer->endEditCommand();
136-
}
137-
else
157+
if ( reshapeDone )
158+
{
159+
vlayer->endEditCommand();
160+
}
161+
else
162+
{
163+
vlayer->destroyEditCommand();
164+
}
165+
}
166+
167+
bool QgsMapToolReshape::isBindingLine( QgsVectorLayer *vlayer, const QgsRectangle &bbox ) const
168+
{
169+
if ( vlayer->geometryType() != QGis::Line )
170+
return false;
171+
172+
bool begin = false;
173+
bool end = false;
174+
const QgsPoint beginPoint = points().first();
175+
const QgsPoint endPoint = points().last();
176+
177+
QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( bbox ).setSubsetOfAttributes( QgsAttributeList() ) );
178+
QgsFeature f;
179+
180+
// check that extremities of the new line are contained by features
181+
while ( fit.nextFeature( f ) )
182+
{
183+
const QgsGeometry *geom = f.geometry();
184+
if ( geom )
138185
{
139-
vlayer->destroyEditCommand();
140-
}
186+
const QgsPolyline line = geom->asPolyline();
141187

142-
stopCapturing();
188+
if ( line.contains( beginPoint ) )
189+
begin = true;
190+
else if ( line.contains( endPoint ) )
191+
end = true;
192+
}
143193
}
194+
195+
return end && begin;
144196
}

src/app/qgsmaptoolreshape.h

+7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ class APP_EXPORT QgsMapToolReshape: public QgsMapToolCapture
2828
QgsMapToolReshape( QgsMapCanvas* canvas );
2929
virtual ~QgsMapToolReshape();
3030
void cadCanvasReleaseEvent( QgsMapMouseEvent * e ) override;
31+
32+
private:
33+
void reshape( QgsVectorLayer *vlayer );
34+
35+
bool isBindingLine( QgsVectorLayer *vlayer, const QgsRectangle &bbox ) const;
36+
37+
friend class TestQgsMapToolReshape;
3138
};
3239

3340
#endif

src/gui/qgsmaptoolcapture.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ int QgsMapToolCapture::size()
713713
return mCaptureCurve.numPoints();
714714
}
715715

716-
QList<QgsPoint> QgsMapToolCapture::points()
716+
QList<QgsPoint> QgsMapToolCapture::points() const
717717
{
718718
QgsPointSequenceV2 pts;
719719
QList<QgsPoint> points;

src/gui/qgsmaptoolcapture.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
192192
* List of digitized points
193193
* @return List of points
194194
*/
195-
QList<QgsPoint> points();
195+
QList<QgsPoint> points() const;
196196

197197
/**
198198
* List of digitized points with z support
@@ -248,6 +248,8 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
248248

249249
QgsVertexMarker* mSnappingMarker;
250250

251+
friend class TestQgsMapToolReshape;
252+
251253
#ifdef Q_OS_WIN
252254
int mSkipNextContextMenuEvent;
253255
#endif

tests/src/app/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
1616
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/gui/attributetable
1717
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/gui/symbology-ng
1818
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/gui/raster
19+
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/gui/layertree
1920
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/python
2021
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/app
2122
${CMAKE_CURRENT_SOURCE_DIR}/../../../src/app/pluginmanager
@@ -108,5 +109,6 @@ ADD_QGIS_TEST(attributetabletest testqgsattributetable.cpp)
108109
ADD_QGIS_TEST(fieldcalculatortest testqgsfieldcalculator.cpp)
109110
ADD_QGIS_TEST(maptoolidentifyaction testqgsmaptoolidentifyaction.cpp)
110111
ADD_QGIS_TEST(maptoolselect testqgsmaptoolselect.cpp)
112+
ADD_QGIS_TEST(maptoolreshape testqgsmaptoolreshape.cpp)
111113
ADD_QGIS_TEST(measuretool testqgsmeasuretool.cpp)
112114
ADD_QGIS_TEST(vectorlayersaveasdialogtest testqgsvectorlayersaveasdialog.cpp)
+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/***************************************************************************
2+
testqgsmaptoolreshape.cpp
3+
--------------------------------
4+
Date : 2017-12-14
5+
Copyright : (C) 2017 by Paul Blottiere
6+
Email : paul.blottiere@oslandia.com
7+
***************************************************************************
8+
* *
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. *
13+
* *
14+
***************************************************************************/
15+
16+
#include <QtTest/QtTest>
17+
#include "qgsapplication.h"
18+
#include "qgsmapcanvas.h"
19+
#include "qgsvectorlayer.h"
20+
#include "qgslinestringv2.h"
21+
#include "qgsmaptoolreshape.h"
22+
#include "qgsvectordataprovider.h"
23+
#include "qgsmaplayerregistry.h"
24+
#include "qgisapp.h"
25+
26+
class TestQgsMapToolReshape : public QObject
27+
{
28+
Q_OBJECT
29+
public:
30+
TestQgsMapToolReshape() = default;
31+
32+
private slots:
33+
void initTestCase(); // will be called before the first testfunction is executed.
34+
void cleanupTestCase(); // will be called after the last testfunction was executed.
35+
void init(); // will be called before each testfunction is executed.
36+
void cleanup(); // will be called after every testfunction.
37+
38+
void reshapeWithBindingLine();
39+
40+
private:
41+
QgisApp *mQgisApp = nullptr;
42+
};
43+
44+
void TestQgsMapToolReshape::initTestCase()
45+
{
46+
QgsApplication::init();
47+
QgsApplication::initQgis();
48+
49+
// Set up the QgsSettings environment
50+
QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
51+
QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
52+
QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
53+
54+
QgsApplication::showSettings();
55+
56+
// enforce C locale because the tests expect it
57+
// (decimal separators / thousand separators)
58+
QLocale::setDefault( QLocale::c() );
59+
60+
mQgisApp = new QgisApp();
61+
}
62+
63+
void TestQgsMapToolReshape::cleanupTestCase()
64+
{
65+
QgsApplication::exitQgis();
66+
}
67+
68+
void TestQgsMapToolReshape::init()
69+
{
70+
}
71+
72+
void TestQgsMapToolReshape::cleanup()
73+
{
74+
}
75+
76+
void TestQgsMapToolReshape::reshapeWithBindingLine()
77+
{
78+
// prepare vector layer
79+
QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326&field=name:string(20)" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
80+
81+
QgsGeometry *g0 = QgsGeometry::fromWkt( "LineString (0 0, 1 1, 1 2)" );
82+
QgsFeature f0;
83+
f0.setGeometry( g0 );
84+
f0.setAttribute( 0, "polyline0" );
85+
86+
QgsGeometry *g1 = QgsGeometry::fromWkt( "LineString (2 1, 3 2, 3 3, 2 2)" );
87+
QgsFeature f1;
88+
f1.setGeometry( g1 );
89+
f1.setAttribute( 0, "polyline1" );
90+
91+
vl->dataProvider()->addFeatures( QgsFeatureList() << f0 << f1 );
92+
93+
std::cout << "Feature count: " << vl->featureCount() << std::endl;
94+
95+
// prepare canvas
96+
QList<QgsMapLayer *> layers;
97+
layers.append( vl );
98+
99+
QgsCoordinateReferenceSystem srs( 4326, QgsCoordinateReferenceSystem::EpsgCrsId );
100+
QgsMapLayerRegistry::instance()->addMapLayer( vl );
101+
mQgisApp->mapCanvas()->setDestinationCrs( srs );
102+
mQgisApp->mapCanvas()->setCurrentLayer( vl );
103+
104+
// reshape to add line to polyline0
105+
QgsLineStringV2 cl0;
106+
cl0.setPoints( QgsPointSequenceV2() << QgsPointV2( 1, 2 ) << QgsPointV2( 2, 1 ) );
107+
108+
QgsAbstractGeometryV2 *curveGgeom0 = cl0.toCurveType();
109+
QgsCompoundCurveV2 curve0;
110+
curve0.fromWkt( curveGgeom0->asWkt() );
111+
112+
QgsMapToolReshape tool0( mQgisApp->mapCanvas() );
113+
tool0.mCaptureCurve = curve0;
114+
115+
vl->startEditing();
116+
tool0.reshape( vl );
117+
118+
vl->getFeatures( QgsFeatureRequest().setFilterFid( 1 ) ).nextFeature( f0 );
119+
QCOMPARE( f0.geometry()->exportToWkt(), QStringLiteral( "LineString (0 0, 1 1, 1 2, 2 1)" ) );
120+
121+
vl->getFeatures( QgsFeatureRequest().setFilterFid( 2 ) ).nextFeature( f1 );
122+
QCOMPARE( f1.geometry()->exportToWkt(), QStringLiteral( "LineString (2 1, 3 2, 3 3, 2 2)" ) );
123+
124+
vl->rollBack();
125+
126+
// reshape to add line to polyline1
127+
QgsLineStringV2 cl1;
128+
cl1.setPoints( QgsPointSequenceV2() << QgsPointV2( 2, 1 ) << QgsPointV2( 1, 2 ) );
129+
130+
QgsAbstractGeometryV2 *curveGeom1 = cl1.toCurveType();
131+
QgsCompoundCurveV2 curve1;
132+
curve1.fromWkt( curveGeom1->asWkt() );
133+
134+
QgsMapToolReshape tool1( mQgisApp->mapCanvas() );
135+
tool1.mCaptureCurve = curve1;
136+
137+
vl->startEditing();
138+
tool1.reshape( vl );
139+
140+
vl->getFeatures( QgsFeatureRequest().setFilterFid( 1 ) ).nextFeature( f0 );
141+
QCOMPARE( f0.geometry()->exportToWkt(), QStringLiteral( "LineString (0 0, 1 1, 1 2)" ) );
142+
143+
vl->getFeatures( QgsFeatureRequest().setFilterFid( 2 ) ).nextFeature( f1 );
144+
QCOMPARE( f1.geometry()->exportToWkt(), QStringLiteral( "LineString (1 2, 2 1, 3 2, 3 3, 2 2)" ) );
145+
146+
vl->rollBack();
147+
}
148+
149+
QTEST_MAIN( TestQgsMapToolReshape )
150+
#include "testqgsmaptoolreshape.moc"

0 commit comments

Comments
 (0)