Skip to content

Commit abcf2aa

Browse files
committedNov 13, 2018
init trim/extend feature
1 parent 530cd5c commit abcf2aa

File tree

14 files changed

+1067
-0
lines changed

14 files changed

+1067
-0
lines changed
 

‎images/images.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,7 @@
728728
<file>themes/default/mIconAllRings.svg</file>
729729
<file>themes/default/mIconExteriorRing.svg</file>
730730
<file>themes/default/mIconInteriorRings.svg</file>
731+
<file>themes/default/mActionTrimExtendFeature.svg</file>
731732
</qresource>
732733
<qresource prefix="/images/tips">
733734
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>
Lines changed: 196 additions & 0 deletions
Loading

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,17 @@ 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( QgsPoint point, QgsPoint p1, 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
@@ -315,6 +316,7 @@ SET (QGIS_APP_MOC_HDRS
315316
qgsmaptoolchangelabelproperties.h
316317
qgsmaptooldeletepart.h
317318
qgsmaptooldeletering.h
319+
qgsmaptooltrimextendfeature.h
318320
qgsmaptoolfeatureaction.h
319321
qgsmaptoolformannotation.h
320322
qgsmaptoolhtmlannotation.h

‎src/app/qgisapp.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
417417
#include "qgsmaptoolreverseline.h"
418418
#include "qgsgeometryvalidationmodel.h"
419419
#include "qgsgeometryvalidationdock.h"
420+
#include "qgsmaptooltrimextendfeature.h"
420421

421422
#include "vertextool/qgsvertextool.h"
422423

@@ -1489,6 +1490,7 @@ QgisApp::~QgisApp()
14891490
delete mMapTools.mChangeLabelProperties;
14901491
delete mMapTools.mDeletePart;
14911492
delete mMapTools.mDeleteRing;
1493+
delete mMapTools.mTrimExtendFeature;
14921494
delete mMapTools.mFeatureAction;
14931495
delete mMapTools.mFormAnnotation;
14941496
delete mMapTools.mHtmlAnnotation;
@@ -2120,6 +2122,7 @@ void QgisApp::createActions()
21202122
connect( mActionSnappingOptions, &QAction::triggered, this, &QgisApp::snappingOptions );
21212123
connect( mActionOffsetCurve, &QAction::triggered, this, &QgisApp::offsetCurve );
21222124
connect( mActionReverseLine, &QAction::triggered, this, &QgisApp::reverseLine );
2125+
connect( mActionTrimExtendFeature, &QAction::triggered, this, &QgisApp::trimExtendFeature );
21232126

21242127
// View Menu Items
21252128
connect( mActionPan, &QAction::triggered, this, &QgisApp::pan );
@@ -2407,6 +2410,7 @@ void QgisApp::createActionGroups()
24072410
mMapToolGroup->addAction( mActionRotateLabel );
24082411
mMapToolGroup->addAction( mActionChangeLabelProperties );
24092412
mMapToolGroup->addAction( mActionReverseLine );
2413+
mMapToolGroup->addAction( mActionTrimExtendFeature );
24102414

24112415
//
24122416
// Preview Modes Group
@@ -3395,6 +3399,7 @@ void QgisApp::setTheme( const QString &themeName )
33953399
mActionDecorationScaleBar->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleBar.svg" ) ) );
33963400
mActionDecorationGrid->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/transformed.svg" ) ) );
33973401
mActionReverseLine->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionReverseLine.svg" ) ) );
3402+
mActionTrimExtendFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionTrimExtendFeature.svg" ) ) );
33983403

33993404
emit currentThemeChanged( themeName );
34003405
}
@@ -3644,6 +3649,8 @@ void QgisApp::createCanvasTools()
36443649
mMapTools.mRotatePointSymbolsTool->setAction( mActionRotatePointSymbols );
36453650
mMapTools.mOffsetPointSymbolTool = new QgsMapToolOffsetPointSymbol( mMapCanvas );
36463651
mMapTools.mOffsetPointSymbolTool->setAction( mActionOffsetPointSymbol );
3652+
mMapTools.mTrimExtendFeature = new QgsMapToolTrimExtendFeature( mMapCanvas );
3653+
mMapTools.mTrimExtendFeature->setAction( mActionTrimExtendFeature );
36473654

36483655
mMapTools.mPinLabels = new QgsMapToolPinLabels( mMapCanvas );
36493656
mMapTools.mPinLabels->setAction( mActionPinLabels );
@@ -7857,6 +7864,11 @@ void QgisApp::reverseLine()
78577864
mMapCanvas->setMapTool( mMapTools.mReverseLine );
78587865
}
78597866

