Skip to content

Commit d1d3a51

Browse files
authoredNov 21, 2018
Merge pull request #8429 from lbartoletti/extendMapTool
[needs-docs][FEATURE] Trim/extend
2 parents f3fbd06 + 792546c commit d1d3a51

File tree

13 files changed

+1060
-3
lines changed

13 files changed

+1060
-3
lines changed
 

‎images/images.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,7 @@
732732
<file>themes/default/mIconExteriorRing.svg</file>
733733
<file>themes/default/mIconInteriorRings.svg</file>
734734
<file>themes/default/mIconFieldBinary.svg</file>
735+
<file>themes/default/mActionTrimExtendFeature.svg</file>
735736
<file>themes/default/mActionTerminal.svg</file>
736737
<file>themes/default/mIconFolder24.svg</file>
737738
<file>themes/default/mActionNewFolder.svg</file>
Lines changed: 196 additions & 0 deletions
Loading

‎python/core/auto_generated/geometry/qgsgeometryutils.sip.in

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,24 @@ Project the point on a segment
217217

218218

219219

220-
static int leftOfLine( double x, double y, double x1, double y1, double x2, double y2 );
220+
static int leftOfLine( const double x, const double y, const double x1, const double y1, const double x2, const double y2 );
221221
%Docstring
222222
Returns a value < 0 if the point (``x``, ``y``) is left of the line from (``x1``, ``y1``) -> ( ``x2``, ``y2``).
223223
A positive return value indicates the point is to the right of the line.
224224

225225
If the return value is 0, then the test was unsuccessful (e.g. due to testing a point exactly
226226
on the line, or exactly in line with the segment) and the result is undefined.
227+
%End
228+
229+
static int leftOfLine( const QgsPoint &point, const QgsPoint &p1, const QgsPoint &p2 );
230+
%Docstring
231+
Returns a value < 0 if the point ``point`` is left of the line from ``p1`` -> ``p2``.
232+
A positive return value indicates the point is to the right of the line.
233+
234+
If the return value is 0, then the test was unsuccessful (e.g. due to testing a point exactly
235+
on the line, or exactly in line with the segment) and the result is undefined.
236+
237+
.. versionadded:: 3.6
227238
%End
228239

229240
static QgsPoint pointOnLineWithDistance( const QgsPoint &startPoint, const QgsPoint &directionPoint, double distance );

