mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
[FEATURE] New node tool implementation
This commit is contained in:
commit
e73a52bb35
@ -827,7 +827,7 @@ class QgsGeometry
|
||||
* @note added in QGIS 2.10
|
||||
* @see vertexNrFromVertexId
|
||||
*/
|
||||
bool vertexIdFromVertexNr( int nr, QgsVertexId& id ) const;
|
||||
bool vertexIdFromVertexNr( int nr, QgsVertexId& id /Out/ ) const;
|
||||
|
||||
/** Returns the vertex number corresponding to a vertex idd
|
||||
* @param i vertex id
|
||||
|
@ -52,6 +52,12 @@ class QgsGeometryUtils
|
||||
|
||||
static double circleTangentDirection( const QgsPointV2& tangentPoint, const QgsPointV2& cp1, const QgsPointV2& cp2, const QgsPointV2& cp3 );
|
||||
|
||||
static void segmentizeArc( const QgsPointV2 &p1, const QgsPointV2 &p2, const QgsPointV2 &p3, QList<QgsPointV2> &points /Out/, double tolerance = M_PI_2 / 90, QgsAbstractGeometry::SegmentationToleranceType toleranceType = QgsAbstractGeometry::MaximumAngle, bool hasZ = false, bool hasM = false );
|
||||
|
||||
static int segmentSide( const QgsPointV2 &pt1, const QgsPointV2 &pt3, const QgsPointV2 &pt2 );
|
||||
|
||||
static double interpolateArcValue( double angle, double a1, double a2, double a3, double zm1, double zm2, double zm3 );
|
||||
|
||||
static double normalizedAngle( double angle );
|
||||
|
||||
static double lineAngle( double x1, double y1, double x2, double y2 );
|
||||
|
@ -13,7 +13,9 @@ class QgsRubberBand: QgsMapCanvasItem
|
||||
ICON_X,
|
||||
ICON_BOX,
|
||||
ICON_CIRCLE,
|
||||
ICON_FULL_BOX
|
||||
ICON_FULL_BOX,
|
||||
ICON_DIAMOND,
|
||||
ICON_FULL_DIAMOND
|
||||
};
|
||||
|
||||
QgsRubberBand( QgsMapCanvas* mapCanvas /TransferThis/, QgsWkbTypes::GeometryType geometryType = QgsWkbTypes::LineGeometry );
|
||||
|
@ -101,6 +101,7 @@ SET(QGIS_APP_SRCS
|
||||
nodetool/qgsselectedfeature.cpp
|
||||
nodetool/qgsvertexentry.cpp
|
||||
nodetool/qgsnodeeditor.cpp
|
||||
nodetool/qgsnodetool2.cpp
|
||||
|
||||
qgslayerstylingwidget.cpp
|
||||
qgsmeasuredialog.cpp
|
||||
@ -282,6 +283,7 @@ SET (QGIS_APP_MOC_HDRS
|
||||
nodetool/qgsmaptoolnodetool.h
|
||||
nodetool/qgsselectedfeature.h
|
||||
nodetool/qgsnodeeditor.h
|
||||
nodetool/qgsnodetool2.h
|
||||
|
||||
qgslayerstylingwidget.h
|
||||
qgsmeasuredialog.h
|
||||
|
1809
src/app/nodetool/qgsnodetool2.cpp
Normal file
1809
src/app/nodetool/qgsnodetool2.cpp
Normal file
File diff suppressed because it is too large
Load Diff
345
src/app/nodetool/qgsnodetool2.h
Normal file
345
src/app/nodetool/qgsnodetool2.h
Normal file
@ -0,0 +1,345 @@
|
||||
/***************************************************************************
|
||||
qgsnodetool2.h
|
||||
--------------------------------------
|
||||
Date : February 2017
|
||||
Copyright : (C) 2017 by Martin Dobias
|
||||
Email : wonder dot sk at gmail 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 QGSNODETOOL2_H
|
||||
#define QGSNODETOOL2_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "qgis_app.h"
|
||||
#include "qgsmaptooladvanceddigitizing.h"
|
||||
#include "qgsgeometry.h"
|
||||
|
||||
class QRubberBand;
|
||||
|
||||
class QgsGeometryValidator;
|
||||
class QgsNodeEditor;
|
||||
class QgsSelectedFeature;
|
||||
class QgsVertexMarker;
|
||||
|
||||
//! helper structure for a vertex being dragged
|
||||
struct Vertex
|
||||
{
|
||||
Vertex( QgsVectorLayer *layer, QgsFeatureId fid, int vertexId )
|
||||
: layer( layer )
|
||||
, fid( fid )
|
||||
, vertexId( vertexId ) {}
|
||||
|
||||
bool operator==( const Vertex &other ) const
|
||||
{
|
||||
return layer == other.layer && fid == other.fid && vertexId == other.vertexId;
|
||||
}
|
||||
bool operator!=( const Vertex &other ) const
|
||||
{
|
||||
return !operator==( other );
|
||||
}
|
||||
|
||||
QgsVectorLayer *layer;
|
||||
QgsFeatureId fid;
|
||||
int vertexId;
|
||||
};
|
||||
|
||||
//! qHash implementation - we use Vertex in QSet
|
||||
uint qHash( const Vertex &v );
|
||||
|
||||
|
||||
|
||||
class APP_EXPORT QgsNodeTool2 : public QgsMapToolAdvancedDigitizing
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QgsNodeTool2( QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDock );
|
||||
|
||||
//! Cleanup canvas items we have created
|
||||
~QgsNodeTool2();
|
||||
|
||||
virtual void cadCanvasPressEvent( QgsMapMouseEvent *e ) override;
|
||||
|
||||
virtual void cadCanvasReleaseEvent( QgsMapMouseEvent *e ) override;
|
||||
|
||||
virtual void cadCanvasMoveEvent( QgsMapMouseEvent *e ) override;
|
||||
|
||||
//! Start addition of a new vertex on double-click
|
||||
virtual void canvasDoubleClickEvent( QgsMapMouseEvent *e ) override;
|
||||
|
||||
virtual void deactivate() override;
|
||||
|
||||
void keyPressEvent( QKeyEvent *e ) override;
|
||||
|
||||
QgsGeometry cachedGeometry( const QgsVectorLayer *layer, QgsFeatureId fid );
|
||||
|
||||
private slots:
|
||||
//! update geometry of our feature
|
||||
void onCachedGeometryChanged( QgsFeatureId fid, const QgsGeometry &geom );
|
||||
|
||||
void onCachedGeometryDeleted( QgsFeatureId fid );
|
||||
|
||||
void showNodeEditor();
|
||||
|
||||
void deleteNodeEditorSelection();
|
||||
|
||||
void validationErrorFound( QgsGeometry::Error e );
|
||||
|
||||
void validationFinished();
|
||||
|
||||
private:
|
||||
|
||||
void buildDragBandsForVertices( const QSet<Vertex> &movingVertices, const QgsPoint &dragVertexMapPoint );
|
||||
|
||||
void addDragBand( const QgsPoint &v1, const QgsPoint &v2 );
|
||||
|
||||
void addDragStraightBand( QgsVectorLayer *layer, QgsPoint v0, QgsPoint v1, bool moving0, bool moving1, const QgsPoint &mapPoint );
|
||||
|
||||
void addDragCircularBand( QgsVectorLayer *layer, QgsPoint v0, QgsPoint v1, QgsPoint v2, bool moving0, bool moving1, bool moving2, const QgsPoint &mapPoint );
|
||||
|
||||
void moveDragBands( const QgsPoint &mapPoint );
|
||||
|
||||
void clearDragBands();
|
||||
|
||||
void mouseMoveDraggingVertex( QgsMapMouseEvent *e );
|
||||
|
||||
void mouseMoveDraggingEdge( QgsMapMouseEvent *e );
|
||||
|
||||
void removeTemporaryRubberBands();
|
||||
|
||||
/** Temporarily override snapping config and snap to vertices and edges
|
||||
of any editable vector layer, to allow selection of node for editing
|
||||
(if snapped to edge, it would offer creation of a new vertex there).
|
||||
*/
|
||||
QgsPointLocator::Match snapToEditableLayer( QgsMapMouseEvent *e );
|
||||
|
||||
//! check whether we are still close to the mEndpointMarker
|
||||
bool isNearEndpointMarker( const QgsPoint &mapPoint );
|
||||
|
||||
bool isMatchAtEndpoint( const QgsPointLocator::Match &match );
|
||||
|
||||
QgsPoint positionForEndpointMarker( const QgsPointLocator::Match &match );
|
||||
|
||||
void mouseMoveNotDragging( QgsMapMouseEvent *e );
|
||||
|
||||
QgsGeometry cachedGeometryForVertex( const Vertex &vertex );
|
||||
|
||||
void startDragging( QgsMapMouseEvent *e );
|
||||
|
||||
void startDraggingMoveVertex( const QgsPoint &mapPoint, const QgsPointLocator::Match &m );
|
||||
|
||||
//! Get list of matches of all vertices of a layer exactly snapped to a map point
|
||||
QList<QgsPointLocator::Match> layerVerticesSnappedToPoint( QgsVectorLayer *layer, const QgsPoint &mapPoint );
|
||||
|
||||
void startDraggingAddVertex( const QgsPointLocator::Match &m );
|
||||
|
||||
void startDraggingAddVertexAtEndpoint( const QgsPoint &mapPoint );
|
||||
|
||||
void startDraggingEdge( const QgsPointLocator::Match &m, const QgsPoint &mapPoint );
|
||||
|
||||
void stopDragging();
|
||||
|
||||
QgsPoint matchToLayerPoint( const QgsVectorLayer *destLayer, const QgsPoint &mapPoint, const QgsPointLocator::Match *match );
|
||||
|
||||
//! Finish moving of an edge
|
||||
void moveEdge( const QgsPoint &mapPoint );
|
||||
|
||||
void moveVertex( const QgsPoint &mapPoint, const QgsPointLocator::Match *mapPointMatch );
|
||||
|
||||
void deleteVertex();
|
||||
|
||||
typedef QHash<QgsVectorLayer *, QHash<QgsFeatureId, QgsGeometry> > NodeEdits;
|
||||
|
||||
void addExtraVerticesToEdits( NodeEdits &edits, const QgsPoint &mapPoint, QgsVectorLayer *dragLayer = nullptr, const QgsPoint &layerPoint = QgsPoint() );
|
||||
|
||||
void applyEditsToLayers( NodeEdits &edits );
|
||||
|
||||
void setHighlightedNodes( QList<Vertex> listNodes );
|
||||
|
||||
void setHighlightedNodesVisible( bool visible );
|
||||
|
||||
//! Allow moving back and forth selected vertex within a feature
|
||||
void highlightAdjacentVertex( double offset );
|
||||
|
||||
//! Initialize rectangle that is being dragged to select nodes.
|
||||
//! Argument point0 is in screen coordinates.
|
||||
void startSelectionRect( const QPoint &point0 );
|
||||
|
||||
//! Update bottom-right corner of the existing selection rectangle.
|
||||
//! Argument point1 is in screen coordinates.
|
||||
void updateSelectionRect( const QPoint &point1 );
|
||||
|
||||
void stopSelectionRect();
|
||||
|
||||
//! Using a given edge match and original map point, find out
|
||||
//! center of the edge and whether we are close enough to the center
|
||||
bool matchEdgeCenterTest( const QgsPointLocator::Match &m, const QgsPoint &mapPoint, QgsPoint *edgeCenterPtr = nullptr );
|
||||
|
||||
void cleanupNodeEditor();
|
||||
|
||||
//! Run validation on a geometry (in a background thread)
|
||||
void validateGeometry( QgsVectorLayer *layer, QgsFeatureId featureId );
|
||||
|
||||
//! Makes sure that the node is visible in map canvas
|
||||
void zoomToNode( const Vertex &node );
|
||||
|
||||
private:
|
||||
|
||||
// members used for temporary highlight of stuff
|
||||
|
||||
//! marker of a snap match (if any) when dragging a vertex
|
||||
QgsVertexMarker *mSnapMarker = nullptr;
|
||||
//! marker in the middle of an edge while pointer is close to a vertex and not dragging anything
|
||||
//! (highlighting point that can be clicked to add a new vertex)
|
||||
QgsVertexMarker *mEdgeCenterMarker = nullptr;
|
||||
//! rubber band for highlight of a whole feature on mouse over and not dragging anything
|
||||
QgsRubberBand *mFeatureBand = nullptr;
|
||||
//! rubber band for highlight of all vertices of a feature on mouse over and not dragging anything
|
||||
QgsRubberBand *mFeatureBandMarkers = nullptr;
|
||||
//! source layer for mFeatureBand (null if mFeatureBand is null)
|
||||
const QgsVectorLayer *mFeatureBandLayer = nullptr;
|
||||
//! source feature id for mFeatureBand (zero if mFeatureBand is null)
|
||||
QgsFeatureId mFeatureBandFid;
|
||||
//! highlight of a vertex while mouse pointer is close to a vertex and not dragging anything
|
||||
QgsRubberBand *mVertexBand = nullptr;
|
||||
//! highlight of an edge while mouse pointer is close to an edge and not dragging anything
|
||||
QgsRubberBand *mEdgeBand = nullptr;
|
||||
|
||||
// members for dragging operation
|
||||
|
||||
enum DraggingVertexType
|
||||
{
|
||||
NotDragging,
|
||||
MovingVertex,
|
||||
AddingVertex,
|
||||
AddingEndpoint,
|
||||
};
|
||||
|
||||
//! markers for points used only for moving standalone point geoetry
|
||||
//! (there are no adjacent vertices so it is not used in mDragBands)
|
||||
QList<QgsVertexMarker *> mDragPointMarkers;
|
||||
//! companion array to mDragPointMarkers: for each marker it keeps offset
|
||||
//! (in map units) from the position of the main vertex
|
||||
QList<QgsVector> mDragPointMarkersOffset;
|
||||
|
||||
//! structure to keep information about a rubber band user for dragging of a straight line segment
|
||||
struct StraightBand
|
||||
{
|
||||
QgsRubberBand *band = nullptr; //!< Pointer to the actual rubber band
|
||||
QgsPoint p0, p1; //!< What are the original positions of points (in map units)
|
||||
bool moving0, moving1; //!< Which points of the band are moving with mouse cursor
|
||||
QgsVector offset0, offset1; //!< If the point is moving, what is the offset from the mouse cursor
|
||||
};
|
||||
|
||||
//! structure to keep information about a rubber band used for dragging of a circular segment
|
||||
struct CircularBand
|
||||
{
|
||||
QgsRubberBand *band = nullptr; //!< Pointer to the actual rubber band
|
||||
QgsPoint p0, p1, p2; //!< What are the original positions of points (in map units)
|
||||
bool moving0, moving1, moving2; //!< Which points of the band are moving with mouse cursor
|
||||
QgsVector offset0, offset1, offset2; //!< If the point is moving, what is the offset from the mouse cursor
|
||||
|
||||
//! update geometry of the rubber band band on the current mouse cursor position (in map units)
|
||||
void updateRubberBand( const QgsPoint &mapPoint );
|
||||
};
|
||||
|
||||
//! list of active straight line rubber bands
|
||||
QList<StraightBand> mDragStraightBands;
|
||||
//! list of active rubber bands for circular segments
|
||||
QList<CircularBand> mDragCircularBands;
|
||||
//! instance of Vertex that is being currently moved or nothing
|
||||
std::unique_ptr<Vertex> mDraggingVertex;
|
||||
//! whether moving a vertex or adding one
|
||||
DraggingVertexType mDraggingVertexType = NotDragging;
|
||||
//! whether we are currently dragging an edge
|
||||
bool mDraggingEdge = false;
|
||||
//! list of Vertex instances of further vertices that are dragged together with
|
||||
//! the main vertex (mDraggingVertex) - either topologically connected points
|
||||
//! (if topo editing is allowed) or the ones coming from the highlight
|
||||
QList<Vertex> mDraggingExtraVertices;
|
||||
//! companion array to mDraggingExtraVertices: for each vertex in mDraggingExtraVertices
|
||||
//! this is offset (in units of the layer) of the vertex from the position of the main
|
||||
//! vertex (mDraggingVertex)
|
||||
QList<QgsVector> mDraggingExtraVerticesOffset;
|
||||
|
||||
// members for selection handling
|
||||
|
||||
//! list of Vertex instances of nodes that are selected
|
||||
QList<Vertex> mSelectedNodes;
|
||||
//! list of vertex markers
|
||||
QList<QgsVertexMarker *> mSelectedNodesMarkers;
|
||||
|
||||
// members for rectangle for selection
|
||||
|
||||
//! QPoint if user is dragging a selection rect
|
||||
std::unique_ptr<QPoint> mSelectionRectStartPos;
|
||||
//! QRect in screen coordinates or null
|
||||
std::unique_ptr<QRect> mSelectionRect;
|
||||
//! QRubberBand to show mSelectionRect
|
||||
QRubberBand *mSelectionRectItem = nullptr;
|
||||
|
||||
// members for addition of vertices at the end of a curve
|
||||
|
||||
//! Vertex instance or None
|
||||
std::unique_ptr<Vertex> mMouseAtEndpoint;
|
||||
//! QgsPoint or None (can't get center from QgsVertexMarker)
|
||||
std::unique_ptr<QgsPoint> mEndpointMarkerCenter;
|
||||
//! marker shown near the end of a curve to suggest that the user
|
||||
//! may add a new vertex at the end of the curve
|
||||
QgsVertexMarker *mEndpointMarker = nullptr;
|
||||
|
||||
//! keeps information about previously used snap match
|
||||
//! to stick with the same highlighted feature next time if there are more options
|
||||
std::unique_ptr<QgsPointLocator::Match> mLastSnap;
|
||||
|
||||
//! List of two points that will be forced into CAD dock with fake mouse events
|
||||
//! to allow correct functioning of node tool with CAD dock.
|
||||
//! (CAD dock does various assumptions that work with simple capture tools, but not with node tool)
|
||||
QList<QgsPoint> mOverrideCadPoints;
|
||||
|
||||
//! When double-clicking to add a new vertex, this member keeps the snap
|
||||
//! match from "press" event used to be used in following "release" event
|
||||
std::unique_ptr<QgsPointLocator::Match> mNewVertexFromDoubleClick;
|
||||
|
||||
//! Geometry cache for fast access to geometries
|
||||
QHash<const QgsVectorLayer *, QHash<QgsFeatureId, QgsGeometry> > mCache;
|
||||
|
||||
// support for node editor
|
||||
|
||||
//! most recent match when moving mouse
|
||||
QgsPointLocator::Match mLastMouseMoveMatch;
|
||||
//! Selected feature for the node editor
|
||||
std::unique_ptr<QgsSelectedFeature> mSelectedFeature;
|
||||
//! Dock widget which allows editing vertices
|
||||
std::unique_ptr<QgsNodeEditor> mNodeEditor;
|
||||
|
||||
// support for validation of geometries
|
||||
|
||||
//! data structure for validation of one geometry of a vector layer
|
||||
struct GeometryValidation
|
||||
{
|
||||
QgsNodeTool2 *tool = nullptr; //!< Pointer to the parent node tool (for connections / canvas)
|
||||
QgsVectorLayer *layer = nullptr; //!< Pointer to the layer of the validated geometry (for reporojection)
|
||||
QgsGeometryValidator *validator = nullptr; //!< Object that does validation. Non-null if active
|
||||
QList<QgsVertexMarker *> errorMarkers; //!< Markers created by validation
|
||||
QString errors; //!< Full error text from validation
|
||||
|
||||
void start( QgsGeometry &geom, QgsNodeTool2 *tool, QgsVectorLayer *l ); //!< Start validation
|
||||
void addError( QgsGeometry::Error e ); //!< Add another error to the validation
|
||||
void cleanup(); //!< Delete everything
|
||||
};
|
||||
|
||||
//! data structure to keep validation details
|
||||
QHash< QPair<QgsVectorLayer *, QgsFeatureId>, GeometryValidation> mValidations;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // QGSNODETOOL2_H
|
@ -347,6 +347,8 @@
|
||||
|
||||
#include "nodetool/qgsmaptoolnodetool.h"
|
||||
|
||||
#include "nodetool/qgsnodetool2.h"
|
||||
|
||||
// Editor widgets
|
||||
#include "qgseditorwidgetregistry.h"
|
||||
//
|
||||
@ -1325,6 +1327,7 @@ QgisApp::~QgisApp()
|
||||
delete mMapTools.mMoveFeatureCopy;
|
||||
delete mMapTools.mMoveLabel;
|
||||
delete mMapTools.mNodeTool;
|
||||
delete mMapTools.mNodeTool2;
|
||||
delete mMapTools.mOffsetCurve;
|
||||
delete mMapTools.mPinLabels;
|
||||
delete mMapTools.mReshapeFeatures;
|
||||
@ -1742,6 +1745,7 @@ void QgisApp::createActions()
|
||||
connect( mActionMergeFeatureAttributes, &QAction::triggered, this, &QgisApp::mergeAttributesOfSelectedFeatures );
|
||||
connect( mActionMultiEditAttributes, &QAction::triggered, this, &QgisApp::modifyAttributesOfSelectedFeatures );
|
||||
connect( mActionNodeTool, &QAction::triggered, this, &QgisApp::nodeTool );
|
||||
connect( mActionNodeTool2, &QAction::triggered, this, &QgisApp::nodeTool2 );
|
||||
connect( mActionRotatePointSymbols, &QAction::triggered, this, &QgisApp::rotatePointSymbols );
|
||||
connect( mActionOffsetPointSymbol, &QAction::triggered, this, &QgisApp::offsetPointSymbol );
|
||||
connect( mActionSnappingOptions, &QAction::triggered, this, &QgisApp::snappingOptions );
|
||||
@ -2013,6 +2017,7 @@ void QgisApp::createActionGroups()
|
||||
mMapToolGroup->addAction( mActionMergeFeatures );
|
||||
mMapToolGroup->addAction( mActionMergeFeatureAttributes );
|
||||
mMapToolGroup->addAction( mActionNodeTool );
|
||||
mMapToolGroup->addAction( mActionNodeTool2 );
|
||||
mMapToolGroup->addAction( mActionRotatePointSymbols );
|
||||
mMapToolGroup->addAction( mActionOffsetPointSymbol );
|
||||
mMapToolGroup->addAction( mActionPinLabels );
|
||||
@ -2768,6 +2773,7 @@ void QgisApp::setTheme( const QString &themeName )
|
||||
mActionSplitParts->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSplitParts.svg" ) ) );
|
||||
mActionDeleteSelected->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelected.svg" ) ) );
|
||||
mActionNodeTool->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNodeTool.svg" ) ) );
|
||||
mActionNodeTool2->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNodeTool.svg" ) ) );
|
||||
mActionSimplifyFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSimplify.svg" ) ) );
|
||||
mActionUndo->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUndo.svg" ) ) );
|
||||
mActionRedo->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRedo.svg" ) ) );
|
||||
@ -3031,6 +3037,8 @@ void QgisApp::createCanvasTools()
|
||||
mMapTools.mDeletePart->setAction( mActionDeletePart );
|
||||
mMapTools.mNodeTool = new QgsMapToolNodeTool( mMapCanvas );
|
||||
mMapTools.mNodeTool->setAction( mActionNodeTool );
|
||||
mMapTools.mNodeTool2 = new QgsNodeTool2( mMapCanvas, mAdvancedDigitizingDockWidget );
|
||||
mMapTools.mNodeTool2->setAction( mActionNodeTool2 );
|
||||
mMapTools.mRotatePointSymbolsTool = new QgsMapToolRotatePointSymbols( mMapCanvas );
|
||||
mMapTools.mRotatePointSymbolsTool->setAction( mActionRotatePointSymbols );
|
||||
mMapTools.mOffsetPointSymbolTool = new QgsMapToolOffsetPointSymbol( mMapCanvas );
|
||||
@ -7572,6 +7580,11 @@ void QgisApp::nodeTool()
|
||||
mMapCanvas->setMapTool( mMapTools.mNodeTool );
|
||||
}
|
||||
|
||||
void QgisApp::nodeTool2()
|
||||
{
|
||||
mMapCanvas->setMapTool( mMapTools.mNodeTool2 );
|
||||
}
|
||||
|
||||
void QgisApp::rotatePointSymbols()
|
||||
{
|
||||
mMapCanvas->setMapTool( mMapTools.mRotatePointSymbolsTool );
|
||||
@ -10938,6 +10951,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
|
||||
mActionRotateFeature->setEnabled( false );
|
||||
mActionOffsetCurve->setEnabled( false );
|
||||
mActionNodeTool->setEnabled( false );
|
||||
mActionNodeTool2->setEnabled( false );
|
||||
mActionDeleteSelected->setEnabled( false );
|
||||
mActionCutFeatures->setEnabled( false );
|
||||
mActionCopyFeatures->setEnabled( false );
|
||||
@ -11096,6 +11110,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
|
||||
mActionMoveFeatureCopy->setEnabled( isEditable && canChangeGeometry );
|
||||
mActionRotateFeature->setEnabled( isEditable && canChangeGeometry );
|
||||
mActionNodeTool->setEnabled( isEditable && canChangeGeometry );
|
||||
mActionNodeTool2->setEnabled( isEditable && canChangeGeometry );
|
||||
|
||||
if ( vlayer->geometryType() == QgsWkbTypes::PointGeometry )
|
||||
{
|
||||
@ -11242,6 +11257,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
|
||||
mActionFillRing->setEnabled( false );
|
||||
mActionAddPart->setEnabled( false );
|
||||
mActionNodeTool->setEnabled( false );
|
||||
mActionNodeTool2->setEnabled( false );
|
||||
mActionMoveFeature->setEnabled( false );
|
||||
mActionMoveFeatureCopy->setEnabled( false );
|
||||
mActionRotateFeature->setEnabled( false );
|
||||
|
@ -417,6 +417,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
|
||||
QAction *actionDeleteRing() { return mActionDeleteRing; }
|
||||
QAction *actionDeletePart() { return mActionDeletePart; }
|
||||
QAction *actionNodeTool() { return mActionNodeTool; }
|
||||
QAction *actionNodeTool2() { return mActionNodeTool2; }
|
||||
QAction *actionSnappingOptions() { return mActionSnappingOptions; }
|
||||
QAction *actionOffsetCurve() { return mActionOffsetCurve; }
|
||||
QAction *actionPan() { return mActionPan; }
|
||||
@ -1209,6 +1210,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
|
||||
void modifyAttributesOfSelectedFeatures();
|
||||
//! provides operations with nodes
|
||||
void nodeTool();
|
||||
//! provides operations with nodes
|
||||
void nodeTool2();
|
||||
//! activates the rotate points tool
|
||||
void rotatePointSymbols();
|
||||
//! activates the offset point symbol tool
|
||||
@ -1728,6 +1731,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
|
||||
, mDeleteRing( nullptr )
|
||||
, mDeletePart( nullptr )
|
||||
, mNodeTool( nullptr )
|
||||
, mNodeTool2( nullptr )
|
||||
, mRotatePointSymbolsTool( nullptr )
|
||||
, mOffsetPointSymbolTool( nullptr )
|
||||
, mAnnotation( nullptr )
|
||||
@ -1775,6 +1779,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
|
||||
QgsMapTool *mDeleteRing = nullptr;
|
||||
QgsMapTool *mDeletePart = nullptr;
|
||||
QgsMapTool *mNodeTool = nullptr;
|
||||
QgsMapTool *mNodeTool2 = nullptr;
|
||||
QgsMapTool *mRotatePointSymbolsTool = nullptr;
|
||||
QgsMapTool *mOffsetPointSymbolTool = nullptr;
|
||||
QgsMapTool *mAnnotation = nullptr;
|
||||
|
@ -355,7 +355,7 @@ QgsLineString *QgsCircularString::curveToLine( double tolerance, SegmentationTol
|
||||
|
||||
for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
|
||||
{
|
||||
segmentize( pointN( i ), pointN( i + 1 ), pointN( i + 2 ), points, tolerance, toleranceType );
|
||||
QgsGeometryUtils::segmentizeArc( pointN( i ), pointN( i + 1 ), pointN( i + 2 ), points, tolerance, toleranceType, is3D(), isMeasure() );
|
||||
}
|
||||
|
||||
line->setPoints( points );
|
||||
@ -485,150 +485,6 @@ void QgsCircularString::setPoints( const QgsPointSequence &points )
|
||||
}
|
||||
}
|
||||
|
||||
void QgsCircularString::segmentize( const QgsPointV2 &p1, const QgsPointV2 &p2, const QgsPointV2 &p3, QgsPointSequence &points, double tolerance, SegmentationToleranceType toleranceType ) const
|
||||
{
|
||||
bool clockwise = false;
|
||||
int segSide = segmentSide( p1, p3, p2 );
|
||||
if ( segSide == -1 )
|
||||
{
|
||||
clockwise = true;
|
||||
}
|
||||
|
||||
QgsPointV2 circlePoint1 = clockwise ? p3 : p1;
|
||||
QgsPointV2 circlePoint2 = p2;
|
||||
QgsPointV2 circlePoint3 = clockwise ? p1 : p3 ;
|
||||
|
||||
//adapted code from postgis
|
||||
double radius = 0;
|
||||
double centerX = 0;
|
||||
double centerY = 0;
|
||||
QgsGeometryUtils::circleCenterRadius( circlePoint1, circlePoint2, circlePoint3, radius, centerX, centerY );
|
||||
|
||||
|
||||
if ( circlePoint1 != circlePoint3 && ( radius < 0 || qgsDoubleNear( segSide, 0.0 ) ) ) //points are colinear
|
||||
{
|
||||
points.append( p1 );
|
||||
points.append( p2 );
|
||||
points.append( p3 );
|
||||
return;
|
||||
}
|
||||
|
||||
double increment = tolerance; //one segment per degree
|
||||
if ( toleranceType == QgsAbstractGeometry::MaximumDifference )
|
||||
{
|
||||
double halfAngle = acos( -tolerance / radius + 1 );
|
||||
increment = 2 * halfAngle;
|
||||
}
|
||||
|
||||
//angles of pt1, pt2, pt3
|
||||
double a1 = atan2( circlePoint1.y() - centerY, circlePoint1.x() - centerX );
|
||||
double a2 = atan2( circlePoint2.y() - centerY, circlePoint2.x() - centerX );
|
||||
double a3 = atan2( circlePoint3.y() - centerY, circlePoint3.x() - centerX );
|
||||
|
||||
/* Adjust a3 up so we can increment from a1 to a3 cleanly */
|
||||
if ( a3 <= a1 )
|
||||
a3 += 2.0 * M_PI;
|
||||
if ( a2 < a1 )
|
||||
a2 += 2.0 * M_PI;
|
||||
|
||||
bool hasZ = is3D();
|
||||
bool hasM = isMeasure();
|
||||
|
||||
double x, y;
|
||||
double z = 0;
|
||||
double m = 0;
|
||||
|
||||
QList<QgsPointV2> stringPoints;
|
||||
stringPoints.insert( clockwise ? 0 : stringPoints.size(), circlePoint1 );
|
||||
if ( circlePoint2 != circlePoint3 && circlePoint1 != circlePoint2 ) //draw straight line segment if two points have the same position
|
||||
{
|
||||
QgsWkbTypes::Type pointWkbType = QgsWkbTypes::Point;
|
||||
if ( hasZ )
|
||||
pointWkbType = QgsWkbTypes::addZ( pointWkbType );
|
||||
if ( hasM )
|
||||
pointWkbType = QgsWkbTypes::addM( pointWkbType );
|
||||
|
||||
//make sure the curve point p2 is part of the segmentized vertices. But only if p1 != p3
|
||||
bool addP2 = true;
|
||||
if ( qgsDoubleNear( circlePoint1.x(), circlePoint3.x() ) && qgsDoubleNear( circlePoint1.y(), circlePoint3.y() ) )
|
||||
{
|
||||
addP2 = false;
|
||||
}
|
||||
|
||||
for ( double angle = a1 + increment; angle < a3; angle += increment )
|
||||
{
|
||||
if ( ( addP2 && angle > a2 ) )
|
||||
{
|
||||
stringPoints.insert( clockwise ? 0 : stringPoints.size(), circlePoint2 );
|
||||
addP2 = false;
|
||||
}
|
||||
|
||||
x = centerX + radius * cos( angle );
|
||||
y = centerY + radius * sin( angle );
|
||||
|
||||
if ( !hasZ && !hasM )
|
||||
{
|
||||
stringPoints.insert( clockwise ? 0 : stringPoints.size(), QgsPointV2( x, y ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( hasZ )
|
||||
{
|
||||
z = interpolateArc( angle, a1, a2, a3, circlePoint1.z(), circlePoint2.z(), circlePoint3.z() );
|
||||
}
|
||||
if ( hasM )
|
||||
{
|
||||
m = interpolateArc( angle, a1, a2, a3, circlePoint1.m(), circlePoint2.m(), circlePoint3.m() );
|
||||
}
|
||||
|
||||
stringPoints.insert( clockwise ? 0 : stringPoints.size(), QgsPointV2( pointWkbType, x, y, z, m ) );
|
||||
}
|
||||
}
|
||||
stringPoints.insert( clockwise ? 0 : stringPoints.size(), circlePoint3 );
|
||||
points.append( stringPoints );
|
||||
}
|
||||
|
||||
int QgsCircularString::segmentSide( const QgsPointV2 &pt1, const QgsPointV2 &pt3, const QgsPointV2 &pt2 ) const
|
||||
{
|
||||
double side = ( ( pt2.x() - pt1.x() ) * ( pt3.y() - pt1.y() ) - ( pt3.x() - pt1.x() ) * ( pt2.y() - pt1.y() ) );
|
||||
if ( side == 0.0 )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( side < 0 )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if ( side > 0 )
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
double QgsCircularString::interpolateArc( double angle, double a1, double a2, double a3, double zm1, double zm2, double zm3 ) const
|
||||
{
|
||||
/* Counter-clockwise sweep */
|
||||
if ( a1 < a2 )
|
||||
{
|
||||
if ( angle <= a2 )
|
||||
return zm1 + ( zm2 - zm1 ) * ( angle - a1 ) / ( a2 - a1 );
|
||||
else
|
||||
return zm2 + ( zm3 - zm2 ) * ( angle - a2 ) / ( a3 - a2 );
|
||||
}
|
||||
/* Clockwise sweep */
|
||||
else
|
||||
{
|
||||
if ( angle >= a2 )
|
||||
return zm1 + ( zm2 - zm1 ) * ( a1 - angle ) / ( a1 - a2 );
|
||||
else
|
||||
return zm2 + ( zm3 - zm2 ) * ( a2 - angle ) / ( a2 - a3 );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsCircularString::draw( QPainter &p ) const
|
||||
{
|
||||
QPainterPath path;
|
||||
@ -697,7 +553,7 @@ void QgsCircularString::addToPainterPath( QPainterPath &path ) const
|
||||
for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
|
||||
{
|
||||
QgsPointSequence pt;
|
||||
segmentize( QgsPointV2( mX[i], mY[i] ), QgsPointV2( mX[i + 1], mY[i + 1] ), QgsPointV2( mX[i + 2], mY[i + 2] ), pt );
|
||||
QgsGeometryUtils::segmentizeArc( QgsPointV2( mX[i], mY[i] ), QgsPointV2( mX[i + 1], mY[i + 1] ), QgsPointV2( mX[i + 2], mY[i + 2] ), pt );
|
||||
for ( int j = 1; j < pt.size(); ++j )
|
||||
{
|
||||
path.lineTo( pt.at( j ).x(), pt.at( j ).y() );
|
||||
|
@ -117,10 +117,6 @@ class CORE_EXPORT QgsCircularString: public QgsCurve
|
||||
QVector<double> mZ;
|
||||
QVector<double> mM;
|
||||
|
||||
//helper methods for curveToLine
|
||||
void segmentize( const QgsPointV2 &p1, const QgsPointV2 &p2, const QgsPointV2 &p3, QgsPointSequence &points, double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const;
|
||||
int segmentSide( const QgsPointV2 &pt1, const QgsPointV2 &pt3, const QgsPointV2 &pt2 ) const;
|
||||
double interpolateArc( double angle, double a1, double a2, double a3, double zm1, double zm2, double zm3 ) const;
|
||||
static void arcTo( QPainterPath &path, QPointF pt1, QPointF pt2, QPointF pt3 );
|
||||
//bounding box of a single segment
|
||||
static QgsRectangle segmentBoundingBox( const QgsPointV2 &pt1, const QgsPointV2 &pt2, const QgsPointV2 &pt3 );
|
||||
|
@ -2102,6 +2102,83 @@ void QgsGeometry::draw( QPainter &p ) const
|
||||
}
|
||||
}
|
||||
|
||||
static bool vertexIndexInfo( const QgsAbstractGeometry *g, int vertexIndex, int &partIndex, int &ringIndex, int &vertex )
|
||||
{
|
||||
if ( vertexIndex < 0 )
|
||||
return false; // clearly something wrong
|
||||
|
||||
if ( const QgsGeometryCollection *geomCollection = dynamic_cast<const QgsGeometryCollection *>( g ) )
|
||||
{
|
||||
partIndex = 0;
|
||||
int offset = 0;
|
||||
for ( int i = 0; i < geomCollection->numGeometries(); ++i )
|
||||
{
|
||||
const QgsAbstractGeometry *part = geomCollection->geometryN( i );
|
||||
|
||||
// count total number of vertices in the part
|
||||
int numPoints = 0;
|
||||
for ( int k = 0; k < part->ringCount(); ++k )
|
||||
numPoints += part->vertexCount( 0, k );
|
||||
|
||||
if ( vertexIndex < numPoints )
|
||||
{
|
||||
int nothing;
|
||||
return vertexIndexInfo( part, vertexIndex, nothing, ringIndex, vertex ); // set ring_index + index
|
||||
}
|
||||
vertexIndex -= numPoints;
|
||||
offset += numPoints;
|
||||
partIndex++;
|
||||
}
|
||||
}
|
||||
else if ( const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( g ) )
|
||||
{
|
||||
const QgsCurve *ring = curvePolygon->exteriorRing();
|
||||
if ( vertexIndex < ring->numPoints() )
|
||||
{
|
||||
partIndex = 0;
|
||||
ringIndex = 0;
|
||||
vertex = vertexIndex;
|
||||
return true;
|
||||
}
|
||||
vertexIndex -= ring->numPoints();
|
||||
ringIndex = 1;
|
||||
for ( int i = 0; i < curvePolygon->numInteriorRings(); ++i )
|
||||
{
|
||||
const QgsCurve *ring = curvePolygon->interiorRing( i );
|
||||
if ( vertexIndex < ring->numPoints() )
|
||||
{
|
||||
partIndex = 0;
|
||||
vertex = vertexIndex;
|
||||
return true;
|
||||
}
|
||||
vertexIndex -= ring->numPoints();
|
||||
ringIndex += 1;
|
||||
}
|
||||
}
|
||||
else if ( const QgsCurve *curve = dynamic_cast<const QgsCurve *>( g ) )
|
||||
{
|
||||
if ( vertexIndex < curve->numPoints() )
|
||||
{
|
||||
partIndex = 0;
|
||||
ringIndex = 0;
|
||||
vertex = vertexIndex;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ( dynamic_cast<const QgsPointV2 *>( g ) )
|
||||
{
|
||||
if ( vertexIndex == 0 )
|
||||
{
|
||||
partIndex = 0;
|
||||
ringIndex = 0;
|
||||
vertex = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QgsGeometry::vertexIdFromVertexNr( int nr, QgsVertexId &id ) const
|
||||
{
|
||||
if ( !d->geometry )
|
||||
@ -2109,29 +2186,33 @@ bool QgsGeometry::vertexIdFromVertexNr( int nr, QgsVertexId &id ) const
|
||||
return false;
|
||||
}
|
||||
|
||||
QgsCoordinateSequence coords = d->geometry->coordinateSequence();
|
||||
id.type = QgsVertexId::SegmentVertex;
|
||||
|
||||
int vertexCount = 0;
|
||||
for ( int part = 0; part < coords.size(); ++part )
|
||||
bool res = vertexIndexInfo( d->geometry, nr, id.part, id.ring, id.vertex );
|
||||
if ( !res )
|
||||
return false;
|
||||
|
||||
// now let's find out if it is a straight or circular segment
|
||||
const QgsAbstractGeometry *g = d->geometry;
|
||||
if ( const QgsGeometryCollection *geomCollection = dynamic_cast<const QgsGeometryCollection *>( g ) )
|
||||
{
|
||||
const QgsRingSequence &featureCoords = coords.at( part );
|
||||
for ( int ring = 0; ring < featureCoords.size(); ++ring )
|
||||
{
|
||||
const QgsPointSequence &ringCoords = featureCoords.at( ring );
|
||||
for ( int vertex = 0; vertex < ringCoords.size(); ++vertex )
|
||||
{
|
||||
if ( vertexCount == nr )
|
||||
{
|
||||
id.part = part;
|
||||
id.ring = ring;
|
||||
id.vertex = vertex;
|
||||
return true;
|
||||
}
|
||||
++vertexCount;
|
||||
}
|
||||
}
|
||||
g = geomCollection->geometryN( id.part );
|
||||
}
|
||||
return false;
|
||||
|
||||
if ( const QgsCurvePolygon *curvePolygon = dynamic_cast<const QgsCurvePolygon *>( g ) )
|
||||
{
|
||||
g = id.ring == 0 ? curvePolygon->exteriorRing() : curvePolygon->interiorRing( id.ring - 1 );
|
||||
}
|
||||
|
||||
if ( const QgsCurve *curve = dynamic_cast<const QgsCurve *>( g ) )
|
||||
{
|
||||
QgsPointV2 p;
|
||||
res = curve->pointAt( id.vertex, p, id.type );
|
||||
if ( !res )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int QgsGeometry::vertexNrFromVertexId( QgsVertexId id ) const
|
||||
|
@ -595,6 +595,146 @@ double QgsGeometryUtils::circleTangentDirection( const QgsPointV2 &tangentPoint,
|
||||
}
|
||||
}
|
||||
|
||||
void QgsGeometryUtils::segmentizeArc( const QgsPointV2 &p1, const QgsPointV2 &p2, const QgsPointV2 &p3, QgsPointSequence &points, double tolerance, QgsAbstractGeometry::SegmentationToleranceType toleranceType, bool hasZ, bool hasM )
|
||||
{
|
||||
bool clockwise = false;
|
||||
int segSide = segmentSide( p1, p3, p2 );
|
||||
if ( segSide == -1 )
|
||||
{
|
||||
clockwise = true;
|
||||
}
|
||||
|
||||
QgsPointV2 circlePoint1 = clockwise ? p3 : p1;
|
||||
QgsPointV2 circlePoint2 = p2;
|
||||
QgsPointV2 circlePoint3 = clockwise ? p1 : p3 ;
|
||||
|
||||
//adapted code from postgis
|
||||
double radius = 0;
|
||||
double centerX = 0;
|
||||
double centerY = 0;
|
||||
circleCenterRadius( circlePoint1, circlePoint2, circlePoint3, radius, centerX, centerY );
|
||||
|
||||
if ( circlePoint1 != circlePoint3 && ( radius < 0 || qgsDoubleNear( segSide, 0.0 ) ) ) //points are colinear
|
||||
{
|
||||
points.append( p1 );
|
||||
points.append( p2 );
|
||||
points.append( p3 );
|
||||
return;
|
||||
}
|
||||
|
||||
double increment = tolerance; //one segment per degree
|
||||
if ( toleranceType == QgsAbstractGeometry::MaximumDifference )
|
||||
{
|
||||
double halfAngle = acos( -tolerance / radius + 1 );
|
||||
increment = 2 * halfAngle;
|
||||
}
|
||||
|
||||
//angles of pt1, pt2, pt3
|
||||
double a1 = atan2( circlePoint1.y() - centerY, circlePoint1.x() - centerX );
|
||||
double a2 = atan2( circlePoint2.y() - centerY, circlePoint2.x() - centerX );
|
||||
double a3 = atan2( circlePoint3.y() - centerY, circlePoint3.x() - centerX );
|
||||
|
||||
/* Adjust a3 up so we can increment from a1 to a3 cleanly */
|
||||
if ( a3 <= a1 )
|
||||
a3 += 2.0 * M_PI;
|
||||
if ( a2 < a1 )
|
||||
a2 += 2.0 * M_PI;
|
||||
|
||||
double x, y;
|
||||
double z = 0;
|
||||
double m = 0;
|
||||
|
||||
QList<QgsPointV2> stringPoints;
|
||||
stringPoints.insert( clockwise ? 0 : stringPoints.size(), circlePoint1 );
|
||||
if ( circlePoint2 != circlePoint3 && circlePoint1 != circlePoint2 ) //draw straight line segment if two points have the same position
|
||||
{
|
||||
QgsWkbTypes::Type pointWkbType = QgsWkbTypes::Point;
|
||||
if ( hasZ )
|
||||
pointWkbType = QgsWkbTypes::addZ( pointWkbType );
|
||||
if ( hasM )
|
||||
pointWkbType = QgsWkbTypes::addM( pointWkbType );
|
||||
|
||||
//make sure the curve point p2 is part of the segmentized vertices. But only if p1 != p3
|
||||
bool addP2 = true;
|
||||
if ( qgsDoubleNear( circlePoint1.x(), circlePoint3.x() ) && qgsDoubleNear( circlePoint1.y(), circlePoint3.y() ) )
|
||||
{
|
||||
addP2 = false;
|
||||
}
|
||||
|
||||
for ( double angle = a1 + increment; angle < a3; angle += increment )
|
||||
{
|
||||
if ( ( addP2 && angle > a2 ) )
|
||||
{
|
||||
stringPoints.insert( clockwise ? 0 : stringPoints.size(), circlePoint2 );
|
||||
addP2 = false;
|
||||
}
|
||||
|
||||
x = centerX + radius * cos( angle );
|
||||
y = centerY + radius * sin( angle );
|
||||
|
||||
if ( !hasZ && !hasM )
|
||||
{
|
||||
stringPoints.insert( clockwise ? 0 : stringPoints.size(), QgsPointV2( x, y ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( hasZ )
|
||||
{
|
||||
z = interpolateArcValue( angle, a1, a2, a3, circlePoint1.z(), circlePoint2.z(), circlePoint3.z() );
|
||||
}
|
||||
if ( hasM )
|
||||
{
|
||||
m = interpolateArcValue( angle, a1, a2, a3, circlePoint1.m(), circlePoint2.m(), circlePoint3.m() );
|
||||
}
|
||||
|
||||
stringPoints.insert( clockwise ? 0 : stringPoints.size(), QgsPointV2( pointWkbType, x, y, z, m ) );
|
||||
}
|
||||
}
|
||||
stringPoints.insert( clockwise ? 0 : stringPoints.size(), circlePoint3 );
|
||||
points.append( stringPoints );
|
||||
}
|
||||
|
||||
int QgsGeometryUtils::segmentSide( const QgsPointV2 &pt1, const QgsPointV2 &pt3, const QgsPointV2 &pt2 )
|
||||
{
|
||||
double side = ( ( pt2.x() - pt1.x() ) * ( pt3.y() - pt1.y() ) - ( pt3.x() - pt1.x() ) * ( pt2.y() - pt1.y() ) );
|
||||
if ( side == 0.0 )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( side < 0 )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if ( side > 0 )
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
double QgsGeometryUtils::interpolateArcValue( double angle, double a1, double a2, double a3, double zm1, double zm2, double zm3 )
|
||||
{
|
||||
/* Counter-clockwise sweep */
|
||||
if ( a1 < a2 )
|
||||
{
|
||||
if ( angle <= a2 )
|
||||
return zm1 + ( zm2 - zm1 ) * ( angle - a1 ) / ( a2 - a1 );
|
||||
else
|
||||
return zm2 + ( zm3 - zm2 ) * ( angle - a2 ) / ( a3 - a2 );
|
||||
}
|
||||
/* Clockwise sweep */
|
||||
else
|
||||
{
|
||||
if ( angle >= a2 )
|
||||
return zm1 + ( zm2 - zm1 ) * ( a1 - angle ) / ( a1 - a2 );
|
||||
else
|
||||
return zm2 + ( zm3 - zm2 ) * ( a2 - angle ) / ( a2 - a3 );
|
||||
}
|
||||
}
|
||||
|
||||
QgsPointSequence QgsGeometryUtils::pointsFromWKT( const QString &wktCoordinateList, bool is3D, bool isMeasure )
|
||||
{
|
||||
int dim = 2 + is3D + isMeasure;
|
||||
|
@ -171,6 +171,22 @@ class CORE_EXPORT QgsGeometryUtils
|
||||
//! Calculates the direction angle of a circle tangent (clockwise from north in radians)
|
||||
static double circleTangentDirection( const QgsPointV2 &tangentPoint, const QgsPointV2 &cp1, const QgsPointV2 &cp2, const QgsPointV2 &cp3 );
|
||||
|
||||
/** Convert circular arc defined by p1, p2, p3 (p1/p3 being start resp. end point, p2 lies on the arc) into a sequence of points.
|
||||
* @note added in 3.0
|
||||
*/
|
||||
static void segmentizeArc( const QgsPointV2 &p1, const QgsPointV2 &p2, const QgsPointV2 &p3, QgsPointSequence &points, double tolerance = M_PI_2 / 90, QgsAbstractGeometry::SegmentationToleranceType toleranceType = QgsAbstractGeometry::MaximumAngle, bool hasZ = false, bool hasM = false );
|
||||
|
||||
/** For line defined by points pt1 and pt3, find out on which side of the line is point pt3.
|
||||
* Returns -1 if pt3 on the left side, 1 if pt3 is on the right side or 0 if pt3 lies on the line.
|
||||
* @note added in 3.0
|
||||
*/
|
||||
static int segmentSide( const QgsPointV2 &pt1, const QgsPointV2 &pt3, const QgsPointV2 &pt2 );
|
||||
|
||||
/** Interpolate a value at given angle on circular arc given values (zm1, zm2, zm3) at three different angles (a1, a2, a3).
|
||||
* @note added in 3.0
|
||||
*/
|
||||
static double interpolateArcValue( double angle, double a1, double a2, double a3, double zm1, double zm2, double zm3 );
|
||||
|
||||
/** Returns a list of points contained in a WKT string.
|
||||
* \note not available in Python bindings
|
||||
*/
|
||||
|
@ -104,6 +104,7 @@ class QgsPointLocator_VisitorNearestVertex : public IVisitor
|
||||
QgsGeometry *geom = mLocator->mGeoms.value( id );
|
||||
int vertexIndex, beforeVertex, afterVertex;
|
||||
double sqrDist;
|
||||
|
||||
QgsPoint pt = geom->closestVertex( mSrcPoint, vertexIndex, beforeVertex, afterVertex, sqrDist );
|
||||
if ( sqrDist < 0 )
|
||||
return; // probably empty geometry
|
||||
|
@ -209,7 +209,9 @@ inline QgsRectangle _areaOfInterest( const QgsPoint &point, double tolerance )
|
||||
QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPoint &pointMap, QgsPointLocator::MatchFilter *filter )
|
||||
{
|
||||
if ( !mMapSettings.hasValidSettings() || !mSnappingConfig.enabled() )
|
||||
{
|
||||
return QgsPointLocator::Match();
|
||||
}
|
||||
|
||||
if ( mSnappingConfig.mode() == QgsSnappingConfig::ActiveLayer )
|
||||
{
|
||||
|
@ -198,6 +198,14 @@ QgsMapCanvas::~QgsMapCanvas()
|
||||
}
|
||||
mLastNonZoomMapTool = nullptr;
|
||||
|
||||
// rendering job may still end up writing into canvas map item
|
||||
// so kill it before deleting canvas items
|
||||
if ( mJob )
|
||||
{
|
||||
whileBlocking( mJob )->cancel();
|
||||
delete mJob;
|
||||
}
|
||||
|
||||
// delete canvas items prior to deleting the canvas
|
||||
// because they might try to update canvas when it's
|
||||
// already being destructed, ends with segfault
|
||||
@ -215,12 +223,6 @@ QgsMapCanvas::~QgsMapCanvas()
|
||||
// mCanvasProperties auto-deleted via QScopedPointer
|
||||
// CanvasProperties struct has its own dtor for freeing resources
|
||||
|
||||
if ( mJob )
|
||||
{
|
||||
whileBlocking( mJob )->cancel();
|
||||
delete mJob;
|
||||
}
|
||||
|
||||
delete mCache;
|
||||
|
||||
delete mLabelingResults;
|
||||
|
@ -490,6 +490,22 @@ void QgsRubberBand::drawShape( QPainter *p, QVector<QPointF> &pts )
|
||||
case ICON_CIRCLE:
|
||||
p->drawEllipse( x - s, y - s, mIconSize, mIconSize );
|
||||
break;
|
||||
|
||||
case ICON_DIAMOND:
|
||||
case ICON_FULL_DIAMOND:
|
||||
{
|
||||
QPointF pts[] =
|
||||
{
|
||||
QPointF( x, y - s ),
|
||||
QPointF( x + s, y ),
|
||||
QPointF( x, y + s ),
|
||||
QPointF( x - s, y )
|
||||
};
|
||||
if ( mIconType == ICON_FULL_DIAMOND )
|
||||
p->drawPolygon( pts, 4 );
|
||||
else
|
||||
p->drawPolyline( pts, 4 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -515,10 +531,9 @@ void QgsRubberBand::updateRect()
|
||||
|
||||
const QgsMapToPixel &m2p = *( mMapCanvas->getCoordinateTransform() );
|
||||
|
||||
qreal res = m2p.mapUnitsPerPixel();
|
||||
qreal w = ( ( mIconSize - 1 ) / 2 + mPen.width() ) / res;
|
||||
qreal w = ( ( mIconSize - 1 ) / 2 + mPen.width() ); // in canvas units
|
||||
|
||||
QgsRectangle r;
|
||||
QgsRectangle r; // in canvas units
|
||||
for ( int i = 0; i < mPoints.size(); ++i )
|
||||
{
|
||||
QList<QgsPoint>::const_iterator it = mPoints.at( i ).constBegin(),
|
||||
@ -543,6 +558,7 @@ void QgsRubberBand::updateRect()
|
||||
|
||||
// This is an hack to pass QgsMapCanvasItem::setRect what it
|
||||
// expects (encoding of position and size of the item)
|
||||
qreal res = m2p.mapUnitsPerPixel();
|
||||
QgsPoint topLeft = m2p.toMapPoint( r.xMinimum(), r.yMinimum() );
|
||||
QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + r.width()*res, topLeft.y() - r.height()*res );
|
||||
|
||||
|
@ -68,7 +68,19 @@ class GUI_EXPORT QgsRubberBand: public QgsMapCanvasItem
|
||||
/**
|
||||
* A full box is used to highlight points (■)
|
||||
*/
|
||||
ICON_FULL_BOX
|
||||
ICON_FULL_BOX,
|
||||
|
||||
/**
|
||||
* A diamond is used to highlight points (◇)
|
||||
* @note added in QGIS 3.0
|
||||
*/
|
||||
ICON_DIAMOND,
|
||||
|
||||
/**
|
||||
* A diamond is used to highlight points (◆)
|
||||
* @note added in QGIS 3.0
|
||||
*/
|
||||
ICON_FULL_DIAMOND,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -17,7 +17,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1018</width>
|
||||
<height>25</height>
|
||||
<height>18</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
@ -377,6 +377,7 @@
|
||||
<addaction name="mActionSaveLayerEdits"/>
|
||||
<addaction name="mActionAddFeature"/>
|
||||
<addaction name="mActionNodeTool"/>
|
||||
<addaction name="mActionNodeTool2"/>
|
||||
<addaction name="mActionDeleteSelected"/>
|
||||
<addaction name="mActionCutFeatures"/>
|
||||
<addaction name="mActionCopyFeatures"/>
|
||||
@ -2603,6 +2604,21 @@ Acts on currently active editable layer</string>
|
||||
<string>Install plugin from ZIP...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="mActionNodeTool2">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/mActionNodeTool.svg</normaloff>:/images/themes/default/mActionNodeTool.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Node Tool 2</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Node Tool 2</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../images/images.qrc"/>
|
||||
|
@ -90,4 +90,5 @@ ADD_QGIS_TEST(fieldcalculatortest testqgsfieldcalculator.cpp)
|
||||
ADD_QGIS_TEST(maptoolidentifyaction testqgsmaptoolidentifyaction.cpp)
|
||||
ADD_QGIS_TEST(maptoolselect testqgsmaptoolselect.cpp)
|
||||
ADD_QGIS_TEST(measuretool testqgsmeasuretool.cpp)
|
||||
ADD_QGIS_TEST(nodetool testqgsnodetool.cpp)
|
||||
ADD_QGIS_TEST(vectorlayersaveasdialogtest testqgsvectorlayersaveasdialog.cpp)
|
||||
|
476
tests/src/app/testqgsnodetool.cpp
Normal file
476
tests/src/app/testqgsnodetool.cpp
Normal file
@ -0,0 +1,476 @@
|
||||
/***************************************************************************
|
||||
testqgsnodetool.cpp
|
||||
----------------------
|
||||
Date : 2017-03-01
|
||||
Copyright : (C) 2017 by Martin Dobias
|
||||
Email : wonder dot sk at gmail 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 "qgsadvanceddigitizingdockwidget.h"
|
||||
#include "qgsgeometry.h"
|
||||
#include "qgsmapcanvas.h"
|
||||
#include "qgsmapcanvassnappingutils.h"
|
||||
#include "qgsproject.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
|
||||
#include "nodetool/qgsnodetool2.h"
|
||||
|
||||
bool operator==( const QgsGeometry &g1, const QgsGeometry &g2 )
|
||||
{
|
||||
if ( g1.isNull() && g2.isNull() )
|
||||
return true;
|
||||
else
|
||||
return g1.isGeosEqual( g2 );
|
||||
}
|
||||
|
||||
namespace QTest
|
||||
{
|
||||
// pretty printing of geometries in comparison tests
|
||||
template<> char *toString( const QgsGeometry &geom )
|
||||
{
|
||||
QByteArray ba = geom.exportToWkt().toAscii();
|
||||
return qstrdup( ba.data() );
|
||||
}
|
||||
}
|
||||
|
||||
/** \ingroup UnitTests
|
||||
* This is a unit test for the node tool
|
||||
*/
|
||||
class TestQgsNodeTool : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TestQgsNodeTool();
|
||||
|
||||
private slots:
|
||||
void initTestCase();// will be called before the first testfunction is executed.
|
||||
void cleanupTestCase();// will be called after the last testfunction was executed.
|
||||
|
||||
void testMoveVertex();
|
||||
void testMoveEdge();
|
||||
void testAddVertex();
|
||||
void testAddVertexAtEndpoint();
|
||||
void testDeleteVertex();
|
||||
void testMoveMultipleVertices();
|
||||
|
||||
private:
|
||||
QPoint mapToScreen( double mapX, double mapY )
|
||||
{
|
||||
QgsPoint pt = mCanvas->mapSettings().mapToPixel().transform( mapX, mapY );
|
||||
return QPoint( qRound( pt.x() ), qRound( pt.y() ) );
|
||||
}
|
||||
|
||||
void mouseMove( double mapX, double mapY )
|
||||
{
|
||||
QgsMapMouseEvent e( mCanvas, QEvent::MouseMove, mapToScreen( mapX, mapY ) );
|
||||
mNodeTool->cadCanvasMoveEvent( &e );
|
||||
}
|
||||
|
||||
void mousePress( double mapX, double mapY, Qt::MouseButton button, Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers() )
|
||||
{
|
||||
QgsMapMouseEvent e1( mCanvas, QEvent::MouseButtonPress, mapToScreen( mapX, mapY ), button, button, stateKey );
|
||||
mNodeTool->cadCanvasPressEvent( &e1 );
|
||||
}
|
||||
|
||||
void mouseRelease( double mapX, double mapY, Qt::MouseButton button, Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers() )
|
||||
{
|
||||
QgsMapMouseEvent e2( mCanvas, QEvent::MouseButtonRelease, mapToScreen( mapX, mapY ), button, Qt::MouseButton(), stateKey );
|
||||
mNodeTool->cadCanvasReleaseEvent( &e2 );
|
||||
}
|
||||
|
||||
void mouseClick( double mapX, double mapY, Qt::MouseButton button, Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers() )
|
||||
{
|
||||
mousePress( mapX, mapY, button, stateKey );
|
||||
mouseRelease( mapX, mapY, button, stateKey );
|
||||
}
|
||||
|
||||
void keyClick( int key )
|
||||
{
|
||||
QKeyEvent e1( QEvent::KeyPress, key, Qt::KeyboardModifiers() );
|
||||
mNodeTool->keyPressEvent( &e1 );
|
||||
|
||||
QKeyEvent e2( QEvent::KeyRelease, key, Qt::KeyboardModifiers() );
|
||||
mNodeTool->keyReleaseEvent( &e2 );
|
||||
}
|
||||
|
||||
private:
|
||||
QgsMapCanvas *mCanvas = nullptr;
|
||||
QgsAdvancedDigitizingDockWidget *mAdvancedDigitizingDockWidget = nullptr;
|
||||
QgsNodeTool2 *mNodeTool = nullptr;
|
||||
QgsVectorLayer *mLayerLine = nullptr;
|
||||
QgsVectorLayer *mLayerPolygon = nullptr;
|
||||
QgsVectorLayer *mLayerPoint = nullptr;
|
||||
QgsFeatureId mFidLineF1 = 0;
|
||||
QgsFeatureId mFidPolygonF1 = 0;
|
||||
QgsFeatureId mFidPointF1 = 0;
|
||||
};
|
||||
|
||||
TestQgsNodeTool::TestQgsNodeTool()
|
||||
: mCanvas( nullptr )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//runs before all tests
|
||||
void TestQgsNodeTool::initTestCase()
|
||||
{
|
||||
qDebug() << "TestQgisAppClipboard::initTestCase()";
|
||||
// init QGIS's paths - true means that all path will be inited from prefix
|
||||
QgsApplication::init();
|
||||
QgsApplication::initQgis();
|
||||
|
||||
// Set up the QSettings environment
|
||||
QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
|
||||
QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
|
||||
QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
|
||||
|
||||
mCanvas = new QgsMapCanvas();
|
||||
|
||||
mCanvas->setDestinationCrs( QgsCoordinateReferenceSystem( "EPSG:27700" ) );
|
||||
|
||||
mAdvancedDigitizingDockWidget = new QgsAdvancedDigitizingDockWidget( mCanvas );
|
||||
|
||||
// make testing layers
|
||||
mLayerLine = new QgsVectorLayer( "LineString?crs=EPSG:27700", "layer line", "memory" );
|
||||
QVERIFY( mLayerLine->isValid() );
|
||||
mLayerPolygon = new QgsVectorLayer( "Polygon?crs=EPSG:27700", "layer polygon", "memory" );
|
||||
QVERIFY( mLayerPolygon->isValid() );
|
||||
mLayerPoint = new QgsVectorLayer( "Point?crs=EPSG:27700", "layer point", "memory" );
|
||||
QVERIFY( mLayerPoint->isValid() );
|
||||
QgsProject::instance()->addMapLayers( QList<QgsMapLayer *>() << mLayerLine << mLayerPolygon << mLayerPoint );
|
||||
|
||||
QgsPolyline line1;
|
||||
line1 << QgsPoint( 2, 1 ) << QgsPoint( 1, 1 ) << QgsPoint( 1, 3 );
|
||||
QgsFeature lineF1;
|
||||
lineF1.setGeometry( QgsGeometry::fromPolyline( line1 ) );
|
||||
|
||||
QgsPolygon polygon1;
|
||||
QgsPolyline polygon1exterior;
|
||||
polygon1exterior << QgsPoint( 4, 1 ) << QgsPoint( 7, 1 ) << QgsPoint( 7, 4 ) << QgsPoint( 4, 4 ) << QgsPoint( 4, 1 );
|
||||
polygon1 << polygon1exterior;
|
||||
QgsFeature polygonF1;
|
||||
polygonF1.setGeometry( QgsGeometry::fromPolygon( polygon1 ) );
|
||||
|
||||
QgsFeature pointF1;
|
||||
pointF1.setGeometry( QgsGeometry::fromPoint( QgsPoint( 2, 3 ) ) );
|
||||
|
||||
mLayerLine->startEditing();
|
||||
mLayerLine->addFeature( lineF1 );
|
||||
mFidLineF1 = lineF1.id();
|
||||
QCOMPARE( mLayerLine->featureCount(), ( long )1 );
|
||||
|
||||
mLayerPolygon->startEditing();
|
||||
mLayerPolygon->addFeature( polygonF1 );
|
||||
mFidPolygonF1 = polygonF1.id();
|
||||
QCOMPARE( mLayerPolygon->featureCount(), ( long )1 );
|
||||
|
||||
mLayerPoint->startEditing();
|
||||
mLayerPoint->addFeature( pointF1 );
|
||||
mFidPointF1 = pointF1.id();
|
||||
QCOMPARE( mLayerPoint->featureCount(), ( long )1 );
|
||||
|
||||
// just one added feature in each undo stack
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 1 );
|
||||
QCOMPARE( mLayerPolygon->undoStack()->index(), 1 );
|
||||
QCOMPARE( mLayerPoint->undoStack()->index(), 1 );
|
||||
|
||||
mCanvas->setFrameStyle( QFrame::NoFrame );
|
||||
mCanvas->resize( 512, 512 );
|
||||
mCanvas->setExtent( QgsRectangle( 0, 0, 8, 8 ) );
|
||||
mCanvas->show(); // to make the canvas resize
|
||||
mCanvas->hide();
|
||||
QCOMPARE( mCanvas->mapSettings().outputSize(), QSize( 512, 512 ) );
|
||||
QCOMPARE( mCanvas->mapSettings().visibleExtent(), QgsRectangle( 0, 0, 8, 8 ) );
|
||||
|
||||
mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerLine << mLayerPolygon << mLayerPoint );
|
||||
|
||||
// TODO: set up snapping
|
||||
|
||||
mCanvas->setSnappingUtils( new QgsMapCanvasSnappingUtils( mCanvas, this ) );
|
||||
|
||||
// create node tool
|
||||
mNodeTool = new QgsNodeTool2( mCanvas, mAdvancedDigitizingDockWidget );
|
||||
|
||||
mCanvas->setMapTool( mNodeTool );
|
||||
}
|
||||
|
||||
//runs after all tests
|
||||
void TestQgsNodeTool::cleanupTestCase()
|
||||
{
|
||||
delete mNodeTool;
|
||||
delete mAdvancedDigitizingDockWidget;
|
||||
delete mCanvas;
|
||||
QgsApplication::exitQgis();
|
||||
}
|
||||
|
||||
void TestQgsNodeTool::testMoveVertex()
|
||||
{
|
||||
QCOMPARE( mCanvas->mapSettings().outputSize(), QSize( 512, 512 ) );
|
||||
QCOMPARE( mCanvas->mapSettings().visibleExtent(), QgsRectangle( 0, 0, 8, 8 ) );
|
||||
|
||||
// move vertex of linestring
|
||||
|
||||
mouseClick( 2, 1, Qt::LeftButton );
|
||||
mouseClick( 2, 2, Qt::LeftButton );
|
||||
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 2, 1 1, 1 3)" ) );
|
||||
|
||||
mLayerLine->undoStack()->undo();
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 1 );
|
||||
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
|
||||
|
||||
mouseClick( 1, 1, Qt::LeftButton );
|
||||
mouseClick( 0.5, 0.5, Qt::LeftButton );
|
||||
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 0.5 0.5, 1 3)" ) );
|
||||
|
||||
mLayerLine->undoStack()->undo();
|
||||
|
||||
// move point
|
||||
|
||||
mouseClick( 2, 3, Qt::LeftButton );
|
||||
mouseClick( 1, 4, Qt::LeftButton );
|
||||
|
||||
QCOMPARE( mLayerPoint->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerPoint->getFeature( mFidPointF1 ).geometry(), QgsGeometry::fromWkt( "POINT(1 4)" ) );
|
||||
|
||||
mLayerPoint->undoStack()->undo();
|
||||
|
||||
QCOMPARE( mLayerPoint->getFeature( mFidPointF1 ).geometry(), QgsGeometry::fromWkt( "POINT(2 3)" ) );
|
||||
|
||||
// move vertex of polygon
|
||||
|
||||
mouseClick( 4, 1, Qt::LeftButton );
|
||||
mouseClick( 5, 2, Qt::LeftButton );
|
||||
|
||||
QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((5 2, 7 1, 7 4, 4 4, 5 2))" ) );
|
||||
|
||||
mLayerPolygon->undoStack()->undo();
|
||||
|
||||
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
|
||||
|
||||
mouseClick( 4, 4, Qt::LeftButton );
|
||||
mouseClick( 5, 5, Qt::LeftButton );
|
||||
|
||||
QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 5 5, 4 1))" ) );
|
||||
|
||||
mLayerPolygon->undoStack()->undo();
|
||||
|
||||
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
|
||||
|
||||
// cancel moving of a linestring with right mouse button
|
||||
mouseClick( 2, 1, Qt::LeftButton );
|
||||
mouseClick( 2, 2, Qt::RightButton );
|
||||
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 1 );
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
|
||||
|
||||
// clicks somewhere away from features - should do nothing
|
||||
mouseClick( 2, 2, Qt::LeftButton );
|
||||
mouseClick( 2, 4, Qt::LeftButton );
|
||||
|
||||
// no other unexpected changes happened
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 1 );
|
||||
QCOMPARE( mLayerPolygon->undoStack()->index(), 1 );
|
||||
QCOMPARE( mLayerPoint->undoStack()->index(), 1 );
|
||||
}
|
||||
|
||||
void TestQgsNodeTool::testMoveEdge()
|
||||
{
|
||||
// move edge of linestring
|
||||
|
||||
mouseClick( 1.2, 1, Qt::LeftButton );
|
||||
mouseClick( 1.2, 2, Qt::LeftButton );
|
||||
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 2, 1 2, 1 3)" ) );
|
||||
|
||||
mLayerLine->undoStack()->undo();
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 1 );
|
||||
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
|
||||
|
||||
// move edge of polygon
|
||||
|
||||
mouseClick( 5, 1, Qt::LeftButton );
|
||||
mouseClick( 6, 1, Qt::LeftButton );
|
||||
|
||||
QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((5 1, 8 1, 7 4, 4 4, 5 1))" ) );
|
||||
|
||||
mLayerPolygon->undoStack()->undo();
|
||||
|
||||
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
|
||||
|
||||
// no other unexpected changes happened
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 1 );
|
||||
QCOMPARE( mLayerPolygon->undoStack()->index(), 1 );
|
||||
QCOMPARE( mLayerPoint->undoStack()->index(), 1 );
|
||||
}
|
||||
|
||||
|
||||
void TestQgsNodeTool::testAddVertex()
|
||||
{
|
||||
// add vertex in linestring
|
||||
|
||||
mouseClick( 1.5, 1, Qt::LeftButton );
|
||||
mouseClick( 1.5, 2, Qt::LeftButton );
|
||||
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1.5 2, 1 1, 1 3)" ) );
|
||||
|
||||
mLayerLine->undoStack()->undo();
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 1 );
|
||||
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
|
||||
|
||||
// add vertex in polygon
|
||||
|
||||
mouseClick( 4, 2.5, Qt::LeftButton );
|
||||
mouseClick( 3, 2.5, Qt::LeftButton );
|
||||
|
||||
QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 3 2.5, 4 1))" ) );
|
||||
|
||||
mLayerPolygon->undoStack()->undo();
|
||||
|
||||
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
|
||||
|
||||
// no other unexpected changes happened
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 1 );
|
||||
QCOMPARE( mLayerPolygon->undoStack()->index(), 1 );
|
||||
QCOMPARE( mLayerPoint->undoStack()->index(), 1 );
|
||||
}
|
||||
|
||||
|
||||
void TestQgsNodeTool::testAddVertexAtEndpoint()
|
||||
{
|
||||
// offset of the endpoint marker - currently set as 15px away from the last node in direction of the line
|
||||
double offsetInMapUnits = 15 * mCanvas->mapSettings().mapUnitsPerPixel();
|
||||
|
||||
// add vertex at the end
|
||||
|
||||
mouseMove( 1, 3 ); // first we need to move to the vertex
|
||||
mouseClick( 1, 3 + offsetInMapUnits, Qt::LeftButton );
|
||||
mouseClick( 2, 3, Qt::LeftButton );
|
||||
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3, 2 3)" ) );
|
||||
|
||||
mLayerLine->undoStack()->undo();
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 1 );
|
||||
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
|
||||
|
||||
// add vertex at the start
|
||||
|
||||
mouseMove( 2, 1 ); // first we need to move to the vertex
|
||||
mouseClick( 2 + offsetInMapUnits, 1, Qt::LeftButton );
|
||||
mouseClick( 2, 2, Qt::LeftButton );
|
||||
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 2, 2 1, 1 1, 1 3)" ) );
|
||||
|
||||
mLayerLine->undoStack()->undo();
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 1 );
|
||||
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
|
||||
}
|
||||
|
||||
|
||||
void TestQgsNodeTool::testDeleteVertex()
|
||||
{
|
||||
// delete vertex in linestring
|
||||
|
||||
mouseClick( 1, 1, Qt::LeftButton );
|
||||
keyClick( Qt::Key_Delete );
|
||||
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 3)" ) );
|
||||
|
||||
mLayerLine->undoStack()->undo();
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 1 );
|
||||
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
|
||||
|
||||
// delete vertex in polygon
|
||||
|
||||
mouseClick( 7, 4, Qt::LeftButton );
|
||||
keyClick( Qt::Key_Delete );
|
||||
|
||||
QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 4 4, 4 1))" ) );
|
||||
|
||||
mLayerPolygon->undoStack()->undo();
|
||||
|
||||
QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
|
||||
|
||||
// delete vertex in point - deleting its geometry
|
||||
|
||||
mouseClick( 2, 3, Qt::LeftButton );
|
||||
keyClick( Qt::Key_Delete );
|
||||
|
||||
QCOMPARE( mLayerPoint->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerPoint->getFeature( mFidPointF1 ).geometry(), QgsGeometry() );
|
||||
|
||||
mLayerPoint->undoStack()->undo();
|
||||
|
||||
QCOMPARE( mLayerPoint->getFeature( mFidPointF1 ).geometry(), QgsGeometry::fromWkt( "POINT(2 3)" ) );
|
||||
|
||||
// delete a vertex by dragging a selection rect
|
||||
|
||||
mousePress( 0.5, 2.5, Qt::LeftButton );
|
||||
mouseMove( 1.5, 3.5 );
|
||||
mouseRelease( 1.5, 3.5, Qt::LeftButton );
|
||||
keyClick( Qt::Key_Delete );
|
||||
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1)" ) );
|
||||
|
||||
mLayerLine->undoStack()->undo();
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 1 );
|
||||
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
|
||||
|
||||
// no other unexpected changes happened
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 1 );
|
||||
QCOMPARE( mLayerPolygon->undoStack()->index(), 1 );
|
||||
QCOMPARE( mLayerPoint->undoStack()->index(), 1 );
|
||||
}
|
||||
|
||||
void TestQgsNodeTool::testMoveMultipleVertices()
|
||||
{
|
||||
// select two vertices
|
||||
mousePress( 0.5, 0.5, Qt::LeftButton );
|
||||
mouseMove( 1.5, 3.5 );
|
||||
mouseRelease( 1.5, 3.5, Qt::LeftButton );
|
||||
|
||||
// move them by -1,-1
|
||||
mouseClick( 1, 1, Qt::LeftButton );
|
||||
mouseClick( 0, 0, Qt::LeftButton );
|
||||
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 2 );
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 0 0, 0 2)" ) );
|
||||
|
||||
mLayerLine->undoStack()->undo();
|
||||
QCOMPARE( mLayerLine->undoStack()->index(), 1 );
|
||||
|
||||
QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
|
||||
}
|
||||
|
||||
QGSTEST_MAIN( TestQgsNodeTool )
|
||||
#include "testqgsnodetool.moc"
|
@ -73,6 +73,11 @@ void TestQgsRubberband::initTestCase()
|
||||
myPolygonFileInfo.completeBaseName(), QStringLiteral( "ogr" ) );
|
||||
|
||||
mCanvas = new QgsMapCanvas();
|
||||
mCanvas->setFrameStyle( QFrame::NoFrame );
|
||||
mCanvas->resize( 512, 512 );
|
||||
mCanvas->show(); // to make the canvas resize
|
||||
mCanvas->hide();
|
||||
|
||||
mRubberband = 0;
|
||||
}
|
||||
|
||||
@ -111,13 +116,9 @@ void TestQgsRubberband::testAddSingleMultiGeometries()
|
||||
|
||||
void TestQgsRubberband::testBoundingRect()
|
||||
{
|
||||
QSizeF mapSize = mCanvas->mapSettings().outputSize();
|
||||
|
||||
// Set extent to match canvas size.
|
||||
// This is to ensure a 1:1 scale
|
||||
mCanvas->setExtent( QgsRectangle( QRectF(
|
||||
QPointF( 0, 0 ), mapSize
|
||||
) ) );
|
||||
mCanvas->setExtent( QgsRectangle( 0, 0, 512, 512 ) );
|
||||
QCOMPARE( mCanvas->mapUnitsPerPixel(), 1.0 );
|
||||
|
||||
// Polygon extent is 10,10 to 30,30
|
||||
@ -129,26 +130,24 @@ void TestQgsRubberband::testBoundingRect()
|
||||
mRubberband->setWidth( 1 ); // default, but better be explicit
|
||||
mRubberband->addGeometry( geom, mPolygonLayer );
|
||||
|
||||
// 20 pixels for the extent + 3 for pen & icon per side + 2 of padding
|
||||
// 20 pixels for the extent + 3 for pen & icon per side + 2 of extra padding from setRect()
|
||||
QCOMPARE( mRubberband->boundingRect(), QRectF( QPointF( -1, -1 ), QSizeF( 28, 28 ) ) );
|
||||
QCOMPARE( mRubberband->pos(), QPointF(
|
||||
// 10 for extent minx - 3 for pen & icon
|
||||
7,
|
||||
10 - 3,
|
||||
// 30 for extent maxy - 3 for pen & icon
|
||||
mapSize.height() - 30 - 3
|
||||
512 - 30 - 3
|
||||
) );
|
||||
|
||||
mCanvas->setExtent( QgsRectangle( QRectF(
|
||||
QPointF( 0, 0 ), mapSize / 2
|
||||
) ) );
|
||||
mCanvas->setExtent( QgsRectangle( 0, 0, 256, 256 ) );
|
||||
|
||||
// 40 pixels for the extent + 6 for pen & icon per side + 2 of padding
|
||||
QCOMPARE( mRubberband->boundingRect(), QRectF( QPointF( -1, -1 ), QSizeF( 54, 54 ) ) );
|
||||
// 40 pixels for the extent + 3 for pen & icon per side + 2 of extra padding from setRect()
|
||||
QCOMPARE( mRubberband->boundingRect(), QRectF( QPointF( -1, -1 ), QSizeF( 48, 48 ) ) );
|
||||
QCOMPARE( mRubberband->pos(), QPointF(
|
||||
// 10 for extent minx - 3 for pen & icon
|
||||
7 * 2,
|
||||
10 * 2 - 3,
|
||||
// 30 for extent maxy - 3 for pen & icon
|
||||
mapSize.height() - ( 30 + 3 ) * 2
|
||||
512 - 30 * 2 - 3
|
||||
) );
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user