Skip to content

Commit

Permalink
[offset tool] handle rings and allow selecting by area
Browse files Browse the repository at this point in the history
* correctly handles rings and parts
* allow to select polygon by area (not only edge)
  • Loading branch information
3nids committed Feb 17, 2018
1 parent 08bcb1c commit 2f385da
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 46 deletions.
12 changes: 10 additions & 2 deletions python/core/qgssnappingutils.sip.in
Expand Up @@ -53,9 +53,17 @@ Snap to map according to the current configuration. Optional filter allows disca
%End
QgsPointLocator::Match snapToMap( const QgsPointXY &pointMap, QgsPointLocator::MatchFilter *filter = 0 );

QgsPointLocator::Match snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter = 0 );

QgsPointLocator::Match snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter = 0, double tolerance = 0.0 );
%Docstring
Snap to current layer
snapToCurrentLayer snap to current layer with given settings

:param point: the point to snap from
:param type: the matching type (vertex, edge, area)
:param filter: the matching filter
:param tolerance: the tolerance in project units. If left to 0, it is calculated from current map settings

:return: the snapping match
%End


Expand Down
232 changes: 194 additions & 38 deletions src/app/qgsmaptooloffsetcurve.cpp
Expand Up @@ -72,33 +72,17 @@ void QgsMapToolOffsetCurve::canvasReleaseEvent( QgsMapMouseEvent *e )
deleteRubberBandAndGeometry();
mGeometryModified = false;

QgsSnappingUtils *snapping = mCanvas->snappingUtils();

// store previous settings
QgsSnappingConfig oldConfig = snapping->config();
QgsSnappingConfig config = snapping->config();
// setup new settings (temporary)
QgsSettings settings;
config.setEnabled( true );
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() ) );
snapping->setConfig( config );

QgsPointLocator::Match match = snapping->snapToMap( e->pos() );

// restore old settings
snapping->setConfig( oldConfig );

if ( match.hasEdge() && match.layer() )
QgsPointLocator::Match match = mCanvas->snappingUtils()->snapToCurrentLayer( e->pos(),
QgsPointLocator::Types( QgsPointLocator::Edge | QgsPointLocator::Area ) );

