Merge pull request #8429 from lbartoletti/extendMapTool

[needs-docs][FEATURE] Trim/extend
This commit is contained in:
Denis Rouzaud 2018-11-21 06:36:08 +01:00 committed by GitHub
commit d1d3a51efb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1060 additions and 3 deletions

View File

@ -732,6 +732,7 @@
<file>themes/default/mIconExteriorRing.svg</file>
<file>themes/default/mIconInteriorRings.svg</file>
<file>themes/default/mIconFieldBinary.svg</file>
<file>themes/default/mActionTrimExtendFeature.svg</file>
<file>themes/default/mActionTerminal.svg</file>
<file>themes/default/mIconFolder24.svg</file>
<file>themes/default/mActionNewFolder.svg</file>

View File

@ -0,0 +1,196 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="24"
width="24"
version="1.1"
id="svg14115"
sodipodi:docname="trim_extend_final.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
inkscape:export-filename="/home/sbe/icon.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<metadata
id="metadata14121">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs14119">
<linearGradient
inkscape:collect="always"
id="linearGradient1108">
<stop
style="stop-color:#9e9e9e;stop-opacity:0.56849313"
offset="0"
id="stop1104" />
<stop
id="stop1112"
offset="0.5"
style="stop-color:#9e9e9e;stop-opacity:1" />
<stop
style="stop-color:#9e9e9e;stop-opacity:0.57191783"
offset="1"
id="stop1106" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1108"
id="linearGradient1110"
x1="0.41561731"
y1="10.903339"
x2="23.584382"
y2="10.903339"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1171"
id="namedview14117"
showgrid="false"
inkscape:zoom="14.479178"
inkscape:cx="7.4047109"
inkscape:cy="16.874197"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg14115"
inkscape:showpageshadow="false" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#498049;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.74823844;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 14.892578,19.973633 V 24 h 2 v -4.026367 z"
id="path1524-6-5-67-9-3"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:url(#linearGradient1110);stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 1.4298598,10.903339 H 22.57014"
id="path981"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#498049;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.81594944;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 9.3339844,-0.02945891 V 0.79495923 9.8474569 10.671875 H 11.333984 V 9.8474569 0.79495923 -0.02945891 Z"
id="path1524-9-1-9-5-7-6"
inkscape:connector-curvature="0" />
<path
style="fill:#ffe6d5;stroke:#ffcba8;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="M 10.333256,19.623725 V 11.584457"
id="path1524-9-3-8-2-3-3-1"
inkscape:connector-curvature="0" />
<path
style="fill:#8cbe8c;fill-opacity:1;stroke:#cee3ce;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 15.892872,19.999884 V 10.422903"
id="path1524-62-9-2-5-6-0"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#5ba15b;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.06707275;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 15.607917,10.423828 V 20 h 0.569322 v -9.576172 z"
id="path1524-62-9-2-5-6-0-6"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e80000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.88558096;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 10.100419,11.583984 v 8.040229 h 0.467131 v -8.040229 z"
id="path1524-9-3-8-2-3-3-1-3"
inkscape:connector-curvature="0" />
<rect
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:#7e7e7e;stroke-width:1.18957305;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"
id="rect919-6-5-6-7-4-7"
width="2.810427"
height="2.810427"
x="8.9280491"
y="9.5409317" />
<rect
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:#7e7e7e;stroke-width:1.18957305;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"
id="rect919-6-5-6-7-4-5-6"
width="2.810427"
height="2.810427"
x="14.487658"
y="9.5409317" />
<rect
style="opacity:0.53600003;fill:#ffffff;fill-opacity:1;stroke:#7e7e7e;stroke-width:0.95076692;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"
id="rect919-6-5-6-7-4-5-6-2"
width="2.2462351"
height="2.2462351"
x="14.778381"
y="19.052471" />
<rect
style="opacity:0.53600003;fill:#ffffff;fill-opacity:1;stroke:#7e7e7e;stroke-width:0.95076692;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"
id="rect919-6-5-6-7-4-5-6-2-6"
width="2.2462351"
height="2.2462351"
x="9.2237778"
y="17.994656" />
<g
id="g1368"
transform="matrix(0.70000007,0,0,0.69999993,-32.671712,30.638281)">
<g
id="g1379">
<rect
style="fill:#d26464"
height="10.000001"
rx="2.0114901"
width="9.999999"
x="47.889584"
y="-20.177546"
id="rect1223" />
<path
style="opacity:0.3;fill:#fcffff;fill-rule:evenodd"
inkscape:connector-curvature="0"
d="m 48.889584,-15.177545 h 7.999999 c 0,0 0,0 0,-2 0,-2 -0.5,-2 -4,-2 -3.499999,0 -3.999999,0 -3.999999,2 0,2 0,2 0,2 z"
id="path1225" />
<path
inkscape:connector-curvature="0"
d="m 50.768263,-13.056225 4.24264,-4.24264"
style="overflow:visible;fill:#ffffff;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
id="path1227-9" />
<path
inkscape:connector-curvature="0"
d="m 50.768263,-17.298865 4.24264,4.24264"
style="overflow:visible;fill:#ffffff;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
id="path1229" />
</g>
</g>
<g
id="g1373"
transform="matrix(0.63636364,0,0,0.63636364,30.841475,21.941358)">
<rect
style="fill:#c4a000"
height="11"
rx="2.0114901"
width="11"
x="-23.785124"
y="-32.72242"
id="rect1320" />
<path
style="fill:#fcffff"
inkscape:connector-curvature="0"
d="m -18.785123,-31.722418 v 2.0625 c -0.537663,0.111041 -1.024662,0.383291 -1.375,0.78125 l -1.78125,-1.03125 -0.5,0.875 1.78125,1.03125 c -0.08206,0.247432 -0.125,0.506395 -0.125,0.78125 0,0.274855 0.04294,0.533818 0.125,0.78125 l -1.78125,1.03125 0.5,0.875 1.78125,-1.03125 c 0.352503,0.40042 0.832682,0.670182 1.375,0.78125 v 2.0625 h 1 v -2.0625 c 0.537663,-0.111041 1.024662,-0.383291 1.375,-0.78125 l 1.78125,1.03125 0.5,-0.875 -1.78125,-1.03125 c 0.08206,-0.247432 0.125,-0.506395 0.125,-0.78125 0,-0.274855 -0.04294,-0.533818 -0.125,-0.78125 l 1.78125,-1.03125 -0.5,-0.875 -1.78125,1.03125 c -0.352503,-0.40042 -0.832682,-0.670182 -1.375,-0.78125 v -2.0625 z m 0.5,3.5 c 0.552,0 1,0.448 1,1 0,0.552 -0.448,1 -1,1 -0.552,0 -1,-0.448 -1,-1 0,-0.552 0.448,-1 1,-1 z"
id="path1322" />
<path
style="opacity:0.3;fill:#fcffff;fill-rule:evenodd"
inkscape:connector-curvature="0"
d="m -22.785123,-26.722418 9,-0.0096 c 0,0 0,0 0,-2 0,-2.9904 -1,-2.9904 -4.5,-2.9904 -3.5,0 -4.5,0 -4.5,3 0,2 0,2 0,2 z"
id="path1324" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -217,13 +217,24 @@ Project the point on a segment
static int leftOfLine( double x, double y, double x1, double y1, double x2, double y2 );
static int leftOfLine( const double x, const double y, const double x1, const double y1, const double x2, const double y2 );
%Docstring
Returns a value < 0 if the point (``x``, ``y``) is left of the line from (``x1``, ``y1``) -> ( ``x2``, ``y2``).
A positive return value indicates the point is to the right of the line.
If the return value is 0, then the test was unsuccessful (e.g. due to testing a point exactly
on the line, or exactly in line with the segment) and the result is undefined.
%End
static int leftOfLine( const QgsPoint &point, const QgsPoint &p1, const QgsPoint &p2 );
%Docstring
Returns a value < 0 if the point ``point`` is left of the line from ``p1`` -> ``p2``.
A positive return value indicates the point is to the right of the line.
If the return value is 0, then the test was unsuccessful (e.g. due to testing a point exactly
on the line, or exactly in line with the segment) and the result is undefined.
.. versionadded:: 3.6
%End
static QgsPoint pointOnLineWithDistance( const QgsPoint &startPoint, const QgsPoint &directionPoint, double distance );

View File

@ -81,6 +81,7 @@ SET(QGIS_APP_SRCS
qgsmaptoolchangelabelproperties.cpp
qgsmaptooldeletering.cpp
qgsmaptooldeletepart.cpp
qgsmaptooltrimextendfeature.cpp
qgsmaptoolfeatureaction.cpp
qgsmaptoolformannotation.cpp
qgsmaptoolhtmlannotation.cpp
@ -317,6 +318,7 @@ SET (QGIS_APP_MOC_HDRS
qgsmaptoolchangelabelproperties.h
qgsmaptooldeletepart.h
qgsmaptooldeletering.h
qgsmaptooltrimextendfeature.h
qgsmaptoolfeatureaction.h
qgsmaptoolformannotation.h
qgsmaptoolhtmlannotation.h

View File

@ -418,6 +418,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgsmaptoolreverseline.h"
#include "qgsgeometryvalidationmodel.h"
#include "qgsgeometryvalidationdock.h"
#include "qgsmaptooltrimextendfeature.h"
#include "vertextool/qgsvertextool.h"
@ -1488,6 +1489,7 @@ QgisApp::~QgisApp()
delete mMapTools.mChangeLabelProperties;
delete mMapTools.mDeletePart;
delete mMapTools.mDeleteRing;
delete mMapTools.mTrimExtendFeature;
delete mMapTools.mFeatureAction;
delete mMapTools.mFormAnnotation;
delete mMapTools.mHtmlAnnotation;
@ -2121,6 +2123,7 @@ void QgisApp::createActions()
connect( mActionSnappingOptions, &QAction::triggered, this, &QgisApp::snappingOptions );
connect( mActionOffsetCurve, &QAction::triggered, this, &QgisApp::offsetCurve );
connect( mActionReverseLine, &QAction::triggered, this, &QgisApp::reverseLine );
connect( mActionTrimExtendFeature, &QAction::triggered, this, [ = ] { mMapCanvas->setMapTool( mMapTools.mTrimExtendFeature ); } );
// View Menu Items
connect( mActionPan, &QAction::triggered, this, &QgisApp::pan );
@ -2409,6 +2412,7 @@ void QgisApp::createActionGroups()
mMapToolGroup->addAction( mActionRotateLabel );
mMapToolGroup->addAction( mActionChangeLabelProperties );
mMapToolGroup->addAction( mActionReverseLine );
mMapToolGroup->addAction( mActionTrimExtendFeature );
//
// Preview Modes Group
@ -3387,6 +3391,7 @@ void QgisApp::setTheme( const QString &themeName )
mActionDecorationScaleBar->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleBar.svg" ) ) );
mActionDecorationGrid->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/grid.svg" ) ) );
mActionReverseLine->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionReverseLine.svg" ) ) );
mActionTrimExtendFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionTrimExtendFeature.svg" ) ) );
emit currentThemeChanged( themeName );
}
@ -3638,6 +3643,8 @@ void QgisApp::createCanvasTools()
mMapTools.mRotatePointSymbolsTool->setAction( mActionRotatePointSymbols );
mMapTools.mOffsetPointSymbolTool = new QgsMapToolOffsetPointSymbol( mMapCanvas );
mMapTools.mOffsetPointSymbolTool->setAction( mActionOffsetPointSymbol );
mMapTools.mTrimExtendFeature = new QgsMapToolTrimExtendFeature( mMapCanvas );
mMapTools.mTrimExtendFeature->setAction( mActionTrimExtendFeature );
mMapTools.mPinLabels = new QgsMapToolPinLabels( mMapCanvas );
mMapTools.mPinLabels->setAction( mActionPinLabels );
@ -12407,6 +12414,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
mActionCopyLayer->setEnabled( false );
mActionPasteLayer->setEnabled( false );
mActionReverseLine->setEnabled( false );
mActionTrimExtendFeature->setEnabled( false );
mUndoDock->widget()->setEnabled( false );
mActionUndo->setEnabled( false );
@ -12482,6 +12490,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
mActionLabeling->setEnabled( isSpatial );
mActionDiagramProperties->setEnabled( isSpatial );
mActionReverseLine->setEnabled( false );
mActionTrimExtendFeature->setEnabled( false );
mActionSelectFeatures->setEnabled( isSpatial );
mActionSelectPolygon->setEnabled( isSpatial );
@ -12627,6 +12636,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
mActionSimplifyFeature->setEnabled( isEditable && canChangeGeometry );
mActionOffsetCurve->setEnabled( isEditable && canAddFeatures && canChangeAttributes );
mActionReverseLine->setEnabled( isEditable && canChangeGeometry );
mActionTrimExtendFeature->setEnabled( isEditable && canChangeGeometry );
mActionAddRing->setEnabled( false );
mActionFillRing->setEnabled( false );
@ -12647,6 +12657,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
mActionSimplifyFeature->setEnabled( isEditable && canChangeGeometry );
mActionDeleteRing->setEnabled( isEditable && canChangeGeometry );
mActionOffsetCurve->setEnabled( isEditable && canAddFeatures && canChangeAttributes );
mActionTrimExtendFeature->setEnabled( isEditable && canChangeGeometry );
}
else if ( vlayer->geometryType() == QgsWkbTypes::NullGeometry )
{

View File

@ -2084,6 +2084,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QgsMapTool *mRotateLabel = nullptr;
QgsMapTool *mChangeLabelProperties = nullptr;
QgsMapTool *mReverseLine = nullptr ;
QgsMapTool *mTrimExtendFeature = nullptr ;
} mMapTools;
QgsMapTool *mNonEditMapTool = nullptr;

View File

@ -0,0 +1,279 @@
/***************************************************************************
qgmaptooltrimextendfeature.cpp - map tool to trim or extend feature
---------------------
begin : October 2018
copyright : (C) 2018 by Loïc Bartoletti
email : loic dot bartoletti at oslandia dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsmaptooltrimextendfeature.h"
#include "qgsmapcanvas.h"
#include "qgsvertexmarker.h"
#include "qgsvectorlayer.h"
#include "qgsgeometry.h"
#include "qgssnappingutils.h"
#include "qgstolerance.h"
#include "qgisapp.h"
#include "qgsgeometryutils.h"
#include "qgsmapmouseevent.h"
#include "qgssnapindicator.h"
class QgsRubberBand;
class FeatureFilter : public QgsPointLocator::MatchFilter
{
public:
FeatureFilter()
{}
bool acceptMatch( const QgsPointLocator::Match &match ) override
{
if ( mLayer )
return match.layer() == mLayer && match.hasEdge();
return match.hasEdge();
}
// We only want to modify the current layer. When geometries are overlapped, this makes it possible to snap onto the current layer.
void setLayer( QgsVectorLayer *layer ) { mLayer = layer; }
private:
const QgsVectorLayer *mLayer = nullptr;
};
QgsMapToolTrimExtendFeature::QgsMapToolTrimExtendFeature( QgsMapCanvas *canvas )
: QgsMapToolEdit( canvas )
{
mToolName = tr( "Trim/Extend feature" );
}
static void getPoints( const QgsPointLocator::Match &match, QgsPoint &p1, QgsPoint &p2 )
{
const QgsFeatureId fid = match.featureId();
const int vertex = match.vertexIndex();
const QgsGeometry geom = match.layer()->getGeometry( fid );
if ( !( geom.isNull() || geom.isEmpty() ) )
{
p1 = geom.vertexAt( vertex );
p2 = geom.vertexAt( vertex + 1 );
}
}
void QgsMapToolTrimExtendFeature::canvasMoveEvent( QgsMapMouseEvent *e )
{
mMapPoint = e->mapPoint();
FeatureFilter filter;
QgsPointLocator::Match match;
switch ( mStep )
{
case StepLimit:
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter );
if ( match.isValid() )
{
mIs3DLayer = QgsWkbTypes::hasZ( match.layer()->wkbType() );
QgsPointXY p1, p2;
match.edgePoints( p1, p2 );
mRubberBandLimit.reset( createRubberBand( QgsWkbTypes::LineGeometry ) );
mRubberBandLimit->addPoint( p1 );
mRubberBandLimit->addPoint( p2 );
mRubberBandLimit->show();
}
else if ( mRubberBandLimit )
{
mRubberBandLimit->hide();
}
break;
case StepExtend:
QgsMapLayer *currentLayer = mCanvas->currentLayer();
if ( !currentLayer )
break;
mVlayer = qobject_cast<QgsVectorLayer *>( currentLayer );
if ( !mVlayer )
break;
if ( !mVlayer->isEditable() )
break;
filter.setLayer( mVlayer );
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter );
if ( match.isValid() )
{
if ( match.layer() != mVlayer )
break;
QgsPointXY p1, p2;
match.edgePoints( p1, p2 );
getPoints( match, pExtend1, pExtend2 );
// No need to trim/extend if segments are continuous
if ( ( ( pLimit1 == pExtend1 ) || ( pLimit1 == pExtend2 ) ) || ( ( pLimit2 == pExtend1 ) || ( pLimit2 == pExtend2 ) ) )
break;
mSegmentIntersects = QgsGeometryUtils::segmentIntersection( pLimit1, pLimit2, pExtend1, pExtend2, mIntersection, mIsIntersection, 1e-8, true );
if ( mIs3DLayer && QgsWkbTypes::hasZ( match.layer()->wkbType() ) )
{
/* Z Interpolation */
QgsLineString line( pLimit1, pLimit2 );
mIntersection = QgsGeometryUtils::closestPoint( line, QgsPoint( mIntersection ) );
}
if ( mIsIntersection )
{
mRubberBandIntersection.reset( createRubberBand( QgsWkbTypes::PointGeometry ) );
mRubberBandIntersection->addPoint( QgsPointXY( mIntersection ) );
mRubberBandIntersection->show();
mRubberBandExtend.reset( createRubberBand( match.layer()->geometryType() ) );
mGeom = match.layer()->getGeometry( match.featureId() );
int index = match.vertexIndex();
if ( !mSegmentIntersects )
{
QgsPoint ptInter( mIntersection.x(), mIntersection.y() );
if ( pExtend2.distance( ptInter ) < pExtend1.distance( ptInter ) )
index += 1;
}
// TRIM PART
else if ( QgsGeometryUtils::leftOfLine( QgsPoint( mMapPoint ), pLimit1, pLimit2 ) != QgsGeometryUtils::leftOfLine( pExtend1, pLimit1, pLimit2 ) )
{
// Part where the mouse is (+) will be trimed
/* |
* +
* |
* ----- --> -----
* | |
* | |
*/
/* | |
* | |
* ----- --> -----
* |
* +
* |
*/
index += 1;
}
mIsModified = mGeom.moveVertex( mIntersection, index );
if ( mIsModified )
{
mRubberBandExtend->setToGeometry( mGeom );
mRubberBandExtend->show();
}
}
else
{
if ( mRubberBandExtend )
mRubberBandExtend->hide();
if ( mRubberBandIntersection )
mRubberBandIntersection->hide();
}
}
else
{
if ( mRubberBandExtend )
mRubberBandExtend->hide();
if ( mRubberBandIntersection )
mRubberBandIntersection->hide();
}
break;
}
}
void QgsMapToolTrimExtendFeature::canvasReleaseEvent( QgsMapMouseEvent *e )
{
mMapPoint = e->mapPoint();
FeatureFilter filter;
QgsPointLocator::Match match;
if ( e->button() == Qt::LeftButton )
{
switch ( mStep )
{
case StepLimit:
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter );
if ( mRubberBandLimit && mRubberBandLimit->isVisible() )
{
getPoints( match, pLimit1, pLimit2 );
mStep = StepExtend;
}
break;
case StepExtend:
if ( mIsModified )
{
filter.setLayer( mVlayer );
match = mCanvas->snappingUtils()->snapToMap( mMapPoint, &filter );
match.layer()->beginEditCommand( tr( "Trim/Extend feature" ) );
match.layer()->changeGeometry( match.featureId(), mGeom );
match.layer()->endEditCommand();
match.layer()->triggerRepaint();
emit messageEmitted( tr( "Feature trimed/extended." ) );
}
else
{
emit messageEmitted( tr( "Couldn't trim or extend the feature." ) );
}
deactivate();
break;
}
}
else if ( e->button() == Qt::RightButton )
{
deactivate();
}
}
void QgsMapToolTrimExtendFeature::keyPressEvent( QKeyEvent *e )
{
if ( e && e->isAutoRepeat() )
{
return;
}
if ( e && e->key() == Qt::Key_Escape )
{
deactivate();
}
}
void QgsMapToolTrimExtendFeature::deactivate()
{
mStep = StepLimit;
mIsModified = false;
mIs3DLayer = false;
mIsIntersection = false;
mSegmentIntersects = false;
mRubberBandLimit.reset();
mRubberBandExtend.reset();
mRubberBandIntersection.reset();
QgsMapTool::deactivate();
}