‎src/app/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ SET(QGIS_APP_SRCS
8181
qgsmaptoolchangelabelproperties.cpp
8282
qgsmaptooldeletering.cpp
8383
qgsmaptooldeletepart.cpp
84+
qgsmaptooltrimextendfeature.cpp
8485
qgsmaptoolfeatureaction.cpp
8586
qgsmaptoolformannotation.cpp
8687
qgsmaptoolhtmlannotation.cpp
@@ -317,6 +318,7 @@ SET (QGIS_APP_MOC_HDRS
317318
qgsmaptoolchangelabelproperties.h
318319
qgsmaptooldeletepart.h
319320
qgsmaptooldeletering.h
321+
qgsmaptooltrimextendfeature.h
320322
qgsmaptoolfeatureaction.h
321323
qgsmaptoolformannotation.h
322324
qgsmaptoolhtmlannotation.h

‎src/app/qgisapp.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
418418
#include "qgsmaptoolreverseline.h"
419419
#include "qgsgeometryvalidationmodel.h"
420420
#include "qgsgeometryvalidationdock.h"
421+
#include "qgsmaptooltrimextendfeature.h"
421422

422423
#include "vertextool/qgsvertextool.h"
423424

@@ -1488,6 +1489,7 @@ QgisApp::~QgisApp()
14881489
delete mMapTools.mChangeLabelProperties;
14891490
delete mMapTools.mDeletePart;
14901491
delete mMapTools.mDeleteRing;
1492+
delete mMapTools.mTrimExtendFeature;
14911493
delete mMapTools.mFeatureAction;
14921494
delete mMapTools.mFormAnnotation;
14931495
delete mMapTools.mHtmlAnnotation;
@@ -2121,6 +2123,7 @@ void QgisApp::createActions()
21212123
connect( mActionSnappingOptions, &QAction::triggered, this, &QgisApp::snappingOptions );
21222124
connect( mActionOffsetCurve, &QAction::triggered, this, &QgisApp::offsetCurve );
21232125
connect( mActionReverseLine, &QAction::triggered, this, &QgisApp::reverseLine );
2126+
connect( mActionTrimExtendFeature, &QAction::triggered, this, [ = ] { mMapCanvas->setMapTool( mMapTools.mTrimExtendFeature ); } );
21242127

21252128
// View Menu Items
21262129
connect( mActionPan, &QAction::triggered, this, &QgisApp::pan );
@@ -2409,6 +2412,7 @@ void QgisApp::createActionGroups()
24092412
mMapToolGroup->addAction( mActionRotateLabel );
24102413
mMapToolGroup->addAction( mActionChangeLabelProperties );
24112414
mMapToolGroup->addAction( mActionReverseLine );
2415+
mMapToolGroup->addAction( mActionTrimExtendFeature );
24122416

24132417
//
24142418
// Preview Modes Group
@@ -3387,6 +3391,7 @@ void QgisApp::setTheme( const QString &themeName )
33873391
mActionDecorationScaleBar->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleBar.svg" ) ) );
33883392
mActionDecorationGrid->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/grid.svg" ) ) );
33893393
mActionReverseLine->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionReverseLine.svg" ) ) );
3394+
mActionTrimExtendFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionTrimExtendFeature.svg" ) ) );
33903395

33913396
emit currentThemeChanged( themeName );
33923397
}
@@ -3638,6 +3643,8 @@ void QgisApp::createCanvasTools()
36383643
mMapTools.mRotatePointSymbolsTool->setAction( mActionRotatePointSymbols );
36393644
mMapTools.mOffsetPointSymbolTool = new QgsMapToolOffsetPointSymbol( mMapCanvas );
36403645
mMapTools.mOffsetPointSymbolTool->setAction( mActionOffsetPointSymbol );
3646+
mMapTools.mTrimExtendFeature = new QgsMapToolTrimExtendFeature( mMapCanvas );
3647+
mMapTools.mTrimExtendFeature->setAction( mActionTrimExtendFeature );
36413648

36423649
mMapTools.mPinLabels = new QgsMapToolPinLabels( mMapCanvas );
36433650
mMapTools.mPinLabels->setAction( mActionPinLabels );
@@ -12407,6 +12414,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
1240712414
mActionCopyLayer->setEnabled( false );
1240812415
mActionPasteLayer->setEnabled( false );
1240912416
mActionReverseLine->setEnabled( false );
12417+
mActionTrimExtendFeature->setEnabled( false );
1241012418

1241112419
mUndoDock->widget()->setEnabled( false );
1241212420
mActionUndo->setEnabled( false );
@@ -12482,6 +12490,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
1248212490
mActionLabeling->setEnabled( isSpatial );
1248312491
mActionDiagramProperties->setEnabled( isSpatial );
1248412492
mActionReverseLine->setEnabled( false );
12493+
mActionTrimExtendFeature->setEnabled( false );
1248512494

1248612495
mActionSelectFeatures->setEnabled( isSpatial );
1248712496
mActionSelectPolygon->setEnabled( isSpatial );
@@ -12627,6 +12636,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
1262712636
mActionSimplifyFeature->setEnabled( isEditable && canChangeGeometry );
1262812637
mActionOffsetCurve->setEnabled( isEditable && canAddFeatures && canChangeAttributes );
1262912638
mActionReverseLine->setEnabled( isEditable && canChangeGeometry );
12639+
mActionTrimExtendFeature->setEnabled( isEditable && canChangeGeometry );
1263012640

1263112641
mActionAddRing->setEnabled( false );
1263212642
mActionFillRing->setEnabled( false );
@@ -12647,6 +12657,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
1264712657
mActionSimplifyFeature->setEnabled( isEditable && canChangeGeometry );
1264812658
mActionDeleteRing->setEnabled( isEditable && canChangeGeometry );
1264912659
mActionOffsetCurve->setEnabled( isEditable && canAddFeatures && canChangeAttributes );
12660+
mActionTrimExtendFeature->setEnabled( isEditable && canChangeGeometry );
1265012661
}
1265112662
else if ( vlayer->geometryType() == QgsWkbTypes::NullGeometry )
1265212663
{

‎src/app/qgisapp.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,6 +2084,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
20842084
QgsMapTool *mRotateLabel = nullptr;
20852085
QgsMapTool *mChangeLabelProperties = nullptr;
20862086
QgsMapTool *mReverseLine = nullptr ;
2087+
QgsMapTool *mTrimExtendFeature = nullptr ;
20872088
} mMapTools;
20882089

