Skip to content

Commit 2f385da

Browse files
committed
[offset tool] handle rings and allow selecting by area
* correctly handles rings and parts * allow to select polygon by area (not only edge)
1 parent 08bcb1c commit 2f385da

File tree

5 files changed

+220
-46
lines changed

5 files changed

+220
-46
lines changed

python/core/qgssnappingutils.sip.in

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,17 @@ Snap to map according to the current configuration. Optional filter allows disca
5353
%End
5454
QgsPointLocator::Match snapToMap( const QgsPointXY &pointMap, QgsPointLocator::MatchFilter *filter = 0 );
5555

56-
QgsPointLocator::Match snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter = 0 );
56+
57+
QgsPointLocator::Match snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter = 0, double tolerance = 0.0 );
5758
%Docstring
58-
Snap to current layer
59+
snapToCurrentLayer snap to current layer with given settings
60+
61+
:param point: the point to snap from
62+
:param type: the matching type (vertex, edge, area)
63+
:param filter: the matching filter
64+
:param tolerance: the tolerance in project units. If left to 0, it is calculated from current map settings
65+
66+
:return: the snapping match
5967
%End
6068

6169

src/app/qgsmaptooloffsetcurve.cpp

Lines changed: 194 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -72,33 +72,17 @@ void QgsMapToolOffsetCurve::canvasReleaseEvent( QgsMapMouseEvent *e )
7272
deleteRubberBandAndGeometry();
7373
mGeometryModified = false;
7474

