Skip to content

Commit

Permalink
[offset curve tool] fix two serious issues (#5760)
Browse files Browse the repository at this point in the history
- broken polygon support (fixes #15222)
- lost of parts when using tool with mutlipart features
  • Loading branch information
nirvn authored Nov 29, 2017
1 parent e0290a9 commit 2c65dbc
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 122 deletions.
3 changes: 1 addition & 2 deletions src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11404,7 +11404,6 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
mActionDeleteRing->setEnabled( false );
mActionDeletePart->setEnabled( false );
mActionReshapeFeatures->setEnabled( false );
mActionOffsetCurve->setEnabled( false );
mActionSplitFeatures->setEnabled( false );
mActionSplitParts->setEnabled( false );
mActionMergeFeatures->setEnabled( false );
Expand Down Expand Up @@ -11627,7 +11626,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
mActionSplitParts->setEnabled( isEditable && canChangeGeometry && isMultiPart );
mActionSimplifyFeature->setEnabled( isEditable && canChangeGeometry );
mActionDeleteRing->setEnabled( isEditable && canChangeGeometry );
mActionOffsetCurve->setEnabled( false );
mActionOffsetCurve->setEnabled( isEditable && canAddFeatures && canChangeAttributes );
}
else if ( vlayer->geometryType() == QgsWkbTypes::NullGeometry )
{
Expand Down
222 changes: 105 additions & 117 deletions src/app/qgsmaptooloffsetcurve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ void QgsMapToolOffsetCurve::canvasReleaseEvent( QgsMapMouseEvent *e )
// setup new settings (temporary)
QgsSettings settings;
config.setEnabled( true );
config.setMode( QgsSnappingConfig::AllLayers );
config.setMode( QgsSnappingConfig::ActiveLayer );
config.setType( QgsSnappingConfig::Segment );
config.setTolerance( settings.value( QStringLiteral( "qgis/digitizing/search_radius_vertex_edit" ), 10 ).toDouble() );
config.setUnits( static_cast<QgsTolerance::UnitType>( settings.value( QStringLiteral( "qgis/digitizing/search_radius_vertex_edit_unit" ), QgsTolerance::Pixels ).toInt() ) );
Expand All @@ -111,11 +111,11 @@ void QgsMapToolOffsetCurve::canvasReleaseEvent( QgsMapMouseEvent *e )
if ( match.layer()->getFeatures( QgsFeatureRequest( match.featureId() ) ).nextFeature( fet ) )
{
mCtrlWasHeldOnFeatureSelection = ( e->modifiers() & Qt::ControlModifier ); //no geometry modification if ctrl is pressed
mOriginalGeometry = createOriginGeometry( match.layer(), match, fet );
prepareGeometry( match.layer(), match, fet );
mRubberBand = createRubberBand();
if ( mRubberBand )
{
mRubberBand->setToGeometry( mOriginalGeometry, layer );
mRubberBand->setToGeometry( mManipulatedGeometry, layer );
}
mModifiedFeature = fet.id();
createDistanceWidget();
Expand Down Expand Up @@ -155,7 +155,56 @@ void QgsMapToolOffsetCurve::applyOffset( bool forceCopy )

if ( mMultiPartGeometry )
{
mModifiedGeometry.convertToMultiType();
QgsGeometry geometry;
int partIndex = 0;
QgsWkbTypes::Type geomType = mOriginalGeometry.wkbType();
if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::LineGeometry )
{
QgsMultiPolylineXY newMultiLine;
QgsMultiPolylineXY multiLine = mOriginalGeometry.asMultiPolyline();
QgsMultiPolylineXY::const_iterator it = multiLine.constBegin();
for ( ; it != multiLine.constEnd(); ++it )
{
if ( partIndex == mModifiedPart )
{
newMultiLine.append( mModifiedGeometry.asPolyline() );
}
else
{
newMultiLine.append( *it );
}
partIndex++;
}
geometry = QgsGeometry::fromMultiPolylineXY( newMultiLine );
}
else
{
QgsMultiPolygonXY newMultiPoly;
QgsMultiPolygonXY multiPoly = mOriginalGeometry.asMultiPolygon();
QgsMultiPolygonXY::const_iterator multiPolyIt = multiPoly.constBegin();
for ( ; multiPolyIt != multiPoly.constEnd(); ++multiPolyIt )
{
if ( partIndex == mModifiedPart )
{
if ( mModifiedGeometry.isMultipart() )
{
newMultiPoly += mModifiedGeometry.asMultiPolygon();
}
else
{
newMultiPoly.append( mModifiedGeometry.asPolygon() );
}
}
else
{
newMultiPoly.append( *multiPolyIt );
}
partIndex++;
}
geometry = QgsGeometry::fromMultiPolygonXY( newMultiPoly );
}
geometry.convertToMultiType();
mModifiedGeometry = geometry;
}

layer->beginEditCommand( tr( "Offset curve" ) );
Expand Down Expand Up @@ -248,73 +297,79 @@ void QgsMapToolOffsetCurve::canvasMoveEvent( QgsMapMouseEvent *e )
{
return;
}

offset = leftOf < 0 ? offset : -offset;


if ( mDistanceWidget )
{
// this will also set the rubber band
mDistanceWidget->setValue( leftOf < 0 ? offset : -offset );
mDistanceWidget->setValue( offset );
mDistanceWidget->setFocus( Qt::TabFocusReason );
}
else
{
//create offset geometry using geos
setOffsetForRubberBand( leftOf < 0 ? offset : -offset );
setOffsetForRubberBand( offset );
}
}

QgsGeometry QgsMapToolOffsetCurve::createOriginGeometry( QgsVectorLayer *vl, const QgsPointLocator::Match &match, QgsFeature &snappedFeature )
void QgsMapToolOffsetCurve::prepareGeometry( QgsVectorLayer *vl, const QgsPointLocator::Match &match, QgsFeature &snappedFeature )
{
if ( !vl )
{
return QgsGeometry();
return;
}

mOriginalGeometry = QgsGeometry();
mManipulatedGeometry = QgsGeometry();
mMultiPartGeometry = false;
//assign feature part by vertex number (snap to vertex) or by before vertex number (snap to segment)
int partVertexNr = match.vertexIndex();
mModifiedPart = 0;

if ( vl == currentVectorLayer() && !mCtrlWasHeldOnFeatureSelection )
//assign feature part by vertex number (snap to vertex) or by before vertex number (snap to segment)
QgsGeometry geom = snappedFeature.geometry();
if ( geom.isNull() )
{
//don't consider selected geometries, only the snap result
return convertToSingleLine( snappedFeature.geometry(), partVertexNr, mMultiPartGeometry );
return;
}
else //snapped to a background layer
mOriginalGeometry = geom;

QgsWkbTypes::Type geomType = geom.wkbType();
if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::LineGeometry )
{
//if source layer is polygon / multipolygon, create a linestring from the snapped ring
if ( vl->geometryType() == QgsWkbTypes::PolygonGeometry )
if ( !geom.isMultipart() )
{
//make linestring from polygon ring and return this geometry
return linestringFromPolygon( snappedFeature.geometry(), partVertexNr );
mManipulatedGeometry = geom;
}
else
{
mMultiPartGeometry = true;

int vertex = match.vertexIndex();
QgsVertexId vertexId;
geom.vertexIdFromVertexNr( vertex, vertexId );
mModifiedPart = vertexId.part;

//for background layers, try to merge selected entries together if snapped feature is contained in selection
const QgsFeatureIds &selection = vl->selectedFeatureIds();
if ( selection.empty() || !selection.contains( match.featureId() ) )
QgsMultiPolylineXY multiLine = geom.asMultiPolyline();
mManipulatedGeometry = QgsGeometry::fromPolylineXY( multiLine.at( mModifiedPart ) );
}
}
else if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::PolygonGeometry )
{
if ( !geom.isMultipart() )
{
return convertToSingleLine( snappedFeature.geometry(), partVertexNr, mMultiPartGeometry );
mManipulatedGeometry = geom;
}
else
{
//merge together if several features
QgsFeatureList selectedFeatures = vl->selectedFeatures();
QgsFeatureList::iterator selIt = selectedFeatures.begin();
QgsGeometry geom = selIt->geometry();
++selIt;
for ( ; selIt != selectedFeatures.end(); ++selIt )
{
geom = geom.combine( selIt->geometry() );
}
mMultiPartGeometry = true;

//if multitype, return only the snapped to geometry
if ( geom.isMultipart() )
{
return convertToSingleLine( snappedFeature.geometry(),
match.vertexIndex(), mMultiPartGeometry );
}
int vertex = match.vertexIndex();
QgsVertexId vertexId;
geom.vertexIdFromVertexNr( vertex, vertexId );
mModifiedPart = vertexId.part;

return geom;
QgsMultiPolygonXY multiPoly = geom.asMultiPolygon();
mManipulatedGeometry = QgsGeometry::fromPolygonXY( multiPoly.at( mModifiedPart ) );
}
}
}
Expand Down Expand Up @@ -356,6 +411,7 @@ void QgsMapToolOffsetCurve::deleteDistanceWidget()
void QgsMapToolOffsetCurve::deleteRubberBandAndGeometry()
{
mOriginalGeometry.set( nullptr );
mManipulatedGeometry.set( nullptr );
delete mRubberBand;
mRubberBand = nullptr;
}
Expand All @@ -378,7 +434,16 @@ void QgsMapToolOffsetCurve::setOffsetForRubberBand( double offset )
int quadSegments = s.value( QStringLiteral( "/qgis/digitizing/offset_quad_seg" ), 8 ).toInt();
double miterLimit = s.value( QStringLiteral( "/qgis/digitizing/offset_miter_limit" ), 5.0 ).toDouble();

