Skip to content

Commit 14e6df4

Browse files
authored
Merge pull request #5780 from pblottiere/bugfix_reshape2
[bugfix] Do not add binding line in both side in reshape map tool
2 parents ed50762 + b7fe407 commit 14e6df4

7 files changed

+252
-57
lines changed

python/gui/qgsmaptoolcapture.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ Removes the last vertex from mRubberBand and mCaptureList
151151
:rtype: int
152152
%End
153153

154-
QVector<QgsPointXY> points();
154+
QVector<QgsPointXY> points() const;
155155
%Docstring
156156
List of digitized points
157157
:return: List of points

src/app/qgsmaptoolreshape.cpp

+96-54
Original file line numberDiff line numberDiff line change
@@ -74,74 +74,116 @@ void QgsMapToolReshape::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
7474
stopCapturing();
7575
return;
7676
}
77-
QgsPointXY firstPoint = points().at( 0 );
78-
QgsRectangle bbox( firstPoint.x(), firstPoint.y(), firstPoint.x(), firstPoint.y() );
79-
for ( int i = 1; i < size(); ++i )
77+
78+
reshape( vlayer );
79+
80+
stopCapturing();
81+
}
82+
}
83+
84+
bool QgsMapToolReshape::isBindingLine( QgsVectorLayer *vlayer, const QgsRectangle &bbox ) const
85+
{
86+
if ( vlayer->geometryType() != QgsWkbTypes::LineGeometry )
87+
return false;
88+
89+
bool begin = false;
90+
bool end = false;
91+
const QgsPointXY beginPoint = points().first();
92+
const QgsPointXY endPoint = points().last();
93+
94+
QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( bbox ).setSubsetOfAttributes( QgsAttributeList() ) );
95+
QgsFeature f;
96+
97+
// check that extremities of the new line are contained by features
98+
while ( fit.nextFeature( f ) )
99+
{
100+
const QgsGeometry geom = f.geometry();
101+
if ( !geom.isNull() )
80102
{
81-
bbox.combineExtentWith( points().at( i ).x(), points().at( i ).y() );
103+
const QgsPolylineXY line = geom.asPolyline();
104+
105+
if ( line.contains( beginPoint ) )
106+
begin = true;
107+
else if ( line.contains( endPoint ) )
108+
end = true;
82109
}
110+
}
83111

84-
QgsLineString reshapeLineString( points() );
85-
if ( QgsWkbTypes::hasZ( vlayer->wkbType() ) )
86-
reshapeLineString.addZValue( defaultZValue() );
112+
return end && begin;
113+
}
114+
115+
void QgsMapToolReshape::reshape( QgsVectorLayer *vlayer )
116+
{
117+
QgsPointXY firstPoint = points().at( 0 );
118+
QgsRectangle bbox( firstPoint.x(), firstPoint.y(), firstPoint.x(), firstPoint.y() );
119+
for ( int i = 1; i < size(); ++i )
120+
{
121+
bbox.combineExtentWith( points().at( i ).x(), points().at( i ).y() );
122+
}
87123

88-
//query all the features that intersect bounding box of capture line
89-
QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( bbox ).setSubsetOfAttributes( QgsAttributeList() ) );
90-
QgsFeature f;
91-
int reshapeReturn;
92-
bool reshapeDone = false;
124+
QgsLineString reshapeLineString( points() );
125+
if ( QgsWkbTypes::hasZ( vlayer->wkbType() ) )
126+
reshapeLineString.addZValue( defaultZValue() );
93127