20892090
QgsMapTool *mNonEditMapTool = nullptr;
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
/***************************************************************************
2+
qgmaptooltrimextendfeature.cpp - map tool to trim or extend feature
3+
---------------------
4+
begin : October 2018
5+
copyright : (C) 2018 by Loïc Bartoletti
6+
email : loic dot bartoletti at oslandia dot 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 "qgsmaptooltrimextendfeature.h"
17+
18+
#include "qgsmapcanvas.h"
19+
#include "qgsvertexmarker.h"
20+
#include "qgsvectorlayer.h"
21+
#include "qgsgeometry.h"
22+
#include "qgssnappingutils.h"
23+
#include "qgstolerance.h"
24+
#include "qgisapp.h"
25+
#include "qgsgeometryutils.h"
26+
#include "qgsmapmouseevent.h"
27+
#include "qgssnapindicator.h"
28+
29+
class QgsRubberBand;
30+
31+
class FeatureFilter : public QgsPointLocator::MatchFilter
32+
{
33+
public:
34+
FeatureFilter()
35+
{}
36+
37+
bool acceptMatch( const QgsPointLocator::Match &match ) override
38+
{
39+
if ( mLayer )
40+
return match.layer() == mLayer && match.hasEdge();
41+
42+
return match.hasEdge();
43+
}
44+
// We only want to modify the current layer. When geometries are overlapped, this makes it possible to snap onto the current layer.
45+
void setLayer( QgsVectorLayer *layer ) { mLayer = layer; }
46+
47+
private:
48+
const QgsVectorLayer *mLayer = nullptr;
49+
};
50+
51+
QgsMapToolTrimExtendFeature::QgsMapToolTrimExtendFeature( QgsMapCanvas *canvas )
52+
: QgsMapToolEdit( canvas )
53+
{
54+
mToolName = tr( "Trim/Extend feature" );
55+
}
56+
57+
static void getPoints( const QgsPointLocator::Match &match, QgsPoint &p1, QgsPoint &p2 )
58+
{
59+
const QgsFeatureId fid = match.featureId();
60+
const int vertex = match.vertexIndex();
61+
62+
const QgsGeometry geom = match.layer()->getGeometry( fid );
63+
64+
if ( !( geom.isNull() || geom.isEmpty() ) )
65+
{
66+
p1 = geom.vertexAt( vertex );
67+
p2 = geom.vertexAt( vertex + 1 );
68+
}
69+
}
70+
71+
void QgsMapToolTrimExtendFeature::canvasMoveEvent( QgsMapMouseEvent *e )
72+
{
73+
mMapPoint = e->mapPoint();
74+
75+
FeatureFilter filter;
76+
QgsPointLocator::Match match;
77+
78+
switch ( mStep )
79+
{
80+
case StepLimit:
81+
82+
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter );
83+
if ( match.isValid() )
84+
{
85+
mIs3DLayer = QgsWkbTypes::hasZ( match.layer()->wkbType() );
86+
87+
QgsPointXY p1, p2;
88+
match.edgePoints( p1, p2 );
89+
90+
mRubberBandLimit.reset( createRubberBand( QgsWkbTypes::LineGeometry ) );
91+
mRubberBandLimit->addPoint( p1 );
92+
mRubberBandLimit->addPoint( p2 );
93+
mRubberBandLimit->show();
94+
95+
}
96+
else if ( mRubberBandLimit )
97+
{
98+
mRubberBandLimit->hide();
99+
}
100+
break;
101+
case StepExtend:
102+
103+
QgsMapLayer *currentLayer = mCanvas->currentLayer();
104+
if ( !currentLayer )
105+
break;
106+
107+
mVlayer = qobject_cast<QgsVectorLayer *>( currentLayer );
108+
if ( !mVlayer )
109+
break;
110+
111+
if ( !mVlayer->isEditable() )
112+
break;
113+
114+
filter.setLayer( mVlayer );
115+
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter );
116+
117+
if ( match.isValid() )
118+
{
119+
if ( match.layer() != mVlayer )
120+
break;
121+
122+
QgsPointXY p1, p2;
123+
match.edgePoints( p1, p2 );
124+
125+
getPoints( match, pExtend1, pExtend2 );
126+
127+
// No need to trim/extend if segments are continuous
128+
if ( ( ( pLimit1 == pExtend1 ) || ( pLimit1 == pExtend2 ) ) || ( ( pLimit2 == pExtend1 ) || ( pLimit2 == pExtend2 ) ) )
129+
break;
130+
131+
mSegmentIntersects = QgsGeometryUtils::segmentIntersection( pLimit1, pLimit2, pExtend1, pExtend2, mIntersection, mIsIntersection, 1e-8, true );
132+
133+
if ( mIs3DLayer && QgsWkbTypes::hasZ( match.layer()->wkbType() ) )
134+
{
135+
/* Z Interpolation */
136+
QgsLineString line( pLimit1, pLimit2 );
137+
138+
mIntersection = QgsGeometryUtils::closestPoint( line, QgsPoint( mIntersection ) );
139+
}
140+
141+
if ( mIsIntersection )
142+
{
143+
mRubberBandIntersection.reset( createRubberBand( QgsWkbTypes::PointGeometry ) );
144+
mRubberBandIntersection->addPoint( QgsPointXY( mIntersection ) );
145+
mRubberBandIntersection->show();
146+
147+
mRubberBandExtend.reset( createRubberBand( match.layer()->geometryType() ) );
148+
149+
mGeom = match.layer()->getGeometry( match.featureId() );
150+
int index = match.vertexIndex();
151+
152+
if ( !mSegmentIntersects )
153+
{
154+
QgsPoint ptInter( mIntersection.x(), mIntersection.y() );
155+
if ( pExtend2.distance( ptInter ) < pExtend1.distance( ptInter ) )
156+
index += 1;
157+
}
158+
// TRIM PART
159+
else if ( QgsGeometryUtils::leftOfLine( QgsPoint( mMapPoint ), pLimit1, pLimit2 ) != QgsGeometryUtils::leftOfLine( pExtend1, pLimit1, pLimit2 ) )
160+
{
161+
// Part where the mouse is (+) will be trimed
162+
/* |
163+
* +
164+
* |
165+
* ----- --> -----
166+
* | |
167+
* | |
168+
*/
169+
170+
/* | |
171+
* | |
172+
* ----- --> -----
173+
* |
174+
* +
175+
* |
176+
*/
177+
index += 1;
178+
}
179+
180+
mIsModified = mGeom.moveVertex( mIntersection, index );
181+
182+
if ( mIsModified )
183+
{
184+
mRubberBandExtend->setToGeometry( mGeom );
185+
mRubberBandExtend->show();
186+
}
187+
}
188+
else
189+
{
190+
if ( mRubberBandExtend )
191+
mRubberBandExtend->hide();
192+
if ( mRubberBandIntersection )
193+
mRubberBandIntersection->hide();
194+
}
195+
}
196+
else
197+
{
198+
if ( mRubberBandExtend )
199+
mRubberBandExtend->hide();
200+
if ( mRubberBandIntersection )
201+
mRubberBandIntersection->hide();
202+
}
203+
break;
204+
}
205+
}
206+
207+
void QgsMapToolTrimExtendFeature::canvasReleaseEvent( QgsMapMouseEvent *e )
208+
{
209+
mMapPoint = e->mapPoint();
210+
211+
FeatureFilter filter;
212+
QgsPointLocator::Match match;
213+
214+
if ( e->button() == Qt::LeftButton )
215+
{
216+
switch ( mStep )
217+
{
218+
case StepLimit:
219+
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter );
220+
if ( mRubberBandLimit && mRubberBandLimit->isVisible() )
221+
{
222+
getPoints( match, pLimit1, pLimit2 );
223+
mStep = StepExtend;
224+
}
225+
break;
226+
case StepExtend:
227+
if ( mIsModified )
228+
{
229+
filter.setLayer( mVlayer );
230+
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter );
231+
232+
match.layer()->beginEditCommand( tr( "Trim/Extend feature" ) );
233+
match.layer()->changeGeometry( match.featureId(), mGeom );
234+
match.layer()->endEditCommand();
235+
match.layer()->triggerRepaint();
236+
237+
emit messageEmitted( tr( "Feature trimed/extended." ) );
238+
}
239+
else
240+
{
241+
emit messageEmitted( tr( "Couldn't trim or extend the feature." ) );
242+
}
243+
deactivate();
244+
break;
245+
}
246+
}
247+
else if ( e->button() == Qt::RightButton )
248+
{
249+
deactivate();
250+
}
251+
252+
}
253+
254+
void QgsMapToolTrimExtendFeature::keyPressEvent( QKeyEvent *e )
255+
{
256+
if ( e && e->isAutoRepeat() )
257+
{
258+
return;
259+
}
260+
261+
if ( e && e->key() == Qt::Key_Escape )
262+
{
263+
deactivate();
264+
}
265+
}
266+
267+
void QgsMapToolTrimExtendFeature::deactivate()
268+
{
269+
mStep = StepLimit;
270+
mIsModified = false;
271+
mIs3DLayer = false;
272+
mIsIntersection = false;
273+
mSegmentIntersects = false;
274+
mRubberBandLimit.reset();
275+
mRubberBandExtend.reset();
276+
mRubberBandIntersection.reset();
277+
QgsMapTool::deactivate();
278+
}
279+