QgsGeometry offsetGeom = mOriginalGeometry.offsetCurve( offset, quadSegments, joinStyle, miterLimit );
QgsGeometry offsetGeom;
if ( QgsWkbTypes::geometryType( mOriginalGeometry.wkbType() ) == QgsWkbTypes::LineGeometry )
{
offsetGeom = mManipulatedGeometry.offsetCurve( offset, quadSegments, joinStyle, miterLimit );
}
else
{
offsetGeom = mManipulatedGeometry.buffer( offset, quadSegments, QgsGeometry::CapRound, joinStyle, miterLimit );
}

if ( !offsetGeom )
{
deleteRubberBandAndGeometry();
Expand All @@ -396,80 +461,3 @@ void QgsMapToolOffsetCurve::setOffsetForRubberBand( double offset )
mRubberBand->setToGeometry( mModifiedGeometry, sourceLayer );
}
}

QgsGeometry QgsMapToolOffsetCurve::linestringFromPolygon( const QgsGeometry &featureGeom, int vertex )
{
if ( featureGeom.isNull() )
{
return QgsGeometry();
}

QgsWkbTypes::Type geomType = featureGeom.wkbType();
int currentVertex = 0;
QgsMultiPolygonXY multiPoly;

if ( geomType == QgsWkbTypes::Polygon || geomType == QgsWkbTypes::Polygon25D )
{
QgsPolygonXY polygon = featureGeom.asPolygon();
multiPoly.append( polygon );
}
else if ( geomType == QgsWkbTypes::MultiPolygon || geomType == QgsWkbTypes::MultiPolygon25D )
{
//iterate all polygons / rings
multiPoly = featureGeom.asMultiPolygon();
}
else
{
return QgsGeometry();
}

QgsMultiPolygonXY::const_iterator multiPolyIt = multiPoly.constBegin();
for ( ; multiPolyIt != multiPoly.constEnd(); ++multiPolyIt )
{
QgsPolygonXY::const_iterator polyIt = multiPolyIt->constBegin();
for ( ; polyIt != multiPolyIt->constEnd(); ++polyIt )
{
currentVertex += polyIt->size();
if ( vertex < currentVertex )
{
//found, return ring
return QgsGeometry::fromPolylineXY( *polyIt );
}
}
}

return QgsGeometry();
}