94-
vlayer->beginEditCommand( tr( "Reshape" ) );
95-
while ( fit.nextFeature( f ) )
128+
//query all the features that intersect bounding box of capture line
129+
QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( bbox ).setSubsetOfAttributes( QgsAttributeList() ) );
130+
QgsFeature f;
131+
int reshapeReturn;
132+
bool reshapeDone = false;
133+
bool isBinding = isBindingLine( vlayer, bbox );
134+
135+
vlayer->beginEditCommand( tr( "Reshape" ) );
136+
while ( fit.nextFeature( f ) )
137+
{
138+
//query geometry
139+
//call geometry->reshape(mCaptureList)
140+
//register changed geometry in vector layer
141+
QgsGeometry geom = f.geometry();
142+
if ( !geom.isNull() )
96143
{
97-
//query geometry
98-
//call geometry->reshape(mCaptureList)
99-
//register changed geometry in vector layer
100-
QgsGeometry geom = f.geometry();
101-
if ( !geom.isNull() )
144+
// in case of a binding line, we just want to update the line from
145+
// the starting point and not both side
146+
if ( isBinding && !geom.asPolyline().contains( points().first() ) )
147+
continue;
148+
149+
reshapeReturn = geom.reshapeGeometry( reshapeLineString );
150+
if ( reshapeReturn == 0 )
102151
{
103-
reshapeReturn = geom.reshapeGeometry( reshapeLineString );
104-
if ( reshapeReturn == 0 )
152+
//avoid intersections on polygon layers
153+
if ( vlayer->geometryType() == QgsWkbTypes::PolygonGeometry )
105154
{
106-
//avoid intersections on polygon layers
107-
if ( vlayer->geometryType() == QgsWkbTypes::PolygonGeometry )
155+
//ignore all current layer features as they should be reshaped too
156+
QHash<QgsVectorLayer *, QSet<QgsFeatureId> > ignoreFeatures;
157+
ignoreFeatures.insert( vlayer, vlayer->allFeatureIds() );
158+
159+
if ( geom.avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers(), ignoreFeatures ) != 0 )
108160
{
109-
//ignore all current layer features as they should be reshaped too
110-
QHash<QgsVectorLayer *, QSet<QgsFeatureId> > ignoreFeatures;
111-
ignoreFeatures.insert( vlayer, vlayer->allFeatureIds() );
112-
113-
if ( geom.avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers(), ignoreFeatures ) != 0 )
114-
{
115-
emit messageEmitted( tr( "An error was reported during intersection removal" ), QgsMessageBar::CRITICAL );
116-
vlayer->destroyEditCommand();
117-
stopCapturing();
118-
return;
119-
}
120-
121-
if ( geom.isEmpty() ) //intersection removal might have removed the whole geometry
122-
{
123-
emit messageEmitted( tr( "The feature cannot be reshaped because the resulting geometry is empty" ), QgsMessageBar::CRITICAL );
124-
vlayer->destroyEditCommand();
125-
stopCapturing();
126-
return;
127-
}
161+
emit messageEmitted( tr( "An error was reported during intersection removal" ), QgsMessageBar::CRITICAL );
162+
vlayer->destroyEditCommand();
163+
stopCapturing();
164+
return;
128165
}
129166

130-
vlayer->changeGeometry( f.id(), geom );
131-
reshapeDone = true;
167+
if ( geom.isEmpty() ) //intersection removal might have removed the whole geometry
168+
{
169+
emit messageEmitted( tr( "The feature cannot be reshaped because the resulting geometry is empty" ), QgsMessageBar::CRITICAL );
170+
vlayer->destroyEditCommand();
171+
return;
172+
}
132173
}
133-
}
134-
}
135174

136-
if ( reshapeDone )
137-
{
138-
vlayer->endEditCommand();
139-
}
140-
else
141-
{
142-
vlayer->destroyEditCommand();
175+
vlayer->changeGeometry( f.id(), geom );
176+
reshapeDone = true;
177+
}
143178
}
179+
}
144180

145-
stopCapturing();
181+
if ( reshapeDone )
182+
{
183+
vlayer->endEditCommand();
184+
}
185+
else
186+
{
187+
vlayer->destroyEditCommand();
146188
}
147189
}

src/app/qgsmaptoolreshape.h

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

3239
#endif

src/gui/qgsmaptoolcapture.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,7 @@ int QgsMapToolCapture::size()
763763
return mCaptureCurve.numPoints();
764764
}
765765