7867+
void QgisApp::trimExtendFeature()
7868+
{
7869+
mMapCanvas->setMapTool( mMapTools.mTrimExtendFeature );
7870+
}
7871+
78607872
QgsGeometry QgisApp::unionGeometries( const QgsVectorLayer *vl, QgsFeatureList &featureList, bool &canceled )
78617873
{
78627874
canceled = false;
@@ -12415,6 +12427,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
1241512427
mActionCopyLayer->setEnabled( false );
1241612428
mActionPasteLayer->setEnabled( false );
1241712429
mActionReverseLine->setEnabled( false );
12430+
mActionTrimExtendFeature->setEnabled( false );
1241812431

1241912432
mUndoDock->widget()->setEnabled( false );
1242012433
mActionUndo->setEnabled( false );
@@ -12490,6 +12503,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
1249012503
mActionLabeling->setEnabled( isSpatial );
1249112504
mActionDiagramProperties->setEnabled( isSpatial );
1249212505
mActionReverseLine->setEnabled( false );
12506+
mActionTrimExtendFeature->setEnabled( false );
1249312507

1249412508
mActionSelectFeatures->setEnabled( isSpatial );
1249512509
mActionSelectPolygon->setEnabled( isSpatial );
@@ -12634,6 +12648,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
1263412648
mActionSimplifyFeature->setEnabled( isEditable && canChangeGeometry );
1263512649
mActionOffsetCurve->setEnabled( isEditable && canAddFeatures && canChangeAttributes );
1263612650
mActionReverseLine->setEnabled( isEditable && canChangeGeometry );
12651+
mActionTrimExtendFeature->setEnabled( isEditable && canChangeGeometry );
1263712652

1263812653
mActionAddRing->setEnabled( false );
1263912654
mActionFillRing->setEnabled( false );
@@ -12654,6 +12669,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
1265412669
mActionSimplifyFeature->setEnabled( isEditable && canChangeGeometry );
1265512670
mActionDeleteRing->setEnabled( isEditable && canChangeGeometry );
1265612671
mActionOffsetCurve->setEnabled( isEditable && canAddFeatures && canChangeAttributes );
12672+
mActionTrimExtendFeature->setEnabled( isEditable && canChangeGeometry );
1265712673
}
1265812674
else if ( vlayer->geometryType() == QgsWkbTypes::NullGeometry )
1265912675
{

‎src/app/qgisapp.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,6 +1432,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
14321432
void offsetPointSymbol();
14331433
//! activates the reverse line tool
14341434
void reverseLine();
1435+
//! activates the trim/extend feature tool
1436+
void trimExtendFeature();
14351437
//! activates the tool
14361438
void setMapTool( QgsMapTool *tool, bool clean = false );
14371439

@@ -2085,6 +2087,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
20852087
QgsMapTool *mRotateLabel = nullptr;
20862088
QgsMapTool *mChangeLabelProperties = nullptr;
20872089
QgsMapTool *mReverseLine = nullptr ;
2090+
QgsMapTool *mTrimExtendFeature = nullptr ;
20882091
} mMapTools;
20892092

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