QgsGeometry QgsMapToolOffsetCurve::convertToSingleLine( const QgsGeometry &geom, int vertex, bool &isMulti )
{
if ( geom.isNull() )
{
return QgsGeometry();
}

isMulti = false;
QgsWkbTypes::Type geomType = geom.wkbType();
if ( geomType == QgsWkbTypes::LineString || geomType == QgsWkbTypes::LineString25D )
{
return geom;
}
else if ( geomType == QgsWkbTypes::MultiLineString || geomType == QgsWkbTypes::MultiLineString25D )
{
//search vertex
isMulti = true;
int currentVertex = 0;
QgsMultiPolylineXY multiLine = geom.asMultiPolyline();
QgsMultiPolylineXY::const_iterator it = multiLine.constBegin();
for ( ; it != multiLine.constEnd(); ++it )
{
currentVertex += it->size();
if ( vertex < currentVertex )
{
return QgsGeometry::fromPolylineXY( *it );
}
}
}
return QgsGeometry();
}
7 changes: 5 additions & 2 deletions src/app/qgsmaptooloffsetcurve.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class APP_EXPORT QgsMapToolOffsetCurve: public QgsMapToolEdit
QgsRubberBand *mRubberBand = nullptr;
//! Geometry to manipulate
QgsGeometry mOriginalGeometry;
//! Geometry being manipulated
QgsGeometry mManipulatedGeometry;
//! Geometry after manipulation
QgsGeometry mModifiedGeometry;
//! ID of manipulated feature
Expand All @@ -62,17 +64,18 @@ class APP_EXPORT QgsMapToolOffsetCurve: public QgsMapToolEdit
//! Forces geometry copy (no modification of geometry in current layer)
bool mCtrlWasHeldOnFeatureSelection = false;
bool mMultiPartGeometry = false;
int mModifiedPart = 0;

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

void deleteRubberBandAndGeometry();
QgsGeometry createOriginGeometry( QgsVectorLayer *vl, const QgsPointLocator::Match &match, QgsFeature &snappedFeature );
void createDistanceWidget();
void deleteDistanceWidget();
void setOffsetForRubberBand( double offset );
//! Creates a linestring from the polygon ring containing the snapped vertex. Caller takes ownership of the created object
QgsGeometry linestringFromPolygon( const QgsGeometry &featureGeom, int vertex );
//! Returns a single line from a multiline (or does nothing if geometry is already a single line). Deletes the input geometry
QgsGeometry convertToSingleLine( const QgsGeometry &geom, int vertex, bool &isMulti );
QgsGeometry convertToSingleLine( const QgsGeometry &geom, int vertex );
};

#endif // QGSMAPTOOLOFFSETCURVE_H
1 change: 0 additions & 1 deletion src/gui/qgsrubberband.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,6 @@ void QgsRubberBand::addGeometry( const QgsGeometry &geometry, const QgsCoordinat
case QgsWkbTypes::MultiPolygon:
case QgsWkbTypes::MultiPolygon25D:
{

const QgsMultiPolygonXY multipoly = geom.asMultiPolygon();
for ( const QgsPolygonXY &poly : multipoly )
{
Expand Down

0 comments on commit 2c65dbc

Please sign in to comment.