75-
QgsSnappingUtils *snapping = mCanvas->snappingUtils();
76-
77-
// store previous settings
78-
QgsSnappingConfig oldConfig = snapping->config();
79-
QgsSnappingConfig config = snapping->config();
80-
// setup new settings (temporary)
81-
QgsSettings settings;
82-
config.setEnabled( true );
83-
config.setMode( QgsSnappingConfig::ActiveLayer );
84-
config.setType( QgsSnappingConfig::Segment );
85-
config.setTolerance( settings.value( QStringLiteral( "qgis/digitizing/search_radius_vertex_edit" ), 10 ).toDouble() );
86-
config.setUnits( static_cast<QgsTolerance::UnitType>( settings.value( QStringLiteral( "qgis/digitizing/search_radius_vertex_edit_unit" ), QgsTolerance::Pixels ).toInt() ) );
87-
snapping->setConfig( config );
88-
89-
QgsPointLocator::Match match = snapping->snapToMap( e->pos() );
90-
91-
// restore old settings
92-
snapping->setConfig( oldConfig );
93-
94-
if ( match.hasEdge() && match.layer() )
75+
QgsPointLocator::Match match = mCanvas->snappingUtils()->snapToCurrentLayer( e->pos(),
76+
QgsPointLocator::Types( QgsPointLocator::Edge | QgsPointLocator::Area ) );
77+
78+
if ( ( match.hasEdge() || match.hasArea() ) && match.layer() )
9579
{
9680
mLayer = match.layer();
9781
QgsFeature fet;
9882
if ( match.layer()->getFeatures( QgsFeatureRequest( match.featureId() ) ).nextFeature( fet ) )
9983
{
10084
mCtrlHeldOnFirstClick = ( e->modifiers() & Qt::ControlModifier ); //no geometry modification if ctrl is pressed
101-
prepareGeometry( match.layer(), match, fet );
85+
prepareGeometry( match, fet );
10286
mRubberBand = createRubberBand();
10387
if ( mRubberBand )
10488
{
@@ -143,7 +127,7 @@ void QgsMapToolOffsetCurve::applyOffset( const double &offset, const Qt::Keyboar
143127
return;
144128
}
145129

146-
if ( mMultiPartGeometry )
130+
if ( mModifiedPart >= 0 )
147131
{
148132
QgsGeometry geometry;
149133
int partIndex = 0;
@@ -170,19 +154,75 @@ void QgsMapToolOffsetCurve::applyOffset( const double &offset, const Qt::Keyboar
170154
else
171155
{
172156
QgsMultiPolygonXY newMultiPoly;
173-
QgsMultiPolygonXY multiPoly = mOriginalGeometry.asMultiPolygon();
157+
const QgsMultiPolygonXY multiPoly = mOriginalGeometry.asMultiPolygon();
174158
QgsMultiPolygonXY::const_iterator multiPolyIt = multiPoly.constBegin();
175159
for ( ; multiPolyIt != multiPoly.constEnd(); ++multiPolyIt )
176160
{
177161
if ( partIndex == mModifiedPart )
178162
{
179163
if ( mModifiedGeometry.isMultipart() )
180164
{
181-
newMultiPoly += mModifiedGeometry.asMultiPolygon();
165+
// not a ring
166+
if ( mModifiedRing <= 0 )
167+
{
168+
// part became mulitpolygon, that means discard original rings from the part
169+
newMultiPoly += mModifiedGeometry.asMultiPolygon();
170+
}
171+
else
172+
{
173+
// ring became multipolygon, oh boy!
174+
QgsPolygonXY newPoly;
175+
int ringIndex = 0;
176+
QgsPolygonXY::const_iterator polyIt = multiPolyIt->constBegin();
177+
for ( ; polyIt != multiPolyIt->constEnd(); ++polyIt )
178+
{
179+
if ( ringIndex == mModifiedRing )
180+
{
181+
const QgsMultiPolygonXY ringParts = mModifiedGeometry.asMultiPolygon();
182+
QgsPolygonXY newRings;
183+
QgsMultiPolygonXY::const_iterator ringIt = ringParts.constBegin();
184+
for ( ; ringIt != ringParts.constEnd(); ++ringIt )
185+
{
186+
// the different parts of the new rings cannot have rings themselves
187+
newRings.append( ringIt->at( 0 ) );
188+
}
189+
newPoly += newRings;
190+
}
191+
else
192+
{
193+
newPoly.append( *polyIt );
194+
}
195+
ringIndex++;
196+
}
197+
newMultiPoly.append( newPoly );
198+
}
182199
}
183200
else
184201
{
185-
newMultiPoly.append( mModifiedGeometry.asPolygon() );
202+
// original part had no ring
203+
if ( mModifiedRing == -1 )
204+
{
205+
newMultiPoly.append( mModifiedGeometry.asPolygon() );
206+
}
207+
else
208+
{
209+
QgsPolygonXY newPoly;
210+
int ringIndex = 0;
211+
QgsPolygonXY::const_iterator polyIt = multiPolyIt->constBegin();
212+
for ( ; polyIt != multiPolyIt->constEnd(); ++polyIt )
213+
{
214+
if ( ringIndex == mModifiedRing )
215+
{
216+
newPoly.append( mModifiedGeometry.asPolygon().at( 0 ) );
217+
}
218+
else
219+
{
220+
newPoly.append( *polyIt );
221+
}
222+
ringIndex++;
223+
}
224+
newMultiPoly.append( newPoly );
225+
}
186226
}
187227
}
188228
else
@@ -196,6 +236,72 @@ void QgsMapToolOffsetCurve::applyOffset( const double &offset, const Qt::Keyboar
196236
geometry.convertToMultiType();
197237
mModifiedGeometry = geometry;
198238
}
239+
else if ( mModifiedRing >= 0 )
240+
{
241+
// original geometry had some rings
242+
if ( mModifiedGeometry.isMultipart() )
243+
{
244+
// not a ring
245+
if ( mModifiedRing == 0 )
246+
{
247+
// polygon became mulitpolygon, that means discard original rings from the part
248+
// keep the modified geometry as is
249+
}
250+
else
251+
{
252+
QgsPolygonXY newPoly;
253+
const QgsPolygonXY poly = mOriginalGeometry.asPolygon();
254+
255+
// ring became multipolygon, oh boy!
256+
int ringIndex = 0;
257+
QgsPolygonXY::const_iterator polyIt = poly.constBegin();
258+
for ( ; polyIt != poly.constEnd(); ++polyIt )
259+
{
260+
if ( ringIndex == mModifiedRing )
261+
{
262+
QgsMultiPolygonXY ringParts = mModifiedGeometry.asMultiPolygon();
263+
QgsPolygonXY newRings;
264+
QgsMultiPolygonXY::const_iterator ringIt = ringParts.constBegin();
265+
for ( ; ringIt != ringParts.constEnd(); ++ringIt )
266+
{
267+
// the different parts of the new rings cannot have rings themselves
268+
newRings.append( ringIt->at( 0 ) );
269+
}
270+
newPoly += newRings;
271+
}
272+
else
273+
{
274+
newPoly.append( *polyIt );
275+
}
276+
ringIndex++;
277+
}
278+
mModifiedGeometry = QgsGeometry::fromPolygonXY( newPoly );
279+
}
280+
}
281+
else
282+
{
283+
// simple case where modified geom is a polygon (not multi)
284+
QgsPolygonXY newPoly;
285+
const QgsPolygonXY poly = mOriginalGeometry.asPolygon();
286+
287+
int ringIndex = 0;
288+
QgsPolygonXY::const_iterator polyIt = poly.constBegin();
289+
for ( ; polyIt != poly.constEnd(); ++polyIt )
290+
{
291+
if ( ringIndex == mModifiedRing )
292+
{
293+
newPoly.append( mModifiedGeometry.asPolygon().at( 0 ) );
294+
}
295+
else
296+
{
297+
newPoly.append( *polyIt );
298+
}
299+
ringIndex++;
300+
}
301+
mModifiedGeometry = QgsGeometry::fromPolygonXY( newPoly );
302+
}
303+
304+
}
199305

200306
mLayer->beginEditCommand( tr( "Offset curve" ) );
201307

@@ -288,17 +394,18 @@ void QgsMapToolOffsetCurve::canvasMoveEvent( QgsMapMouseEvent *e )
288394
updateGeometryAndRubberBand( offset );
289395
}
290396

291-
void QgsMapToolOffsetCurve::prepareGeometry( QgsVectorLayer *vl, const QgsPointLocator::Match &match, QgsFeature &snappedFeature )
397+
void QgsMapToolOffsetCurve::prepareGeometry( const QgsPointLocator::Match &match, QgsFeature &snappedFeature )
292398
{
399+
QgsVectorLayer *vl = match.layer();
293400
if ( !vl )
294401
{
295402
return;
296403
}
297404

298405
mOriginalGeometry = QgsGeometry();
299406
mManipulatedGeometry = QgsGeometry();
300-
mMultiPartGeometry = false;
301-
mModifiedPart = 0;
407+
mModifiedPart = -1;
408+
mModifiedRing = -1;
302409

303410
//assign feature part by vertex number (snap to vertex) or by before vertex number (snap to segment)
304411
QgsGeometry geom = snappedFeature.geometry();
@@ -311,14 +418,16 @@ void QgsMapToolOffsetCurve::prepareGeometry( QgsVectorLayer *vl, const QgsPointL
311418
QgsWkbTypes::Type geomType = geom.wkbType();
312419
if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::LineGeometry )
313420
{
421+
if ( !match.hasEdge() )
422+
{
423+
return;
424+
}
314425
if ( !geom.isMultipart() )
315426
{
316427
mManipulatedGeometry = geom;
317428
}
318429
else
319430
{
320-
mMultiPartGeometry = true;
321-
322431
int vertex = match.vertexIndex();
323432
QgsVertexId vertexId;
324433
geom.vertexIdFromVertexNr( vertex, vertexId );
@@ -330,21 +439,67 @@ void QgsMapToolOffsetCurve::prepareGeometry( QgsVectorLayer *vl, const QgsPointL
330439
}
331440
else if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::PolygonGeometry )
332441
{
333-
if ( !geom.isMultipart() )
442+
if ( !match.hasEdge() && match.hasArea() )
334443
{
335-
mManipulatedGeometry = geom;
444+
if ( !geom.isMultipart() )
445+
{
446+
mManipulatedGeometry = geom;
447+
}
448+
else
449+
{
450+
// get the correct part
451+
QgsMultiPolygonXY mpolygon = geom.asMultiPolygon();
452+
for ( int part = 0; part < mpolygon.count(); part++ ) // go through the polygons
453+
{
454+
const QgsPolygonXY &polygon = mpolygon[part];
455+
QgsGeometry partGeo = QgsGeometry::fromPolygonXY( polygon );
456+
const QgsPointXY layerCoords = match.point();
457+
if ( partGeo.contains( &layerCoords ) )
458+
{
459+
mModifiedPart = part;
460+
mManipulatedGeometry = partGeo;
461+
}
462+
}
463+
}
336464
}
337-
else
465+
else if ( match.hasEdge() )
338466
{
339-
mMultiPartGeometry = true;
340-
341467
int vertex = match.vertexIndex();
342468
QgsVertexId vertexId;
343469
geom.vertexIdFromVertexNr( vertex, vertexId );
344-
mModifiedPart = vertexId.part;
470+
QgsDebugMsg( QString( "%1" ).arg( vertexId.ring ) );
345471

346-
QgsMultiPolygonXY multiPoly = geom.asMultiPolygon();
347-
mManipulatedGeometry = QgsGeometry::fromPolygonXY( multiPoly.at( mModifiedPart ) );
472+
if ( !geom.isMultipart() )
473+
{
474+
QgsPolygonXY poly = geom.asPolygon();
475+
// if has rings
476+
if ( poly.count() > 0 )
477+
{
478+
mModifiedRing = vertexId.ring;
479+
mManipulatedGeometry = QgsGeometry::fromPolygonXY( QgsPolygonXY() << poly.at( mModifiedRing ) );
480+
}
481+
else
482+
{
483+
mManipulatedGeometry = QgsGeometry::fromPolygonXY( poly );
484+
}
485+
486+
}
487+
else
488+
{
489+
mModifiedPart = vertexId.part;
490+
// get part, get ring
491+
QgsMultiPolygonXY multiPoly = geom.asMultiPolygon();
492+
// if has rings
493+
if ( multiPoly.at( mModifiedPart ).count() > 0 )
494+
{
495+
mModifiedRing = vertexId.ring;
496+
mManipulatedGeometry = QgsGeometry::fromPolygonXY( QgsPolygonXY() << multiPoly.at( mModifiedPart ).at( mModifiedRing ) );
497+
}
498+
else
499+
{
500+
mManipulatedGeometry = QgsGeometry::fromPolygonXY( multiPoly.at( mModifiedPart ) );
501+
}
502+
}
348503
}
349504
}
350505
}
@@ -421,6 +576,7 @@ void QgsMapToolOffsetCurve::updateGeometryAndRubberBand( double offset )
421576
deleteUserInputWidget();
422577
mLayer = nullptr;
423578
mGeometryModified = false;
579+
emit messageDiscarded();
424580
emit messageEmitted( tr( "Creating offset geometry failed: %1" ).arg( offsetGeom.lastError() ), Qgis::Critical );
425581
}
426582
else

src/app/qgsmaptooloffsetcurve.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ class APP_EXPORT QgsMapToolOffsetCurve: public QgsMapToolEdit
8888
QgsGeometry mModifiedGeometry;
8989
//! ID of manipulated feature
9090
QgsFeatureId mModifiedFeature = -1;
91-
bool mMultiPartGeometry = false;
92-
int mModifiedPart = 0;
91+
int mModifiedPart = -1;
92+
int mModifiedRing = -1;
9393

9494
//! Internal flag to distinguish move from click
9595
bool mGeometryModified = false;
@@ -105,7 +105,7 @@ class APP_EXPORT QgsMapToolOffsetCurve: public QgsMapToolEdit
105105
void createUserInputWidget();
106106
void deleteUserInputWidget();
107107

108-
void prepareGeometry( QgsVectorLayer *vl, const QgsPointLocator::Match &match, QgsFeature &snappedFeature );
108+
void prepareGeometry( const QgsPointLocator::Match &match, QgsFeature &snappedFeature );
109109

110110
void deleteRubberBandAndGeometry();
111111

src/core/qgssnappingutils.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,13 +440,14 @@ void QgsSnappingUtils::toggleEnabled()
440440
emit configChanged( mSnappingConfig );
441441
}
442442

443-
QgsPointLocator::Match QgsSnappingUtils::snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter )
443+
QgsPointLocator::Match QgsSnappingUtils::snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter, double tolerance )
444444
{
445445
if ( !mCurrentLayer )
446446
return QgsPointLocator::Match();
447447

448448
QgsPointXY pointMap = mMapSettings.mapToPixel().toMapCoordinates( point );
449-
double tolerance = QgsTolerance::vertexSearchRadius( mMapSettings );
449+
if ( tolerance == 0.0 )
450+
tolerance = QgsTolerance::vertexSearchRadius( mMapSettings );
450451

451452
QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
452453
if ( !loc )

src/core/qgssnappingutils.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,16 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
6666
QgsPointLocator::Match snapToMap( const QgsPointXY &pointMap, QgsPointLocator::MatchFilter *filter = nullptr );
6767

6868
//! Snap to current layer
69-
QgsPointLocator::Match snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter = nullptr );
69+
70+
/**
71+
* \brief snapToCurrentLayer snap to current layer with given settings
72+
* \param point the point to snap from
73+
* \param type the matching type (vertex, edge, area)
74+
* \param filter the matching filter
75+
* \param tolerance the tolerance in project units. If left to 0, it is calculated from current map settings
76+
* \return the snapping match
77+
*/
78+
QgsPointLocator::Match snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter = nullptr, double tolerance = 0.0 );
7079

7180
// environment setup
7281

0 commit comments

Comments
 (0)