‎src/app/qgsmaptooltrimextendfeature.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/***************************************************************************
2+
qgmaptoolextendfeature.h - map tool for extending 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 3 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 "qgspointlocator.h"
22+
23+
24+
class APP_EXPORT QgsMapToolTrimExtendFeature : public QgsMapToolEdit
25+
{
26+
public:
27+
Q_OBJECT
28+
29+
public:
30+
QgsMapToolTrimExtendFeature( QgsMapCanvas *canvas );
31+
~QgsMapToolTrimExtendFeature() override;
32+
33+
void canvasMoveEvent( QgsMapMouseEvent *e ) override;
34+
35+
void canvasReleaseEvent( QgsMapMouseEvent *e ) override;
36+
37+
void keyPressEvent( QKeyEvent *e ) override;
38+
39+
//! called when map tool is being deactivated
40+
void deactivate() override;
41+
42+
private:
43+
//! Rubberband that shows the limit
44+
std::unique_ptr<QgsRubberBand>mRubberBandLimit;
45+
//! Rubberband that shows the feature being extended
46+
std::unique_ptr<QgsRubberBand>mRubberBandExtend;
47+
//! Rubberband that shows the intersection point
48+
std::unique_ptr<QgsRubberBand>mRubberBandIntersection;
49+
//! Points for the limit
50+
QgsPoint pLimit1, pLimit2;
51+
//! Points for extend
52+
QgsPoint pExtend1, pExtend2;
53+
//! intersection point between the projection of [pExtend1 - pExtend2] on [pLimit1 - pLimit2]
54+
QgsPoint intersection;
55+
//! map point used to determine which edges will be used for trim the feature
56+
QgsPointXY mapPoint;
57+
//! geometry that will be returned
58+
QgsGeometry geom;
59+
//! Current layer which will be modified
60+
QgsVectorLayer *vlayer = nullptr;
61+
//! Keep information about the state of the intersection
62+
bool isIntersection = false;
63+
//! Keep information of the first layer snapped is 3D or not
64+
bool is3DLayer = false;
65+
//! if feature is modified
66+
bool isModified = false;
67+
//! if the segments are intersected = trim
68+
bool segmentIntersects = false;
69+
//! The first step (0): choose the limit. The second step (1): choose the segment to trim/extend
70+
int step = 0;
71+
};
72+
73+
#endif // QGSMAPTOOLTRIMEXTENDFEATURE_H

‎src/core/geometry/qgsgeometryutils.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,11 @@ QVector<QgsGeometryUtils::SelfIntersection> QgsGeometryUtils::selfIntersections(
529529
return intersections;
530530
}
531531

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

‎src/core/geometry/qgsgeometryutils.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,17 @@ class CORE_EXPORT QgsGeometryUtils
246246
*/
247247
static int leftOfLine( double x, double y, double x1, double y1, double x2, double y2 );
248248

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( QgsPoint point, QgsPoint p1, QgsPoint p2 );
259+
249260
/**
250261
* Returns a point a specified \a distance toward a second point.
251262
*/

‎src/core/qgsvector3d.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include "qgis_core.h"
2020
#include "qgis.h"
21+
#include "qgspoint.h"
2122

2223
#include <QVector3D>
2324

‎src/ui/qgisapp.ui

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@
363363
<addaction name="mActionRotatePointSymbols"/>
364364
<addaction name="mActionOffsetPointSymbol"/>
365365
<addaction name="mActionReverseLine"/>
366+
<addaction name="mActionTrimExtendFeature"/>
366367
</widget>
367368
<addaction name="mProjectMenu"/>
368369
<addaction name="mEditMenu"/>
@@ -471,6 +472,7 @@
471472
<addaction name="mActionReshapeFeatures"/>
472473
<addaction name="mActionOffsetCurve"/>
473474
<addaction name="mActionReverseLine"/>
475+
<addaction name="mActionTrimExtendFeature"/>
474476
<addaction name="mActionSplitFeatures"/>
475477
<addaction name="mActionSplitParts"/>
476478
<addaction name="mActionMergeFeatures"/>
@@ -1062,6 +1064,18 @@
10621064
<string>Reverse line</string>
10631065
</property>
10641066
</action>
1067+
<action name="mActionTrimExtendFeature">
1068+
<property name="checkable">
1069+
<bool>true</bool>
1070+
</property>
1071+
<property name="icon">
1072+
<iconset resource="../../images/images.qrc">
1073+
<normaloff>:/images/themes/default/mActionTrimExtendFeature.svg</normaloff>:/images/themes/default/mActionTrimExtendFeature.svg</iconset>
1074+
</property>
1075+
<property name="text">
1076+
<string>Trim/Extend Feature</string>
1077+
</property>
1078+
</action>
10651079
<action name="mActionSnappingOptions">
10661080
<property name="text">
10671081
<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.