Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Highlight Trim/Extend plane of intersection #59941

Merged
merged 6 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 118 additions & 13 deletions src/app/qgsmaptooltrimextendfeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ QgsMapToolTrimExtendFeature::QgsMapToolTrimExtendFeature( QgsMapCanvas *canvas )
: QgsMapToolEdit( canvas )
{
mToolName = tr( "Trim/Extend feature" );
connect( mCanvas, &QgsMapCanvas::extentsChanged, this, &QgsMapToolTrimExtendFeature::extendLimit );
}

static bool getPoints( const QgsPointLocator::Match &match, QgsPoint &p1, QgsPoint &p2 )
Expand Down Expand Up @@ -89,15 +90,19 @@ void QgsMapToolTrimExtendFeature::canvasMoveEvent( QgsMapMouseEvent *e )

QgsPointXY p1, p2;
match.edgePoints( p1, p2 );

mLimitLayer = match.layer();
mRubberBandLimit.reset( createRubberBand( Qgis::GeometryType::Line ) );
mRubberBandLimitExtend.reset( createRubberBand( Qgis::GeometryType::Line ) );
mRubberBandLimitExtend->setLineStyle( Qt::DotLine );
mRubberBandLimit->addPoint( p1 );
mRubberBandLimit->addPoint( p2 );
mRubberBandLimit->show();
extendLimit();
}
else if ( mRubberBandLimit )
{
mRubberBandLimit->hide();
mRubberBandLimitExtend.reset();
}
break;
case StepExtend:
Expand Down Expand Up @@ -136,16 +141,30 @@ void QgsMapToolTrimExtendFeature::canvasMoveEvent( QgsMapMouseEvent *e )
if ( !getPoints( match, pExtend1, pExtend2 ) )
break;

QgsCoordinateTransform transform( mLimitLayer->crs(), mVlayer->crs(), mCanvas->mapSettings().transformContext() );

QgsPoint pLimit1Projected( pLimit1 );
QgsPoint pLimit2Projected( pLimit2 );
if ( !transform.isShortCircuited() )
{
const QgsPointXY transformedP1 = transform.transform( pLimit1 );
const QgsPointXY transformedP2 = transform.transform( pLimit2 );
pLimit1Projected.setX( transformedP1.x() );
pLimit1Projected.setY( transformedP1.y() );
pLimit2Projected.setX( transformedP2.x() );
pLimit2Projected.setY( transformedP2.y() );
}

// No need to trim/extend if segments are continuous
if ( ( ( pLimit1 == pExtend1 ) || ( pLimit1 == pExtend2 ) ) || ( ( pLimit2 == pExtend1 ) || ( pLimit2 == pExtend2 ) ) )
if ( ( ( pLimit1Projected == pExtend1 ) || ( pLimit1Projected == pExtend2 ) ) || ( ( pLimit2Projected == pExtend1 ) || ( pLimit2Projected == pExtend2 ) ) )
break;

mSegmentIntersects = QgsGeometryUtils::segmentIntersection( pLimit1, pLimit2, pExtend1, pExtend2, mIntersection, mIsIntersection, 1e-8, true );
mSegmentIntersects = QgsGeometryUtils::segmentIntersection( pLimit1Projected, pLimit2Projected, pExtend1, pExtend2, mIntersection, mIsIntersection, 1e-8, true );