766-
QVector<QgsPointXY> QgsMapToolCapture::points()
766+
QVector<QgsPointXY> QgsMapToolCapture::points() const
767767
{
768768
QgsPointSequence pts;
769769
QVector<QgsPointXY> 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
* \returns List of points
194194
*/
195-
QVector<QgsPointXY> points();
195+
QVector<QgsPointXY> points() const;
196196

197197
/**
198198
* Set the points on which to work
@@ -259,6 +259,8 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
259259
*/
260260
QgsPointXY mTracingStartPoint;
261261

262+
friend class TestQgsMapToolReshape;
263+
262264
#ifdef Q_OS_WIN
263265
int mSkipNextContextMenuEvent;
264266
#endif

tests/src/app/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ ADD_QGIS_TEST(fieldcalculatortest testqgsfieldcalculator.cpp)
9898
ADD_QGIS_TEST(maptooladdfeature testqgsmaptooladdfeature.cpp)
9999
ADD_QGIS_TEST(maptoolidentifyaction testqgsmaptoolidentifyaction.cpp)
100100
ADD_QGIS_TEST(maptoolselect testqgsmaptoolselect.cpp)
101+
ADD_QGIS_TEST(maptoolreshape testqgsmaptoolreshape.cpp)
101102
ADD_QGIS_TEST(measuretool testqgsmeasuretool.cpp)
102103
ADD_QGIS_TEST(nodetool testqgsnodetool.cpp)
103104
ADD_QGIS_TEST(vectorlayersaveasdialogtest testqgsvectorlayersaveasdialog.cpp)
+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/***************************************************************************
2+
testqgsmaptoolreshape.cpp
3+
--------------------------------
4+
Date : 2017-12-1
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 "qgstest.h"
17+
#include "qgsapplication.h"
18+
#include "qgsmapcanvas.h"
19+
#include "qgsvectorlayer.h"
20+
#include "qgslinestring.h"
21+
#include "qgsmaptoolreshape.h"
22+
#include "qgisapp.h"
23+
24+
class TestQgsMapToolReshape : public QObject
25+
{
26+
Q_OBJECT
27+
public:
28+
TestQgsMapToolReshape() = default;
29+
30+
private slots:
31+
void initTestCase(); // will be called before the first testfunction is executed.
32+
void cleanupTestCase(); // will be called after the last testfunction was executed.
33+
void init(); // will be called before each testfunction is executed.
34+
void cleanup(); // will be called after every testfunction.
35+
36+
void reshapeWithBindingLine();
37+
38+
private:
39+
QgisApp *mQgisApp = nullptr;
40+
};
41+
42+
void TestQgsMapToolReshape::initTestCase()
43+
{
44+
QgsApplication::init();
45+
QgsApplication::initQgis();
46+
47+
// Set up the QgsSettings environment
48+
QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
49+
QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
50+
QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
51+
52+
QgsApplication::showSettings();
53+
54+
// enforce C locale because the tests expect it
55+
// (decimal separators / thousand separators)
56+
QLocale::setDefault( QLocale::c() );
57+
58+
mQgisApp = new QgisApp();
59+
}
60+
61+
void TestQgsMapToolReshape::cleanupTestCase()
62+
{
63+
QgsApplication::exitQgis();
64+
}
65+
66+
void TestQgsMapToolReshape::init()
67+
{
68+
}
69+
70+
void TestQgsMapToolReshape::cleanup()
71+
{
72+
}
73+
74+
void TestQgsMapToolReshape::reshapeWithBindingLine()
75+
{
76+
// prepare vector layer
77+
std::unique_ptr<QgsVectorLayer> vl;
78+
vl.reset( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326&field=name:string(20)" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
79+
80+
QgsGeometry g0 = QgsGeometry::fromWkt( "LineString (0 0, 1 1, 1 2)" );
81+
QgsFeature f0;
82+
f0.setGeometry( g0 );
83+
f0.setAttribute( 0, "polyline0" );
84+
85+
QgsGeometry g1 = QgsGeometry::fromWkt( "LineString (2 1, 3 2, 3 3, 2 2)" );
86+
QgsFeature f1;
87+
f1.setGeometry( g1 );
88+
f1.setAttribute( 0, "polyline1" );
89+
90+
vl->dataProvider()->addFeatures( QgsFeatureList() << f0 << f1 );
91+
92+
// prepare canvas
93+
QList<QgsMapLayer *> layers;
94+
layers.append( vl.get() );
95+
96+
QgsCoordinateReferenceSystem srs( 4326, QgsCoordinateReferenceSystem::EpsgCrsId );
97+
mQgisApp->mapCanvas()->setDestinationCrs( srs );
98+
mQgisApp->mapCanvas()->setLayers( layers );
99+
mQgisApp->mapCanvas()->setCurrentLayer( vl.get() );
100+
101+
// reshape to add line to polyline0
102+
QgsLineString cl0;
103+
cl0.setPoints( QgsPointSequence() << QgsPoint( 1, 2 ) << QgsPoint( 2, 1 ) );
104+
105+
QgsCompoundCurve curve0( *cl0.toCurveType() );
106+
107+
QgsMapToolReshape tool0( mQgisApp->mapCanvas() );
108+
tool0.mCaptureCurve = curve0;
109+
110+
vl->startEditing();
111+
tool0.reshape( vl.get() );
112+
113+
f0 = vl->getFeature( 1 );
114+
QCOMPARE( f0.geometry().asWkt(), QStringLiteral( "LineString (0 0, 1 1, 1 2, 2 1)" ) );
115+
116+
f1 = vl->getFeature( 2 );
117+
QCOMPARE( f1.geometry().asWkt(), QStringLiteral( "LineString (2 1, 3 2, 3 3, 2 2)" ) );
118+
119+
vl->rollBack();
120+
121+
// reshape to add line to polyline1
122+
QgsLineString cl1;
123+
cl1.setPoints( QgsPointSequence() << QgsPoint( 2, 1 ) << QgsPoint( 1, 2 ) );
124+
125+
QgsCompoundCurve curve1( *cl1.toCurveType() );
126+
127+
QgsMapToolReshape tool1( mQgisApp->mapCanvas() );
128+
tool1.mCaptureCurve = curve1;
129+
130+
vl->startEditing();
131+
tool1.reshape( vl.get() );
132+
133+
f0 = vl->getFeature( 1 );
134+
QCOMPARE( f0.geometry().asWkt(), QStringLiteral( "LineString (0 0, 1 1, 1 2)" ) );
135+
136+
f1 = vl->getFeature( 2 );
137+
QCOMPARE( f1.geometry().asWkt(), QStringLiteral( "LineString (1 2, 2 1, 3 2, 3 3, 2 2)" ) );
138+
139+
vl->rollBack();
140+
}
141+
142+
QGSTEST_MAIN( TestQgsMapToolReshape )
143+
#include "testqgsmaptoolreshape.moc"

0 commit comments

Comments
 (0)