View File

@ -0,0 +1,77 @@
/***************************************************************************
qgmaptooltrimextendfeature.h - map tool to trim or extend feature
---------------------
begin : October 2018
copyright : (C) 2018 by Loïc Bartoletti
email : loic dot bartoletti at oslandia dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSMAPTOOLTRIMEXTENDFEATURE_H
#define QGSMAPTOOLTRIMEXTENDFEATURE_H
#include "qgsmaptooledit.h"
#include "qgis_app.h"
#include "qgsrubberband.h"
class APP_EXPORT QgsMapToolTrimExtendFeature : public QgsMapToolEdit
{
public:
Q_OBJECT
public:
QgsMapToolTrimExtendFeature( QgsMapCanvas *canvas );
~QgsMapToolTrimExtendFeature() override = default;
void canvasMoveEvent( QgsMapMouseEvent *e ) override;
void canvasReleaseEvent( QgsMapMouseEvent *e ) override;
void keyPressEvent( QKeyEvent *e ) override;
//! called when map tool is being deactivated
void deactivate() override;
private:
//! Rubberband that shows the limit
std::unique_ptr<QgsRubberBand>mRubberBandLimit;
//! Rubberband that shows the feature being extended
std::unique_ptr<QgsRubberBand>mRubberBandExtend;
//! Rubberband that shows the intersection point
std::unique_ptr<QgsRubberBand>mRubberBandIntersection;
//! Points for the limit
QgsPoint pLimit1, pLimit2;
//! Points for extend
QgsPoint pExtend1, pExtend2;
//! intersection point between the projection of [pExtend1 - pExtend2] on [pLimit1 - pLimit2]
QgsPoint mIntersection;
//! map point used to determine which edges will be used for trim the feature
QgsPointXY mMapPoint;
//! geometry that will be returned
QgsGeometry mGeom;
//! Current layer which will be modified
QgsVectorLayer *mVlayer = nullptr;
//! Keep information about the state of the intersection
bool mIsIntersection = false;
//! Keep information of the first layer snapped is 3D or not
bool mIs3DLayer = false;
//! if feature is modified
bool mIsModified = false;
//! if the segments are intersected = trim
bool mSegmentIntersects = false;
enum Step
{
StepLimit,
StepExtend,
};
//! The first step (0): choose the limit. The second step (1): choose the segment to trim/extend
Step mStep = StepLimit;
};
#endif // QGSMAPTOOLTRIMEXTENDFEATURE_H

View File

@ -529,7 +529,12 @@ QVector<QgsGeometryUtils::SelfIntersection> QgsGeometryUtils::selfIntersections(
return intersections;
}
int QgsGeometryUtils::leftOfLine( double x, double y, double x1, double y1, double x2, double y2 )
int QgsGeometryUtils::leftOfLine( const QgsPoint &point, const QgsPoint &p1, const QgsPoint &p2 )
{
return leftOfLine( point.x(), point.y(), p1.x(), p1.y(), p2.x(), p2.y() );
}
int QgsGeometryUtils::leftOfLine( const double x, const double y, const double x1, const double y1, const double x2, const double y2 )
{
double f1 = x - x1;
double f2 = y2 - y1;

View File

@ -244,7 +244,18 @@ class CORE_EXPORT QgsGeometryUtils
* If the return value is 0, then the test was unsuccessful (e.g. due to testing a point exactly
* on the line, or exactly in line with the segment) and the result is undefined.
*/
static int leftOfLine( double x, double y, double x1, double y1, double x2, double y2 );
static int leftOfLine( const double x, const double y, const double x1, const double y1, const double x2, const double y2 );
/**
* Returns a value < 0 if the point \a point is left of the line from \a p1 -> \a p2.
* A positive return value indicates the point is to the right of the line.
*
* If the return value is 0, then the test was unsuccessful (e.g. due to testing a point exactly
* on the line, or exactly in line with the segment) and the result is undefined.
*
* \since QGIS 3.6
*/
static int leftOfLine( const QgsPoint &point, const QgsPoint &p1, const QgsPoint &p2 );
/**
* Returns a point a specified \a distance toward a second point.

View File

@ -365,6 +365,7 @@
<addaction name="mActionRotatePointSymbols"/>
<addaction name="mActionOffsetPointSymbol"/>
<addaction name="mActionReverseLine"/>
<addaction name="mActionTrimExtendFeature"/>
</widget>
<addaction name="mProjectMenu"/>
<addaction name="mEditMenu"/>
@ -472,6 +473,7 @@
<addaction name="mActionReshapeFeatures"/>
<addaction name="mActionOffsetCurve"/>
<addaction name="mActionReverseLine"/>
<addaction name="mActionTrimExtendFeature"/>
<addaction name="mActionSplitFeatures"/>
<addaction name="mActionSplitParts"/>
<addaction name="mActionMergeFeatures"/>
@ -1063,6 +1065,18 @@
<string>Reverse line</string>
</property>
</action>
<action name="mActionTrimExtendFeature">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionTrimExtendFeature.svg</normaloff>:/images/themes/default/mActionTrimExtendFeature.svg</iconset>
</property>
<property name="text">
<string>Trim/Extend Feature</string>
</property>
</action>
<action name="mActionSnappingOptions">
<property name="text">
<string>&amp;Snapping Options…</string>

View File

@ -111,4 +111,5 @@ ADD_QGIS_TEST(measuretool testqgsmeasuretool.cpp)
ADD_QGIS_TEST(vertextool testqgsvertextool.cpp)
ADD_QGIS_TEST(vectorlayersaveasdialogtest testqgsvectorlayersaveasdialog.cpp)
ADD_QGIS_TEST(maptoolreverselinetest testqgsmaptoolreverseline.cpp)
ADD_QGIS_TEST(maptooltrimextendfeaturetest testqgsmaptooltrimextendfeature.cpp)

View File

@ -0,0 +1,448 @@
/***************************************************************************
TestQgsMapToolTrimExtendFeature.cpp
--------------------------------
Date : October 2018
Copyright : (C) 2018 by Loïc Bartoletti
Email : loic dot bartoletti at oslandia dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgstest.h"
#include <QObject>
#include <QString>
#include "qgsapplication.h"
#include "qgsvectorlayer.h"
#include "qgsvectordataprovider.h"
#include "qgsgeometry.h"
#include "qgsproject.h"
#include "qgssnappingutils.h"
#include "qgssnappingconfig.h"
#include "qgscategorizedsymbolrenderer.h"
#include "qgssettings.h"
#include "qgsmapcanvas.h"
#include "qgsmapmouseevent.h"
#include "qgsmapcanvassnappingutils.h"
#include "qgsmaptooltrimextendfeature.h"
class TestQgsMapToolTrimExtendFeature : public QObject
{
Q_OBJECT
public:
TestQgsMapToolTrimExtendFeature() = default;
private:
std::unique_ptr<QgsVectorLayer> vlPolygon, vlMultiLine, vlLineZ;
QgsFeature f1, f2;
QgsMapCanvas *mCanvas = nullptr;
private slots:
void initTestCase()
{
QgsApplication::init();
QgsApplication::initQgis();
// Will make sure the settings dir with the style file for color ramp is created
QgsApplication::createDatabase();
QgsApplication::showSettings();
// vector layer with a triangle in a rectangle:
// (0,3) +-------------------+ (3,3)
// | (1,2) +---+ (2,2) |
// | \ | |
// | \ | |
// | \| |
// | + (2,1) |
// (0,0) +-------------------+ (3,0)
vlPolygon.reset( new QgsVectorLayer( QStringLiteral( "MultiPolygon?field=fld:int" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) ) );
int idx = vlPolygon->fields().indexFromName( QStringLiteral( "fld" ) );
QVERIFY( idx != -1 );
f1.initAttributes( 1 );
f2.initAttributes( 1 );
QgsPolygonXY polygon;
QgsPolylineXY polyline;
polyline << QgsPointXY( 1, 2 ) << QgsPointXY( 2, 1 ) << QgsPointXY( 2, 2 ) << QgsPointXY( 1, 2 );
polygon << polyline;
QgsGeometry polygonGeom = QgsGeometry::fromPolygonXY( polygon );
f1.setGeometry( polygonGeom );
f1.setAttribute( idx, QVariant( 1 ) );
QgsFeatureList flist;
flist << f1;
vlPolygon->dataProvider()->addFeatures( flist );
QgsPolygonXY polygon2;
QgsPolylineXY polyline2;
polyline2 << QgsPointXY( 0, 0 ) << QgsPointXY( 3, 0 ) << QgsPointXY( 3, 3 ) << QgsPointXY( 0, 3 ) << QgsPointXY( 0, 0 );
polygon2 << polyline2;
QgsGeometry polygonGeom2 = QgsGeometry::fromPolygonXY( polygon2 );
f2.setGeometry( polygonGeom2 );
f2.setAttribute( idx, QVariant( 2 ) );
QgsFeatureList flist2;
flist2 << f2;
vlPolygon->dataProvider()->addFeatures( flist2 );
/*
* |
* | |
* |
* -----|
* |
*/
vlMultiLine.reset( new QgsVectorLayer( QStringLiteral( "MultiLineString?crs=EPSG:3946&field=pk:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
QVERIFY( vlMultiLine->isValid() );
QgsFeature multi( vlMultiLine->dataProvider()->fields(), 1 );
multi.setAttribute( QStringLiteral( "pk" ), 1 );
multi.setGeometry( QgsGeometry::fromWkt( QStringLiteral(
"MultiLineString ((10 0, 14 0),(11 1, 11 0.5),(14 -2, 14 2))" ) ) );
vlMultiLine->dataProvider()->addFeatures( QgsFeatureList() << multi );
/* (3 8 200)
/
/ (2 6 10)
/ \
(0 5 100) (3 5 5)
*/
vlLineZ.reset( new QgsVectorLayer( QStringLiteral( "LineStringZ?crs=EPSG:3946&field=pk:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
QVERIFY( vlLineZ->isValid() );
QgsFeature linez( vlLineZ->dataProvider()->fields(), 1 );
linez.setAttribute( QStringLiteral( "pk" ), 1 );
linez.setGeometry( QgsGeometry::fromWkt( QStringLiteral(
"LineStringZ (3 5 5, 2 6 10)" ) ) );
QgsFeature linez2( vlLineZ->dataProvider()->fields(), 2 );
linez2.setAttribute( QStringLiteral( "pk" ), 2 );
linez2.setGeometry( QgsGeometry::fromWkt( QStringLiteral(
"LineStringZ (0 5 100, 3 8 200)" ) ) );
vlLineZ->dataProvider()->addFeatures( QgsFeatureList() << linez << linez2 );
mCanvas = new QgsMapCanvas();
mCanvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3946" ) ) );
mCanvas->setLayers( QList<QgsMapLayer *>() << vlPolygon.get() << vlMultiLine.get() << vlLineZ.get() );
QgsMapSettings mapSettings;
mapSettings.setOutputSize( QSize( 50, 50 ) );
mapSettings.setExtent( QgsRectangle( -1, -1, 4, 4 ) );
QVERIFY( mapSettings.hasValidSettings() );
mapSettings.setLayers( QList<QgsMapLayer *>() << vlPolygon.get() << vlMultiLine.get() << vlLineZ.get() );
QgsSnappingUtils *mSnappingUtils = new QgsMapCanvasSnappingUtils( mCanvas, this );
QgsSnappingConfig snappingConfig = mSnappingUtils->config();
mSnappingUtils->setMapSettings( mapSettings );
snappingConfig.setEnabled( true );
snappingConfig.setTolerance( 100 );
snappingConfig.setType( QgsSnappingConfig::VertexAndSegment );
snappingConfig.setUnits( QgsTolerance::Pixels );
snappingConfig.setMode( QgsSnappingConfig::AllLayers );
mSnappingUtils->setConfig( snappingConfig );
mCanvas->setSnappingUtils( mSnappingUtils );
}
void cleanupTestCase()
{
QgsApplication::exitQgis();
}
void testPolygon()
{
// vector layer with a triangle in a rectangle:
// (0,3) +-------------------+ (3,3)
// | (1,2) +---+ (2,2) |
// | \ | |
// | \ | |
// | \| |
// | + (2,1) |
// (0,0) +-------------------+ (3,0)
mCanvas->setCurrentLayer( vlPolygon.get() );
std::unique_ptr< QgsMapToolTrimExtendFeature > tool( new QgsMapToolTrimExtendFeature( mCanvas ) );
vlPolygon->startEditing();
// Limit
QgsPointXY pt;
pt = tool->canvas()->mapSettings().mapToPixel().transform( 0, 0 );
std::unique_ptr< QgsMapMouseEvent > event( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseMove,
QPoint( std::round( pt.x() ), std::round( pt.y() ) )
) );
tool->canvasMoveEvent( event.get() );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseButtonRelease,
QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
Qt::LeftButton
) );
tool->canvasReleaseEvent( event.get() );
// Extend
pt = tool->canvas()->mapSettings().mapToPixel().transform( 1, 1.5 );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseMove,
QPoint( std::round( pt.x() ), std::round( pt.y() ) )
) );
tool->canvasMoveEvent( event.get() );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseButtonRelease,
QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
Qt::LeftButton
) );
tool->canvasReleaseEvent( event.get() );
// vector layer with a trianglev in a rectangle:
// (0,3) +-------------------+ (3,3)
// | (1,2) +---+ (2,2) |
// | \ \ |
// | \ \ |
// | \ \ |
// | \|
// (0,0) +-------------------+ (3,0)
QgsFeature f = vlPolygon->getFeature( 1 );
QString wkt = "Polygon ((1 2, 3 0, 2 2, 1 2))";
QCOMPARE( f.geometry().asWkt(), wkt );
vlPolygon->rollBack();
}
void testMultiLine()
{
/*
* |
* | |
* |
* -----|
* |
*/
mCanvas->setCurrentLayer( vlMultiLine.get() );
std::unique_ptr< QgsMapToolTrimExtendFeature > tool( new QgsMapToolTrimExtendFeature( mCanvas ) );
vlMultiLine->startEditing();
// Limit
QgsPointXY pt;
pt = tool->canvas()->mapSettings().mapToPixel().transform( 12, 0 );
std::unique_ptr< QgsMapMouseEvent > event( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseMove,
QPoint( std::round( pt.x() ), std::round( pt.y() ) )
) );
tool->canvasMoveEvent( event.get() );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseButtonRelease,
QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
Qt::LeftButton
) );
tool->canvasReleaseEvent( event.get() );
// Extend
pt = tool->canvas()->mapSettings().mapToPixel().transform( 11, 0.8 );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseMove,
QPoint( std::round( pt.x() ), std::round( pt.y() ) )
) );
tool->canvasMoveEvent( event.get() );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseButtonRelease,
QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
Qt::LeftButton
) );
tool->canvasReleaseEvent( event.get() );
/*
*(11 1)|
* | |
* | |
* -----|
*(11 0)|
*/
QgsFeature f = vlMultiLine->getFeature( 1 );
QString wkt = "MultiLineString ((10 0, 14 0),(11 1, 11 0),(14 -2, 14 2))";
QCOMPARE( f.geometry().asWkt(), wkt );
// Limit
pt = tool->canvas()->mapSettings().mapToPixel().transform( 12, 0 );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseMove,
QPoint( std::round( pt.x() ), std::round( pt.y() ) )
) );
tool->canvasMoveEvent( event.get() );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseButtonRelease,
QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
Qt::LeftButton
) );
tool->canvasReleaseEvent( event.get() );
// Extend
pt = tool->canvas()->mapSettings().mapToPixel().transform( 14, 1 );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseMove,
QPoint( std::round( pt.x() ), std::round( pt.y() ) )
) );
tool->canvasMoveEvent( event.get() );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseButtonRelease,
QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
Qt::LeftButton
) );
tool->canvasReleaseEvent( event.get() );
/*
*
* |
* |
* ------ (14 0)
* |
* (14 -2)
*/
f = vlMultiLine->getFeature( 1 );
wkt = "MultiLineString ((10 0, 14 0),(11 1, 11 0),(14 -2, 14 0))";
QCOMPARE( f.geometry().asWkt(), wkt );
vlMultiLine->rollBack();
vlMultiLine->startEditing();
// Limit
pt = tool->canvas()->mapSettings().mapToPixel().transform( 12, 0 );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseMove,
QPoint( std::round( pt.x() ), std::round( pt.y() ) )
) );
tool->canvasMoveEvent( event.get() );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseButtonRelease,
QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
Qt::LeftButton
) );
tool->canvasReleaseEvent( event.get() );
// Extend
pt = tool->canvas()->mapSettings().mapToPixel().transform( 14, -1 );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseMove,
QPoint( std::round( pt.x() ), std::round( pt.y() ) )
) );
tool->canvasMoveEvent( event.get() );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseButtonRelease,
QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
Qt::LeftButton
) );
tool->canvasReleaseEvent( event.get() );
/*
* | (14 2)
* | |
* |
* ------ (14 0)
*(10 0)
*/
f = vlMultiLine->getFeature( 1 );
wkt = "MultiLineString ((10 0, 14 0),(11 1, 11 0.5),(14 0, 14 2))";
QCOMPARE( f.geometry().asWkt(), wkt );
}
void testLineZ()
{
/* (3 8 200)
/
/ (2 6 10)
/ \
(0 5 100) (3 5 5)
*/
mCanvas->setCurrentLayer( vlLineZ.get() );
std::unique_ptr< QgsMapToolTrimExtendFeature > tool( new QgsMapToolTrimExtendFeature( mCanvas ) );
vlLineZ->startEditing();
// Limit
QgsPointXY pt;
pt = tool->canvas()->mapSettings().mapToPixel().transform( 0, 5 );
std::unique_ptr< QgsMapMouseEvent > event( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseMove,
QPoint( std::round( pt.x() ), std::round( pt.y() ) )
) );
tool->canvasMoveEvent( event.get() );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseButtonRelease,
QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
Qt::LeftButton
) );
tool->canvasReleaseEvent( event.get() );
// Extend
pt = tool->canvas()->mapSettings().mapToPixel().transform( 3, 5 );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseMove,
QPoint( std::round( pt.x() ), std::round( pt.y() ) )
) );
tool->canvasMoveEvent( event.get() );
event.reset( new QgsMapMouseEvent(
mCanvas,
QEvent::MouseButtonRelease,
QPoint( std::round( pt.x() ), std::round( pt.y() ) ),
Qt::LeftButton
) );
tool->canvasReleaseEvent( event.get() );
/* (3 8 200)
/
/\ (1.5 6.5 150)
/ \
(0 5 100) (3 5 5)
*/
QgsFeature f = vlLineZ->getFeature( 1 );
QString wkt = "LineStringZ (3 5 5, 1.5 6.5 150)";
QCOMPARE( f.geometry().asWkt(), wkt );
vlLineZ->rollBack();
}
};
QGSTEST_MAIN( TestQgsMapToolTrimExtendFeature )
#include "testqgsmaptooltrimextendfeature.moc"