if ( mIs3DLayer && QgsWkbTypes::hasZ( match.layer()->wkbType() ) )
{
/* Z Interpolation */
const QgsLineString line( pLimit1, pLimit2 );
const QgsLineString line( pLimit1Projected, pLimit2Projected );

mIntersection = QgsGeometryUtils::closestPoint( line, QgsPoint( mIntersection ) );
}
Expand All @@ -168,7 +187,7 @@ void QgsMapToolTrimExtendFeature::canvasMoveEvent( QgsMapMouseEvent *e )
index += 1;
}
// TRIM PART
else if ( QgsGeometryUtils::leftOfLine( QgsPoint( mMapPoint ), pLimit1, pLimit2 ) != QgsGeometryUtils::leftOfLine( pExtend1, pLimit1, pLimit2 ) )
else if ( QgsGeometryUtils::leftOfLine( QgsPoint( mMapPoint ), pLimit1Projected, pLimit2Projected ) != QgsGeometryUtils::leftOfLine( pExtend1, pLimit1Projected, pLimit2Projected ) )
{
// Part where the mouse is (+) will be trimmed
/* |
Expand All @@ -194,8 +213,10 @@ void QgsMapToolTrimExtendFeature::canvasMoveEvent( QgsMapMouseEvent *e )
if ( mIsModified )
{
mGeom.removeDuplicateNodes();
mRubberBandExtend->setToGeometry( mGeom, mVlayer );
// Densify by count to better display the intersection when layer crs != map crs
mRubberBandExtend->setToGeometry( mGeom.densifyByCount( 10 ), mVlayer );
mRubberBandExtend->show();
extendLimit();
}
}
else
Expand Down Expand Up @@ -265,15 +286,15 @@ void QgsMapToolTrimExtendFeature::canvasReleaseEvent( QgsMapMouseEvent *e )
emit messageEmitted( tr( "Couldn't trim or extend the feature." ) );
}

// If Ctrl or Shift is pressed, keep the tool active with its reference feature
if ( !( e->modifiers() & ( Qt::ControlModifier | Qt::ShiftModifier ) ) )
deactivate();
// If Shift is pressed, keep the tool active with its reference feature
if ( !( e->modifiers() & Qt::ShiftModifier ) )
reset();
break;
}
}
else if ( e->button() == Qt::RightButton )
{
deactivate();
reset();
}
}

Expand All @@ -286,21 +307,105 @@ void QgsMapToolTrimExtendFeature::keyPressEvent( QKeyEvent *e )

if ( e && e->key() == Qt::Key_Escape )
{
deactivate();
reset();
}
}

void QgsMapToolTrimExtendFeature::deactivate()

void QgsMapToolTrimExtendFeature::extendLimit()
{
if ( !mRubberBandLimitExtend )
{
return;
}

QgsVectorLayer *refLayer = qobject_cast<QgsVectorLayer *>( mCanvas->currentLayer() );
refLayer = refLayer ? refLayer : mVlayer ? mVlayer
: mLimitLayer;

// Compute intersection between the line that extends the limit segment and the
// edges of the map canvas
QgsPointXY p1 = toLayerCoordinates( refLayer, *mRubberBandLimit->getPoint( 0, 0 ) );
QgsPointXY p2 = toLayerCoordinates( refLayer, *mRubberBandLimit->getPoint( 0, 1 ) );
QgsPoint canvasTopLeft = QgsPoint( toLayerCoordinates( refLayer, QPoint( 0, 0 ) ) );
QgsPoint canvasTopRight = QgsPoint( toLayerCoordinates( refLayer, QPoint( mCanvas->width(), 0 ) ) );
QgsPoint canvasBottomLeft = QgsPoint( toLayerCoordinates( refLayer, QPoint( 0, mCanvas->height() ) ) );
QgsPoint canvasBottomRight = QgsPoint( toLayerCoordinates( refLayer, QPoint( mCanvas->width(), mCanvas->height() ) ) );

QList<QgsPointXY> points;
points << p1 << p2;

QgsPoint intersection;
if ( QgsGeometryUtils::lineIntersection( QgsPoint( p1 ), QgsPoint( p2 ) - QgsPoint( p1 ), canvasTopLeft, canvasTopRight - canvasTopLeft, intersection ) )
{
points << QgsPointXY( intersection );
}
if ( QgsGeometryUtils::lineIntersection( QgsPoint( p1 ), QgsPoint( p2 ) - QgsPoint( p1 ), canvasTopRight, canvasBottomRight - canvasTopRight, intersection ) )
{
points << QgsPointXY( intersection );
}
if ( QgsGeometryUtils::lineIntersection( QgsPoint( p1 ), QgsPoint( p2 ) - QgsPoint( p1 ), canvasBottomRight, canvasBottomLeft - canvasBottomRight, intersection ) )
{
points << QgsPointXY( intersection );
}
if ( QgsGeometryUtils::lineIntersection( QgsPoint( p1 ), QgsPoint( p2 ) - QgsPoint( p1 ), canvasBottomLeft, canvasTopLeft - canvasBottomLeft, intersection ) )
{
points << QgsPointXY( intersection );
}

// Reorder the points by x/y coordinates
std::sort( points.begin(), points.end(), []( const QgsPointXY &a, const QgsPointXY &b ) -> bool {
if ( a.x() == b.x() )
return a.y() < b.y();
return a.x() < b.x();
} );

// Keep only the closest intersection points from the original points
const int p1Idx = points.indexOf( p1 );
const int p2Idx = points.indexOf( p2 );
const int first = std::max( 0, std::min( p1Idx, p2Idx ) - 1 );
const int last = std::min( static_cast<int>( points.size() ) - 1, std::max( p1Idx, p2Idx ) + 1 );
const QgsPolylineXY polyline = points.mid( first, last - first + 1 ).toVector();

// Densify the polyline to display a more accurate prediction when layer crs != canvas crs
QgsGeometry geom = QgsGeometry::fromPolylineXY( polyline ).densifyByCount( 10 );

mRubberBandLimitExtend->setToGeometry( geom, refLayer );
mRubberBandLimitExtend->show();
}
void QgsMapToolTrimExtendFeature::reset()
{
mStep = StepLimit;
mIsModified = false;
mIs3DLayer = false;
mIsIntersection = false;
mSegmentIntersects = false;
mRubberBandLimit.reset();
mRubberBandLimitExtend.reset();
mRubberBandExtend.reset();
mRubberBandIntersection.reset();
QgsMapTool::deactivate();
mVlayer = nullptr;
mLimitLayer = nullptr;
}
void QgsMapToolTrimExtendFeature::activate()
{
QgsMapTool::activate();

// Save the original snapping configuration
mOriginalSnappingConfig = mCanvas->snappingUtils()->config();

// Enable Snapping & Snapping on Segment
QgsSnappingConfig snappingConfig = mOriginalSnappingConfig;
snappingConfig.setEnabled( true );
Qgis::SnappingTypes flags = snappingConfig.typeFlag();
flags |= Qgis::SnappingType::Segment;
snappingConfig.setTypeFlag( flags );
mCanvas->snappingUtils()->setConfig( snappingConfig );
}
void QgsMapToolTrimExtendFeature::deactivate()
{
reset();
// Restore the original snapping configuration
mCanvas->snappingUtils()->setConfig( mOriginalSnappingConfig );
QgsMapTool::deactivate();
}
16 changes: 15 additions & 1 deletion src/app/qgsmaptooltrimextendfeature.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "qgsmaptooledit.h"
#include "qgis_app.h"
#include "qgsrubberband.h"
#include "qgssnappingconfig.h"

class APP_EXPORT QgsMapToolTrimExtendFeature : public QgsMapToolEdit
{
Expand All @@ -35,12 +36,22 @@ class APP_EXPORT QgsMapToolTrimExtendFeature : public QgsMapToolEdit

void keyPressEvent( QKeyEvent *e ) override;

//! called when map tool is being activated
void activate() override;

//! called when map tool is being deactivated
void deactivate() override;

private slots:
// Recompute the extended limit
void extendLimit();
void reset();

private:
//! Rubberband that shows the limit
//! Rubberband that highlights the limit segment
std::unique_ptr<QgsRubberBand> mRubberBandLimit;
//! Rubberband that extends the limit segment
std::unique_ptr<QgsRubberBand> mRubberBandLimitExtend;
//! Rubberband that shows the feature being extended
std::unique_ptr<QgsRubberBand> mRubberBandExtend;
//! Rubberband that shows the intersection point
Expand Down Expand Up @@ -74,6 +85,9 @@ class APP_EXPORT QgsMapToolTrimExtendFeature : public QgsMapToolEdit
};
//! The first step (0): choose the limit. The second step (1): choose the segment to trim/extend
Step mStep = StepLimit;

//! Snapping config that will be restored on deactivation
QgsSnappingConfig mOriginalSnappingConfig;
};

#endif // QGSMAPTOOLTRIMEXTENDFEATURE_H
2 changes: 1 addition & 1 deletion src/ui/qgisapp.ui
Original file line number Diff line number Diff line change
Expand Up @@ -1181,7 +1181,7 @@ Shift+O to turn segments into straight or curve lines.</string>
<property name="toolTip">
<string>Trim/Extend Feature
- Click to set the reference segment
- Click on another segment to trim it or extend it to the reference segment (Use Shift or Ctrl to keep the reference segment active)
- Click on another segment to trim it or extend it to the reference segment (Use Shift to keep the reference segment active)
Note : Segment snapping must be enabled in the snapping Toolbar</string>
</property>
</action>
Expand Down
4 changes: 2 additions & 2 deletions tests/src/app/testqgsmaptooltrimextendfeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class TestQgsMapToolTrimExtendFeature : public QObject
// | \| |
// | + (2,1) |
// (0,0) +-------------------+ (3,0)
vlPolygon.reset( new QgsVectorLayer( QStringLiteral( "MultiPolygon?field=fld:int" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) ) );
vlPolygon.reset( new QgsVectorLayer( QStringLiteral( "MultiPolygon?crs=EPSG:3946&field=fld:int" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) ) );
const int idx = vlPolygon->fields().indexFromName( QStringLiteral( "fld" ) );
QVERIFY( idx != -1 );
f1.initAttributes( 1 );
Expand Down Expand Up @@ -606,7 +606,7 @@ class TestQgsMapToolTrimExtendFeature : public QObject
pt.toQPointF().toPoint(),
Qt::LeftButton,
Qt::NoButton,
Qt::ControlModifier
Qt::ShiftModifier
) );
tool->canvasReleaseEvent( event.get() );

Expand Down
Loading