if ( ( match.hasEdge() || match.hasArea() ) && match.layer() )
{
mLayer = match.layer();
QgsFeature fet;
if ( match.layer()->getFeatures( QgsFeatureRequest( match.featureId() ) ).nextFeature( fet ) )
{
mCtrlHeldOnFirstClick = ( e->modifiers() & Qt::ControlModifier ); //no geometry modification if ctrl is pressed
prepareGeometry( match.layer(), match, fet );
prepareGeometry( match, fet );
mRubberBand = createRubberBand();
if ( mRubberBand )
{
Expand Down Expand Up @@ -143,7 +127,7 @@ void QgsMapToolOffsetCurve::applyOffset( const double &offset, const Qt::Keyboar
return;
}

if ( mMultiPartGeometry )
if ( mModifiedPart >= 0 )
{
QgsGeometry geometry;
int partIndex = 0;
Expand All @@ -170,19 +154,75 @@ void QgsMapToolOffsetCurve::applyOffset( const double &offset, const Qt::Keyboar
else
{
QgsMultiPolygonXY newMultiPoly;
QgsMultiPolygonXY multiPoly = mOriginalGeometry.asMultiPolygon();
const QgsMultiPolygonXY multiPoly = mOriginalGeometry.asMultiPolygon();
QgsMultiPolygonXY::const_iterator multiPolyIt = multiPoly.constBegin();
for ( ; multiPolyIt != multiPoly.constEnd(); ++multiPolyIt )
{
if ( partIndex == mModifiedPart )
{
if ( mModifiedGeometry.isMultipart() )
{
newMultiPoly += mModifiedGeometry.asMultiPolygon();
// not a ring
if ( mModifiedRing <= 0 )
{
// part became mulitpolygon, that means discard original rings from the part
newMultiPoly += mModifiedGeometry.asMultiPolygon();
}
else
{
// ring became multipolygon, oh boy!
QgsPolygonXY newPoly;
int ringIndex = 0;
QgsPolygonXY::const_iterator polyIt = multiPolyIt->constBegin();
for ( ; polyIt != multiPolyIt->constEnd(); ++polyIt )
{
if ( ringIndex == mModifiedRing )
{
const QgsMultiPolygonXY ringParts = mModifiedGeometry.asMultiPolygon();
QgsPolygonXY newRings;
QgsMultiPolygonXY::const_iterator ringIt = ringParts.constBegin();
for ( ; ringIt != ringParts.constEnd(); ++ringIt )
{
// the different parts of the new rings cannot have rings themselves
newRings.append( ringIt->at( 0 ) );
}
newPoly += newRings;
}
else
{
newPoly.append( *polyIt );
}
ringIndex++;
}
newMultiPoly.append( newPoly );
}
}
else
{
newMultiPoly.append( mModifiedGeometry.asPolygon() );
// original part had no ring
if ( mModifiedRing == -1 )
{
newMultiPoly.append( mModifiedGeometry.asPolygon() );
}
else
{
QgsPolygonXY newPoly;
int ringIndex = 0;
QgsPolygonXY::const_iterator polyIt = multiPolyIt->constBegin();
for ( ; polyIt != multiPolyIt->constEnd(); ++polyIt )
{
if ( ringIndex == mModifiedRing )
{
newPoly.append( mModifiedGeometry.asPolygon().at( 0 ) );
}
else
{
newPoly.append( *polyIt );
}
ringIndex++;
}
newMultiPoly.append( newPoly );
}
}
}
else
Expand All @@ -196,6 +236,72 @@ void QgsMapToolOffsetCurve::applyOffset( const double &offset, const Qt::Keyboar
geometry.convertToMultiType();
mModifiedGeometry = geometry;
}
else if ( mModifiedRing >= 0 )
{
// original geometry had some rings
if ( mModifiedGeometry.isMultipart() )
{
// not a ring
if ( mModifiedRing == 0 )
{
// polygon became mulitpolygon, that means discard original rings from the part
// keep the modified geometry as is
}
else
{
QgsPolygonXY newPoly;
const QgsPolygonXY poly = mOriginalGeometry.asPolygon();

// ring became multipolygon, oh boy!
int ringIndex = 0;
QgsPolygonXY::const_iterator polyIt = poly.constBegin();
for ( ; polyIt != poly.constEnd(); ++polyIt )
{
if ( ringIndex == mModifiedRing )
{
QgsMultiPolygonXY ringParts = mModifiedGeometry.asMultiPolygon();
QgsPolygonXY newRings;
QgsMultiPolygonXY::const_iterator ringIt = ringParts.constBegin();
for ( ; ringIt != ringParts.constEnd(); ++ringIt )
{
// the different parts of the new rings cannot have rings themselves
newRings.append( ringIt->at( 0 ) );
}
newPoly += newRings;
}
else
{
newPoly.append( *polyIt );
}
ringIndex++;
}
mModifiedGeometry = QgsGeometry::fromPolygonXY( newPoly );
}
}
else
{
// simple case where modified geom is a polygon (not multi)
QgsPolygonXY newPoly;
const QgsPolygonXY poly = mOriginalGeometry.asPolygon();

int ringIndex = 0;
QgsPolygonXY::const_iterator polyIt = poly.constBegin();
for ( ; polyIt != poly.constEnd(); ++polyIt )
{
if ( ringIndex == mModifiedRing )
{
newPoly.append( mModifiedGeometry.asPolygon().at( 0 ) );
}
else
{
newPoly.append( *polyIt );
}
ringIndex++;
}
mModifiedGeometry = QgsGeometry::fromPolygonXY( newPoly );
}

}

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

Expand Down Expand Up @@ -288,17 +394,18 @@ void QgsMapToolOffsetCurve::canvasMoveEvent( QgsMapMouseEvent *e )
updateGeometryAndRubberBand( offset );
}

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

mOriginalGeometry = QgsGeometry();
mManipulatedGeometry = QgsGeometry();
mMultiPartGeometry = false;
mModifiedPart = 0;
mModifiedPart = -1;
mModifiedRing = -1;

//assign feature part by vertex number (snap to vertex) or by before vertex number (snap to segment)
QgsGeometry geom = snappedFeature.geometry();
Expand All @@ -311,14 +418,16 @@ void QgsMapToolOffsetCurve::prepareGeometry( QgsVectorLayer *vl, const QgsPointL
QgsWkbTypes::Type geomType = geom.wkbType();
if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::LineGeometry )
{
if ( !match.hasEdge() )
{
return;
}
if ( !geom.isMultipart() )
{
mManipulatedGeometry = geom;
}
else
{
mMultiPartGeometry = true;

int vertex = match.vertexIndex();
QgsVertexId vertexId;
geom.vertexIdFromVertexNr( vertex, vertexId );
Expand All @@ -330,21 +439,67 @@ void QgsMapToolOffsetCurve::prepareGeometry( QgsVectorLayer *vl, const QgsPointL
}
else if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::PolygonGeometry )
{
if ( !geom.isMultipart() )
if ( !match.hasEdge() && match.hasArea() )
{
mManipulatedGeometry = geom;
if ( !geom.isMultipart() )
{
mManipulatedGeometry = geom;
}
else
{
// get the correct part
QgsMultiPolygonXY mpolygon = geom.asMultiPolygon();
for ( int part = 0; part < mpolygon.count(); part++ ) // go through the polygons
{
const QgsPolygonXY &polygon = mpolygon[part];
QgsGeometry partGeo = QgsGeometry::fromPolygonXY( polygon );
const QgsPointXY layerCoords = match.point();
if ( partGeo.contains( &layerCoords ) )
{
mModifiedPart = part;
mManipulatedGeometry = partGeo;
}
}
}
}
else
else if ( match.hasEdge() )
{
mMultiPartGeometry = true;

int vertex = match.vertexIndex();
QgsVertexId vertexId;
geom.vertexIdFromVertexNr( vertex, vertexId );
mModifiedPart = vertexId.part;
QgsDebugMsg( QString( "%1" ).arg( vertexId.ring ) );

QgsMultiPolygonXY multiPoly = geom.asMultiPolygon();
mManipulatedGeometry = QgsGeometry::fromPolygonXY( multiPoly.at( mModifiedPart ) );
if ( !geom.isMultipart() )
{
QgsPolygonXY poly = geom.asPolygon();
// if has rings
if ( poly.count() > 0 )
{
mModifiedRing = vertexId.ring;
mManipulatedGeometry = QgsGeometry::fromPolygonXY( QgsPolygonXY() << poly.at( mModifiedRing ) );
}
else
{
mManipulatedGeometry = QgsGeometry::fromPolygonXY( poly );
}

}
else
{
mModifiedPart = vertexId.part;
// get part, get ring
QgsMultiPolygonXY multiPoly = geom.asMultiPolygon();
// if has rings
if ( multiPoly.at( mModifiedPart ).count() > 0 )
{
mModifiedRing = vertexId.ring;
mManipulatedGeometry = QgsGeometry::fromPolygonXY( QgsPolygonXY() << multiPoly.at( mModifiedPart ).at( mModifiedRing ) );
}
else
{
mManipulatedGeometry = QgsGeometry::fromPolygonXY( multiPoly.at( mModifiedPart ) );
}
}
}
}
}
Expand Down Expand Up @@ -421,6 +576,7 @@ void QgsMapToolOffsetCurve::updateGeometryAndRubberBand( double offset )
deleteUserInputWidget();
mLayer = nullptr;
mGeometryModified = false;
emit messageDiscarded();
emit messageEmitted( tr( "Creating offset geometry failed: %1" ).arg( offsetGeom.lastError() ), Qgis::Critical );
}
else
Expand Down
6 changes: 3 additions & 3 deletions src/app/qgsmaptooloffsetcurve.h
Expand Up @@ -88,8 +88,8 @@ class APP_EXPORT QgsMapToolOffsetCurve: public QgsMapToolEdit
QgsGeometry mModifiedGeometry;
//! ID of manipulated feature
QgsFeatureId mModifiedFeature = -1;
bool mMultiPartGeometry = false;
int mModifiedPart = 0;
int mModifiedPart = -1;
int mModifiedRing = -1;

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

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

void deleteRubberBandAndGeometry();

Expand Down
5 changes: 3 additions & 2 deletions src/core/qgssnappingutils.cpp
Expand Up @@ -440,13 +440,14 @@ void QgsSnappingUtils::toggleEnabled()
emit configChanged( mSnappingConfig );
}

QgsPointLocator::Match QgsSnappingUtils::snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter )
QgsPointLocator::Match QgsSnappingUtils::snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter, double tolerance )
{
if ( !mCurrentLayer )
return QgsPointLocator::Match();

QgsPointXY pointMap = mMapSettings.mapToPixel().toMapCoordinates( point );
double tolerance = QgsTolerance::vertexSearchRadius( mMapSettings );
if ( tolerance == 0.0 )
tolerance = QgsTolerance::vertexSearchRadius( mMapSettings );

QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
if ( !loc )
Expand Down
11 changes: 10 additions & 1 deletion src/core/qgssnappingutils.h
Expand Up @@ -66,7 +66,16 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
QgsPointLocator::Match snapToMap( const QgsPointXY &pointMap, QgsPointLocator::MatchFilter *filter = nullptr );

//! Snap to current layer
QgsPointLocator::Match snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter = nullptr );

/**
* \brief snapToCurrentLayer snap to current layer with given settings
* \param point the point to snap from
* \param type the matching type (vertex, edge, area)
* \param filter the matching filter
* \param tolerance the tolerance in project units. If left to 0, it is calculated from current map settings
* \return the snapping match
*/
QgsPointLocator::Match snapToCurrentLayer( QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter = nullptr, double tolerance = 0.0 );

// environment setup

Expand Down

0 comments on commit 2f385da

Please sign in to comment.