‎src/app/qgsmaptooltrimextendfeature.h

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/***************************************************************************
2+
qgmaptooltrimextendfeature.h - map tool to trim or extend feature
3+
---------------------
4+
begin : October 2018
5+
copyright : (C) 2018 by Loïc Bartoletti
6+
email : loic dot bartoletti at oslandia dot 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+
#ifndef QGSMAPTOOLTRIMEXTENDFEATURE_H
17+
#define QGSMAPTOOLTRIMEXTENDFEATURE_H
18+
19+
#include "qgsmaptooledit.h"
20+
#include "qgis_app.h"
21+
#include "qgsrubberband.h"
22+
23+
class APP_EXPORT QgsMapToolTrimExtendFeature : public QgsMapToolEdit
24+
{
25+
public:
26+
Q_OBJECT
27+
28+
public:
29+
QgsMapToolTrimExtendFeature( QgsMapCanvas *canvas );
30+
~QgsMapToolTrimExtendFeature() override = default;
31+
32+
void canvasMoveEvent( QgsMapMouseEvent *e ) override;
33+
34+
void canvasReleaseEvent( QgsMapMouseEvent *e ) override;
35+
36+
void keyPressEvent( QKeyEvent *e ) override;
37+
38+
//! called when map tool is being deactivated
39+
void deactivate() override;
40+
41+
private:
42+
//! Rubberband that shows the limit
43+
std::unique_ptr<QgsRubberBand>mRubberBandLimit;
44+
//! Rubberband that shows the feature being extended
45+
std::unique_ptr<QgsRubberBand>mRubberBandExtend;
46+
//! Rubberband that shows the intersection point
47+
std::unique_ptr<QgsRubberBand>mRubberBandIntersection;
48+
//! Points for the limit
49+
QgsPoint pLimit1, pLimit2;
50+
//! Points for extend
51+
QgsPoint pExtend1, pExtend2;
52+
//! intersection point between the projection of [pExtend1 - pExtend2] on [pLimit1 - pLimit2]
53+
QgsPoint mIntersection;
54+
//! map point used to determine which edges will be used for trim the feature
55+
QgsPointXY mMapPoint;
56+
//! geometry that will be returned
57+
QgsGeometry mGeom;
58+
//! Current layer which will be modified
59+
QgsVectorLayer *mVlayer = nullptr;
60+
//! Keep information about the state of the intersection
61+
bool mIsIntersection = false;
62+
//! Keep information of the first layer snapped is 3D or not
63+
bool mIs3DLayer = false;
64+
//! if feature is modified
65+
bool mIsModified = false;
66+
//! if the segments are intersected = trim
67+
bool mSegmentIntersects = false;
68+
enum Step
69+
{
70+
StepLimit,
71+
StepExtend,
72+
};
73+
//! The first step (0): choose the limit. The second step (1): choose the segment to trim/extend
74+
Step mStep = StepLimit;
75+
};
76+
77+
#endif // QGSMAPTOOLTRIMEXTENDFEATURE_H

‎src/core/geometry/qgsgeometryutils.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,12 @@ QVector<QgsGeometryUtils::SelfIntersection> QgsGeometryUtils::selfIntersections(
529529
return intersections;
530530
}
531531

532-
int QgsGeometryUtils::leftOfLine( double x, double y, double x1, double y1, double x2, double y2 )
532+
int QgsGeometryUtils::leftOfLine( const QgsPoint &point, const QgsPoint &p1, const QgsPoint &p2 )
533+
{
534+
return leftOfLine( point.x(), point.y(), p1.x(), p1.y(), p2.x(), p2.y() );
535+
}
536+
537+
int QgsGeometryUtils::leftOfLine( const double x, const double y, const double x1, const double y1, const double x2, const double y2 )
533538
{
534539
double f1 = x - x1;
535540
double f2 = y2 - y1;

‎src/core/geometry/qgsgeometryutils.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,18 @@ class CORE_EXPORT QgsGeometryUtils
244244
* If the return value is 0, then the test was unsuccessful (e.g. due to testing a point exactly
245245
* on the line, or exactly in line with the segment) and the result is undefined.
246246
*/
247-
static int leftOfLine( double x, double y, double x1, double y1, double x2, double y2 );
247+
static int leftOfLine( const double x, const double y, const double x1, const double y1, const double x2, const double y2 );
248+
249+
/**
250+
* Returns a value < 0 if the point \a point is left of the line from \a p1 -> \a p2.
251+
* A positive return value indicates the point is to the right of the line.
252+
*
253+
* If the return value is 0, then the test was unsuccessful (e.g. due to testing a point exactly
254+
* on the line, or exactly in line with the segment) and the result is undefined.
255+
*
256+
* \since QGIS 3.6
257+
*/
258+
static int leftOfLine( const QgsPoint &point, const QgsPoint &p1, const QgsPoint &p2 );
248259

249260
/**
250261
* Returns a point a specified \a distance toward a second point.

‎src/ui/qgisapp.ui

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@
365365
<addaction name="mActionRotatePointSymbols"/>
366366
<addaction name="mActionOffsetPointSymbol"/>
367367
<addaction name="mActionReverseLine"/>
368+
<addaction name="mActionTrimExtendFeature"/>
368369
</widget>
369370
<addaction name="mProjectMenu"/>
370371
<addaction name="mEditMenu"/>
@@ -472,6 +473,7 @@
472473
<addaction name="mActionReshapeFeatures"/>
473474
<addaction name="mActionOffsetCurve"/>
474475
<addaction name="mActionReverseLine"/>
476+
<addaction name="mActionTrimExtendFeature"/>
475477
<addaction name="mActionSplitFeatures"/>
476478
<addaction name="mActionSplitParts"/>
477479
<addaction name="mActionMergeFeatures"/>
@@ -1063,6 +1065,18 @@
10631065
<string>Reverse line</string>
10641066
</property>
10651067
</action>
1068+
<action name="mActionTrimExtendFeature">
1069+
<property name="checkable">
1070+
<bool>true</bool>
1071+
</property>
1072+
<property name="icon">
1073+
<iconset resource="../../images/images.qrc">
1074+
<normaloff>:/images/themes/default/mActionTrimExtendFeature.svg</normaloff>:/images/themes/default/mActionTrimExtendFeature.svg</iconset>
1075+
</property>
1076+
<property name="text">
1077+
<string>Trim/Extend Feature</string>
1078+
</property>
1079+
</action>
10661080
<action name="mActionSnappingOptions">
10671081
<property name="text">
10681082
<string>&amp;Snapping Options…</string>

‎tests/src/app/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,5 @@ ADD_QGIS_TEST(measuretool testqgsmeasuretool.cpp)
111111
ADD_QGIS_TEST(vertextool testqgsvertextool.cpp)
112112
ADD_QGIS_TEST(vectorlayersaveasdialogtest testqgsvectorlayersaveasdialog.cpp)
113113
ADD_QGIS_TEST(maptoolreverselinetest testqgsmaptoolreverseline.cpp)
114+
ADD_QGIS_TEST(maptooltrimextendfeaturetest testqgsmaptooltrimextendfeature.cpp)
114115

‎tests/src/app/testqgsmaptooltrimextendfeature.cpp

Lines changed: 448 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.